* @copyright Copyright (c) 2008-9 TypeOneError Studios (http://www.typeoneerror.com) * @license MIT ~ http://www.opensource.org/licenses/mit-license.php * @version $Id$ * @link http://typeoneerror.com/asra * @category Asra * @package Utils */ /** * Handles uploads of any type and perform * resizing and filtration of images. * * @todo finish docs * @todo clean up example * *
// -- simple upload
* $upload = new Asra_Utils_Image($_FILES['MyFile'], 'uploads');
*
* // -- resize an image to 300x200, flip horitzontal and invert
* $upload = new Asra_Utils_Image($_FILES['file'],
* 'upload_dir',
* null,
* array('type' => 'resize', 'size' => array(300,200), 'filter' => array('fliph','invert')),
* );
* echo $upload->fullPath; // -- path to the uploaded image
* echo $upload->result; // -- the filename
*
* @author Ben Borowski * This will either be the base name of the file name * you have specified or a timestamp if no file name is specified. * In both cases the file name is unique because the class looks to * make sure the file doesn't already exist with that name. *
* * @var string */ private $__fileName; /** * Location to write file to * * @var string */ private $__destination; /** * The new name of the file * * @var string */ private $__name; /** * The new name without the extension * * @var string */ private $__nameBase; /** * Write for using subdirectories * * @var string */ private $__writePath = ""; /** * How to handle the image upload * * @var array */ private $__rules; /** * Errors happened during write or upload and are stored here * * @var array */ public $errors; /** * File name is saved here if successful * * @var string */ public $result; // -- file name is saved here if successful /** * Write path is saved here if successful (destination + name) * * @var string */ public $resultPath; /** * Full path is saved here if successful (absolute path to uploaded file) * * @var string */ public $fullPath; /** * Upload a file with optional resize or filtering of images * * @param string $file A file (file to upload) $_FILES[FILE_NAME] * @param string $destination Where to upload to * @param string $name Override name of file to write * @param string $rules How to handle an image upload * * - rules['type'] Type of resize ('resize','resizemin','resizecrop','crop') * - rules['size'] Size of image, Array (x, y) or single number * - rules['output'] String ('gif','png','jpg') * - rules['quality'] Integer (quality of output image) note: won't apply to png * - rules['filter'] An array of filter names (flip|fliph|flipv|greyscale|invert) * - rules['auto_thumb'] An array (x, y) or single number (if set generates a thumb of same size) * * @param array $allowed Allowed filetypes * @param mixed $subdirs Move the file into subdirectories (passing (bool) true sets defaults) * * - subdirs['subdir_depth'] How deep to make the auto sub directories, default = 2 * - subdirs['subdir_length'] How many folders to create per depth, default = 16 * * @return void */ function __construct($file, $destination, $name = null, $rules = null, $allowed = null, $subdirs = null) { // -- setup $this->result = false; $this->error = false; // -- save parameters $this->__file = $file; $this->__destination = realpath($destination); if (!is_null($rules)) $this->__rules = $rules; // -- only allow certain file types? if (!is_null($allowed)) { $this->__allowed = $allowed; foreach($this->__allowed as $key => $val) $this->__allowed[$key] = strtolower($val); } // -- hack dir if / not provided if (substr($this->__destination, -1) != '/') { $this->__destination .= '/'; } // -- add subdirectories if (!is_null($subdirs)) { // -- defaults $subdir_depth = isset($subdirs['subdir_depth']) ? $subdirs['subdir_depth'] : 2; $subdir_length = isset($subdirs['subdir_length']) ? $subdirs['subdir_length'] : 16; // -- loop through depths and pick random directories for ($i = 0; $i < $subdir_depth; $i++) { // -- pick directory $dir = rand(0, $subdir_length - 1); $dir = str_pad($dir, strlen($subdir_length), '0', STR_PAD_LEFT); if (!is_dir($this->__destination . $dir)) { if (!mkdir($this->__destination . $dir, 0755)) { return $this->_error('mkdir didn\'t work'); } } // -- add directory to the path $this->__destination .= $dir . '/'; $this->__writePath .= $dir . '/'; } } else { $tmp = $this->__destination; $dirsToAdd = array(); while (!is_dir($tmp)) { $dirsToAdd[] = $tmp; $tmp = dirname($tmp); } // -- $tmp is now the basepath for ($i = count($dirsToAdd)-1; $i >= 0 ; $i--) { if (!mkdir($dirsToAdd[$i], 0755)) { return $this->_error('mkdir didn\'t work'); } } } // -- check that FILE array is even set if (isset($file) && is_array($file) && !$this->__uploadError($file['error'])) { // -- cool, now set some variables $fileName = (is_null($name)) ? $this->uniqueName($this->__destination . $file['name']) : $this->__destination . $name; $this->__fileName = $this->getName($fileName); if (!$this->__fileName) { $this->_error('Filename conversion failed! Try renaming the file.'); } $fileTmp = $file['tmp_name']; $fileSize = $file['size']; $fileType = $file['type']; $fileError = $file['error']; // -- update name $this->__name = $fileName; $this->__nameBase = substr($fileName, 0, strrpos($fileName, ".")); $this->fullPath = $fileName; // -- error if not correct extension if (!is_null($this->__allowed) && !in_array(strtolower($this->ext($fileName)), $this->__allowed)) { $this->_error("File type not allowed."); } else { // -- it's been uploaded with php if (is_uploaded_file($fileTmp)) { // -- how are we handling this file if (is_null($rules)) { // -- where to put the file? $output = $fileName; // -- just upload it if (move_uploaded_file($fileTmp, $output)) { chmod($output, 0644); $this->result = basename($this->__name); $this->resultPath = $this->__name; $this->__writePath .= $this->result; if (!is_null($rules['auto_thumb'])) { if (!is_array($rules['auto_thumb'])) { if (is_bool($rules["auto_thumb"])) $rules["auto_thumb"] = self::SIZE; $v = intval($rules['auto_thumb']); $rules['auto_thumb'] = array($v, $v); } $this->image($this->__name, 'resizecrop', $rules['auto_thumb'], null, null, null, true); } } else { $this->_error("Could not move '{$fileName}' to '{$destination}'"); } } else { // -- gd lib check if (function_exists("imagecreatefromjpeg")) { if (!isset($rules['output'])) $rules['output'] = null; if (!isset($rules['quality'])) $rules['quality'] = null; // -- handle it based on rules if (isset($rules['type']) && isset($rules['size'])) { $this->image($fileTmp, $rules['type'], $rules['size'], $rules['output'], $rules['quality'], $rules['filter']); // -- create a thumb if requested if ($this->result != false) { if (!is_null($rules['auto_thumb'])) { if (!is_array($rules['auto_thumb'])) { $v = intval($rules['auto_thumb']); $rules['auto_thumb'] = array($v, $v); } $this->image($fileTmp, 'resizecrop', $rules['auto_thumb'], $rules['output'], $rules['quality'], $rules['filter'], true); } } } else { $this->_error("Invalid \"rules\" parameter"); } } else { $this->_error("GD library is not installed"); } } } else { $this->_error("Possible file upload attack on '$fileName'"); } } } else { $this->_error("Possible file upload attack"); } } /** * Add a message to error stack (for outside checking) * * @param string $message Error message to add to stack * @return void */ protected function _error($message) { if (!is_array($this->errors)) $this->errors = array(); array_push($this->errors, $message); } /** * Return the extension of a file * * @param string $file File name * @return string Just the extension */ public function ext($file) { $ext = trim(substr($file, strrpos($file, ".") + 1, strlen($file))); return $ext; } /** * Grab the errors array * * @return array An array of errors incurred during upload */ public function getErrors() { return $this->errors; } /** * Get just the name of the file * * @param file File name to analyze * @return string The name of the file */ public function getName($file) { $name = trim(substr($file, strrpos($file, "/") + 1)); return $name; } /** * Get the path to the file * * @return string The path */ public function getResultFile() { if ($this->result) { return $this->result; } return null; } /** * Get the path to the file * * @return string The path */ public function getResultPath() { if (!is_null($this->result)) { return $this->__destination . $this->result; } return null; } /** * Get the path to the file from the web root * * @param string $fromRoot Place to start looking * @return string The web path */ public function getWebPath($fromRoot = null) { if (is_null($fromRoot)) $fromRoot = $_SERVER["DOCUMENT_ROOT"]; if (strpos($this->getResultPath(), $fromRoot) === false) return null; $path = substr($this->getResultPath(), strlen($fromRoot)); return $path; } /** * Where the file was written based on subdirectories added. * *If there is no subdirectory support used, this will just be the filename
* * @return string The written path */ public function getWritePath() { return $this->__writePath; } /** * Resizes an uploaded image based on a set of rules * * @param array $file A $_FILE array * @param string $type Type of resize to perform * @param mixed $size Dimensional contraints of resize * @param string $output Force output to a specifc image format * @param int $quality Quality of rendered image from 0-100 * @param array $filter Apply filters to the resized image * @return void */ public function image($file, $type, $size, $output = null, $quality = null, $filter = null, $autoThumb = false) { if (is_null($type)) $type = self::RESIZE; if (is_null($size)) $size = self::SIZE; if (is_null($quality)) $quality = self::QUALITY; if (is_null($output)) { $output = $this->ext($this->__name); } // -- format variables $type = strtolower($type); $output = strtolower($output); if (is_array($size)) { $maxW = intval($size[0]); $maxH = intval($size[1]); } else { $maxW = $maxH = intval($size); } // -- check output if ($output != 'jpg' && $output != 'png' && $output != 'gif') { $output = 'jpg'; } // -- check quality if (is_numeric($quality)) { $quality = intval($quality); if ($quality > 100 || $quality < 1) { $quality = self::QUALITY; } } else { $quality = self::QUALITY; } // -- get some information about the file $uploadSize = getimagesize($file); $uploadWidth = $uploadSize[0]; $uploadHeight = $uploadSize[1]; $uploadType = $uploadSize[2]; if ($uploadType != 1 && $uploadType != 2 && $uploadType != 3) { $this->_error("File type must be GIF, PNG, or JPG to resize"); } switch ($uploadType) { case 1: $srcImg = imagecreatefromgif($file); break; case 2: $srcImg = imagecreatefromjpeg($file); break; case 3: $srcImg = imagecreatefrompng($file); break; default: $this->_error("File type must be GIF, PNG, or JPG to resize"); } switch ($type) { case self::RESIZE: // -- Maintains the aspect ratio of the image and makes sure that it fits // -- within the maxW and maxH (thus some side will be smaller) // -- get ratios $ratioX = $maxW / $uploadWidth; $ratioY = $maxH / $uploadHeight; if (($uploadWidth <= $maxW) && ($uploadHeight <= $maxH)) { $newX = $uploadWidth; $newY = $uploadHeight; } else if (($ratioX * $uploadHeight) < $maxH) { $newX = $maxW; $newY = ceil($ratioX * $uploadHeight); } else { $newX = ceil($ratioY * $uploadWidth); $newY = $maxH; } $dstImg = imagecreatetruecolor($newX, $newY); if ($uploadType == 3) imagealphablending($dstImg, false); imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, $newX, $newY, $uploadWidth, $uploadHeight); break; case self::RESIZE_MIN: // -- Maintains aspect ratio but resizes the image so that once // -- one side meets its maxW or maxH condition, it stays at that size // -- (thus one side will be larger) // -- get ratios $ratioX = $maxW / $uploadWidth; $ratioY = $maxH / $uploadHeight; // -- figure out new dimensions if (($uploadWidth == $maxW) && ($uploadHeight == $maxH)) { $newX = $uploadWidth; $newY = $uploadHeight; } else if (($ratioX * $uploadHeight) > $maxH) { $newX = $maxW; $newY = ceil($ratioX * $uploadHeight); } else { $newX = ceil($ratioY * $uploadWidth); $newY = $maxH; } $dstImg = imagecreatetruecolor($newX,$newY); if ($uploadType == 3) imagealphablending($dstImg, false); imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, $newX, $newY, $uploadWidth, $uploadHeight); break; case self::RESIZE_WIDTH: case self::RESIZE_HEIGHT: $ratioX = $maxW / $uploadWidth; $ratioY = $maxH / $uploadHeight; if ($type == self::RESIZE_WIDTH) { $newX = $maxW; $newY = ceil($ratioX * $uploadHeight); } else { $newX = ceil($ratioY * $uploadWidth); $newY = $maxH; } $dstImg = imagecreatetruecolor($newX,$newY); if ($uploadType == 3) imagealphablending($dstImg, false); imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, $newX, $newY, $uploadWidth, $uploadHeight); break; case self::RESIZE_CROP: // -- resize to max, then crop to center $ratioX = $maxW / $uploadWidth; $ratioY = $maxH / $uploadHeight; if ($ratioX < $ratioY) { $newX = round(($uploadWidth - ($maxW / $ratioY))/2); $newY = 0; $uploadWidth = round($maxW / $ratioY); $uploadHeight = $uploadHeight; } else { $newX = 0; $newY = round(($uploadHeight - ($maxH / $ratioX))/2); $uploadWidth = $uploadWidth; $uploadHeight = round($maxH / $ratioX); } $dstImg = imagecreatetruecolor($maxW, $maxH); if ($uploadType == 3) imagealphablending($dstImg, false); imagecopyresampled($dstImg, $srcImg, 0, 0, $newX, $newY, $maxW, $maxH, $uploadWidth, $uploadHeight); break; case self::CROP: // -- a straight centered crop $newY = ($uploadHeight - $maxH)/2; $newX = ($uploadWidth - $maxW)/2; $dstImg = imageCreateTrueColor($maxW, $maxH); if ($uploadType == 3) imagealphablending($dstImg, false); imagecopyresampled($dstImg, $srcImg, 0, 0, $newX, $newY, $maxW, $maxH, $maxW, $maxH); break; case self::SET_CROP: $cropX = $size[2]; $cropY = $size[3]; $cropW = $size[4]; $cropH = $size[5]; // -- setting explicit crop points $newY = ($uploadHeight - $maxH)/2; $newX = ($uploadWidth - $maxW)/2; $dstImg = imageCreateTrueColor($maxW, $maxH); if ($uploadType == 3) imagealphablending($dstImg, false); imagecopyresampled($dstImg, $srcImg, 0, 0, $cropX, $cropY, $maxW, $maxH, $cropW, $cropH); break; default: $this->_error("Resize function \"$type\" does not exist"); throw new Exception("Resize function \"$type\" does not exist"); } if ($filter && is_array($filter)) { foreach($filter as $var => $val) $filter[$var] = strtolower($val); $img_x = $newX; $img_y = $newY; $flipped = false; // -- flip v+h if (in_array("flip", $filter) && !$flipped) { $flipped = true; $copy = imagecreatetruecolor($img_x, $img_y); imagecopy($copy, $dstImg, 0, 0, 0, 0, $img_x, $img_y); for ($i = 0; $i < $img_x; $i++) { for ($j = 0; $j < $img_y; $j++) { $rgb = imagecolorat($copy, $i, $j); imagesetpixel($dstImg, $img_x - $i - 1, $img_y - $j - 1, $rgb); } } } // -- flip horizontally if (in_array("fliph", $filter) && !$flipped) { $flipped = true; $copy = imagecreatetruecolor($img_x, $img_y); imagecopy($copy, $dstImg, 0, 0, 0, 0, $img_x, $img_y); for ($i = 0; $i < $img_x; $i++) { for ($j = 0; $j < $img_y; $j++) { $rgb = imagecolorat($copy, $i, $j); imagesetpixel($dstImg, $img_x - $i - 1, $j, $rgb); } } } // -- flip vertically if (in_array("flipv", $filter) && !$flipped) { $flipped = true; $copy = imagecreatetruecolor($img_x, $img_y); imagecopy($copy, $dstImg, 0, 0, 0, 0, $img_x, $img_y); for ($i = 0; $i < $img_x; $i++) { for ($j = 0; $j < $img_y; $j++) { $rgb = imagecolorat($copy, $i, $j); imagesetpixel($dstImg, $i, $img_y - $j - 1, $rgb); } } } // -- convert to greyscale if (in_array("greyscale", $filter)) { for ($i = 0; $i < $img_x; $i++) { for ($j = 0; $j < $img_y; $j++) { $rgb = imagecolorat($dstImg, $i, $j); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; $new_r = $new_g = $new_b = round(0.56*$r) + round(.33*$g) + round(.11*$b); $newrgb = ($new_r << 16) + ($new_g << 8) + $new_b; imagesetpixel($dstImg, $i, $j, $newrgb); } } } // -- invert the image if (in_array("invert", $filter)) { for ($i = 0; $i < $img_x; $i++) { for ($j = 0; $j < $img_y; $j++) { $rgb = imagecolorat($dstImg, $i, $j); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; $new_r = 255 - $r; $new_g = 255 - $g; $new_b = 255 - $b; $newrgb = ($new_r << 16) + ($new_g << 8) + $new_b; imagesetpixel($dstImg, $i, $j, $newrgb); } } } } if (!$autoThumb) { $outputName = $this->__destination . $this->__nameBase . "." . $output; } else { $outputName = $this->__destination . "tn_" . $this->getName($this->__nameBase) . "." . $output; } // -- try to write switch ($output) { case 'jpg': $write = imagejpeg($dstImg, $outputName, $quality); break; case 'png': imagesavealpha($dstImg, true); $write = imagepng($dstImg, $outputName); // -- actually some bugs with png "quality" param break; case 'gif': $write = imagegif($dstImg, $outputName, $quality); break; } // -- clean up imagedestroy($dstImg); if ($write) { if (!$autoThumb) { $this->result = basename($outputName); $this->resultPath = $outputName; } } else { $this->error("Could not write " . $outputName . " to " . $this->__destination); } } /** * Get the new name of the file based on the time * * @param string $file File name * @return string The new file name */ public function newName($file) { return time() . "." . $this->ext($file); } /** * Get a unique of the file based on what already exists * * @param string $file The name you want to use * @return string The next available name in the directory */ public function uniqueName($file) { //pick a unique name $parts = pathinfo($file); $dir = $parts['dirname']; $file = ereg_replace('[^[:alnum:]_.-]','',$parts['basename']); $ext = $parts['extension']; if ($ext) { $ext = '.'.$ext; $file = substr($file,0,-strlen($ext)); } $i = ''; while (file_exists($dir.'/'.$file.$i.$ext)) { if ($i === '') $i = 0; else $i++; } return $dir.'/'.$file.$i.$ext; } /** * Checks upload errors against file upload constants * * @param error $errorobj The php error object * @return string The error string * @private */ private function __uploadError($errorobj) { $error = false; switch ($errorobj) { case UPLOAD_ERR_OK: break; case UPLOAD_ERR_INI_SIZE: $error = "The uploaded file exceeds the upload_max_filesize directive (".ini_get("upload_max_filesize").") in php.ini."; break; case UPLOAD_ERR_FORM_SIZE: $error = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form."; break; case UPLOAD_ERR_PARTIAL: $error = "The uploaded file was only partially uploaded."; break; case UPLOAD_ERR_NO_FILE: $error = "No file was uploaded."; break; case UPLOAD_ERR_NO_TMP_DIR: $error = "Missing a temporary folder."; break; case UPLOAD_ERR_CANT_WRITE: $error = "Failed to write file to disk"; break; default: $error = "Unknown File Error"; } return ($error); } }