Home    posts    gallery with thumbnails

Posted on: January 30, 2018Updated: February 9, 2018

Creating a gallery with thumbnails and pagination

In this tutorial, I'll be showing you how to make a safe to use, yet simple, gallery with thumbnails, without database, using PHP only. We will also include pagination and sorting of the images by creation date(when they were uploaded) that will somewhat replicate the SQL datetime effects. The basic concept is to allow uploading up to three files simultaneously, limit them on file size, on file name length and on file type, just basic inspection before actually uploading them to a temporary folder and then creating images and thumbnails from these files and upload both to separate folders. After this process is complete, the script will delete the original files from temporary folder. This will make any attempt to fool the conditions by masking the file type or even malware inside the image type files uneffective. It will wear down performance quality a bit but I figured that security is more important in this case. Note that this will have no performance drop on the frontend of the application, only when uploading files.

Anyway, now that the concept has been explained, we shall proceed to coding. The tutorial will be done in 4 parts. First part will contain the include file which will contain pagination, location of the images and thumbnails and a returned array of the images and thumbnails by the PHP's glob() function. In this part we will also define sorting of the files. The second part will contain file upload, creation of images and thumbnails, moving them to the correct folders and then deleting the original files. In the third part, we will create a delete button for both images and thumbnails that will then be appended to each image in the backend output. The final part will be about displaying the thumbnails in the frontend with hyperlinks to the full scale images.

PART 1 - Create pagination and other needed functions (functions.php)

As the title says it, we will create functions in a separate file that will be used by both backend and frontend files to display the thumbnails and images in the gallery. The first function will provide us paths to the images and thumbnails and will use PHP's glob() function to return an array of files based on a pattern, image type files in our case. Other built-in functions used here will be count(), ceil(), array_slice() and filectime(). There will also be additional comments when these functions appear in the code. So to finally proceed to coding, we'll create the location function first.


<?php

function location() {

	// Path to images, use glob() to find jpg,jpeg,gif,png pattern and use GLOB_BRACE to find either or these patterns
	// Repeat the same for thumbnails and return all the four variables as an array that will be unpacked in the main files
	$path = "uploads/";
	$images = glob($path."*.{jpg,jpeg,gif,png}",GLOB_BRACE);

	$thumbpath = "uploads/thumbs/";
	$thumbs = glob($thumbpath."*.{jpg,jpeg,gif,png}",GLOB_BRACE);

	return array($path, $images, $thumbpath, $thumbs);
}

Next is pagination function which will use $images from location as an input variable


function pagina($images) {

	// Use count function to count all the images got from $images variable(see above)
	// Define how many items(images) per page and then get total pages by ceiling the division of total images and per page
	$count = count($images);
	$perpage = 16;
	$total = ceil($count / $perpage);

	// If the extra parameter in the url is set(page= in our case), then that is the current page.
	// Else the current page is 1, the first page.
	if(isset($_GET['page'])) {$currentpage = $_GET['page'];} 

	else {$currentpage = 1;}

	// While the above code should be enough for the logic, PHP needs details.
	// So if the current page is lower than 1, which shouldn't be unless we try manipulating the URL parameter, then current page again is the first page
	// Simiarily if the current page is greater than total, then the current page is total  
	if($currentpage < 1) {$currentpage = 1;}

	if($currentpage > $total) {$currentpage = $total;} 

	// Calculate the offset and then slice the number of images based on offset and perpage
	$offset = ($currentpage - 1) * $perpage;
	$result = array_slice($images, $offset, $perpage);

	// If current page is greater than first page, then display the previous button. Link to the same url with parameter page and substract 1 from the current page to get the correct page
	// Do the same if current page is smaller than total, display next button
	if($currentpage > 1) {$prev = '<a class="pagina" href="'.$_SERVER['PHP_SELF'].'?page='.($currentpage-1).'">lt; Prev </a>';}

	if($currentpage < $total) {$next = '<a class="pagina" href="'.$_SERVER['PHP_SELF'].'?page='.($currentpage+1).'"> Next gt;</a>';}

	// Set the range of pages (current + range to both left and right)
	// Create empty variables for the for loop so values can be appended based on logic
	$range = 2;
	$pages = "";
	$cpage = "";

	// Create a for loop to get the range spectre of the pages displayed
	// For example(range of 3) if current page is 1, then it would be for($page = -2; $page < 5; $page++), always a span of 7(3 to the left, current, 3 to the right)
	// But that's just where it will loop, further conditions are below...
	for($x = ($currentpage - $range); $x < (($currentpage + $range) + 1); $x++) {

		// Valid output is only when x is smaller or equal to total and bigger than 0
		if(($x <= $total) && ($x > 0)) {

			// If the image count is smaller or equal to per page, display no pages(in our case append this to the outside the loop variable)
			if($count <= $perpage) {$pages .= "";}

			// If x is equal to current page, display the page without hyperlink, also add the number to outside the loop variable to be used later
			elseif($x == $currentpage) {$pages .= '<span class="pagina">'.$x.'</span>'."\n"; $cpage .= $x;}

			// Else display the pages within the range with the hyperlink to that page
			else {$pages .= '<a class="pagina" href="'.$_SERVER['PHP_SELF'].'?page='.$x.'">'.$x.'</a>'."\n";} 
		}
	}

	// Return $result(sliced array of images), $pages(all that were valid in the for loop), $prev and $next buttons(when valid else empty), $perpage and $cpage(the number of the current page)
	return array($result, $prev, $pages, $next, $perpage, $cpage);
}

