1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * Core utility and class required for file processing system
5: *
6: * @package PHPLucidFrame\File
7: * @since PHPLucidFrame v 1.0.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: *
12: * This source file is subject to the MIT license that is bundled
13: * with this source code in the file LICENSE
14: */
15:
16: namespace LucidFrame\File;
17:
18: /**
19: * This class is part of the PHPLucidFrame library.
20: * Helper for file processing system
21: */
22: class File extends \SplFileInfo
23: {
24: /** @var string The unique name string for this instance */
25: private $name;
26: /** @var string The unique string ID to append to the file name */
27: private $uniqueId;
28: /** @var array The dimension to be created for image upload */
29: private $dimensions;
30: /** @var string The upload directory path */
31: private $uploadPath;
32: /** @var string The original uploaded file name */
33: private $originalFileName;
34: /** @var string The file name generated */
35: private $fileName;
36: /** @var array The uploaded file information */
37: private $uploads;
38: /** @var array The file upload error information */
39: private $error;
40: /** @var array The image filter setting */
41: private $imageFilterSet;
42: /** @var bool Flag to save the uploaded file with original file name */
43: private $useOriginalFileName = false;
44:
45: /**
46: * Constructor
47: * @param string $fileName Path to the file
48: */
49: public function __construct($fileName = '')
50: {
51: $this->name = $fileName;
52: $this->uploadPath = FILE . 'tmp' . _DS_;
53: $this->defaultImageFilterSet();
54: if ($fileName) {
55: parent::__construct($fileName);
56: }
57: }
58:
59: /**
60: * Set default image filter set and merge with user's options
61: * @return object File
62: */
63: private function defaultImageFilterSet()
64: {
65: $this->imageFilterSet = array(
66: 'maxDimension' => '800x600',
67: 'resizeMode' => FILE_RESIZE_BOTH,
68: 'jpgQuality' => 75
69: );
70: $this->imageFilterSet = array_merge($this->imageFilterSet, _cfg('imageFilterSet'));
71: $this->setImageResizeMode($this->imageFilterSet['resizeMode']);
72: return $this;
73: }
74:
75: /**
76: * Set image resize mode
77: * @param string $value FILE_RESIZE_BOTH, FILE_RESIZE_WIDTH or FILE_RESIZE_HEIGHT
78: * @return object File
79: */
80: private function setImageResizeMode($value)
81: {
82: if (in_array($value, array(FILE_RESIZE_BOTH, FILE_RESIZE_WIDTH, FILE_RESIZE_HEIGHT))) {
83: $this->imageFilterSet['resizeMode'] = $value;
84: } else {
85: $this->imageFilterSet['resizeMode'] = FILE_RESIZE_BOTH;
86: }
87: return $this;
88: }
89:
90: /**
91: * Setter for the class properties
92: * @param string $key The property name
93: * @param mixed $value The value to be set
94: * @return object
95: */
96: public function set($key, $value)
97: {
98: if ($key === 'resize' || $key === 'resizeMode') {
99: $this->setImageResizeMode($value);
100: return $this;
101: }
102:
103: if ($key === 'maxDimension') {
104: $this->imageFilterSet['maxDimension'] = $value;
105: return $this;
106: }
107:
108: if ($key === 'jpgQuality') {
109: $this->imageFilterSet['jpgQuality'] = $value;
110: return $this;
111: }
112:
113: # if $uniqueId is explicitly given and $name was not explicitly given
114: # make $name and $uniqueId same
115: if ($key === 'uniqueId' && $value & $this->name === $this->uniqueId) {
116: $this->name = $value;
117: }
118:
119: if ($key === 'uploadDir' || $key === 'uploadPath') {
120: $value = rtrim(rtrim($value, '/'), _DS_) . _DS_;
121: $this->uploadPath = $value;
122: }
123:
124: $this->{$key} = $value;
125:
126: return $this;
127: }
128:
129: /**
130: * Getter for the class properties
131: * @param string $key The property name
132: * @return mixed $value The value of the property or null if $name does not exist.
133: */
134: public function get($key)
135: {
136: if ($key === 'uploadDir') {
137: return $this->uploadPath;
138: }
139: if (isset($this->{$key})) {
140: return $this->{$key};
141: }
142: return null;
143: }
144:
145: /**
146: * Getter for the original uploaded file name
147: */
148: public function getOriginalFileName()
149: {
150: return $this->originalFileName;
151: }
152:
153: /**
154: * Getter for the file name generated
155: */
156: #[\ReturnTypeWillChange]
157: public function getFileName()
158: {
159: return $this->fileName;
160: }
161:
162: /**
163: * Getter for the property `error`
164: * @return array The array of error information
165: *
166: * array(
167: * 'code' => 'Error code',
168: * 'message' => 'Error message'
169: * )
170: *
171: */
172: public function getError()
173: {
174: return $this->error;
175: }
176:
177: /**
178: * Get file upload error message for the given error code
179: * @param int $code The error code
180: * @return string The error message
181: */
182: public function getErrorMessage($code)
183: {
184: switch ($code) {
185: case UPLOAD_ERR_INI_SIZE:
186: $message = _t('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
187: break;
188: case UPLOAD_ERR_FORM_SIZE:
189: $message = _t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
190: break;
191: case UPLOAD_ERR_PARTIAL:
192: $message = _t('The uploaded file was only partially uploaded.');
193: break;
194: case UPLOAD_ERR_NO_FILE:
195: $message = _t('No file was uploaded.');
196: break;
197: case UPLOAD_ERR_NO_TMP_DIR:
198: $message = _t('Missing a temporary folder.');
199: break;
200: case UPLOAD_ERR_CANT_WRITE:
201: $message = _t('Failed to write file to disk.');
202: break;
203: case UPLOAD_ERR_EXTENSION:
204: $message = _t('File upload stopped by extension.');
205: break;
206: case FILE_UPLOAD_ERR_MOVE:
207: $message = _t('The uploaded file is not valid.');
208: break;
209: case FILE_UPLOAD_ERR_IMAGE_CREATE:
210: $message = _t('Failed to create image from the uploaded file.');
211: break;
212: default:
213: $message = _t('Unknown upload error.');
214: break;
215: }
216:
217: return $message;
218: }
219:
220: /**
221: * Move the uploaded file into the given directory.
222: * If the uploaded file is image, this will create the various images according to the given $dimension
223: *
224: * @param string|array $file The name 'xxx' from $_FILES['xxx']
225: * or The array of uploaded file information from $_FILES['xxx']
226: *
227: * @return array The array of the uploaded file information:
228: *
229: * array(
230: * 'name' => 'Name of the input element',
231: * 'fileName' => 'The uploaded file name',
232: * 'originalFileName' => 'The original file name user selected',
233: * 'extension'=> 'The selected and uploaded file extension',
234: * 'dir' => 'The uploaded directory',
235: * )
236: *
237: */
238: public function upload($file)
239: {
240: if (is_string($file)) {
241: if (!isset($_FILES[$file])) {
242: $this->error = array(
243: 'code' => UPLOAD_ERR_NO_FILE,
244: 'message' => $this->getErrorMessage(UPLOAD_ERR_NO_FILE)
245: );
246: return null;
247: }
248: $this->name = $file;
249: $file = $_FILES[$file];
250: }
251:
252: if (!isset($file['name']) || !isset($file['tmp_name'])) {
253: $this->error = array(
254: 'code' => UPLOAD_ERR_NO_FILE,
255: 'message' => $this->getErrorMessage(UPLOAD_ERR_NO_FILE)
256: );
257: return null;
258: }
259:
260: $fileName = stripslashes($file['name']);
261: $uploadedFile = $file['tmp_name'];
262: $info = pathinfo($fileName);
263: $extension = strtolower($info['extension']);
264: $uploaded = null;
265:
266: if ($fileName && $file['error'] === UPLOAD_ERR_OK) {
267: $this->originalFileName = $fileName;
268: $newFileName = $this->getNewFileName();
269:
270: if (!in_array($extension, array('jpg', 'jpeg', 'png', 'gif'))) {
271: # non-image file
272: $uploaded = $this->move($uploadedFile, $newFileName);
273: } else {
274: # image file
275: if (isset($this->imageFilterSet['maxDimension']) && $this->imageFilterSet['maxDimension']) {
276: # Upload the primary image by the configured dimension in config
277: $uploaded = $this->resizeImageByDimension($this->imageFilterSet['maxDimension'], $uploadedFile, $newFileName, $extension);
278: } else {
279: $uploaded = $this->move($uploadedFile, $newFileName);
280: }
281: # if the thumbnail dimensions are defined, create them
282: if (is_array($this->dimensions) && count($this->dimensions)) {
283: $this->resizeImageByDimension($this->dimensions, $uploadedFile, $newFileName, $extension);
284: }
285: }
286: } else {
287: $this->error = array(
288: 'code' => $file['error'],
289: 'message' => $this->getErrorMessage($file['error'])
290: );
291: }
292:
293: if ($uploaded) {
294: $this->uploads = array(
295: 'name' => $this->name,
296: 'fileName' => $uploaded,
297: 'originalFileName' => $this->originalFileName,
298: 'extension' => $extension,
299: 'dir' => $this->get('uploadDir')
300: );
301: }
302:
303: return $this->uploads;
304: }
305:
306: /**
307: * Get a new unique file name
308: *
309: * @return string The file name
310: */
311: private function getNewFileName()
312: {
313: $this->fileName = $this->getUniqueId() . '.' . $this->guessExtension();
314:
315: if ($this->useOriginalFileName) {
316: $this->fileName = $this->originalFileName;
317: }
318:
319: return $this->fileName;
320: }
321:
322: /**
323: * Get a unique id string
324: * @return string
325: */
326: private function getUniqueId()
327: {
328: return $this->uniqueId ?: uniqid();
329: }
330:
331: /**
332: * Return the extension of the original file name
333: * @param string $file The optional file name; if it is not given, the original file name will be used
334: * @return string The extension or an empty string if there is no file
335: */
336: public function guessExtension($file = '')
337: {
338: $file = $file ? $file : $this->originalFileName;
339:
340: if ($file) {
341: $info = pathinfo($file);
342: return $info['extension'];
343: }
344:
345: return '';
346: }
347:
348: /**
349: * Move the uploaded file to the new location with new file name
350: * @param string $file The source file
351: * @param string $newFileName The new file name
352: * @return string The new file name or null if any error occurs
353: */
354: protected function move($file, $newFileName)
355: {
356: $targetDir = $this->uploadPath;
357: if (!is_dir($targetDir)) {
358: @mkdir($targetDir, 0777, true);
359: }
360: if (@move_uploaded_file($file, $targetDir . $newFileName)) {
361: return $newFileName;
362: } else {
363: $this->error = array(
364: 'code' => FILE_UPLOAD_ERR_MOVE,
365: 'message' => $this->getErrorMessage(FILE_UPLOAD_ERR_MOVE)
366: );
367: return null;
368: }
369: }
370:
371: /**
372: * Resize the image file into the given width and height
373: * @param string|array $dimensions The dimension or array of dimensions,
374: * e.g., '400x250' or array('400x250', '200x150')
375: * @param string $file The source file
376: * @param string $newFileName The new file name to be created
377: * @param string $extension The file extension
378: * @return string The new file name or null if any error occurs
379: */
380: protected function resizeImageByDimension($dimensions, $file, $newFileName, $extension = null)
381: {
382: $singleDimension = false;
383: if (!is_array($dimensions)) {
384: $dimensions = array($dimensions);
385: $singleDimension = true;
386: }
387:
388: $extension = $extension ?: strtolower(pathinfo($file, PATHINFO_EXTENSION));
389:
390: if ($extension == "jpg" || $extension == "jpeg") {
391: $img = imagecreatefromjpeg($file);
392: } elseif ($extension == "png") {
393: $img = imagecreatefrompng($file);
394: } elseif ($extension == "gif") {
395: $img = imagecreatefromgif($file);
396: }
397:
398: if (isset($img) && $img) {
399: if (isset($this->imageFilterSet['jpgQuality']) && is_numeric($this->imageFilterSet['jpgQuality'])) {
400: $jpgQuality = $this->imageFilterSet['jpgQuality'];
401: } else {
402: $jpgQuality = 75;
403: }
404:
405: foreach ($dimensions as $dimension) {
406: $resize = explode('x', $dimension);
407: $resizeWidth = $resize[0];
408: $resizeHeight = $resize[1];
409:
410: if ($this->imageFilterSet['resizeMode'] == FILE_RESIZE_WIDTH) {
411: $tmp = File::resizeImageWidth($img, $file, $resizeWidth, $extension);
412: } elseif ($this->imageFilterSet['resizeMode'] == FILE_RESIZE_HEIGHT) {
413: $tmp = File::resizeImageHeight($img, $file, $resizeHeight. $extension);
414: } else {
415: $tmp = File::resizeImageBoth($img, $file, $resizeWidth, $resizeHeight, $extension);
416: }
417:
418: $targetDir = $singleDimension ? $this->uploadPath : $this->uploadPath . $dimension . _DS_;
419: if (!is_dir($targetDir)) {
420: @mkdir($targetDir, 0777, true);
421: }
422: $targetFileName = $targetDir . $newFileName;
423:
424: if ($extension == "gif") {
425: imagegif($tmp, $targetFileName);
426: } elseif ($extension == "png") {
427: imagealphablending($tmp, true);
428: imagesavealpha($tmp, true);
429: imagepng($tmp, $targetFileName);
430: } else {
431: imagejpeg($tmp, $targetFileName, $jpgQuality);
432: }
433:
434: imagedestroy($tmp);
435: }
436: if ($img) {
437: imagedestroy($img);
438: return $newFileName;
439: }
440: } else {
441: $this->error = array(
442: 'code' => FILE_UPLOAD_ERR_IMAGE_CREATE,
443: 'message' => $this->getErrorMessage(FILE_UPLOAD_ERR_IMAGE_CREATE)
444: );
445: }
446: return null;
447: }
448:
449: /**
450: * Resize an image to a desired width and height by given width
451: *
452: * @param resource $img The image resource identifier
453: * @param string $file The image file name
454: * @param int $newWidth The new width to resize
455: * @param string $extension The file extension
456: *
457: * @return resource An image resource identifier on success, FALSE on errors
458: */
459: public static function resizeImageWidth(&$img, $file, $newWidth, $extension = null)
460: {
461: $extension = $extension ? $extension : strtolower(pathinfo($file, PATHINFO_EXTENSION));
462: list($width, $height) = getimagesize($file);
463: $newHeight = ($height/$width) * $newWidth;
464:
465: $tmp = imagecreatetruecolor($newWidth, $newHeight);
466: imagealphablending($tmp, false);
467: if ($extension == 'png') {
468: imagesavealpha($tmp, true);
469: }
470:
471: imagecopyresampled($tmp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
472:
473: return $tmp;
474: }
475:
476: /**
477: * Resize an image to a desired width and height by given height
478: *
479: * @param resource $img The image resource identifier
480: * @param string $file The image file name
481: * @param int $newHeight The new height to resize
482: * @param string $extension The file extension
483: *
484: * @return resource An image resource identifier on success, FALSE on errors
485: */
486: public static function resizeImageHeight(&$img, $file, $newHeight, $extension = null)
487: {
488: $extension = $extension ? $extension : strtolower(pathinfo($file, PATHINFO_EXTENSION));
489:
490: list($width, $height) = getimagesize($file);
491: $newWidth = ($width/$height) * $newHeight;
492:
493: $tmp = imagecreatetruecolor($newWidth, $newHeight);
494: imagealphablending($tmp, false);
495: if ($extension == 'png') {
496: imagesavealpha($tmp, true);
497: }
498:
499: imagecopyresampled($tmp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
500:
501: return $tmp;
502: }
503:
504: /**
505: * Resize an image to a desired width and height by given width and height
506: *
507: * @param resource $img The image resource identifier
508: * @param string $file The image file name
509: * @param int $newWidth The new width to resize
510: * @param int $newHeight The new height to resize
511: * @param string $extension The file extension
512: *
513: * @return resource An image resource identifier on success, FALSE on errors
514: */
515: public static function resizeImageBoth(&$img, $file, $newWidth, $newHeight, $extension = null)
516: {
517: $extension = $extension ? $extension : strtolower(pathinfo($file, PATHINFO_EXTENSION));
518:
519: list($width, $height) = getimagesize($file);
520:
521: $scale = min($newWidth/$width, $newHeight/$height);
522: # If the image is larger than the max shrink it
523: if ($scale < 1) {
524: # new width for the image
525: $newWidth = floor($scale * $width);
526: # new height for the image
527: $newHeight = floor($scale * $height);
528: } else {
529: # if the image is small than than the resized width and height
530: $newWidth = $width;
531: $newHeight = $height;
532: }
533:
534: $tmp = imagecreatetruecolor($newWidth, $newHeight);
535: imagealphablending($tmp, false);
536: if ($extension == 'png') {
537: imagesavealpha($tmp, true);
538: }
539:
540: imagecopyresampled($tmp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
541:
542: return $tmp;
543: }
544:
545: /**
546: * Display an image fitting into the desired dimension
547: *
548: * @param string $fileName The file name with an absolute web path
549: * @param string $caption The image caption
550: * @param int $dimension The actual image dimension in "widthxheight"
551: * @param string $desiredDimension The desired dimension in "widthxheight"
552: * @param array $attributes The HTML attributes in array like key => value
553: *
554: * @return string The <img> tag
555: */
556: public static function img($fileName, $caption, $dimension, $desiredDimension = '0x0', array $attributes = array())
557: {
558: $regex = '/^[0-9]+x[0-9]+$/i'; # check the format of "99x99" for the dimensions
559: if (!preg_match($regex, $dimension)) {
560: echo '';
561: return null;
562: }
563: if (!preg_match($regex, $desiredDimension)) {
564: $desiredDimension = '0x0';
565: }
566: list($imgWidth, $imgHeight) = explode('x', strtolower($dimension));
567: list($desiredWidth, $desiredHeight) = explode('x', strtolower($desiredDimension));
568:
569: if ($imgWidth > $desiredWidth || $imgHeight > $desiredHeight) {
570: # scale down
571: if ($desiredWidth == 0 && $desiredHeight > 0) {
572: # resized to height
573: $desiredWidth = floor(($imgWidth/$imgHeight) * $desiredHeight);
574: $imgWidth = $desiredWidth;
575: $imgHeight = $desiredHeight;
576: } elseif ($desiredWidth > 0 && $desiredHeight == 0) {
577: # resized to width
578: $desiredHeight = floor(($imgHeight/$imgWidth) * $desiredWidth);
579: $imgWidth = $desiredWidth;
580: $imgHeight = $desiredHeight;
581: } elseif ($desiredWidth > 0 && $desiredHeight > 0) {
582: # resized both
583: $scale = min($desiredWidth/$imgWidth, $desiredHeight/$imgHeight);
584: # new width for the image
585: $imgWidth = floor($scale * $imgWidth);
586: # new height for the image
587: $imgHeight = floor($scale * $imgHeight);
588: if ($imgWidth < $desiredWidth || $imgHeight < $desiredHeight) {
589: $wDiff = $desiredWidth - $imgWidth;
590: $hDiff = $desiredHeight - $desiredWidth;
591: if ($wDiff > $hDiff) {
592: # resize to width
593: $imgHeight = floor(($imgHeight/$imgWidth) * $desiredWidth);
594: $imgWidth = $desiredWidth;
595: } else {
596: # resize to height
597: $imgWidth = floor(($imgWidth/$imgHeight) * $desiredHeight);
598: $imgHeight = $desiredHeight;
599: }
600: }
601: } else {
602: # if the desired dimension is not given
603: $desiredWidth = $imgWidth;
604: $desiredHeight = $imgHeight;
605: }
606: }
607:
608: $style = '';
609: if ($imgWidth > $desiredWidth) {
610: $marginH = floor(($imgWidth - $desiredWidth)/2);
611: $style = 'margin-left:-'.$marginH.'px';
612: }
613: if ($imgHeight > $desiredHeight) {
614: $marginV = floor(($imgHeight - $desiredHeight)/2);
615: $style = 'margin-top:-'.$marginV.'px';
616: }
617: if (isset($attributes['style']) && $attributes['style']) {
618: $style .= $attributes['style'];
619: }
620: $attributes['src'] = $fileName;
621: $attributes['alt'] = _h($caption);
622: $attributes['title'] = _h($caption);
623: $attributes['width'] = $imgWidth;
624: $attributes['height'] = $imgHeight;
625: $attributes['style'] = $style;
626:
627: $attrHTML = '';
628: foreach ($attributes as $key => $value) {
629: $attrHTML .= ' ' . $key . '="' . $value .'"';
630: }
631: return '<img '.$attrHTML.' />';
632: }
633: }
634: