Validating Image Uploads in CakePHP

Following on from my article on CakePHP – Uploaded File Validation in Models, today’s snippet will show you how to use Cake’s validation rules to reject invalid images, or images which do not conform to a specified mime-type. This code relies on the fact that you have LibGD installed on your webserver.

<?php
Class Image extends AppModel
{
	var $name = 'Image';
	var $validate = array
	(
		'uploaded_image' => array
		(
			// Ensure file uploaded OK.
			'valid_upload' => array
			(
				'rule' => array('validateUploadedfile', false),
				'message' => 'An error occured whilst uploading your image, please try again',
			),
 
			// Check image is valid / of allowed mime-type
			'valid_image' => array
			(
				'rule' => array('isValidImageFile'),
				'message' => 'The file you have uploaded is not a valid or is unsupported, please try again',
			),
		),
	);
 
 
        /**
         * Custom validation rule for uploaded files.
         *
         *  @param Array $data CakePHP File info.
         *  @param Boolean $required Is this field required?
         *  @return Boolean
        */
        function validateUploadedFile($data, $required = false) 
        {
                // Remove first level of Array ($data['Image']['size'] becomes $data['size'])
                $upload_info = array_shift($data);
 
                // No file uploaded.
                if ($required && $upload_info['size'] == 0) {
                        return false;
                }
 
                // Check for Basic PHP file errors.
                if ($upload_info['error'] !== 0) {
                        return false;
                }
 
                // Finally, use PHP’s own file validation method.
                return is_uploaded_file($upload_info['tmp_name']);
        }
 
 
    /**
     * Use LibGD to determine if an uploaded file is a valid Image by
     * running it's mime-type against a list of $valid_mime_types
     *
     *	@param Array $data CakePHP File info
     *	@return Boolean
    */
	function isValidImageFile($data)
    {
        // Allow these image mime-types, all others will be rejected
        $valid_mime_types = array('image/jpeg', 'image/png', 'image/gif');
 
        $data = array_shift($data);
        $filename = $data['tmp_name'];
 
        // Catch I/O Errors.
        if (!is_readable($filename)) {
	        debug(__METHOD__." failed to read input file: {$filename}");
	        return false;
        }
 
        // Retrieve the MimeType of Image, if none is returned, it's invalid
        if (!$mime_type = $this->getImageMimeType($filename)) {
	        debug(__METHOD__." Uploaded file does not have a mime-type");
	        return false;
        }
 
        // Check the MimeType against the array of valid ones specified above
        if (!in_array($mime_type, $valid_mime_types)) {
	        debug(__METHOD__." Uploaded image has rejected Mime Type: {$mime_type}");
	        return false;
        }
 
        if (!$this->__getImageHandleFromFile($filename)) {
	        return false;
        }
 
        return true;
    }
 
 
    /** 
     * Use LibGD to return an uploaded Image's MimeType as a String, FALSE
     * on errors or if the file is not an image
     *
     *	@param String $filename Absolute path to file on disc
     *	@return String Image MimeType of $filename, false on failure
    */
    function getImageMimeType($filename)
    {
		// If this error is thrown LibGD is not installed on your server.
        if (!function_exists('getimagesize')) {
	        debug(__METHOD__." LibGD PHP Extension was not found, please refer to http://www.php.net/manual/en/book.image.php");
	        exit();
        }	        
 
        $result = getimagesize($filename);	        
        if (isset($result['mime'])) {
	        return $result['mime'];
        }
        return false;
    }
 
 
    /**
     * Returns a LibGD Image Handle for a file specified by $filename
     *
     *	@param String $filename Absolute path to image on disk
     *	@return LibGD Image Handle on success, FALSE on failure.
    */
    function __getImageHandleFromFile($filename)
    {
        if (!is_readable($filename)) {
	        debug(__METHOD__." failed to read input file: {$filename}");
	        return false;
        }
 
        // Retrieve the MimeType of Image, if none is returned, it's invalid
        if (!$mime_type = $this->getImageMimeType($filename)) {
	        debug(__METHOD__." failed to assertain MimeType of {$filename}");
	        return false;
        }
 
        switch ($mime_type)
        {
	        case 'image/jpeg':
	        	$handle = @imagecreatefromjpeg($filename);
	        	break;
 
	        case 'image/gif':
	        	$handle = @imagecreatefromgif($filename);
	        	break;
 
	        case 'image/png':
	        	$handle = @imagecreatefrompng($filename);
	        	break;
 
	        default:
	        	debug(__METHOD__." Didn't know how to handle MimeType: {$mime_type}");
	        	$handle = false;
	        	break;
        }
 
        return $handle;
    }
}

As always, comments are welcome.

This entry was posted in CakePHP and tagged , , , . Bookmark the permalink.

2 Responses to Validating Image Uploads in CakePHP

  1. John says:

    Just a comment after reading Chris Shiflett’s article on file uploads. You probably shouldn’t rely on $upload_info['size'] for the file size as this is transmitted from the client. Checking the file size yourself with the filesize() function is better.

  2. Ben says:

    I’ve just tried this and your previous post, but the image.php (resp. artwork.php) model file doesn’t even get included let alone used for validation. Is there a crucial step you’ve left out?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">