We are almost done here in the first part. All we need is to do is to define sorting of the files based on first upload date.


function byctime($x, $y) {

	// Getting values for $x, $y is just for comparison(all other files to $x)
	// If file $x ctime is equal to file $y ctime, return 0
	// If file $x ctime is smaller(older) than $y ctime, return -1
	// Else return 1
	if(filectime($x) === filectime($y)) {return 0;}

	elseif(filectime($x) > filectime($y)) {return -1;}

	else {return 1;}
}
?>

PART 2 - Create images and thumbnails from original files(gallery_admin.php)

In this part the most of the above functions won't be needed but some will, including a variable from pagination() function so we can use a header redirect after the upload was completed. Furthermore, PHP session will be used to store success and error messages and display them after the redirect. We will also add HTML form for uploading files in this part right at the top of the file so we can focus entirely on PHP afterwards. The built-in functions used will be again count() and glob() then the new functions are mb_strlen(), move_uploaded_file(), GD library functions to create images and thumbs, in_array(), array_map(), in a combination with unlink(), header(), usort() and session_start()


<?php
// Delay the buffer output and Start a session
ob_start();
session_start();
?>

<!-- Standard form for uploading files with only difference that array is expected --> 
<form enctype="multipart/form-data" action="" method="post">
<p>Upload Images (Limit 2MB per image)</p>
<input name="picture[]" type="file"><br/>
<input name="picture[]" type="file"><br/>
<input name="picture[]" type="file"><br/><br/>
<input type="submit" name="upload" value="Upload">
</form><br/>

<?php

// Unpacking the location() and pagina() functions
// Sort the images by the byctime function in functions.php
include_once('functions.php');
list($path, $images, $thumbpath, $thumbs) = location();
usort($images, "byctime");
list($result, $prev, $pages, $next, $perpage, $cpage) = pagina($images);

