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\Core;
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: $uploaded = null;
264:
265: if (empty($info['extension'])) {
266: $extension = _mime2ext($file['type']);
267: $fileName .= '.' . $extension;
268: } else {
269: $extension = strtolower($info['extension']);
270: }
271:
272: if ($fileName && $file['error'] === UPLOAD_ERR_OK) {
273: $this->originalFileName = $fileName;
274: $newFileName = $this->getNewFileName();
275:
276: if (!in_array($extension, array('jpg', 'jpeg', 'png', 'gif'))) {
277: # non-image file
278: $uploaded = $this->move($uploadedFile, $newFileName);
279: } else {
280: # image file
281: if (isset($this->imageFilterSet['maxDimension']) && $this->imageFilterSet['maxDimension']) {
282: # Upload the primary image by the configured dimension in config
283: $uploaded = $this->resizeImageByDimension($this->imageFilterSet['maxDimension'], $uploadedFile, $newFileName, $extension);
284: } else {
285: $uploaded = $this->move($uploadedFile, $newFileName);
286: }
287: # if the thumbnail dimensions are defined, create them
288: if (is_array($this->dimensions) && count($this->dimensions)) {
289: $this->resizeImageByDimension($this->dimensions, $uploadedFile, $newFileName, $extension);
290: }
291: }
292: } else {
293: $this->error = array(
294: 'code' => $file['error'],
295: 'message' => $this->getErrorMessage($file['error'])
296: );
297: }
298:
299: if ($uploaded) {
300: $this->uploads = array(
301: 'name' => $this->name,
302: 'fileName' => $uploaded,
303: 'originalFileName' => $this->originalFileName,
304: 'extension' => $extension,
305: 'dir' => $this->get('uploadDir')
306: );
307: }
308:
309: return $this->uploads;
310: }
311:
312: /**
313: * Get a new unique file name
314: *
315: * @return string The file name
316: */
317: private function getNewFileName()
318: {
319: $this->fileName = $this->getUniqueId() . '.' . $this->guessExtension();
320:
321: if ($this->useOriginalFileName) {
322: $this->fileName = $this->originalFileName;
323: }
324:
325: return $this->fileName;
326: }
327:
328: /**
329: * Get a unique id string
330: * @return string
331: */
332: private function getUniqueId()
333: {
334: return $this->uniqueId ?: uniqid();
335: }
336:
337: /**
338: * Return the extension of the original file name
339: * @param string $file The optional file name; if it is not given, the original file name will be used
340: * @return string The extension or an empty string if there is no file
341: */
342: public function guessExtension($file = '')
343: {
344: $file = $file ?: $this->originalFileName;
345:
346: if ($file) {
347: $info = pathinfo($file);
348: return $info['extension'];
349: }
350:
351: return '';
352: }
353:
354: /**
355: * Move the uploaded file to the new location with new file name
356: * @param string $file The source file
357: * @param string $newFileName The new file name
358: * @return string The new file name or null if any error occurs
359: */
360: protected function move($file, $newFileName)
361: {
362: $targetDir = $this->uploadPath;
363: if (!is_dir($targetDir)) {
364: @mkdir($targetDir, 0777, true);
365: }
366: if (@move_uploaded_file($file, $targetDir . $newFileName)) {
367: return $newFileName;
368: } else {
369: $this->error = array(
370: 'code' => FILE_UPLOAD_ERR_MOVE,
371: 'message' => $this->getErrorMessage(FILE_UPLOAD_ERR_MOVE)
372: );
373: return null;
374: }
375: }
376:
377: /**
378: * Resize the image file into the given width and height
379: * @param string|array $dimensions The dimension or array of dimensions,
380: * e.g., '400x250' or array('400x250', '200x150')
381: * @param string $file The source file
382: * @param string $newFileName The new file name to be created
383: * @param string $extension The file extension
384: * @return string The new file name or null if any error occurs
385: */
386: protected function resizeImageByDimension($dimensions, $file, $newFileName, $extension = null)
387: {
388: $singleDimension = false;
389: if (!is_array($dimensions)) {
390: $dimensions = array($dimensions);
391: $singleDimension = true;
392: }
393:
394: $extension = $extension ?: strtolower(pathinfo($file, PATHINFO_EXTENSION));
395:
396: if ($extension == "jpg" || $extension == "jpeg") {
397: $img = imagecreatefromjpeg($file);
398: } elseif ($extension == "png") {
399: $img = imagecreatefrompng($file);
400: } elseif ($extension == "gif") {
401: $img = imagecreatefromgif($file);
402: }
403:
404: if (isset($img) && $img) {
405: if (isset($this->imageFilterSet['jpgQuality']) && is_numeric($this->imageFilterSet['jpgQuality'])) {
406: $jpgQuality = $this->imageFilterSet['jpgQuality'];
407: } else {
408: $jpgQuality = 75;
409: }
410:
411: foreach ($dimensions as $dimension) {
412: $resize = explode('x', $dimension);
413: $resizeWidth = $resize[0];
414: $resizeHeight = $resize[1];
415:
416: if ($this->imageFilterSet['resizeMode'] == FILE_RESIZE_WIDTH) {
417: $tmp = File::resizeImageWidth($img, $file, $resizeWidth, $extension);
418: } elseif ($this->imageFilterSet['resizeMode'] == FILE_RESIZE_HEIGHT) {
419: $tmp = File::resizeImageHeight($img, $file, $resizeHeight. $extension);
420: } else {
421: $tmp = File::resizeImageBoth($img, $file, $resizeWidth, $resizeHeight, $extension);
422: }
423:
424: $targetDir = $singleDimension ? $this->uploadPath : $this->uploadPath . $dimension . _DS_;
425: if (!is_dir($targetDir)) {
426: @mkdir($targetDir, 0777, true);
427: }
428: $targetFileName = $targetDir . $newFileName;
429:
430: if ($extension == "gif") {
431: imagegif($tmp, $targetFileName);
432: } elseif ($extension == "png") {
433: imagealphablending($tmp, true);
434: imagesavealpha($tmp, true);
435: imagepng($tmp, $targetFileName);
436: } else {
437: imagejpeg($tmp, $targetFileName, $jpgQuality);
438: }
439:
440: imagedestroy($tmp);
441: }
442: if ($img) {
443: imagedestroy($img);
444: return $newFileName;
445: }
446: } else {
447: $this->error = array(
448: 'code' => FILE_UPLOAD_ERR_IMAGE_CREATE,
449: 'message' => $this->getErrorMessage(FILE_UPLOAD_ERR_IMAGE_CREATE)
450: );
451: }
452: return null;
453: }
454:
455: /**
456: * Resize an image to a desired width and height by given width
457: *
458: * @param resource $img The image resource identifier
459: * @param string $file The image file name
460: * @param int $newWidth The new width to resize
461: * @param string $extension The file extension
462: *
463: * @return resource An image resource identifier on success, FALSE on errors
464: */
465: public static function resizeImageWidth(&$img, $file, $newWidth, $extension = null)
466: {
467: $extension = $extension ? $extension : strtolower(pathinfo($file, PATHINFO_EXTENSION));
468: list($width, $height) = getimagesize($file);
469: $newHeight = ($height/$width) * $newWidth;
470:
471: $tmp = imagecreatetruecolor($newWidth, $newHeight);
472: imagealphablending($tmp, false);
473: if ($extension == 'png') {
474: imagesavealpha($tmp, true);
475: }
476:
477: imagecopyresampled($tmp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
478:
479: return $tmp;
480: }
481:
482: /**
483: * Resize an image to a desired width and height by given height
484: *
485: * @param resource $img The image resource identifier
486: * @param string $file The image file name
487: * @param int $newHeight The new height to resize
488: * @param string $extension The file extension
489: *
490: * @return resource An image resource identifier on success, FALSE on errors
491: */
492: public static function resizeImageHeight(&$img, $file, $newHeight, $extension = null)
493: {
494: $extension = $extension ? $extension : strtolower(pathinfo($file, PATHINFO_EXTENSION));
495:
496: list($width, $height) = getimagesize($file);
497: $newWidth = ($width/$height) * $newHeight;
498:
499: $tmp = imagecreatetruecolor($newWidth, $newHeight);
500: imagealphablending($tmp, false);
501: if ($extension == 'png') {
502: imagesavealpha($tmp, true);
503: }
504:
505: imagecopyresampled($tmp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
506:
507: return $tmp;
508: }
509:
510: /**
511: * Resize an image to a desired width and height by given width and height
512: *
513: * @param resource $img The image resource identifier
514: * @param string $file The image file name
515: * @param int $newWidth The new width to resize
516: * @param int $newHeight The new height to resize
517: * @param string $extension The file extension
518: *
519: * @return resource An image resource identifier on success, FALSE on errors
520: */
521: public static function resizeImageBoth(&$img, $file, $newWidth, $newHeight, $extension = null)
522: {
523: $extension = $extension ? $extension : strtolower(pathinfo($file, PATHINFO_EXTENSION));
524:
525: list($width, $height) = getimagesize($file);
526:
527: $scale = min($newWidth/$width, $newHeight/$height);
528: # If the image is larger than the max shrink it
529: if ($scale < 1) {
530: # new width for the image
531: $newWidth = floor($scale * $width);
532: # new height for the image
533: $newHeight = floor($scale * $height);
534: } else {
535: # if the image is small than than the resized width and height
536: $newWidth = $width;
537: $newHeight = $height;
538: }
539:
540: $tmp = imagecreatetruecolor($newWidth, $newHeight);
541: imagealphablending($tmp, false);
542: if ($extension == 'png') {
543: imagesavealpha($tmp, true);
544: }
545:
546: imagecopyresampled($tmp, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
547:
548: return $tmp;
549: }
550:
551: /**
552: * Display an image fitting into the desired dimension
553: *
554: * @param string $fileName The file name with an absolute web path
555: * @param string $caption The image caption
556: * @param int $dimension The actual image dimension in "widthxheight"
557: * @param string $desiredDimension The desired dimension in "widthxheight"
558: * @param array $attributes The HTML attributes in array like key => value
559: *
560: * @return string The <img> tag
561: */
562: public static function img($fileName, $caption, $dimension, $desiredDimension = '0x0', array $attributes = array())
563: {
564: $regex = '/^[0-9]+x[0-9]+$/i'; # check the format of "99x99" for the dimensions
565: if (!preg_match($regex, $dimension)) {
566: echo '';
567: return null;
568: }
569: if (!preg_match($regex, $desiredDimension)) {
570: $desiredDimension = '0x0';
571: }
572: list($imgWidth, $imgHeight) = explode('x', strtolower($dimension));
573: list($desiredWidth, $desiredHeight) = explode('x', strtolower($desiredDimension));
574:
575: if ($imgWidth > $desiredWidth || $imgHeight > $desiredHeight) {
576: # scale down
577: if ($desiredWidth == 0 && $desiredHeight > 0) {
578: # resized to height
579: $desiredWidth = floor(($imgWidth/$imgHeight) * $desiredHeight);
580: $imgWidth = $desiredWidth;
581: $imgHeight = $desiredHeight;
582: } elseif ($desiredWidth > 0 && $desiredHeight == 0) {
583: # resized to width
584: $desiredHeight = floor(($imgHeight/$imgWidth) * $desiredWidth);
585: $imgWidth = $desiredWidth;
586: $imgHeight = $desiredHeight;
587: } elseif ($desiredWidth > 0 && $desiredHeight > 0) {
588: # resized both
589: $scale = min($desiredWidth/$imgWidth, $desiredHeight/$imgHeight);
590: # new width for the image
591: $imgWidth = floor($scale * $imgWidth);
592: # new height for the image
593: $imgHeight = floor($scale * $imgHeight);
594: if ($imgWidth < $desiredWidth || $imgHeight < $desiredHeight) {
595: $wDiff = $desiredWidth - $imgWidth;
596: $hDiff = $desiredHeight - $desiredWidth;
597: if ($wDiff > $hDiff) {
598: # resize to width
599: $imgHeight = floor(($imgHeight/$imgWidth) * $desiredWidth);
600: $imgWidth = $desiredWidth;
601: } else {
602: # resize to height
603: $imgWidth = floor(($imgWidth/$imgHeight) * $desiredHeight);
604: $imgHeight = $desiredHeight;
605: }
606: }
607: } else {
608: # if the desired dimension is not given
609: $desiredWidth = $imgWidth;
610: $desiredHeight = $imgHeight;
611: }
612: }
613:
614: $style = '';
615: if ($imgWidth > $desiredWidth) {
616: $marginH = floor(($imgWidth - $desiredWidth)/2);
617: $style = 'margin-left:-'.$marginH.'px';
618: }
619: if ($imgHeight > $desiredHeight) {
620: $marginV = floor(($imgHeight - $desiredHeight)/2);
621: $style = 'margin-top:-'.$marginV.'px';
622: }
623: if (isset($attributes['style']) && $attributes['style']) {
624: $style .= $attributes['style'];
625: }
626: $attributes['src'] = $fileName;
627: $attributes['alt'] = _h($caption);
628: $attributes['title'] = _h($caption);
629: $attributes['width'] = $imgWidth;
630: $attributes['height'] = $imgHeight;
631: $attributes['style'] = $style;
632:
633: $attrHTML = '';
634: foreach ($attributes as $key => $value) {
635: $attrHTML .= ' ' . $key . '="' . $value .'"';
636: }
637: return '<img '.$attrHTML.' />';
638: }
639: }
640: