multiple uploads


Now, you can create any table in database and perform the insert, display, update and delete task easily. Let's have a look at a table that reuires mutiple files to be uploaded and deleted with a single command. Here, we'll create an image gallery but before that let's create a table to hold gallery album names prior to that one.

CREATE TABLE ALBUMS

CREATE TABLE albums( 
id int unsigned PRIMARY KEY AUTO_INCREMENT, 
name varchar(100) NOT null, 
INDEX(name) 
)	

ADD ALBUMS

<?php 
if (Request::method() && token::check(Request::post('csrf_token'))){
	$obj = new Album();	
	$obj -> addAlbum();
}
?>

<form method="POST">
	<div class="col-sm-6">
		<?= Validation::displayErrors('validationErrors','alert alert-danger'); ?>		
		<?= Message(); ?>
	</div>	
	<div class="clearfix"></div>
	<div class="col-sm-6">		
		<?=token::input();?>
		<div class="form-group">
			<label>Album Name</label>
			<input type="text" name="name" class="form-control">
		</div>	
		<div class="form-group">
			<button type="submit" class="btn btn-success"><i class="fa fa-image"></i> Add Album</button>
		</div>
	</div>	
</form>	

ALBUM CONTROLLER

<?php
class album extends model
{
	protected $table = "albums";
	protected $key = "id";
	protected $field = "*";

	protected $validationRules = [
		'name' => [
			'required' => true,
			'unique' => 'albums.name',
			'minlength' => 3,
			'maxlength' => 50,
			'label' => 'Album Name', 
		]
	];

	public function __construct()
	{	
		parent::__construct();
		$this->_validation = new Validation();
	}

	public function addAlbum()
	{
		$data['name'] = Request::post('name');
		
		try{
			$this->_validation->validate($this->validationRules);
			if($this->_validation->isValid()){				
					if ($this->save($data)) {
						session::put('success','Album Created Successfully');
						return Redirect::to('admin/viewAlbums');
					}
				}else{
					session::put('validationErrors',$this->_validation->getErrors());
					return Redirect::to('admin/addAlbums');
				}			
		}catch(Exception $e){
			die($e->getMessage());
		}
	}	
}	

Nothing new in this controller. We added the table details and validation rules. Then instantiated the database and validation classes and created a function that checks for validation of the requested data values and inserts into the table if the data is valid. If not, it'll redirect the user back with session messages.

DISPLAY ALBUM

The function to display album is created in Gallery Controller as it'll not only display the resultset from albums table but also from the gallery table. We'll use the SQL JOIN syntax to join both tables matching the albumid we saved in gallery table with the album id from albums table.

Since we didn't create any specific function in our model for this one, we're instantiating the databse straight into this function and sending raw SQL syntax for processing.

public function getAlbumInfo(){
	$db = new Database();
	$this->field = 'COUNT(gallery.image) as total_image, gallery.*,albums.id as album_id,albums.name';
	return $db->select($this->table,$this->field,'',array(),'JOIN albums ON gallery.albumid = albums.id GROUP BY gallery.albumid');
}	

In the database select function, we can send 5 parameters namely $table, $column, $criteria, $value and $clause which we can't ignore. So, we're sending the table value first.

In the field value, we're sending the entire fields we want in our display table along with the count of images with same album id.

We don't have any criteria and it's value to send as parameters but can't ignore it. So, we're sending empty strings.

Finally, through the clause section, we'll send the remaining string of the SQL JOIN syntax.

<?php 
$obj = new Gallery();	
$images = $obj->getAlbumInfo();
?>

<div class="col-sm-12">
	<?= Message(); ?>
	<table class="table table-bordered table-striped table-hover">
		<thead>
			<tr>
				<th>S.N.</th>
				<th>Album Name</th>
				<th>Number of Image</th>
				<th>Action</th>
			</tr>
		</thead>
		<tbody>
			<?php
			if(empty($images)){ ?>
			<td colspan="6">No Data Found <a href="main.php?page=addAlbum">Add Album</a></td>
			<?php }else{ ?>
			<?php foreach($images as $key => $image): ?>	
				<tr>
					<td><?= ++$key ?></td>
					<td><?= ucfirst($image->name)?></td>
					<td><?=$image->total_image?></td>					
					<td>						
						<a href="main.php?page=viewGallery&aid=<?=$image->albumid?>" class="btn btn-sm btn-success"><i class="fa fa-image"></i> Go to Gallery</a> 
					</td>
				</tr>
			<?php endforeach; ?>	
			<?php } ?>
		</tbody>
	</table>
</div>

Here, in the display table, we displayed the album name, image count and added links to each gallery view page where the id'll be sent through url and the same will be retrieved and images under the same album id will be displayed.

CREATE TABLE GALLERY

CREATE TABLE galley(
id int unsigned PRIMARY KEY AUTO_INCREMENT,
image varchar(255),
albumid int unsigned,
FOREIGN KEY (albumid) REFERENCES albums (id) ON UPDATE CASCADE ON DELETE CASCADE 
)	

In this table, we also added a foreign key album id to store id of the album related to the images being uploaded that'll be updated automatically with each insert, updated or delete operation.

CREATE FORM TO INSERT MULTIPLE IMAGES

<?php
$album = new album();
$albums = $album->getAlbum(); 
if (Request::method() && token::check(Request::post('csrf_token'))){
	$obj = new Gallery();	
	$obj -> addGallery();
}
?>

<div class="col-sm-12">
	<a href="main.php?page=viewAlbum" class="btn btn-success">View Albums</a> 
	<a href="main.php?page=addAlbum" class="btn btn-default">Add New Album</a>
</div>

<form method="POST" enctype="multipart/form-data">
	<div class="col-sm-12">
		<?= Validation::displayErrors('validationErrors','alert alert-danger'); ?>		
		<?= displayMessage(); ?>
	</div>	
	<div class="col-sm-6 form-group">		
		<?=token::input();?>
		<label>Choose Multiple Images</label>
		<input type="file" name="image[]" class="form-control" multiple="">
	</div>
	<div class="col-sm-6 form-group">
		<label>Album Name</label>		
		<select name="album" class="form-control">
			<option value="0">--- Select Related Album's Name---</option>
			<?php if(!empty($albums)): ?>
				<?php foreach($albums as $album): ?>
					<option value="<?=$album->id?>"><?=ucfirst($album->name)?></option>
				<?php endforeach; ?>
			<?php else: ?>
				<option>No Album Found</option>
			<?php endif; ?>	
		</select>
	</div>	
	<div class="col-sm-12 form-group">
		<button type="submit" class="btn btn-success"><i class="fa fa-image"></i> Upload</button>
	</div>
</form>	

Along with the image upload form, we also added links to go to the albums view page and insert page. This form is quite simple as it's got only two fields, one to select the images and the other to select the album name. Let's create the controller and the function then.

ADD GALLERY IMAGES

<?php
class Gallery extends Model
{
	protected $table = "gallery";
	protected $key = "id";
	protected $field = "*";

	protected $validationRules = [
		'album' => [
			'required' => true,
			'label' => 'Album', 
		]
	];

	public function __construct()
	{	
		parent::__construct();
		$this->_validation = new Validation();
	}
	
	public function addGallery()
	{
		$data['albumid'] = Request::post('album');
		
		try{
			$this->_validation->validate($this->validationRules);
			if($this->_validation->isValid()){	

					$files = $_FILES['image'];
					$number = count($files['name']);

					$uploadCount = 0;
					$error = false;

					for($i=0;$i<$number;$i++){
						$gallery['name'] = $files['name'][$i];
						$gallery['error'] = $files['error'][$i];
						$gallery['tmp_name'] = $files['tmp_name'][$i];
						$gallery['size'] = $files['size'][$i];

						$configs['upload_size'] = 2000000;
						$configs['upload_ext'] = 'jpg|jpeg|png|gif';
						$configs['upload_path'] = ROOT.'admin/Upload/Gallery/';

						Upload::initialize($configs);

						$filename = Upload::load($gallery);
						
						if($filename){
							$data['image'] = $filename;

							if($this->save($data)) {
								$uploadCount++;
							}
						}else{
							$error = true;
						}						
					}
					if($number === $uploadCount && $error === false){
						session::put('success',"All images were uploaded to the album. Count {$uploadCount}");
						return;
					}else if($error===true){
						session::put('validationErrors',$this->_validation->getErrors());
						session::put('success',"{$uploadCount} of {$number} images were uploaded to the album");
						return;
					}	
				}else{
					session::put('validationErrors',$this->_validation->getErrors());
					return;
				}			
		}catch(Exception $e){
			die($e->getMessage());
		}

	}	
}	

We sent the id of album from the form via request and that's the one we'll validate and save in array $data for insert operation. If the album name is valid, we'll go through the file upload action.

We are allowing multiple file uploads, so we'll count the number of files being requested for upload by counting any one of the file information received.

We also created variables to check the upload count and error count as all the images might not get uploaded at times.

Since, we can't return the user anywhere from the script file until each of the uploaded files are processed, we'll reiterate the files informations using for loop and validate and upload each of them while still in the loop.

With each upload success, we'll update the upload count or change the value of $error to true in case of failure.

Finally, we'll tally the number of files requested for upload and the number of files uploaded and set success message in session or set error messages accordingly and redirect the user back with session messages.

DISPLAY IMAGES FROM GALLERY

Since the images in gallery are corelated with the album ids, we won't return any result set if the id is not set in this function. If the id is set which is the albumid, we'll return all the images with that album id.

