# Handling Image Uploads

### Pre-Reqs

- Understanding of how file uploads work in PHP

### Add a 'Files' Table to the Database

Take a look at the following SQL statement and then run it in PHPMyAdmin to create the table (in the my-new-site database):

    DROP TABLE IF EXISTS files;
    CREATE TABLE IF NOT EXISTS files (
      fileId int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
      fileName varchar(200) NOT NULL,
      fileDescription varchar(200) NOT NULL,
      fileExtension varchar(10) NOT NULL,
      fileSize int(11) NOT NULL
    ) ENGINE=InnoDB;

Note that we are going to use this table to keep track of all the images that we add to our blog posts. But it could potentially be used to attach other files to blog posts as well, which is why the table is called 'files' rather than 'images'.

### Create a FileDataAccess Class

1. Create a file in the 'includes' folder and name it FileDataAccess.inc.php.
2. Paste this code into the file.
   Note the following about this code:
   - It stubs out the methods that make up the API of the FileDataAccess class (an API is the methods that you can invoke to 'interface' with an object)
   - Take a good look at the comments in every method, and make sure you understand the purpose for each method, and the steps that are required in order to make each method work.
3. Create a file inside the 'tests' folder called file-data-access-tests.php and add this code to it.
   This page will test the methods in the FileDataAccess class.
   The tests will not work right now because FileDataAccess class is not yet complete. But in the following steps you'll implement code for each method in the FileDataAccess class.
   When you finish writing the code for a method in the FileDataAccess class, you'll test it in this page.
   Make sure that you are not getting any errors, and confirm that the data returned by each method is correct.
4. Start out by writing the code for insertFile() method in the FileDataAccess class.
   Refer to the PageDataAccess class for help, this method is similar to insertPage().
   Make sure to run the test page after adding the body of the insertFile() method.
5. Implement the code for the getFileList() method in the FileDataAccess class.
   The method should fetch all the columns from the 'files' table.
   To help you, look at the similar method in the PageDataAcess class.
   Test the getFileList() method in the test page.
6. Implement the code for the getFileById() method in the FileDataAccess class.
   For the getFileById() method, you should fetch all the columns from the 'files' table.
   To help you, look at the similar method in the PageDataAcess class.
   Test the getFileById() method in the test page.
7. Implement the code for the updateFile() method in the FileDataAccess class. To help you, look at the similar method in the PageDataAcess class.
   Test the updateFile() method in the test page.

### Create a File List Page

- Create a file in the 'control-panel' folder named file-list.php
- Paste this code into file-list.php. You will have to implement the code for the displayFiles() function. You will also have to write the PHP code that appears inside the MAIN tag of the page. There are comments there to help you with each step. Refer to the blog-list.php file for help. Note: If you do not want to display the fileDescription in the list, that's fine.
- Open the control panel home page and add a link to the file list page.

### Setting Up the File Details Page

Eventually, this page will allow you to upload image files to your web server. But we won't worry about that yet. For now, we'll just focus on inserting and updating rows in the files table.

- Add a folder inside the my-new-site folder called uploaded-files.

- Add constants to the config file that point to the uploaded-files dir (for both LIVE and DEV envornments, but you can use the same values for both environments). The constants should look like this:

      define("UPLOAD_FOLDER", PROJECT_DIR . "uploaded-files/");
      define("SERVER_UPLOAD_FOLDER", $_SERVER['DOCUMENT_ROOT'] . UPLOAD_FOLDER);

  Why do we need both UPLOAD_FOLDER and SERVER_UPLOAD_FOLDER? Well, when we are uploading files on the server we'll use a path that is relative to the root directory the computer. But when we display images (that have been uploaded) in the browser, we'll use a path that is relative to the document root directory of the web server.

- Add a method named getFileExtenstion() to the FileDataAccess class. Here is the code for it:

      // extract the extension from a file name
      // Note: In hindsight, I should have used pathinfo() for this (it's a function that's built into PHP)
      function getFileExtension($fileName){

      	$parts = explode(".", $fileName);

      	//Not sure if we should really throw an exception here!
      	if(count($parts) < 2){
      		$this->handle_error("$fileName has no file extension");
      	}

      	return array_pop($parts);
      }

  We could spend some time discussing this code.

