initial commit
[namibia] / public / scripts / blueimp-jQuery-File-Upload / server / php / upload.class.php
1 <?php
2 /*
3  * jQuery File Upload Plugin PHP Class 5.11
4  * https://github.com/blueimp/jQuery-File-Upload
5  *
6  * Copyright 2010, Sebastian Tschan
7  * https://blueimp.net
8  *
9  * Licensed under the MIT license:
10  * http://www.opensource.org/licenses/MIT
11  */
12
13 class UploadHandler
14 {
15     protected $options;
16
17     function __construct($options=null) {
18         $this->options = array(
19             'script_url' => $this->getFullUrl().'/',
20             'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/',
21             'upload_url' => $this->getFullUrl().'/files/',
22             'param_name' => 'files',
23             // Set the following option to 'POST', if your server does not support
24             // DELETE requests. This is a parameter sent to the client:
25             'delete_type' => 'DELETE',
26             // The php.ini settings upload_max_filesize and post_max_size
27             // take precedence over the following max_file_size setting:
28             'max_file_size' => null,
29             'min_file_size' => 1,
30             'accept_file_types' => '/.+$/i',
31             // The maximum number of files for the upload directory:
32             'max_number_of_files' => null,
33             // Image resolution restrictions:
34             'max_width' => null,
35             'max_height' => null,
36             'min_width' => 1,
37             'min_height' => 1,
38             // Set the following option to false to enable resumable uploads:
39             'discard_aborted_uploads' => true,
40             // Set to true to rotate images based on EXIF meta data, if available:
41             'orient_image' => false,
42             'image_versions' => array(
43                 // Uncomment the following version to restrict the size of
44                 // uploaded images. You can also add additional versions with
45                 // their own upload directories:
46                 /*
47                 'large' => array(
48                     'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/',
49                     'upload_url' => $this->getFullUrl().'/files/',
50                     'max_width' => 1920,
51                     'max_height' => 1200,
52                     'jpeg_quality' => 95
53                 ),
54                 */
55                 'thumbnail' => array(
56                     'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/thumbnails/',
57                     'upload_url' => $this->getFullUrl().'/thumbnails/',
58                     'max_width' => 80,
59                     'max_height' => 80
60                 )
61             )
62         );
63         if ($options) {
64             $this->options = array_replace_recursive($this->options, $options);
65         }
66     }
67
68     protected function getFullUrl() {
69         return
70                 (isset($_SERVER['HTTPS']) ? 'https://' : 'http://').
71                 (isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
72                 (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
73                 (isset($_SERVER['HTTPS']) && $_SERVER['SERVER_PORT'] === 443 ||
74                 $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
75                 substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
76     }
77
78     protected function set_file_delete_url($file) {
79         $file->delete_url = $this->options['script_url']
80             .'?file='.rawurlencode($file->name);
81         $file->delete_type = $this->options['delete_type'];
82         if ($file->delete_type !== 'DELETE') {
83             $file->delete_url .= '&_method=DELETE';
84         }
85     }
86
87     protected function get_file_object($file_name) {
88         $file_path = $this->options['upload_dir'].$file_name;
89         if (is_file($file_path) && $file_name[0] !== '.') {
90             $file = new stdClass();
91             $file->name = $file_name;
92             $file->size = filesize($file_path);
93             $file->url = $this->options['upload_url'].rawurlencode($file->name);
94             foreach($this->options['image_versions'] as $version => $options) {
95                 if (is_file($options['upload_dir'].$file_name)) {
96                     $file->{$version.'_url'} = $options['upload_url']
97                         .rawurlencode($file->name);
98                 }
99             }
100             $this->set_file_delete_url($file);
101             return $file;
102         }
103         return null;
104     }
105
106     protected function get_file_objects() {
107         return array_values(array_filter(array_map(
108             array($this, 'get_file_object'),
109             scandir($this->options['upload_dir'])
110         )));
111     }
112
113     protected function create_scaled_image($file_name, $options) {
114         $file_path = $this->options['upload_dir'].$file_name;
115         $new_file_path = $options['upload_dir'].$file_name;
116         list($img_width, $img_height) = @getimagesize($file_path);
117         if (!$img_width || !$img_height) {
118             return false;
119         }
120         $scale = min(
121             $options['max_width'] / $img_width,
122             $options['max_height'] / $img_height
123         );
124         if ($scale >= 1) {
125             if ($file_path !== $new_file_path) {
126                 return copy($file_path, $new_file_path);
127             }
128             return true;
129         }
130         $new_width = $img_width * $scale;
131         $new_height = $img_height * $scale;
132         $new_img = @imagecreatetruecolor($new_width, $new_height);
133         switch (strtolower(substr(strrchr($file_name, '.'), 1))) {
134             case 'jpg':
135             case 'jpeg':
136                 $src_img = @imagecreatefromjpeg($file_path);
137                 $write_image = 'imagejpeg';
138                 $image_quality = isset($options['jpeg_quality']) ?
139                     $options['jpeg_quality'] : 75;
140                 break;
141             case 'gif':
142                 @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
143                 $src_img = @imagecreatefromgif($file_path);
144                 $write_image = 'imagegif';
145                 $image_quality = null;
146                 break;
147             case 'png':
148                 @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
149                 @imagealphablending($new_img, false);
150                 @imagesavealpha($new_img, true);
151                 $src_img = @imagecreatefrompng($file_path);
152                 $write_image = 'imagepng';
153                 $image_quality = isset($options['png_quality']) ?
154                     $options['png_quality'] : 9;
155                 break;
156             default:
157                 $src_img = null;
158         }
159         $success = $src_img && @imagecopyresampled(
160             $new_img,
161             $src_img,
162             0, 0, 0, 0,
163             $new_width,
164             $new_height,
165             $img_width,
166             $img_height
167         ) && $write_image($new_img, $new_file_path, $image_quality);
168         // Free up memory (imagedestroy does not delete files):
169         @imagedestroy($src_img);
170         @imagedestroy($new_img);
171         return $success;
172     }
173
174     protected function validate($uploaded_file, $file, $error, $index) {
175         if ($error) {
176             $file->error = $error;
177             return false;
178         }
179         if (!$file->name) {
180             $file->error = 'missingFileName';
181             return false;
182         }
183         if (!preg_match($this->options['accept_file_types'], $file->name)) {
184             $file->error = 'acceptFileTypes';
185             return false;
186         }
187         if ($uploaded_file && is_uploaded_file($uploaded_file)) {
188             $file_size = filesize($uploaded_file);
189         } else {
190             $file_size = $_SERVER['CONTENT_LENGTH'];
191         }
192         if ($this->options['max_file_size'] && (
193                 $file_size > $this->options['max_file_size'] ||
194                 $file->size > $this->options['max_file_size'])
195             ) {
196             $file->error = 'maxFileSize';
197             return false;
198         }
199         if ($this->options['min_file_size'] &&
200             $file_size < $this->options['min_file_size']) {
201             $file->error = 'minFileSize';
202             return false;
203         }
204         if (is_int($this->options['max_number_of_files']) && (
205                 count($this->get_file_objects()) >= $this->options['max_number_of_files'])
206             ) {
207             $file->error = 'maxNumberOfFiles';
208             return false;
209         }
210         list($img_width, $img_height) = @getimagesize($uploaded_file);
211         if (is_int($img_width)) {
212             if ($this->options['max_width'] && $img_width > $this->options['max_width'] ||
213                     $this->options['max_height'] && $img_height > $this->options['max_height']) {
214                 $file->error = 'maxResolution';
215                 return false;
216             }
217             if ($this->options['min_width'] && $img_width < $this->options['min_width'] ||
218                     $this->options['min_height'] && $img_height < $this->options['min_height']) {
219                 $file->error = 'minResolution';
220                 return false;
221             }
222         }
223         return true;
224     }
225
226     protected function upcount_name_callback($matches) {
227         $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1;
228         $ext = isset($matches[2]) ? $matches[2] : '';
229         return ' ('.$index.')'.$ext;
230     }
231
232     protected function upcount_name($name) {
233         return preg_replace_callback(
234             '/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/',
235             array($this, 'upcount_name_callback'),
236             $name,
237             1
238         );
239     }
240
241     protected function trim_file_name($name, $type, $index) {
242         // Remove path information and dots around the filename, to prevent uploading
243         // into different directories or replacing hidden system files.
244         // Also remove control characters and spaces (\x00..\x20) around the filename:
245         $file_name = trim(basename(stripslashes($name)), ".\x00..\x20");
246         // Add missing file extension for known image types:
247         if (strpos($file_name, '.') === false &&
248             preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
249             $file_name .= '.'.$matches[1];
250         }
251         if ($this->options['discard_aborted_uploads']) {
252             while(is_file($this->options['upload_dir'].$file_name)) {
253                 $file_name = $this->upcount_name($file_name);
254             }
255         }
256         return $file_name;
257     }
258
259     protected function handle_form_data($file, $index) {
260         // Handle form data, e.g. $_REQUEST['description'][$index]
261     }
262
263     protected function orient_image($file_path) {
264         $exif = @exif_read_data($file_path);
265         if ($exif === false) {
266             return false;
267         }
268         $orientation = intval(@$exif['Orientation']);
269         if (!in_array($orientation, array(3, 6, 8))) { 
270             return false;
271         }
272         $image = @imagecreatefromjpeg($file_path);
273         switch ($orientation) {
274                   case 3:
275                     $image = @imagerotate($image, 180, 0);
276                     break;
277                   case 6:
278                     $image = @imagerotate($image, 270, 0);
279                     break;
280                   case 8:
281                     $image = @imagerotate($image, 90, 0);
282                     break;
283                 default:
284                     return false;
285         }
286         $success = imagejpeg($image, $file_path);
287         // Free up memory (imagedestroy does not delete files):
288         @imagedestroy($image);
289         return $success;
290     }
291
292     protected function handle_file_upload($uploaded_file, $name, $size, $type, $error, $index) {
293         $file = new stdClass();
294         $file->name = $this->trim_file_name($name, $type, $index);
295         $file->size = intval($size);
296         $file->type = $type;
297         if ($this->validate($uploaded_file, $file, $error, $index)) {
298             $this->handle_form_data($file, $index);
299             $file_path = $this->options['upload_dir'].$file->name;
300             $append_file = !$this->options['discard_aborted_uploads'] &&
301                 is_file($file_path) && $file->size > filesize($file_path);
302             clearstatcache();
303             if ($uploaded_file && is_uploaded_file($uploaded_file)) {
304                 // multipart/formdata uploads (POST method uploads)
305                 if ($append_file) {
306                     file_put_contents(
307                         $file_path,
308                         fopen($uploaded_file, 'r'),
309                         FILE_APPEND
310                     );
311                 } else {
312                     move_uploaded_file($uploaded_file, $file_path);
313                 }
314             } else {
315                 // Non-multipart uploads (PUT method support)
316                 file_put_contents(
317                     $file_path,
318                     fopen('php://input', 'r'),
319                     $append_file ? FILE_APPEND : 0
320                 );
321             }
322             $file_size = filesize($file_path);
323             if ($file_size === $file->size) {
324                 if ($this->options['orient_image']) {
325                         $this->orient_image($file_path);
326                 }
327                 $file->url = $this->options['upload_url'].rawurlencode($file->name);
328                 foreach($this->options['image_versions'] as $version => $options) {
329                     if ($this->create_scaled_image($file->name, $options)) {
330                         if ($this->options['upload_dir'] !== $options['upload_dir']) {
331                             $file->{$version.'_url'} = $options['upload_url']
332                                 .rawurlencode($file->name);
333                         } else {
334                             clearstatcache();
335                             $file_size = filesize($file_path);
336                         }
337                     }
338                 }
339             } else if ($this->options['discard_aborted_uploads']) {
340                 unlink($file_path);
341                 $file->error = 'abort';
342             }
343             $file->size = $file_size;
344             $this->set_file_delete_url($file);
345         }
346         return $file;
347     }
348
349     public function get() {
350         $file_name = isset($_REQUEST['file']) ?
351             basename(stripslashes($_REQUEST['file'])) : null;
352         if ($file_name) {
353             $info = $this->get_file_object($file_name);
354         } else {
355             $info = $this->get_file_objects();
356         }
357         header('Content-type: application/json');
358         echo json_encode($info);
359     }
360
361     public function post() {
362         if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') {
363             return $this->delete();
364         }
365         $upload = isset($_FILES[$this->options['param_name']]) ?
366             $_FILES[$this->options['param_name']] : null;
367         $info = array();
368         if ($upload && is_array($upload['tmp_name'])) {
369             // param_name is an array identifier like "files[]",
370             // $_FILES is a multi-dimensional array:
371             foreach ($upload['tmp_name'] as $index => $value) {
372                 $info[] = $this->handle_file_upload(
373                     $upload['tmp_name'][$index],
374                     isset($_SERVER['HTTP_X_FILE_NAME']) ?
375                         $_SERVER['HTTP_X_FILE_NAME'] : $upload['name'][$index],
376                     isset($_SERVER['HTTP_X_FILE_SIZE']) ?
377                         $_SERVER['HTTP_X_FILE_SIZE'] : $upload['size'][$index],
378                     isset($_SERVER['HTTP_X_FILE_TYPE']) ?
379                         $_SERVER['HTTP_X_FILE_TYPE'] : $upload['type'][$index],
380                     $upload['error'][$index],
381                     $index
382                 );
383             }
384         } elseif ($upload || isset($_SERVER['HTTP_X_FILE_NAME'])) {
385             // param_name is a single object identifier like "file",
386             // $_FILES is a one-dimensional array:
387             $info[] = $this->handle_file_upload(
388                 isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
389                 isset($_SERVER['HTTP_X_FILE_NAME']) ?
390                     $_SERVER['HTTP_X_FILE_NAME'] : (isset($upload['name']) ?
391                         $upload['name'] : null),
392                 isset($_SERVER['HTTP_X_FILE_SIZE']) ?
393                     $_SERVER['HTTP_X_FILE_SIZE'] : (isset($upload['size']) ?
394                         $upload['size'] : null),
395                 isset($_SERVER['HTTP_X_FILE_TYPE']) ?
396                     $_SERVER['HTTP_X_FILE_TYPE'] : (isset($upload['type']) ?
397                         $upload['type'] : null),
398                 isset($upload['error']) ? $upload['error'] : null
399             );
400         }
401         header('Vary: Accept');
402         $json = json_encode($info);
403         $redirect = isset($_REQUEST['redirect']) ?
404             stripslashes($_REQUEST['redirect']) : null;
405         if ($redirect) {
406             header('Location: '.sprintf($redirect, rawurlencode($json)));
407             return;
408         }
409         if (isset($_SERVER['HTTP_ACCEPT']) &&
410             (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) {
411             header('Content-type: application/json');
412         } else {
413             header('Content-type: text/plain');
414         }
415         echo $json;
416     }
417
418     public function delete() {
419         $file_name = isset($_REQUEST['file']) ?
420             basename(stripslashes($_REQUEST['file'])) : null;
421         $file_path = $this->options['upload_dir'].$file_name;
422         $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path);
423         if ($success) {
424             foreach($this->options['image_versions'] as $version => $options) {
425                 $file = $options['upload_dir'].$file_name;
426                 if (is_file($file)) {
427                     unlink($file);
428                 }
429             }
430         }
431         header('Content-type: application/json');
432         echo json_encode($success);
433     }
434
435 }