PageRenderTime 27ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/attachment.php

http://github.com/tute/Thumbnail-component-for-CakePHP
PHP | 412 lines | 295 code | 30 blank | 87 comment | 35 complexity | 577aed419c83797eb5e562a2d252b9a0 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /*
  3. * File: /app/controllers/components/attachment.php
  4. * A file uploader and image crop/thumbnailer component for CakePHP
  5. *
  6. * @link https://github.com/tute/Thumbnail-component-for-CakePHP
  7. * @author TuteC (Eugenio Costa)
  8. * @version 0.9
  9. * @license MIT
  10. *
  11. */
  12. App::import('Core', 'Inflector');
  13. class AttachmentComponent extends Object {
  14. /**
  15. * Configuration options
  16. * @var $config array
  17. */
  18. var $config = array(
  19. 'files_dir' => 'photos',
  20. 'rm_tmp_file' => false,
  21. 'allow_non_image_files' => true,
  22. 'images_size' => array(
  23. /* You may define as many options as you like */
  24. 'big' => array(640, 480, 'resize'),
  25. 'med' => array(263, 263, 'resizeCrop'),
  26. 'small' => array( 90, 90, 'resizeCrop')
  27. )
  28. );
  29. /**
  30. * Initialization method. You may override configuration options from a controller
  31. *
  32. * @param $controller object
  33. * @param $config array
  34. */
  35. function initialize(&$controller, $config) {
  36. $this->controller = $controller;
  37. $model_prefix = Inflector::tableize($controller->modelClass); // lower case, studley caps -> underscores
  38. $prefix = Inflector::singularize($model_prefix); // make singular. 'GalleryImage' becomes 'gallery_image'
  39. $this->config = array_merge(
  40. array('default_col' => $prefix), /* default column prefix is lowercase, singular model name */
  41. $this->config, /* default general configuration */
  42. $config /* overriden configurations */
  43. );
  44. }
  45. /**
  46. * Uploads file to file system, according to $config.
  47. * Example usage:
  48. * $this->Attachment->upload($this->data['Model']['Attachment']);
  49. *
  50. * @return mixed boolean true on success, or error string
  51. * @param $data array the file input array
  52. * @param $column_prefix string The prefix of the fields used to store the uploaded file data
  53. *
  54. */
  55. function upload(&$data, $column_prefix = null) {
  56. if ($column_prefix == null) {
  57. $column_prefix = $this->config['default_col'];
  58. } else {
  59. $this->config['default_col'] = $column_prefix;
  60. }
  61. $file = $data[$this->config['default_col']];
  62. if ($file['error'] === UPLOAD_ERR_OK) {
  63. return $this->upload_FS($data);
  64. } else {
  65. return $this->log_proper_error($file['error']);
  66. }
  67. }
  68. /**
  69. * Creates the relevant dir's and processes the file
  70. *
  71. * @return mixed boolean true on success, or error string
  72. * @param $data array The array of data from the controlle
  73. */
  74. function upload_FS(&$data) {
  75. $column_prefix = $this->config['default_col'];
  76. $error = 0;
  77. $tmpuploaddir = WWW_ROOT.'attachments'.DS.'tmp'; // /tmp/ folder (should delete image after upload)
  78. $fileuploaddir = WWW_ROOT.'attachments'.DS.'files';
  79. // Make sure the required directories exist, and create them if necessary
  80. if (!is_dir($tmpuploaddir)) mkdir($tmpuploaddir, 0755, true);
  81. if (!is_dir($fileuploaddir)) mkdir($fileuploaddir, 0755, true);
  82. /* Generate a unique name for the file */
  83. $filetype = end(split('\.', $data[$column_prefix]['name']));
  84. $filename = String::uuid();
  85. settype($filename, 'string');
  86. $filename .= '.' . $filetype;
  87. /* Security check */
  88. if (!is_uploaded_file($data[$column_prefix]['tmp_name'])) {
  89. return $this->log_cakephp_error_and_return('Error uploading file (sure it was a POST request?).');
  90. }
  91. /* If it's image get image size and make thumbnail copies. */
  92. if ($this->is_image($filetype)) {
  93. $this->copy_or_log_error($data[$column_prefix]['tmp_name'], $tmpuploaddir, $filename);
  94. /* Create each thumbnail_size */
  95. foreach ($this->config['images_size'] as $dir => $opts) {
  96. $this->thumbnail($tmpuploaddir.DS.$filename, $dir, $opts[0], $opts[1], $opts[2]);
  97. }
  98. if ($this->config['rm_tmp_file'])
  99. unlink($tmpuploaddir.DS.$filename);
  100. } else {
  101. if (!$this->config['allow_non_image_files']) {
  102. return $this->log_cakephp_error_and_return('File type not allowed (only images files).');
  103. } else {
  104. $this->copy_or_log_error($data[$column_prefix]['tmp_name'], $fileuploaddir, $filename);
  105. }
  106. }
  107. /* File uploaded, return modified data array */
  108. $res[$column_prefix.'_file_path'] = $filename;
  109. $res[$column_prefix.'_file_name'] = $data[$column_prefix]['name'];
  110. $res[$column_prefix.'_file_size'] = $data[$column_prefix]['size'];
  111. $res[$column_prefix.'_content_type'] = $data[$column_prefix]['type'];
  112. unset($data[$column_prefix]); /* delete $_FILES indirection */
  113. $data = array_merge($data, $res); /* add default fields */
  114. return true;
  115. }
  116. /**
  117. * Creates resized copies of input image
  118. * E.g;
  119. * $this->Attachment->thumbnail($this->data['Model']['Attachment'], $upload_dir, 640, 480, false);
  120. *
  121. * @param $tmpfile array The image data array from the form
  122. * @param upload_dir string The name of the parent folder of the images
  123. * @param $maxw int Maximum width for resizing thumbnails
  124. * @param $maxh int Maximum height for resizing thumbnails
  125. * @param $crop string either 'resize', 'resizeCrop' or 'crop'
  126. */
  127. function thumbnail($tmpfile, $upload_dir, $maxw, $maxh, $crop = 'resize') {
  128. // Make sure the required directory exist; create it if necessary
  129. $upload_dir = WWW_ROOT.'attachments'.DS.$this->config['files_dir'].DS.$upload_dir;
  130. if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
  131. /* Directory Separator for windows users */
  132. $ds = (strcmp('\\', DS) == 0) ? '\\\\' : DS;
  133. $file_name = end(split($ds, $tmpfile));
  134. $this->resize_image($crop, $tmpfile, $upload_dir, $file_name, $maxw, $maxh, 85);
  135. }
  136. /**
  137. * Deletes file, or image and associated thumbnail
  138. * e.g;
  139. * $this->Attachment->delete_files('file_name.jpg');
  140. *
  141. * @param $filename string The file to delete
  142. */
  143. function delete_files($filename) {
  144. /* Non image files */
  145. if (is_file(WWW_ROOT.'attachments'.DS.'files'.DS.$filename)) {
  146. unlink(WWW_ROOT.'attachments'.DS.'files'.DS.$filename);
  147. }
  148. /* tmp files (if not pruned while uploading) */
  149. if (is_file(WWW_ROOT.'attachments'.DS.'tmp'.DS.$filename)) {
  150. unlink(WWW_ROOT.'attachments'.DS.'tmp'.DS.$filename);
  151. }
  152. /* Thumbnail copies */
  153. foreach ($this->config['images_size'] as $size => $opts) {
  154. $photo = WWW_ROOT.'attachments'.DS.$this->config['files_dir'].DS.$size.DS.$filename;
  155. if (is_file($photo)) unlink($photo);
  156. }
  157. }
  158. /*
  159. * Creates resized image copy
  160. *
  161. * Parameters:
  162. * cType: Conversion type {resize (default) | resizeCrop (square) | crop (from center)}
  163. * tmpfile: original (tmp) file name
  164. * newName: include extension (if desired)
  165. * newWidth: the max width or crop width
  166. * newHeight: the max height or crop height
  167. * quality: the quality of the image
  168. */
  169. function resize_image($cType = 'resize', $tmpfile, $dst_folder, $dstname = false, $newWidth=false, $newHeight=false, $quality = 75) {
  170. $srcimg = $tmpfile;
  171. list($oldWidth, $oldHeight, $type) = getimagesize($srcimg);
  172. $ext = $this->image_type_to_extension($type);
  173. // If file is writeable, create destination (tmp) image
  174. if (is_writeable($dst_folder)) {
  175. $dstimg = $dst_folder.DS.$dstname;
  176. } else {
  177. // if dst_folder not writeable, let developer know
  178. debug('You must allow proper permissions for image processing. And the folder has to be writable.');
  179. debug("Run 'chmod 755 $dst_folder', and make sure the web server is it's owner.");
  180. return $this->log_cakephp_error_and_return('No write permissions on attachments folder.');
  181. }
  182. /* Check if something is requested, otherwise do not resize */
  183. if ($newWidth or $newHeight) {
  184. /* Delete tmp file if it exists */
  185. if (file_exists($dstimg)) {
  186. unlink($dstimg);
  187. } else {
  188. switch ($cType) {
  189. default:
  190. case 'resize':
  191. // Maintains the aspect ratio of the image and makes sure
  192. // that it fits within the maxW and maxH
  193. $widthScale = 2;
  194. $heightScale = 2;
  195. /* Check if we're overresizing (or set new scale) */
  196. if ($newWidth) {
  197. if ($newWidth > $oldWidth) $newWidth = $oldWidth;
  198. $widthScale = $newWidth / $oldWidth;
  199. }
  200. if ($newHeight) {
  201. if ($newHeight > $oldHeight) $newHeight = $oldHeight;
  202. $heightScale = $newHeight / $oldHeight;
  203. }
  204. if ($widthScale < $heightScale) {
  205. $maxWidth = $newWidth;
  206. $maxHeight = false;
  207. } elseif ($widthScale > $heightScale ) {
  208. $maxHeight = $newHeight;
  209. $maxWidth = false;
  210. } else {
  211. $maxHeight = $newHeight;
  212. $maxWidth = $newWidth;
  213. }
  214. if ($maxWidth > $maxHeight){
  215. $applyWidth = $maxWidth;
  216. $applyHeight = ($oldHeight*$applyWidth)/$oldWidth;
  217. } elseif ($maxHeight > $maxWidth) {
  218. $applyHeight = $maxHeight;
  219. $applyWidth = ($applyHeight*$oldWidth)/$oldHeight;
  220. } else {
  221. $applyWidth = $maxWidth;
  222. $applyHeight = $maxHeight;
  223. }
  224. $startX = 0;
  225. $startY = 0;
  226. break;
  227. case 'resizeCrop':
  228. /* Check if we're overresizing (or set new scale) */
  229. /* resize to max, then crop to center */
  230. if ($newWidth > $oldWidth) $newWidth = $oldWidth;
  231. $ratioX = $newWidth / $oldWidth;
  232. if ($newHeight > $oldHeight) $newHeight = $oldHeight;
  233. $ratioY = $newHeight / $oldHeight;
  234. if ($ratioX < $ratioY) {
  235. $startX = round(($oldWidth - ($newWidth / $ratioY))/2);
  236. $startY = 0;
  237. $oldWidth = round($newWidth / $ratioY);
  238. $oldHeight = $oldHeight;
  239. } else {
  240. $startX = 0;
  241. $startY = round(($oldHeight - ($newHeight / $ratioX))/2);
  242. $oldWidth = $oldWidth;
  243. $oldHeight = round($newHeight / $ratioX);
  244. }
  245. $applyWidth = $newWidth;
  246. $applyHeight = $newHeight;
  247. break;
  248. case 'crop':
  249. // straight centered crop
  250. $startY = ($oldHeight - $newHeight)/2;
  251. $startX = ($oldWidth - $newWidth)/2;
  252. $oldHeight = $newHeight;
  253. $applyHeight = $newHeight;
  254. $oldWidth = $newWidth;
  255. $applyWidth = $newWidth;
  256. break;
  257. }
  258. switch($ext) {
  259. case 'gif' :
  260. $oldImage = imagecreatefromgif($srcimg);
  261. break;
  262. case 'png' :
  263. $oldImage = imagecreatefrompng($srcimg);
  264. break;
  265. case 'jpg' :
  266. case 'jpeg' :
  267. $oldImage = imagecreatefromjpeg($srcimg);
  268. break;
  269. default :
  270. // image type is not a possible option
  271. return false;
  272. break;
  273. }
  274. // Create new image
  275. $newImage = imagecreatetruecolor($applyWidth, $applyHeight);
  276. // Put old image on top of new image
  277. imagealphablending($newImage, false);
  278. imagesavealpha($newImage, true);
  279. imagecopyresampled($newImage, $oldImage, 0, 0, $startX, $startY, $applyWidth, $applyHeight, $oldWidth, $oldHeight);
  280. switch($ext) {
  281. case 'gif' :
  282. imagegif($newImage, $dstimg, $quality);
  283. break;
  284. case 'png' :
  285. imagepng($newImage, $dstimg, round($quality/10));
  286. break;
  287. case 'jpg' :
  288. case 'jpeg' :
  289. imagejpeg($newImage, $dstimg, $quality);
  290. break;
  291. default :
  292. return false;
  293. break;
  294. }
  295. imagedestroy($newImage);
  296. imagedestroy($oldImage);
  297. return true;
  298. }
  299. } else { /* Nothing requested */
  300. return false;
  301. }
  302. }
  303. /* Many helper functions */
  304. function copy_or_log_error($tmp_name, $dst_folder, $dst_filename) {
  305. if (is_writeable($dst_folder)) {
  306. if (!copy($tmp_name, $dst_folder.DS.$dst_filename)) {
  307. unset($dst_filename);
  308. return $this->log_cakephp_error_and_return('Error uploading file.', 'publicaciones');
  309. }
  310. } else {
  311. // if dst_folder not writeable, let developer know
  312. debug('You must allow proper permissions for image processing. And the folder has to be writable.');
  313. debug("Run 'chmod 755 $dst_folder', and make sure the web server is it's owner.");
  314. return $this->log_cakephp_error_and_return('No write permissions on attachments folder.');
  315. }
  316. }
  317. function is_image($file_type) {
  318. $image_types = array('jpeg', 'jpg', 'gif', 'png');
  319. return in_array(strtolower($file_type), $image_types);
  320. }
  321. function log_proper_error($err_code) {
  322. switch ($err_code) {
  323. case UPLOAD_ERR_NO_FILE:
  324. return 0;
  325. case UPLOAD_ERR_INI_SIZE:
  326. $e = 'The uploaded file exceeds the upload_max_filesize directive in php.ini.';
  327. break;
  328. case UPLOAD_ERR_FORM_SIZE:
  329. $e = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
  330. break;
  331. case UPLOAD_ERR_PARTIAL:
  332. $e = 'The uploaded file was only partially uploaded.';
  333. break;
  334. case UPLOAD_ERR_NO_TMP_DIR:
  335. $e = 'Missing a temporary folder.';
  336. break;
  337. case UPLOAD_ERR_CANT_WRITE:
  338. $e = 'Failed to write file to disk.';
  339. break;
  340. case UPLOAD_ERR_EXTENSION:
  341. $e = 'File upload stopped by extension.';
  342. break;
  343. default:
  344. $e = 'Unknown upload error. Did you add array(\'type\' => \'file\') to your form?';
  345. }
  346. return $this->log_cakephp_error_and_return($e);
  347. }
  348. function log_cakephp_error_and_return($msg) {
  349. $_error["{$this->config['default_col']}_file_name"] = $msg;
  350. $this->controller->{$this->controller->modelClass}->validationErrors = array_merge($_error, $this->controller->{$this->controller->modelClass}->validationErrors);
  351. $this->log($msg, 'attachment-component');
  352. return false;
  353. }
  354. function image_type_to_extension($imagetype) {
  355. if (empty($imagetype)) return false;
  356. switch($imagetype) {
  357. case IMAGETYPE_TIFF_II : return 'tiff';
  358. case IMAGETYPE_TIFF_MM : return 'tiff';
  359. case IMAGETYPE_GIF : return 'gif';
  360. case IMAGETYPE_JPEG : return 'jpg';
  361. case IMAGETYPE_PNG : return 'png';
  362. case IMAGETYPE_SWF : return 'swf';
  363. case IMAGETYPE_PSD : return 'psd';
  364. case IMAGETYPE_BMP : return 'bmp';
  365. case IMAGETYPE_JPC : return 'jpc';
  366. case IMAGETYPE_JP2 : return 'jp2';
  367. case IMAGETYPE_JPX : return 'jpf';
  368. case IMAGETYPE_JB2 : return 'jb2';
  369. case IMAGETYPE_SWC : return 'swc';
  370. case IMAGETYPE_IFF : return 'aiff';
  371. case IMAGETYPE_WBMP : return 'wbmp';
  372. case IMAGETYPE_XBM : return 'xbm';
  373. default : return false;
  374. }
  375. }
  376. }