public function getGallery($id=""){		
	$id = (int)Request::get('aid');
	if(empty($id)) return false;
	return $this->getBy('albumid = ?',array($id));
}	

The function to display images is ready. Now, we can create the view page to display related images.

<?php 
$obj = new Gallery();	
$images = $obj->getGallery();
if (Request::method() && token::check(Request::post('csrf_token'))){{
	$result=$obj->deleteMultiple();
}
?>

<div class="col-sm-12">
	<?= Message(); ?>
	<a href="main.php?page=viewAlbum" class="btn btn-success"><i class="fa fa-reply"></i> Go Back</a>
</div>
<div class="row">
	<?php if(empty($images)):?>
		<div class="col-sm-12">
			<h2>No Images Found. <a href="main.php?page=addGallery" class="btn btn-success"><i class="fa fa-image"></i> Add Images</a></h2>
		</div>	
	<?php else: ?>
		<div class="col-sm-12">
			<form method="post" onsubmit="confirm('Are you sure ?')">
				<?= Token::input() ?>
			<?php foreach($images as $image): ?>
			<?php $gid = uniqid($image->image); ?>	
				<div class="col-sm-3">
					<div class="image-box">
						<img src="<?=HTTP.'admin/Upload/Gallery/'.$image->image?>" class="img-thumbnail">
					</div>
					<label for="<?=$gid?>"><input type="checkbox" name="image_id[]" id="<?=$gid?>" value="<?=$image->id?>"> Select</label>	
				</div>	
			<?php endforeach; ?>
				<div class="col-sm-12">
					<button type="submit" class="btn btn-danger"><i class="fa fa-warning"></i> Delete Selected Images</button>
				</div>	
			</form>	
		</div>	
	<?php endif; ?>
</div>

Along with the images, we also added checkboxes with unique ids so that we could delete multiple images when needed from the gallery. Let's create a function for that.

DELETE MULTIPLE IMAGES

public function deleteMultiple(){
	$ids = $_POST['image_id'];
	if(empty($ids)) return false;
	$cleanIds = [];

	foreach ($ids as $id){
		$id = (int)$id;
		if(!empty($id)){
			$cleanIds[] = $id;
		}
	}

	$deleteCount = false;
	$images = $this->getBy('ID in (' . implode(',',$cleanIds).')');
		
	foreach($images as $image){
		$imageName = $image->image;
		$imagePath = $image->image;

		if(unlink(ROOT.'admin/Upload/Gallery/'.$imageName)){
			$deleteCount = true;
		}else{
			$deleteCount = false;
		}
	}

	if($this->multipleDelete($cleanIds)){
		$imgCount = count($cleanIds);
		if($deleteCount === true){
			session::put('success',$imgCount . ' Images were Deleted');
			return;	
		}else{
			session::put('error', 'Only ' . $imgCount . ' Images were Deleted');
			return;	
		}	
	}
}	

Here, we'll retrieve the image ids sent rom post. If there's no id we'll return false.

Then, we'll filter the ids to make sure that only integer ids are sent or further processing.

Since, we're working on multiple images, we'll check if all the data gets deleted or not as we can't return false in the middle of the process. That's why we'll store error count and send back the message to the user after completion of the process.

Then we fetched all the images from gallery table by their ids and deleted them using loop. If the images get deleted from the directory, the delete count will remain true. If not, the value will be false.

Then, we'll call the multipleDelete() function from Model and pass the required parameter i.e. ids in array ro get the data from database deleted.

Then we'll count the number of ids passed through the primary foreach loop.

If value of $deletecount remained true, all the selected images would have been deleted. Hence, we'll show the success message from session with number of images deleted. In another case, we'll display error message and show the number of images deleted.

HTACCESS

HTACCESS is a command file that defines the server's behaviour in certain situations defined. htaccess files have no names but just the extension .htaccess. Generally, we create this file at the root folder of our application and define rules for server to act on certain situations.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ $1.php [NC,L]

Options -Indexes
ErrorDocument 403 "

Unauthorized Access Attempt!!!

Please Go Back!!!

"

RewriteEngine On turns on the server's url rewrite engine.

RewriteCond %{REQUEST_FILENAME} !-d will make the server search for a rewrite rule, if the requested uri is not a project diretory.

RewriteCond %{REQUEST_FILENAME}.php !-f will make the server search for a rewrite rule, if the requested uri is not a project file.

RewriteRule ^(.*)$ $1.php [NC,L] will recognize any file request without .php exrension sent to the url as a file request and opens the file if exists.

Options -Indexes will allow access to only those directories that have index.php file in it.

ErrorDocument 403 "<h1>Unauthorized Access Attempt!!!</h1> <h2>Please Go Back!!!</h2>" will display these lines in error file 403 that is the ACCESS FORBIDDEN file.