





















































In this article by Rob Foster, the author of CodeIgniter Web Application Blueprints, we will create a photo-sharing application. There are quite a few image-sharing websites around at the moment. They all share roughly the same structure: the user uploads an image and that image can be shared, allowing others to view that image. Perhaps limits or constraints are placed on the viewing of an image, perhaps the image only remains viewable for a set period of time, or within set dates, but the general structure is the same. And I'm happy to announce that this project is exactly the same.
We'll create an application allowing users to share pictures; these pictures are accessible from a unique URL. To make this app, we will create two controllers: one to process image uploading and one to process the viewing and displaying of images stored.
We'll create a language file to store the text, allowing you to have support for multiple languages should it be needed.
We'll create all the necessary view files and a model to interface with the database.
In this article, we will cover:
So without further ado, let's get on with it.
(For more resources related to this topic, see here.)
As always, before we start building, we should take a look at what we plan to build.
First, a brief description of our intent: we plan to build an app to allow the user to upload an image. That image will be stored in a folder with a unique name. A URL will also be generated containing a unique code, and the URL and code will be assigned to that image. The image can be accessed via that URL.
The idea of using a unique URL to access that image is so that we can control access to that image, such as allowing an image to be viewed only a set number of times, or for a certain period of time only.
Anyway, to get a better idea of what's happening, let's take a look at the following site map:
So that's the site map. The first thing to notice is how simple the site is. There are only three main areas to this project. Let's go over each item and get a brief idea of what they do:
The user is then presented with a message informing them that their image has been uploaded and that a URL has been created. The user is also presented with the image they have uploaded.
Now that we have a fairly good idea of the structure and form of the site, let's take a look at the wireframes of each page.
The following screenshot shows a wireframe for the create item discussed in the previous section. The user is shown a simple form allowing them to upload an image.
Image2
The following screenshot shows a wireframe from the do_upload item discussed in the previous section. The user is shown the image they have uploaded and the URL that will direct other users to that image.
The following screenshot shows a wireframe from the go item described in the previous section. The go controller takes the unique code in a URL, attempts to find it in the database table images, and if found, supplies the image associated with it. Only the image is supplied, not the actual HTML markup.
This is a relatively small project, and all in all we're only going to create seven files, which are as follows:
The file structure of the preceding seven files is as follows:
application/ ├── controllers/ │ ├── create.php │ ├── go.php ├── models/ │ ├── image_model.php ├── views/create/ │ ├── create.php │ ├── result.php ├── views/nav/ │ ├── top_nav.php ├── language/english/ │ ├── en_admin_lang.php
First, we'll build the database. Copy the following MySQL code into your database:
CREATE DATABASE `imagesdb`; USE `imagesdb`; DROP TABLE IF EXISTS `images`; CREATE TABLE `images` ( `img_id` int(11) NOT NULL AUTO_INCREMENT, `img_url_code` varchar(10) NOT NULL, `img_url_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `img_image_name` varchar(255) NOT NULL, `img_dir_name` varchar(8) NOT NULL, PRIMARY KEY (`img_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Right, let's take a look at each item in every table and see what they mean:
Table: images |
|
Element |
Description |
img_id |
This is the primary key. |
img_url_code |
This stores the unique code that we use to identify the image in the database. |
img_url_created_at |
This is the MySQL timestamp for the record. |
img_image_name |
This is the filename provided by the CodeIgniter upload functionality. |
img_dir_name |
This is the name of the directory we store the image in. |
We'll also need to make amends to the config/database.php file, namely setting the database access details, username, password, and so on.
Open the config/database.php file and find the following lines:
$db['default']['hostname'] = 'localhost'; $db['default']['username'] = 'your username'; $db['default']['password'] = 'your password'; $db['default']['database'] = 'imagesdb';
Edit the values in the preceding code ensuring you substitute those values for the ones more specific to your setup and situation—so enter your username, password, and so on.
We don't actually need to adjust the config.php file in this project as we're not really using sessions or anything like that. So we don't need an encryption key or database information.
So just ensure that you are not autoloading the session in the config/autoload.php file or you will get an error, as we've not set any session variables in the config/config.php file.
We want to redirect the user to the create controller rather than the default CodeIgniter welcome controller. To do this, we will need to amend the default controller settings in the routes.php file to reflect this. The steps are as follows:
$route['default_controller'] = "welcome"; $route['404_override'] = '';
$route['default_controller'] = "welcome";
Replace it with the following lines:
$route['default_controller'] = "create"; $route['404_override'] = '';
Leave a few blank lines underneath the preceding two lines of code (default controller and 404 override) and add the following three lines of code:
$route['create'] = "create/index"; $route['(:any)'] = "go/index"; $route['create/do_upload'] = "create/do_upload";
There is only one model in this project, image_model.php. It contains functions specific to creating and resetting passwords.
Create the /path/to/codeigniter/application/models/image_model.php file and add the following code to it:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class Image_model extends CI_Model { function __construct() { parent::__construct(); } function save_image($data) { do { $img_url_code = random_string('alnum', 8); $this->db->where('img_url_code = ', $img_url_code); $this->db->from('images'); $num = $this->db->count_all_results(); } while ($num >= 1); $query = "INSERT INTO `images` (`img_url_code`, `img_image_name`, `img_dir_name`) VALUES (?,?,?) "; $result = $this->db->query($query, array($img_url_code, $data['image_name'], $data['img_dir_name'])); if ($result) { return $img_url_code; } else { return flase; } } function fetch_image($img_url_code) { $query = "SELECT * FROM `images` WHERE `img_url_code` = ? "; $result = $this->db->query($query, array($img_url_code)); if ($result) { return $result; } else { return false; } } }
There are two main functions in this model, which are as follows:
Okay, let's take save_image() first. The save_image() function accepts an array from the create controller containing image_name (from the upload process) and img_dir_name (this is the folder that the image is stored in).
A unique code is generated using a do…while loop as shown here:
$img_url_code = random_string('alnum', 8);
First a string is created, eight characters in length, containing alpha-numeric characters. The do…while loop checks to see if this code already exists in the database, generating a new code if it is already present. If it does not already exist, this code is used:
do { $img_url_code = random_string('alnum', 8); $this->db->where('img_url_code = ', $img_url_code); $this->db->from('images'); $num = $this->db->count_all_results(); } while ($num >= 1);
This code and the contents of the $data array are then saved to the database using the following code:
$query = "INSERT INTO `images` (`img_url_code`, `img_image_name`, `img_dir_name`) VALUES (?,?,?) "; $result = $this->db->query($query, array($img_url_code, $data['image_name'], $data['img_dir_name']));
The $img_url_code is returned if the INSERT operation was successful, and false if it failed. The code to achieve this is as follows:
if ($result) { return $img_url_code; } else { return false; }
There are only three views in this project, which are as follows:
So those are our views, as I said, there are only three of them as it's a simple project. Now, let's create each view file.
Create the /path/to/codeigniter/application/views/create/create.php file and add the following code to it:
<div class="page-header"> <h1><?php echo $this->lang->line('system_system_name'); ?></h1> </div> <p><?php echo $this->lang->line('encode_instruction_1'); ?></p> <?php echo validation_errors(); ?> <?php if (isset($success) && $success == true) : ?> <div class="alert alert-success"> <strong><?php echo $this->lang->line(' common_form_elements_success_notifty'); ?></strong> <?php echo $this->lang-> line('encode_encode_now_success'); ?> </div> <?php endif ; ?> <?php if (isset($fail) && $fail == true) : ?> <div class="alert alert-danger"> <strong><?php echo $this->lang->line(' common_form_elements_error_notifty'); ?> </strong> <?php echo $this->lang->line('encode_encode_now_error '); ?> <?php echo $fail ; ?> </div> <?php endif ; ?> <?php echo form_open_multipart('create/do_upload');?> <input type="file" name="userfile" size="20" /> <br /> <input type="submit" value="upload" /> <?php echo form_close() ; ?> <br /> <?php if (isset($result) && $result == true) : ?> <div class="alert alert-info"> <strong><?php echo $this->lang->line(' encode_upload_url'); ?> </strong> <?php echo anchor($result, $result) ; ?> </div> <?php endif ; ?>
This view file can be thought of as the main view file; it is here that the user can upload their image. Error messages are displayed here too.
<div class="page-header"> <h1><?php echo $this->lang->line('system_system_name'); ?></h1> </div> <?php if (isset($result) && $result == true) : ?> <strong><?php echo $this->lang->line(' encode_encoded_url'); ?> </strong> <?php echo anchor($result, $result) ; ?> <br /> <img src="<?php echo base_url() . 'upload/' . $img_dir_name . '/' . $file_name ;?>" /> <?php endif ; ?>
This view will display the encoded image resource URL to the user (so they can copy and share it) and the actual image itself.
<!-- Fixed navbar --> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-
toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#"><?php echo $this- >lang->line('system_system_name'); ?></a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li class="active"><?php echo anchor('create', 'Create') ; ?></li> </ul> </div><!--/.nav-collapse --> </div> </div> <div class="container theme-showcase" role="main">
This view is quite basic but still serves an important role. It displays an option to return to the index() function of the create controller.
We're going to create two controllers in this project, which are as follows:
These are two of our controllers for this project, let's now go ahead and create them.
Create the /path/to/codeigniter/application/controllers/create.php file and add the following code to it:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); class Create extends MY_Controller { function __construct() { parent::__construct(); $this->load->helper(array('string')); $this->load->library('form_validation'); $this->load->library('image_lib'); $this->load->model('Image_model'); $this->form_validation->set_error_delimiters('<div class="alert alert-danger">', '</div>'); } public function index() { $page_data = array('fail' => false, 'success' => false); $this->load->view('common/header'); $this->load->view('nav/top_nav'); $this->load->view('create/create', $page_data); $this->load->view('common/footer'); } public function do_upload() { $upload_dir = '/filesystem/path/to/upload/folder/'; do { // Make code $code = random_string('alnum', 8); // Scan upload dir for subdir with same name // name as the code $dirs = scandir($upload_dir); // Look to see if there is already a // directory with the name which we // store in $code if (in_array($code, $dirs)) { // Yes there is $img_dir_name = false; // Set to false to begin again } else { // No there isn't $img_dir_name = $code; // This is a new name } } while ($img_dir_name == false); if (!mkdir($upload_dir.$img_dir_name)) { $page_data = array('fail' => $this->lang-> line('encode_upload_mkdir_error'), 'success' => false); $this->load->view('common/header'); $this->load->view('nav/top_nav'); $this->load->view('create/create', $page_data); $this->load->view('common/footer'); } $config['upload_path'] = $upload_dir.$img_dir_name; $config['allowed_types'] = 'gif|jpg|jpeg|png'; $config['max_size'] = '10000'; $config['max_width'] = '1024'; $config['max_height'] = '768'; $this->load->library('upload', $config); if ( ! $this->upload->do_upload()) { $page_data = array('fail' => $this->upload-> display_errors(), 'success' => false); $this->load->view('common/header'); $this->load->view('nav/top_nav'); $this->load->view('create/create', $page_data); $this->load->view('common/footer'); } else { $image_data = $this->upload->data(); $page_data['result'] = $this->Image_model->save_image( array('image_name' => $image_data['file_name'], 'img_dir_name' => $img_dir_name)); $page_data['file_name'] = $image_data['file_name']; $page_data['img_dir_name'] = $img_dir_name; if ($page_data['result'] == false) { // success - display image and link $page_data = array('fail' => $this->lang-> line('encode_upload_general_error')); $this->load->view('common/header'); $this->load->view('nav/top_nav'); $this->load->view('create/create', $page_data); $this->load->view('common/footer'); } else { // success - display image and link $this->load->view('common/header'); $this->load->view('nav/top_nav'); $this->load->view('create/result', $page_data); $this->load->view('common/footer'); } } } }
Let's start with the index() function. The index() function sets the fail and success elements of the $page_data array to false. This will suppress any initial messages from being displayed to the user. The views are loaded, specifically the create/create.php view, which contains the image upload form's HTML markup.
Once the user submits the form in create/create.php, the form will be submitted to the do_upload() function of the create controller. It is this function that will perform the task of uploading the image to the server.
First off, do_upload() defines an initial location for the upload folder. This is stored in the $upload_dir variable.
Next, we move into a do…while structure. It looks something like this:
do { // something } while ('…a condition is not met');
So that means do something while a condition is not being met. Now with that in mind, think about our problem—we have to save the image being uploaded in a folder. That folder must have a unique name. So what we will do is generate a random string of eight alpha-numeric characters and then look to see if a folder exists with that name. Keeping that in mind, let's look at the code in detail:
do { // Make code $code = random_string('alnum', 8); // Scan uplaod dir for subdir with same name // name as the code $dirs = scandir($upload_dir); // Look to see if there is already a // directory with the name which we // store in $code if (in_array($code, $dirs)) { // Yes there is $img_dir_name = false; // Set to false to begin again } else { // No there isn't $img_dir_name = $code; // This is a new name } } while ($img_dir_name == false);
So we make a string of eight characters, containing only alphanumeric characters, using the following line of code:
$code = random_string('alnum', 8);
We then use the PHP function scandir() to look in $upload_dir. This will store all directory names in the $dirs variable, as follows:
$dirs = scandir($upload_dir);
We then use the PHP function in_array() to look for the value in $code in the list of directors from scandir():
If we don't find a match, then the value in $code must not be taken, so we'll go with that. If the value is found, then we set $img_dir_name to false, which is picked up by the final line of the do…while loop:
... } while ($img_dir_name == false);
Anyway, now that we have our unique folder name, we'll attempt to create it. We use the PHP function mkdir(), passing to it $upload_dir concatenated with $img_dir_name. If mkdir() returns false, the form is displayed again along with the encode_upload_mkdir_error message set in the language file, as shown here:
if (!mkdir($upload_dir.$img_dir_name)) { $page_data = array('fail' => $this->lang-> line('encode_upload_mkdir_error'), 'success' => false); $this->load->view('common/header'); $this->load->view('nav/top_nav'); $this->load->view('create/create', $page_data); $this->load->view('common/footer'); }
Once the folder has been made, we then set the configuration variables for the upload process, as follows:
$config['upload_path'] = $upload_dir.$img_dir_name; $config['allowed_types'] = 'gif|jpg|jpeg|png'; $config['max_size'] = '10000'; $config['max_width'] = '1024'; $config['max_height'] = '768';
Here we are specifying that we only want to upload .gif, .jpg, .jpeg, and .png files. We also specify that an image cannot be above 10,000 KB in size (although you can set this to any value you wish—remember to adjust the upload_max_filesize and post_max_size PHP settings in your php.ini file if you want to have a really big file).
We also set the minimum dimensions that an image must be. As with the file size, you can adjust this as you wish.
We then load the upload library, passing to it the configuration settings, as shown here:
$this->load->library('upload', $config);
Next we will attempt to do the upload. If unsuccessful, the CodeIgniter function $this->upload->do_upload() will return false. We will look for this and reload the upload page if it does return false. We will also pass the specific error as a reason why it failed. This error is stored in the fail item of the $page_data array. This can be done as follows:
if ( ! $this->upload->do_upload()) { $page_data = array('fail' => $this->upload- >display_errors(), 'success' => false); $this->load->view('common/header'); $this->load->view('nav/top_nav'); $this->load->view('create/create', $page_data); $this->load->view('common/footer'); } else { ...
If, however, it did not fail, we grab the information generated by CodeIgniter from the upload. We'll store this in the $image_data array, as follows:
$image_data = $this->upload->data();
Then we try to store a record of the upload in the database. We call the save_image function of Image_model, passing to it file_name from the $image_data array, as well as $img_dir_name, as shown here:
$page_data['result'] = $this->Image_model-> save_image(array('image_name' => $image_data['file_name'], 'img_dir_name' => $img_dir_name));
We then test for the return value of the save_image() function; if it is successful, then Image_model will return the unique URL code generated in the model. If it is unsuccessful, then Image_model will return the Boolean false.
If false is returned, then the form is loaded with a general error. If successful, then the create/result.php view file is loaded. We pass to it the unique URL code (for the link the user needs), and the folder name and image name, necessary to display the image correctly.
Create the /path/to/codeigniter/application/controllers/go.php file and add the following code to it:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Go extends MY_Controller {
function __construct() {
parent::__construct();
$this->load->helper('string');
}
public function index() {
if (!$this->uri->segment(1)) {
redirect (base_url());
} else {
$image_code = $this->uri->segment(1);
$this->load->model('Image_model');
$query = $this->Image_model->fetch_image($image_code);
if ($query->num_rows() == 1) {
foreach ($query->result() as $row) {
$img_image_name = $row->img_image_name;
$img_dir_name = $row->img_dir_name;
} $url_address = base_url() . 'upload/' . $img_dir_name .'/' . $img_image_name; redirect (prep_url($url_address)); } else { redirect('create'); } } } }
The go controller has only one main function, index(). It is called when a user clicks on a URL or a URL is called (perhaps as the src value of an HTML img tag). Here we grab the unique code generated and assigned to an image when it was uploaded in the create controller.
This code is in the first value of the URI. Usually it would occupy the third parameter—with the first and second parameters normally being used to specify the controller and controller function respectively. However, we have changed this behavior using CodeIgniter routing. This is explained fully in the Adjusting the routes.php file section of this article.
Once we have the unique code, we pass it to the fetch_image() function of Image_model:
$image_code = $this->uri->segment(1); $this->load->model('Image_model'); $query = $this->Image_model->fetch_image($image_code);
We test for what is returned. We ask if the number of rows returned equals exactly 1. If not, we will then redirect to the create controller.
Perhaps you may not want to do this. Perhaps you may want to do nothing if the number of rows returned does not equal 1. For example, if the image requested is in an HTML img tag, then if an image is not found a redirect may send someone away from the site they're viewing to the upload page of this project—something you might not want to happen. If you want to remove this functionality, remove the following lines in bold from the code excerpt:
.... $img_dir_name = $row->img_dir_name; } $url_address = base_url() . 'upload/' . $img_dir_name .'/' . $img_image_name; redirect (prep_url($url_address)); } else { redirect('create'); } } } } ....
Anyway, if the returned value is exactly 1, then we'll loop over the returned database object and find img_image_name and img_dir_name, which we'll need to locate the image in the upload folder on the disk. This can be done as follows:
foreach ($query->result() as $row) { $img_image_name = $row->img_image_name; $img_dir_name = $row->img_dir_name; }
We then build the address of the image file and redirect the browser to it, as follows:
$url_address = base_url() . 'upload/' . $img_dir_name .'/' . $img_image_name; redirect (prep_url($url_address));
We make use of the language file to serve text to users. In this way, you can enable multiple region/multiple language support.
Create the /path/to/codeigniter/application/language/english/en_admin_lang.php file and add the following code to it:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); // General $lang['system_system_name'] = "Image Share"; // Upload $lang['encode_instruction_1'] = "Upload your image to share it"; $lang['encode_upload_now'] = "Share Now"; $lang['encode_upload_now_success'] = "Your image was uploaded, you can share it with this URL"; $lang['encode_upload_url'] = "Hey look at this, here's your image:"; $lang['encode_upload_mkdir_error'] = "Cannot make temp folder"; $lang['encode_upload_general_error'] = "The Image cannot be saved at this time";
Let's look at how the user uploads an image. The following is the sequence of events:
CodeIgniter looks in the routes.php config file and finds the following line:
$route['create'] = "create/index";
It directs the request to the create controller's index() function.
Now, let's see how an image is viewed (or fetched). The following is the sequence of events:
A URL with the syntax www.domain.com/226KgfYH comes into the application—either when someone clicks on a link or some other call (<img src="">).
$route['(:any)'] = "go/index";
So here we have a basic image sharing application. It is capable of accepting a variety of images and assigning them to records in a database and unique folders in the filesystem. This is interesting as it leaves things open to you to improve on. For example, you can do the following:
In those terms, you'll want to mention that in order for someone to use the service, they first have to agree that they do not upload and share any images that could be considered illegal. You should also mention that you'll cooperate with any court if information is requested of you.
You really don't want to get into trouble for owning or running a web service that stores unpleasant images; as much as possible you want to make your limits of liability clear and emphasize that it is the uploader who has provided the images.
Further resources on this subject: