/mvc/components/file
[return to app]1
<?php
2 /**
3 * Tools for file management and image modification
4 */
5 class fileComponent {
6 /**
7 * Cache of files as they are processed
8 * @var array
9 */
10 public $files = array(), $resizedFiles = array();
11
12 /**
13 * Get the dimensions of an image file, results are cached in the $this->files array and are indexed by $src
14 *
15 * @param string $src Location of the image file
16 * @return mixed Array on success, boolean false on failure
17 */
18 public function getImageSize($src) {
19 //error-suppression is needed to avoid error warnings from invalid images
20 $imageSize = @getimagesize($src);
21 if ($imageSize) {
22 list($this->files[$src]['width'], $this->files[$src]['height'], $this->files[$src]['type']) =
$imageSize;
23 }
24 return $imageSize;
25 }
26
27 /**
28 * Get the format (gif, jpeg, etc.) of an image file
29 *
30 * @param string $src
31 * @return mixed String for standard image types, int for non-standard, boolean false on failure
32 */
33 public function getImageFormat($src) {
34 if (!isset($this->files[$src])) {
35 if (function_exists('exif_imagetype')) {
36 return exif_imagetype($src);
37 } else if (!$this->getImageSize($src)) {
38 return false;
39 }
40 }
41 //matches the PHP IMAGETYPE constants relevant to standard images (the two TIFF types are 7 & 8)
42 $formats = array(1 => 'gif', 'jpg', 'png', 'swf', 'psd', 'bmp', 'tif', 'tif', 15 => 'bmp');
43 return (isset($formats[$this->files[$src]['type']]) ? $formats[$this->files[$src]['type']]
44 : $this->files[$src]['type']);
45 }
46
47 /**
48 * Validates a file uploaded from a form
49 *
50 * @param string $id
51 * @param boolean or int $imagesOnly, a value of -1 will populate $this->files via getImageSize() but won't
throw an
52 * error for non-images
53 * @return boolean
54 */
55 public function validateUploadedFile($id, $imagesOnly = false) {
56 if (substr($_FILES[$id]['name'], 0, 1) == '.') {
57 $error = 'File names must not begin with a dot';
58 } else if (!isset($_FILES[$id]['error'])) {
59 $error = 'File upload failed, please try again';
60 } else {
61 switch ($_FILES[$id]['error']) {
62 case '0':
63 if ($imagesOnly) {
64 $imageSpecs = $this->getImageSize($_FILES[$id]['tmp_name']);
65 if (isset($this->files[$_FILES[$id]['tmp_name']])) {
66 $this->files[$_FILES[$id]['tmp_name']]['name'] = $_FILES[$id]['name'];
67 }
68 if ($imagesOnly > 0) {
69 if (!$imageSpecs) {
70 $error = 'Upload failed or file is not an image';
71 } else if (!in_array($this->files[$_FILES[$id]['tmp_name']]['type'], range(1, 3))) {
72 $error = 'Image type is not supported. Please upload a jpg, gif or png file.';
73 }
74 }
75 }
76 break;
77 case '1':
78 case '2':
79 $error = 'The file exceeds the maximum permitted filesize';
80 break;
81 case '3':
82 $error = 'The file was only partially uploaded, please try again';
83 break;
84 case '4':
85 $error = 'Please select a file before pressing the upload button';
86 break;
87 default:
88 $error = 'There was a system error and your file could not be saved, please try again';
89 break;
90 }
91 }
92 if (isset($error)) {
93 $_POST['errors'][$id] = $error;
94 return false;
95 }
96 return true;
97 }
98
99 /**
100 * Validates and moves a file uploaded from a form
101 *
102 * @param string $id
103 * @param string $destination
104 * @param boolean $imagesOnly
105 * @return boolean
106 */
107 public function moveUploadFile($id, $destination, $imagesOnly = false) {
108 $return = false;
109 if ($this->validateUploadedFile($id, $imagesOnly)) {
110 if (is_dir($destination)) {
111 $destinationLast = substr($destination, -1);
112 if ($destinationLast != '/' && $destinationLast != '\\') {
113 $destination .= '/';
114 }
115 $destination .= $_FILES[$id]['name'];
116 }
117 if (move_uploaded_file($_FILES[$id]['tmp_name'], $destination)) {
118 chmod($destination, 0777);
119 $return = true;
120 } else {
121 $_POST['errors'][$id] = 'Could not move the uploaded file';
122 }
123 }
124 return $return;
125 }
126
127 /**
128 * Resizes an image or generates a set of resized images, optionally adding a watermark to any of them.
129 *
130 * To resize/watermark/process a file immediately after being uploaded:
131 *
132 * $id = 'example'; //name of form's upload field
133 * $args = array('outputFolder' => 'exampleDestination'); //add args as desired
134 * $fileComponent = get::component('fileComponent');
135 * $fileComponent->validateUploadedFile($id);
136 * $fileComponent->resizeImage($args);
137 *
138 * Requires the PHP GD extension to be enabled.
139 *
140 * $specs array keys:
141 * width - default is original width, if constrain is true then this becomes the maximum width
142 * height - default is original height, if constrain is true then this becomes the maximum height
143 * constrain - Boolean, default is true - resize the image proportionally, if false the end image may be
stretched
144 * onlyReduce - Boolean, default is true - if true the end image can only be reduced, never be enlarged
(which
145 * usually degrades quality)
146 * outputFolder - folder where to output the file, if omitted the system default will be used
147 * outputFile - filename, if omitted the source filename will be used
148 * stdout - return the image bitstream to stdout (echo the image out), overrides outputFolder & outputFile
options
149 * watermark - string containing src or an array with keys:
150 * src - required, the path to the watermark image file
151 * opacity - Optional, default if omitted is 14
152 *
153 * You can specify generation of multiple output files by setting specs to an array of $specs, eg.:
154 * $specs[] = array('width' => 200, 'height' => 100, 'outputFile' => 'thumbnail.jpg');
155 * $specs[] = array('width' => 500, 'height' => 300, 'outputFile' => 'bigfile.jpg');
156 *
157 * @param array $specs
158 * @param string $src Optional, if omitted the last file $this->files will be used which is populated
automatically
159 * with the uploaded-file when you call validateUploadedFile() or uploadFile().
160 * The array is also populated manually from $src when you call getImageSize($src)
161 * if $this->files is empty then the method will exit
162 * @return boolean
163 */
164 public function resizeImage(array $specs, $src = null) {
165 if (!$src && $this->files) {
166 end($this->files);
167 $src = key($this->files);
168 }
169 if (!isset($this->files[$src]) && !$this->getImageSize($src)){
170 return false;
171 }
172 $types = array(1 => 'gif', 'jpeg', 'png');
173 $createImageFunction = 'imagecreatefrom' . $types[$this->files[$src]['type']];
174 $sourceImage = $createImageFunction($src);
175 if (!is_array(current($specs))) {
176 $specs = array($specs);
177 }
178 foreach ($specs as $image) {
179 $width = $image['width'];
180 $height = $image['height'];
181 if (!isset($image['constrain']) || $image['constrain']) {
182 $proportion = ($width / $this->files[$src]['width']);
183 $proposedHeight = round($this->files[$src]['height'] * $proportion);
184 if ($proposedHeight <= $height) {
185 $height = $proposedHeight;
186 } else {
187 $proportion = ($height / $this->files[$src]['height']);
188 $width = round($this->files[$src]['width'] * $proportion);
189 }
190
191 //need to check both height && width as due to rounding it is possible for
192 //one dimension to be equal and the other to be 1px larger than original
193 if ((!isset($image['onlyReduce']) || $image['onlyReduce'])
194 && ($width > $this->files[$src]['width'] || $height > $this->files[$src]['height'])) {
195 $width = $this->files[$src]['width'];
196 $height = $this->files[$src]['height'];
197 }
198 }
199
200 if ($this->files[$src]['type']== 2) {
201 $finalImage = imagecreatetruecolor($width, $height);
202 } else {
203 $finalImage = imagecreate($width, $height);
204 imagecolortransparent($finalImage, imagecolorallocate($finalImage, 0, 0, 0));
205 }
206 imagecopyresampled($finalImage, $sourceImage, 0, 0, 0, 0, $width, $height,
207 $this->files[$src]['width'], $this->files[$src]['height']);
208 if (isset($image['watermark']) && !is_array($image['watermark'])) {
209 $image['watermark'] = array('src' => $image['watermark']);
210 }
211 if (isset($image['watermark']) && isset($image['watermark']['src'])) {
212 $watermarkSrc = $image['watermark']['src'];
213 if (!isset($this->files[$watermarkSrc]) && !$this->getImageSize($watermarkSrc)) {
214 if (DEBUG_MODE) {
215 trigger_error('Watermark file' . $watermarkSrc . ' is invalid', E_USER_NOTICE);
216 }
217 }
218 if (isset($this->files[$watermarkSrc])) {
219 $watermark = $this->files[$watermarkSrc];
220 $watermarkOffsetX = max(round(($width - $watermark['width']) / 2), 0);
221 $watermarkOffsetY = max(round(($height - $watermark['height']) / 2), 0);
222 if ($width > $watermark['width'] && $height > $watermark['height']) {
223 if (!isset($this->files[$watermarkSrc]['image'])) {
224 $this->files[$watermarkSrc]['image'] = imagecreatefrompng($watermarkSrc);
225 if ($this->files[$src]['type'] != 2) {
226 $endOfWatermarkX = ($watermarkOffsetX + $watermark['width']);
227 $endOfWatermarkY = ($watermarkOffsetY + $watermark['height']);
228 $transparency = imagecolorat($this->files[$watermarkSrc]['image'], 1, 1);
229 for ($x = $watermarkOffsetX; $x <= $endOfWatermarkX; $x++) {
230 for ($y = $endOfWatermarkY; $y <= $endOfWatermarkY; $y++) {
231 $rbg = imagecolorsforindex($finalImage, imagecolorat($finalImage, $x,
$y));
232 if ($rbg['alpha'] == 127) {
233 imagesetpixel($this->files[$watermarkSrc]['image'],
234 ($x - $watermarkOffsetX), ($y - $watermarkOffsetY),
235 $transparency);
236 }
237 }
238 }
239 }
240 }
241 $opacity = (isset($image['watermark']['opacity']) ? $image['watermark']['opacity'] : 14);
242 imagecopymerge($finalImage, $this->files[$watermarkSrc]['image'], $watermarkOffsetX,
243 $watermarkOffsetY, 0, 0, $watermark['width'], $watermark['height'],
$opacity);
244 }
245 }
246 }
247
248 if (isset($image['stdout']) && $image['stdout']) {
249 $outputPath = null; //send to stdout
250 } else if (!isset($image['outputFolder']) && !isset($image['outputFile'])) {
251 $outputPath = $src; //overwrite original upload
252 } else {
253 $outputPath = (isset($image['outputFolder']) ? $image['outputFolder'] : '');
254 $outputPathLast = substr($outputPath, -1);
255 if ($outputPath && $outputPathLast != '/' && $outputPathLast != '\\') {
256 $outputPath .= '/';
257 }
258 if (!isset($image['outputFile'])) {
259 if (isset($this->files[$src]['name'])) {
260 $image['outputFile'] = $this->files[$src]['name'];
261 } else {
262 $srcParts = explode('/', str_replace('\\', '/', $src));
263 $image['outputFile'] = end($srcParts);
264 }
265 }
266 $outputPath .= $image['outputFile'];
267 }
268
269 $this->resizedFiles[$outputPath]['width'] = $width;
270 $this->resizedFiles[$outputPath]['height'] = $height;
271 $quality = ($this->files[$src]['type'] == 2 ? 75 : null);
272 $generateImageFunction = 'image' . $types[$this->files[$src]['type']];
273 $generateImageFunction($finalImage, $outputPath, $quality);
274 imagedestroy($finalImage);
275 }
276 imagedestroy($sourceImage);
277 return true;
278 }
279 }