In this tutorial, we'll be creating a secure gallery system with PHP, MySQL, and JavaScript. We'll use PHP and HTML to populate all our images, which will be retrieved from the MySQL database. In addition, we'll create an innovative pop-up window that will display the corresponding image at a higher resolution.

What is a gallery system?

We can use the gallery system to showcase and organize our images. As the system is based online, anyone with the URL can view and upload images.

The Advanced package includes additional features and a download link to the source code. In addition, it contains the tutorial source code and basic version.

1. Getting Started

There are a few steps we need to take before we create our gallery system with PHP. We need to set up our web server environment (if you haven't already) and ensure we have the required extensions enabled.

1.1. Requirements

  • Web Server — If you haven't installed a local web server solution package, I recommend you download and install XAMPP. XAMPP is an open-source web server solution package.
  • PHP PDO Extension — As we are going to use the PHP PDO extension, make sure that it is enabled. It usually is by default, but in some circumstances, it may not be.

1.2. What You Will Learn in this Tutorial

  • Template System — Create a basic template system with PHP. We'll use PHP functions to create the template header and footer functions.
  • Upload Images — Upload images to the server using an HTML form and PHP.
  • MySQL Interaction — Insert image information into a MySQL database and retrieve results.
  • Delete Images — Delete image files from the server (PHP) and delete records from a MySQL database.
  • Image Pop-up Window with JS — Create a pop-up window with JavaScript that will display the corresponding image along with the title and description.
  • Gallery System Design — Design a gallery system with HTML5 and CSS3.

1.3. File Structure & Setup

We will now start our web server and create the files and directories we're going to use for our gallery system.

  • Open the XAMPP Control Panel
  • Next to the Apache module, click Start
  • Next to the MySQL module, click Start
  • Navigate to XAMPPs installation folder (C:\xampp)
  • Open the htdocs directory
  • Create the following directories and files:

File Structure

\-- phpgallery
    |-- functions.php
    |-- index.php
    |-- upload.php
    |-- delete.php
    |-- style.css
    \-- images
     |-- abandoned-building.jpg
     |-- beach.jpg
     |-- city.jpg
     |-- mountain.jpg
     |-- road.jpg
     |-- stars.jpg

The above files and directories will contain the following:

  • functions.php — This file will contain all the functions we need for our gallery system (template header, template footer, and database connection function).
  • index.php — Populate all the images from the MySQL database and implement navigation buttons to link to the paginated pages.
  • upload.php — This file will be used to upload images and insert the image details into our MySQL database.
  • delete.php — This file will be used to delete an image from our server and the associated details from the MySQL database.
  • style.php — The stylesheet (CSS3) for our gallery system.
  • images — This directory will contain all our uploaded images. You can download all the images in the file structure container above.

2. Creating the Database and setting-up Tables

For this part, you will need to access your MySQL database, either using phpMyAdmin (included with XAMPP) or your preferred MySQL database management application.

If you're using phpMyAdmin, follow these instructions:

  • Navigate to http://localhost/phpmyadmin/
  • Click the Databases tab at the top
  • Under Create database, type in phpgallery in the text box
  • Select utf8_general_ci as the collation (UTF-8 is the default encoding in HTML5)
  • Click Create

That will create the MySQL database we're going to use. Now, we need to create the table and insert the example images that are located in the images directory. We can simply execute the below query to insert all the details for our images into the database as opposed to creating all the tables and records individually. Be sure to select the phpgallery database first and click the SQL tab located at the top.

SQL
CREATE TABLE IF NOT EXISTS `images` (
	`id` int(11) NOT NULL AUTO_INCREMENT,
	`title` text NOT NULL,
  	`description` text NOT NULL,
  	`filepath` text NOT NULL,
  	`uploaded_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

INSERT INTO `images` (`id`, `title`, `description`, `filepath`, `uploaded_date`) VALUES
(1, 'Abandoned Building', '', 'images/abandoned-building.jpg', '2019-07-16 20:09:26'),
(2, 'Beach', 'Hot summer day at the beach.', 'images/beach.jpg', '2019-07-16 20:10:05'),
(3, 'City', 'A view down at the city.', 'images/city.jpg', '2019-07-16 20:10:45'),
(4, 'Mountain', '', 'images/mountain.jpg', '2019-07-16 20:11:27'),
(5, 'Road', 'Going down the only road I''ve even known.', 'images/road.jpg', '2019-07-16 20:12:00'),
(6, 'Stars', 'A wonderful view of the night sky.', 'images/stars.jpg', '2019-07-16 20:12:39');

The above statement will create the images table with the following columns:

  • id — The unique identifier of the record. We can use this column to identify which column is which.
  • title — The title of the image.
  • description — The description of the image.
  • filepath — The file path of the image.
  • uploaded_date — The date the image was uploaded.

Take note, we have also inserted the example images into the images table. To make sure they show on your gallery system correctly, you need to download the images from the File Structure section and put them in the images directory.

The images table structure should appear as the following on phpMyAdmin:

http://localhost/phpmyadmin/
MySQL Images Table

That's all we need to do for our MySQL database. Feel free to close phpMyAdmin as we no longer need to use it.

3. Designing the Gallery System with CSS

To make our gallery system look more appealing, we need to add some CSS. Add the following CSS to the style.css file:

CSS
* {
    box-sizing: border-box;
    font-family: -apple-system, BlinkMacSystemFont, "segoe ui", roboto, oxygen, ubuntu, cantarell, "fira sans", "droid sans", "helvetica neue", Arial, sans-serif;
    font-size: 16px;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
body {
    background-color: #FFFFFF;
    margin: 0;
}
.navtop {
    background-color: #3f69a8;
    height: 60px;
    width: 100%;
    border: 0;
}
.navtop div {
    display: flex;
    margin: 0 auto;
    width: 1000px;
    height: 100%;
}
.navtop div h1, .navtop div a {
    display: inline-flex;
    align-items: center;
}
.navtop div h1 {
    flex: 1;
    font-size: 24px;
    padding: 0;
    margin: 0;
    color: #ecf0f6;
    font-weight: normal;
}
.navtop div a {
    padding: 0 20px;
    text-decoration: none;
    color: #c5d2e5;
    font-weight: bold;
}
.navtop div a i {
    padding: 2px 8px 0 0;
}
.navtop div a:hover {
    color: #ecf0f6;
}
.content {
    width: 1000px;
    margin: 0 auto;
}
.content h2 {
    margin: 0;
    padding: 25px 0;
    font-size: 22px;
    border-bottom: 1px solid #ebebeb;
    color: #666666;
}
.image-popup {
    display: none;
    flex-flow: column;
    justify-content: center;
    align-items: center;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    z-index: 99999;
}
.image-popup .con {
    display: flex;
    flex-flow: column;
    background-color: #ffffff;
    padding: 25px;
    border-radius: 5px;
}
.image-popup .con h3 {
    margin: 0;
    font-size: 18px;
}
.image-popup .con .edit, .image-popup .con .trash {
    display: inline-flex;
    justify-content: center;
    align-self: flex-end;
    width: 40px;
    text-decoration: none;
    color: #FFFFFF;
    padding: 10px 12px;
    border-radius: 5px;
    margin-top: 10px;
}
.image-popup .con .trash {
    background-color: #b73737;
}
.image-popup .con .trash:hover {
    background-color: #a33131;
}
.image-popup .con .edit {
    background-color: #37afb7;
}
.image-popup .con .edit:hover {
    background-color: #319ca3;
}
.home .upload-image {
    display: inline-block;
    text-decoration: none;
    background-color: #38b673;
    font-weight: bold;
    font-size: 14px;
    border-radius: 5px;
    color: #FFFFFF;
    padding: 10px 15px;
    margin: 15px 0;
}
.home .upload-image:hover {
    background-color: #32a367;
}
.home .images {
    display: flex;
    flex-flow: wrap;
}
.home .images a {
    display: block;
    text-decoration: none;
    position: relative;
    overflow: hidden;
    width: 300px;
    height: 200px;
    margin: 0 20px 20px 0;
}
.home .images a:hover span {
    opacity: 1;
    transition: opacity 1s;
}
.home .images span {
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
    position: absolute;
    opacity: 0;
    top: 0;
    left: 0;
    color: #fff;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.2);
    padding: 15px;
    transition: opacity 1s;
}
.upload form {
    padding: 15px 0;
    display: flex;
    flex-flow: column;
    width: 400px;
}
.upload form label {
    display: inline-flex;
    width: 100%;
    padding: 10px 0;
    margin-right: 25px;
}
.upload form input, .upload form textarea {
    padding: 10px;
    width: 100%;
    margin-right: 25px;
    margin-bottom: 15px;
    border: 1px solid #cccccc;
}
.upload form textarea {
    height: 200px;
}
.upload form input[type="submit"] {
    display: block;
    background-color: #38b673;
    border: 0;
    font-weight: bold;
    font-size: 14px;
    color: #FFFFFF;
    cursor: pointer;
    width: 200px;
    margin-top: 15px;
    border-radius: 5px;
}
.upload form input[type="submit"]:hover {
    background-color: #32a367;
}
.delete .yesno {
    display: flex;
}
.delete .yesno a {
    display: inline-block;
    text-decoration: none;
    background-color: #38b673;
    font-weight: bold;
    color: #FFFFFF;
    padding: 10px 15px;
    margin: 15px 10px 15px 0;
    border-radius: 5px;
}
.delete .yesno a:hover {
    background-color: #32a367;
}

The above code is the stylesheet we'll use for the gallery system. Simple and clean design. Not as fancy as it could be, but it's a great starting point. You are free to customize it to your liking, just don't forget to clear the cache in your browser when making changes to it.

4. Creating the Functions File

The functions file will contain the template header, template footer, and the database connection function. The functions will enable us to execute code without implementing the same code on all the pages that'll leverage our functions.

Edit the functions.php file and add:

PHP
<?php
function pdo_connect_mysql() {
    // The below variables should reflect your MySQL credentials
    $DATABASE_HOST = 'localhost';
    $DATABASE_USER = 'root';
    $DATABASE_PASS = '';
    $DATABASE_NAME = 'phpgallery';
    try {
        // Connect to MySQL using the PDO extension
    	return new PDO('mysql:host=' . $DATABASE_HOST . ';dbname=' . $DATABASE_NAME . ';charset=utf8', $DATABASE_USER, $DATABASE_PASS);
    } catch (PDOException $exception) {
    	// If there is an error with the connection, stop the script and output the error.
    	exit('Failed to connect to database!');
    }
}

The above function will be used to connect to our MySQL database using the PDO interface. If somehow you fail to connect using the above details, you will have to update the database variables and make sure they reflect your MySQL credentials.

Add after:

PHP
// Template header; feel free to customize it, but do not indent the PHP code or it will throw an error
function template_header($title) {
echo <<<EOT
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>$title</title>
		<link href="style.css" rel="stylesheet" type="text/css">
		<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css">
	</head>
	<body>
    <nav class="navtop">
    	<div>
    		<h1>Gallery System</h1>
            <a href="index.php"><i class="fas fa-image"></i>Gallery</a>
    	</div>
    </nav>
EOT;
}

The above code is the header function we'll use for all the pages we create. All we have to do is execute the function name as opposed to writing the same code in every file that we create. Feel free to customize the header. We're using the awesome Font Awesome library to display icons on our pages.

Add after:

PHP
// Template footer
function template_footer() {
echo <<<EOT
    </body>
</html>
EOT;
}
?>

The above footer template will close all the opening tags that we defined in the header function. We wouldn't want an invalid document containing unclosed tags; definitely not ideal for SEO.

5. Creating the Home Page

The home page will contain all the images that we have uploaded and navigation links to the other pages.

Edit the index.php file and add:

PHP
<?php
include 'functions.php';
// Connect to MySQL
$pdo = pdo_connect_mysql();
// MySQL query that selects all the images
$stmt = $pdo->query('SELECT * FROM images ORDER BY uploaded_date DESC');
$images = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>

First, we include the functions.php file, which contains the functions that we're going to use. After, we connect to our MySQL database by executing the pdo_connect_mysql() function and subsequently retrieve all the details for the images that we have uploaded from our database table images, ordered by the uploaded date column (descending).

Add after:

PHP
<?=template_header('Gallery')?>

<div class="content home">
	<h2>Gallery</h2>
	<p>Welcome to the gallery page! You can view the list of uploaded images below.</p>
	<a href="upload.php" class="upload-image">Upload Image</a>
	<div class="images">
		<?php foreach ($images as $image): ?>
		<?php if (file_exists($image['filepath'])): ?>
		<a href="#">
			<img src="<?=$image['filepath']?>" alt="<?=$image['description']?>" data-id="<?=$image['id']?>" data-title="<?=$image['title']?>" width="300" height="200">
			<span><?=$image['description']?></span>
		</a>
		<?php endif; ?>
		<?php endforeach; ?>
	</div>
</div>
<div class="image-popup"></div>

As you can see above, all that we have to do to include the header template is to execute the function name (along with the title), which we have done above.

In addition, we populate all the images retrieved from the MySQL database in HTML format. With the foreach function, we can iterate the associative array and handle each record accordingly. The file_exists function is self-explanatory; it will check whether the image exists or not.

The data-* attributes will be used to store information related to the image, which we can subsequently retrieve with JavaScript. They will determine what to output in the pop-up window.

Add after:

JavaScript
<script>
// Container we'll use to output the image
let image_popup = document.querySelector('.image-popup');
// Iterate the images and apply the onclick event to each individual image
document.querySelectorAll('.images a').forEach(img_link => {
	img_link.onclick = e => {
		e.preventDefault();
		let img_meta = img_link.querySelector('img');
		let img = new Image();
		img.onload = () => {
			// Create the pop out image
			image_popup.innerHTML = `
				<div class="con">
					<h3>${img_meta.dataset.title}</h3>
					<p>${img_meta.alt}</p>
					<img src="${img.src}" width="${img.width}" height="${img.height}">
					<a href="delete.php?id=${img_meta.dataset.id}" class="trash" title="Delete Image"><i class="fas fa-trash fa-xs"></i></a>
				</div>
			`;
			image_popup.style.display = 'flex';
		};
		img.src = img_meta.src;
	};
});
// Hide the image popup container, but only if the user clicks outside the image
image_popup.onclick = e => {
	if (e.target.className == 'image-popup') {
		image_popup.style.display = "none";
	}
};
</script>

The above JavaScript code will create the pop-up window for the corresponding image. If you prefer to put your JavaScript code in a separate JS file, you can do that too.

Add after:

PHP
<?=template_footer()?>

And now we can end the template for the home page by executing the template footer function.

That's all we need to do for the home page. If we navigate to http://localhost/phpgallery/index.php, we'll see the following:

http://localhost/phpgallery/index.php
PHP MySQL Gallery Home Page

And if we click one of the images, we'll see the following:

http://localhost/phpgallery/index.php
PHP MySQL Gallery Popup

The pop-up image effect is created with JavaScript, and therefore we don't need to reload the page every time we click a different one. We can close the pop-up window by clicking outside of the image container.

6. Creating the Upload Page

The upload page is the page we'll use to upload new images and store information related to those images in our MySQL database images table.

Edit the upload.php file and add:

PHP
<?php
include 'functions.php';
// The output message
$msg = '';
// Check if the user has uploaded a new image
if (isset($_FILES['image'], $_POST['title'], $_POST['description'])) {
	// The folder where the images will be stored
	$target_dir = 'images/';
	// The path of the newly uploaded image
	$image_path = $target_dir . basename($_FILES['image']['name']);
	// Check to make sure the image is valid
	if (!empty($_FILES['image']['tmp_name']) && getimagesize($_FILES['image']['tmp_name'])) {
		if (file_exists($image_path)) {
			$msg = 'Image already exists, please choose another or rename that image.';
		} else if ($_FILES['image']['size'] > 500000) {
			$msg = 'Image file size too large, please choose an image less than 500kb.';
		} else {
			// Everything checks out now we can move the uploaded image
			move_uploaded_file($_FILES['image']['tmp_name'], $image_path);
			// Connect to MySQL
			$pdo = pdo_connect_mysql();
			// Insert image info into the database (title, description, image path, and date added)
			$stmt = $pdo->prepare('INSERT INTO images (title, description, filepath, uploaded_date) VALUES (?, ?, ?, CURRENT_TIMESTAMP)');
	        $stmt->execute([ $_POST['title'], $_POST['description'], $image_path ]);
			$msg = 'Image uploaded successfully!';
		}
	} else {
		$msg = 'Please upload an image!';
	}
}
?>

The above code will upload a new image to the images directory and store details associated with it in the MySQL database, but before the image is uploaded, we first check if the image file already exists and make sure it's actually an image. We use the getimagesize() function to validate the image.

In addition, if the uploaded image already exists, the code will output an error message to the user. The maximum image size is set to 500kb, which you can change if you want to upload images with larger file sizes.

Finally, if no error messages have occurred, the image details will be inserted into our MySQL images table.

Add after:

PHP
<?=template_header('Upload Image')?>

<div class="content upload">
	<h2>Upload Image</h2>
	<form action="upload.php" method="post" enctype="multipart/form-data">
		<label for="image">Choose Image</label>
		<input type="file" name="image" accept="image/*" id="image">
		<label for="title">Title</label>
		<input type="text" name="title" id="title">
		<label for="description">Description</label>
		<textarea name="description" id="description"></textarea>
	    <input type="submit" value="Upload Image" name="submit">
	</form>
	<p><?=$msg?></p>
</div>

<?=template_footer()?>

The above code is the upload template, which consists of an HTML form that we'll use to enter details related to the corresponding image.

Moreover, when the user clicks the submit button, the form is processed using the PHP code that we added previously. The request method is set to POST, which is used to send data to a server. Unlike a GET request, the POST method will not append the parameters to the URL.

If we navigate to the upload page, we'll see the following:

http://localhost/phpgallery/upload.php
PHP MySQL Upload Image Form

To test that it is working correctly, select an image and fill out the form — remember the image needs to be less than 500kb, and click the Upload Image button after you've finished. You should see the response located below the form.

7. Creating the Delete Page

The delete page will be used to delete images that we have uploaded. The specified image will be deleted from the images directory, and information associated with the image will be deleted from the MySQL images table.

Edit the delete.php file and add:

PHP
<?php
include 'functions.php';
$pdo = pdo_connect_mysql();
$msg = '';
// Check that the image ID exists
if (isset($_GET['id'])) {
    // Select the record that is going to be deleted
    $stmt = $pdo->prepare('SELECT * FROM images WHERE id = ?');
    $stmt->execute([ $_GET['id'] ]);
    $image = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$image) {
        exit('Image doesn\'t exist with that ID!');
    }
    // Make sure the user confirms before deletion
    if (isset($_GET['confirm'])) {
        if ($_GET['confirm'] == 'yes') {
            // User clicked the "Yes" button, delete file & delete record
            unlink($image['filepath']);
            $stmt = $pdo->prepare('DELETE FROM images WHERE id = ?');
            $stmt->execute([ $_GET['id'] ]);
            // Output msg
            $msg = 'You have deleted the image!';
        } else {
            // User clicked the "No" button, redirect them back to the home/index page
            header('Location: index.php');
            exit;
        }
    }
} else {
    exit('No ID specified!');
}
?>

Before the corresponding image is deleted, the code requires the ID of the image, which is retrieved from a GET parameter, as it will determine which image we're going to delete. The ID parameter is associated with the ID in the MySQL database.

We also check whether the image is in the database and check whether the user has clicked the Yes confirmation button. If the user clicks the Yes button, the code will delete the file from the server using the unlink() function and subsequently execute a MySQL DELETE FROM statement that will delete the image record from the database.

Add after:

PHP
<?=template_header('Delete')?>

<div class="content delete">
	<h2>Delete Image #<?=$image['id']?></h2>
    <?php if ($msg): ?>
    <p><?=$msg?></p>
    <?php else: ?>
	<p>Are you sure you want to delete <?=$image['title']?>?</p>
    <div class="yesno">
        <a href="delete.php?id=<?=$image['id']?>&confirm=yes">Yes</a>
        <a href="delete.php?id=<?=$image['id']?>&confirm=no">No</a>
    </div>
    <?php endif; ?>
</div>

<?=template_footer()?>

The above code is the template for our delete page, which will confirm to the user with a Yes and No button if they want to delete the image.

If we navigate to the home page and click on one of the images, we will see the delete icon, and if we click on that, we'll see something similar to the following:

http://localhost/tutorial/delete.php?id=6
PHP MySQL Gallery Delete

If we click the Yes button, the image will be deleted from our images directory and from our MySQL database.

Conclusion

Congratulations! You've successfully created a fully functioning secure gallery system with PHP, MySQL, and JavaScript. What next? Consider implementing innovative functionalities and extend the system with unique features.

You should now have a basic understanding of how to populate and upload images with PHP.

Consider sharing the article on social websites if you've found it helpful, and drop a comment below. We greatly appreciate the support.

Enjoy coding, and thank you for reading!