- Create a file inside the control-panel folder named file-details.php. Then copy this code into it.

  Note the hidden fields (fileId, fileName, fileExtension, fileSize). These are all hidden from the user because our code will set their values.

  Note that we've added a few variables near the top of the page to help us validate the files being uploaded ($allowed_file_types, and $max_file_size)

  Take some time to go through the code and understand it before moving on. Step through the code with the debugger! You can use the page to insert and update records in the files table, but you'll notice it has some issues.

### Finishing Up the File Details Page

Notice in the file-details.php page, that there are comments that mark off our next steps. To get started, find the comment that says STEP 1.

- STEP 1 - Add the following code under the STEP 1 comment. Note that this code runs only if a file is being uploaded.

      if($_FILES['upload']['error'] == UPLOAD_ERR_OK){

      	// get details of the uploaded file and store them in the $file array
      	$file['fileName'] = $_FILES['upload']['name'];
      	$file['fileSize'] = $_FILES['upload']['size'];
      	$file['fileExtension'] = $fda->getFileExtension($file['fileName']);

      	// set the full path of where the uploaded file should be moved to
      	$filePath = SERVER_UPLOAD_FOLDER . $file['fileName'];

        	// move the file from the tmp dir to it's final destination
        	if( !move_uploaded_file($_FILES['upload']['tmp_name'], $filePath) ){
        		throw new Exception("Unable to move uploaded file");
        	}
      }

- STEP 2 - Once the file has been successfully uploaded and moved to it's final destination, we are going to rename it to use it's fileId. So for example, test.png might become 101.png. The reason for this is that it will prevent files that happen to have the same name from being overwritten. Under this approach, it should be impossible for two files in the upload folder to have the same name. Here is the code:

      // Now that the image has been uploaded, let's rename it so that it
      // is named by the file id, rather than the original name
      $newFileName = $file['fileId'] . "." . $file['fileExtension'];
      $newFilePath = SERVER_UPLOAD_FOLDER . $newFileName;

      if( !rename($filePath, $newFilePath) ){
      	throw new Exception("Unable too rename file");
      }

- STEP 3 - If we are updating an image that is already in our system, we'll want to display, like this:

      if($file['fileId'] > 0 && !empty($file['fileExtension'])){
      	$realFileName = $file['fileId'] . "." . $file['fileExtension'];
      	echo('<img id="mainImg" src="' . UPLOAD_FOLDER . $realFileName .'" />');
      }

- STEP 4 - There are various errors that can occur in dealing with the image upload, we'll see how these error messages are generated in the final step. But for now, here is the code to display them:

      echo(isset($validationErrors['imageRequired']) ? wrapValidationMsg($validationErrors['imageRequired']) : "");
      echo(isset($validationErrors['invalidImageType']) ? wrapValidationMsg($validationErrors['invalidImageType']) : "");
      echo(isset($validationErrors['invalidImageSize']) ? wrapValidationMsg($validationErrors['invalidImageSize']) : "");

- STEP 5 - Finally, here is the code that validates the file being uploaded. Note that a file upload is not always required. For example, you may want to update the description for a file that you've already entered into the system.

      // on inserts, there must be a file uploaded
      // http://php.net/manual/en/features.file-upload.errors.php
      if($file['fileId'] == 0 && $_FILES['upload']['error'] == UPLOAD_ERR_NO_FILE){
      	$errors['imageRequired'] = "You must upload an image";
      }

      if($_FILES['upload']['error'] == UPLOAD_ERR_OK){

      	// validate the file type
      	if( !in_array($_FILES['upload']['type'], $allowed_file_types) ){
      		$errors['invalidImageType'] = "The file must be an image.";
      	}

      	// validate the file size
      	if($_FILES['upload']['size'] > $max_file_size){
      		$errors['invalidImageSize'] = "The file is too big.";
      	}
      }

### Final Notes

- You may want to delete any files that you uploaded before you add and commit files in Git. It's better to leave those types of files out of the repository. We'll create a .gitignore file together in class to keep the uploaded images out of the Git repository.
- We should add JS validation in addition to server side validation - if you forget to add a desc and press save, you loose the selected file
- We might also want to ad JavaScript code so that when an image is displayed, it should disappear if you use the file input to pick a new one.

### Follow Up Questions:

- Do we have duplicated code in the FileDataAccess and PageDataAccess classes? If so, what could we do avoid duplicating code in these classes?
- What is the purpose of the $_FILES super global array?
- Why did we end up renaming the files (to use the fileId from the database) once they get uploaded?