// This will be a function and as you can see some of the variables are expected inside
function uploadimages($path, $images, $cpage) {

	// When the button is clicked on, execute below...
	if(isset($_POST['upload'])) {

		// Count the files by name
		// Create empty variables for values to be appended from inside the for loop
		$total = count($_FILES['picture']['name']);
		$success = '';
		$emptyerror = '';
		$error = '';

		// Loop range from 0 to $total, each file is to be processed individiually
		for($x = 0; $x < $total; $x++) {

			// Check for empty input, append to $emptyerror for each
			// Why $emptyerror and $error are treated differently will become clear at the end of the function
			if(empty($_FILES['picture']['name'][$x])) {$emptyerror .= '<span style="color:red;">No input file! ('.($x+1).')</span>'."<br/><br/>";}

			// Check if file already exists in the gallery array
			elseif(in_array(($path.$_FILES['picture']['name'][$x]), $images)) {$error .= '<span style="color:red;">File already exists in gallery!(#'.($x+1).')</span>'."<br/><br/>";}

			// Check for file size, if bigger than 2mb, append to $error
			elseif($_FILES['picture']['size'][$x] > 2097152) {$error .= '<span style="color:red;">File size too large!('.($x+1).')</span>'."<br/><br/>";}

			// Check for file name length, if bigger than 100, append to $error
			elseif(mb_strlen($_FILES['picture']['name'][$x],"UTF8") > 100) {$error .= '<span style="color:red;">File name too large!('.($x+1).')</span>'."<br/><br/>";}

			// Check for file type. It can either be gif, jpeg or png but can't be more than that, like gif and png both. If no match, append to $error
			elseif(($_FILES['picture']['type'][$x] !== "image/gif") xor ($_FILES['picture']['type'][$x] !== "image/jpeg") xor ($_FILES['picture']['type'][$x] !== "image/png")) {$error .= '<span style="color:red;">The file is not an image!('.($x+1).')</span>'."<br/><br/>";}

			else {

				// Else move each of the files to tmp folder and create an $image identifier from it.
				// Unfortunately, each type(gif,jpeg,png) has to be processed separately, no other way in GD library
				$temp = "uploads/tmp/".$_FILES['picture']['name'][$x]; 
				move_uploaded_file($_FILES['picture']['tmp_name'][$x], $temp);

				if($_FILES['picture']['type'][$x] === "image/gif") {$image = imagecreatefromgif($temp);}

				if($_FILES['picture']['type'][$x] === "image/jpeg") {$image = imagecreatefromjpeg($temp);}

				if($_FILES['picture']['type'][$x] === "image/png") {$image = imagecreatefrompng($temp);}

				// Define paths for images and thumbnails to be created in, also set the thumbnails width and height(in pixels).
				$creationpath = "uploads/".$_FILES['picture']['name'][$x]; 
				$jpegquality = 55;
				$jpegthumbquality = 75;
				// PNG compression rate (6 is default, higher is better compression but lower quality)
				$pnggquality = 9;
				$pngthumbquality = 6;
				$thumbwidth = 120; 
				$thumbheight = 100; 
				$thumbscreation = "uploads/thumbs/".$_FILES['picture']['name'][$x]; 

				// Process and resize the images, needed for thumbnails
				$width = imagesx($image); 
				$height = imagesy($image); 
				// Uncomment this line to maintain aspect ratio.
				// $thumbheight= ($thumbwidth / $width) * $height;
				$newimage = imagecreatetruecolor($thumbwidth,$thumbheight);
				imagecopyresized($newimage,$image,0,0,0,0,$thumbwidth,$thumbheight,$width,$height);

				// Create images and thumbnails(based on specs) in the defined folders.
				if($_FILES['picture']['type'][$x] === "imagegif") {

					imagegif($image, $creationpath);
					imagegif($newimage, $thumbscreation);
				}

				if($_FILES['picture']['type'][$x] === "imagejpeg") {

					imagejpeg($image, $creationpath, $jpegquality);
					imagejpeg($newimage, $thumbscreation, $jpegthumbquality);
				}

				if($_FILES['picture']['type'][$x] === "imagepng") {

					imagepng($image, $creationpath, $pngquality);
					imagepng($newimage, $thumbscreation, $pngthumbquality);
				}

				// Delete the original file/s from tmp folder.
				$tmpfiles = glob('uploads/tmp/{,.}*', GLOB_BRACE);
				array_map('unlink', $tmpfiles);

				// append to $success message
				$success .= '<span style="color:green;">The image was successfully created.('.($x+1).')</span><br/><br/>';
			}
		}

		// Why $emptyerror and $error were separated
		// If not empty $error and $success, save them to a session and exit the script with redirect
		// Note that it redirects to the $cpage variable value which we got from pagination() function
		if(!empty($success) && !empty($error)) {

			$_SESSION['messages'] = $success.$error;
			exit(header("Location:gallery_admin.php?page=$cpage"));
		}

		// Else no redirect but echo $emptyerror
		else {echo $emptyerror;}
	}
}

Uploading and creating images and thumbnails is now resolved and should work exactly like designed. If that is not the case, make sure to set permissions for all three folders to 755. After that it should process the files, create images and thumbnails and delete original files. This can be tested right away. Just make sure to include the following to your code.(Note that this comes at the end in the script)


// First call the function with the expected input variable for the redirect
// Then echo the session and set session to null afterwards
uploadimages($path, $images, $cpage);
echo $_SESSION['messages'];$_SESSION['messages'] = NULL;

PART 3 - Create delete button and display the gallery admin(same file)

The following part will contain 2 functions, first will contain the button logic to delete both images and thumbnails when it is clicked. The second function will display the images and append the delete button to each of the images. As you'll be able to see, it pretty straightforward. The built-in functions used in this part will be again unlink() and header() and the new ones will be realpath(), file_exists(), strncmp(), strlen() and substr().


// Expected passed in variables from the location() and pagination() functions
function delbutton($path, $thumbpath, $thumbs, $cpage) {

	if(isset($_POST['delete'])) {

		// Since the button will be appended to each image, we can get the local path(from the root folder) of the image from $_POST request
		// And save it along with image name to a variable(E.g. uploads/example.png)
		// Define the full expected path to the images
		// Now get the full path of the $imgfile variable
		$imgfile = $_POST['delete'];
		$expectedpath = '/var/www/html/uploads/';
		$imgfilepath = realpath($imgfile);

		// Check if file exists, returns true or false.
		// Binary check for a match(of the expected path and actual file path for the length of the full expected path($expectedpath variable)).
		// Basically it cuts the $imagefilepath variable so that the string length is the same as in $expectedpath variable.
		// This is because we want to make sure that even if the file exists, that it exists in the correct directory.
		if(file_exists($imgfile) && (strncmp($imgfilepath, $expectedpath, strlen($expectedpath)) === 0)) {
		
			// If everything checks create a loop, strip images and thumbs of prefix local path
			// Cut the uploads/ and uploads/thumbs/ prefix(from uploads/example.png and uploads/thumbs/example.png) 
			foreach($thumbs as $thumb) {

				$thumbprefix = strlen($thumbpath);
				$imgprefix = strlen($path);
				$thumbname = substr($thumb, $thumbprefix);
				$imgname = substr($imgfile, $imgprefix);

				// Check for the name match of thumbnail and image and if it matches, delete thumbnail
				if($thumbname === $imgname) {unlink($thumb);}
			}

			// Then delete the image also
			// Redirect to the same file but the current page, giving the refresh effect
			unlink($imgfile);
			header("Location:gallery_admin.php?page=$cpage");
		}

		else {echo '<span style="color:red;">Unable to delete '.$imgfile.', the file does not exist or is not in the expected folder!</span>'."<br/><br/>";}
	}
}

The delete button is now set to be appended under each of the images in the gallery admin display.



// Expected passed in variables from the pagination() function
function display($result, $prev, $pages, $next, $perpage) {

	// Start by creating HTML table, this is needed because there is more than just image echoed in the cells
	echo "<table>"."\n";
	echo "<tr>"."\n";

	// Define how many images do you want per row and start the counter
	$perrow = 4;
	$count = 0;

	// Start the loop to define how each image will be displayed
	foreach($result as $image) {

		// Strip the image's path and add thumb path so that the thumbs are displayed(for performance)
		$imgprefix = strlen($path);
		$imgname = substr($image, $imgprefix);
		$thumb = $thumbpath.$imgname;
		$count++;

		If the $count is not zero, is not equal to $perpage and remainder of the division of $count to $perrow is zero, make a new row
		if($count != 0 && ($count != $perpage) && (($count % $perrow) == 0)) {

			// Note that beside the thubnail, there is also a hidden form that binds the delete button to the image
			echo '<td><img class="thumbnails" src="'.$thumb.'"/>';
			echo '<form method="post"><input type="hidden" value="'.$image.'" name="delete" />'; 
			echo '<input class="delbutton" type="submit" value="Delete"></form></td>'."\n".'</tr>'."\n".'<tr>'."\n";
		}

		// Else no new row
		else { 

			// Same as above but without the new table row
			echo '<td><img class="thumbnails" src="'.$thumb.'"/>';
			echo '<form method="post"><input type="hidden" value="'.$image.'" name="delete" />'; 
			echo '<input class="delbutton" type="submit" value="Delete"></form></td>'."\n";
		}
	}

	// Close the table and add pagination() variables to the gallery
	echo "</tr>"."\n";
	echo "</table>"."\n";
	echo $prev.$pages.$next."\n";
}
?>

All that is left now is to call all of the functions and the session in which the messages were stored. This was partially done already after part 2 in case you wanted to test the upload function. So make sure to delete that because now we'll add these together.


uploadimages($path, $images, $cpage);
echo $_SESSION['messages'];$_SESSION['messages'] = NULL;
delbutton($path, $thumbpath, $thumbs, $cpage)
display($result, $prev, $pages, $next, $perpage);

That's it, now the images and thumbnails can be safely created and thumbnails displayed with attached delete button according to our own choice how many we want per row and how many per page. Awesome! Only thing missing now is the fronted to display the images.

PART 4 - Display the gallery (gallery.php)

If you have endured until now, this will cheer you up because this is the easy part. We only need to unpack pagination() and byctime() functions to get the pagination and sorting and then display the thumbnails that will link to the fullsized images. Note that since this is a fronted, we will use basic HTML tags instead of tables to display the gallery.


<?php 
include_once('functions.php');

list($path, $images, $thumbpath) = location();
usort($images, "byctime");
list($result, $prev, $pages, $next) = pagina($images);

$perrow = 3;
$count = 0;
foreach ($result as $image) {

	// Generate the original path of the thumb by appending the name generated to the thumbs path.
	$imgprefix = strlen($path);
	$imgname = substr($image, $imgprefix);
	$thumb = $thumbpath.$imgname;
	$count++;

	// Now we have both paths of the images and thumbnails and are able to generate links to images from thumbnails
	if($count != 0 && ($count != $perpage) && (($count % $perrow) == 0)) {echo '<a href="'.$image.'"><img class="images" src="'.$thumb.'"/></a><br/>'."\n";}

	else {echo '<a href="'.$image.'"><img class="images" src="'.$thumb.'"/></a>'."\n";}
}
echo "<br/>."\n";
echo $prev.$pages.$next;
?>

And we're done. One other thing that I have included in the gallery, which btw you can download here, is the CSS file that allows easy customization of the gallery. If you have any questions or comments regarding the gallery, please post them below.


Comments:

Be the first to comment.

Add a comment:










I have read and agree with the Privacy terms and conditions.