JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Fix db_get_value after mysql->mysqli upgrade
[wfpl.git] / upload.php
1 <?php
2
3 # This program is in the public domain within the United States. Additionally,
4 # we waive copyright and related rights in the work worldwide through the CC0
5 # 1.0 Universal public domain dedication, which can be found at
6 # http://creativecommons.org/publicdomain/zero/1.0/
7
8
9 # This file contains functions to accept files being uplodad with the <input
10 # type="file" name="foo"> control.
11 #
12 # ########
13 # # HTML #
14 # ########
15
16 # First, your <form> tag must contain this attribute:
17 # enctype="multipart/form-data"
18
19 # Second, you should indicate to the browser the maximum file size (in bytes)
20 # allowed for uploads with a hidden input field named MAX_FILE_SIZE. You can
21 # use the function upload_max_filesize() to get the maximum allowed size that
22 # PHP will accept.
23
24 # Example:
25
26 # <form action="foo.php" enctype="multipart/form-data" method="post">
27 # <input type="hidden" name="MAX_FILE_SIZE" value="2097152">
28 # <input type="file" name="photo">
29 # <input type="submit" name="save" value="Save">
30 # </form>
31
32 # #######
33 # # PHP #
34 # #######
35 #
36 # In the php code you can use either save_uploaded_file('photo',
37 # 'upload/dir/'); or save_uploaded_image('photo', 'upload/dir/'); The only
38 # difference being that save_uploaded_image() will convert gifs to PNGs.
39
40 # Both functions will generate a reasonable filename based on the filename
41 # passed from the browser (and on the mime-type if there's no extension) unless
42 # you specify a filename. See the comments above the function definitions below
43 # for more details.
44
45 # In a future version of save_uploaded_image(), when you specify a filename, it
46 # will check the image type of the uploaded image, and if it's different than
47 # the type you specified, it will convert the image for you.
48
49
50 $GLOBALS['mime_to_ext'] = array(
51         'text/plain' => 'txt',
52         'text/html'  => 'html',
53         'image/jpeg' => 'jpg',
54         'image/jpe' => 'jpg',
55         'image/jpg'  => 'jpg',
56         'image/gif'  => 'gif',
57         'image/png'  => 'png',
58         'application/pdf' => 'pdf'
59 );
60
61 $GLOBALS['ext_to_ext'] = array(
62         'text' => 'txt',
63         'jpe'  => 'jpg',
64         'jpeg' => 'jpg',
65         'htm'  => 'html'
66 );
67
68 # return the upload_max_filesize in bytes
69 function upload_max_filesize() {
70         $max = ini_get('upload_max_filesize');
71         $postfix = strtolower(substr($max, -1));
72         if($postfix == 'g') {
73                 return substr($max, 0, -1) * 1073741824;
74         } elseif($postfix == 'm') {
75                 return substr($max, 0, -1) * 1048576;
76         } elseif ($postfix == 'k') {
77                 return substr($max, 0, -1) * 1024;
78         } else {
79                 return $max;
80         }
81 }
82
83 # return the extension this path should have WITHOUT the period
84 function path_or_mime_to_ext($path, $mime = 'text/plain', $default = 'txt') {
85         $last_dot = strrpos($path, '.');
86         if($last_dot === false || $last_dot === 0 || $last_dot === (strlen($path) - 1)) {
87                 # no extension
88                 if(isset($GLOBALS['mime_to_ext'][$mime])) {
89                         return $GLOBALS['mime_to_ext'][$mime];
90                 } else {
91                         return $default;
92                 }
93         } else {
94                 $ext = strtolower(substr($path, $last_dot + 1));
95                 if(isset($GLOBALS['ext_to_ext'][$ext])) {
96                         return $GLOBALS['ext_to_ext'][$ext];
97                 }
98                 return $ext;
99         }
100 }
101
102 # Add or fix extension on path
103 # This just does string manipulation (ie  doesn't move/open/etc any files.)
104 # Mime type used to generate extension ONLY IF it doesn't have one already.
105 function path_fix_ext($path, $mime = 'text/plain', $default_ext = '.txt') {
106         $last_dot = strrpos($path, '.');
107         if($last_dot === false || $last_dot === 0) { # no extension
108                 $path .= '.' . path_or_mime_to_ext($path, $mime, $default_ext);
109         } else {
110                 $basename = substr($path, 0, $last_dot + 1); # keep dot
111                 $path = $basename . path_or_mime_to_ext($path, $mime, $default_ext);
112         }
113
114         return $path;
115 }
116
117 # pass in the client's path that came from an html <input type="file"/> tag
118 #
119 # mime time used to generate extension ONLY IF it doesn't have one already.
120 function generate_filename($path, $mime = 'text/plain', $default_ext = '.txt') {
121         # lower case
122         $filename = strtolower($path);
123
124         # remove directories (unix, windows and mac paths)
125         $last = strrpos($filename, '/');
126         if($last === false) {
127                 $last = strrpos($filename, '\\');
128         }
129         if($last === false) {
130                 $last = strrpos($filename, ':');
131         }
132         if($last) {
133                 $filename = substr($filename, $last + 1);
134         }
135
136         # replace symbols with underscores
137         $filename = preg_replace('|[^a-z0-9_.]|', '_', $filename);
138
139         # limit length
140         if(strlen($filename > 80)) {
141                 $filename = substr($filename, -80);
142         }
143
144         # remove dots from the beginning (no invisible files)
145         $filename = preg_replace('|^\.*|', '', $filename);
146
147         # make sure there's something before the extension
148         if ($filename == '') {
149                 return '_';
150         }
151
152         $filename = path_fix_ext($filename, $mime, $default_ext);
153
154         return $filename;
155 }
156
157
158
159 # Move uploaded file, and return the new filename.
160 #
161 # $key: Pass in the index into the $_FILES array (the name of the html input tag) and
162 # the path to the folder you'd like it saved to.
163 #
164 # $path: If path ends with a slash this function will generate a filename based
165 # on the client's name. If it ends with a period, the dot will be removed and
166 # the client's name appended. Otherwise $path will be used as the filename
167 # exactly as is, even if extensions differ between the client's name and $path.
168 #
169 # where user uploads "c:\foo\Bar baz.PDF" at <input name="in" type="file">
170 #    save_uploaded_file('in', 'uploaded_pdfs/'); yeilds:
171 #       "uploaded_pdfs/bar_baz.pdf"
172 #    save_uploaded_file('in', 'uploaded_pdfs/prefix.'); yeilds:
173 #       "uploaded_pdfs/prefixbar_baz.pdf"
174 #    save_uploaded_file('in', 'uploaded_pdfs/qux.pdf'); yeilds:
175 #       "uploaded_pdfs/qux.pdf"
176 function save_uploaded_file($key, $path) {
177         $end = substr($path, -1);
178         if($end == '.' || $end == '/') {
179                 if($end == '.') {
180                         $path = substr($path, 0, -1);
181                 }
182                 $filename = $path . generate_filename($_FILES[$key]['name'], $_FILES[$key]['type']);
183         } else {
184                 $filename = $path;
185         }
186
187         if(!move_uploaded_file($_FILES[$key]['tmp_name'], $filename)) {
188                 return false;
189         }
190
191         return $filename;
192 }
193
194 # this function exists to deal with cases where binaries are installed in very
195 # standard places (like /usr/bin or /usr/local bin) and PHP's PATH environment
196 # variable is not set appropriately.
197 function path_to($prog, $or_die = true) {
198         $prog = preg_replace('|[^a-z0-9_.-]|i', '', $prog);
199         $prog = preg_replace('|^[-.]*|', '', $prog);
200         if($prog == '') {
201                 die('Invalid argument to path_to()');
202         }
203
204         if(!isset($GLOBALS["path_to_$prog"])) {
205                 $ret = _path_to($prog, $or_die);
206                 if($ret == false) {
207                         return false;
208                 }
209                 $GLOBALS["path_to_$prog"] = $ret;
210         }
211
212         return $GLOBALS["path_to_$prog"];
213 }
214         
215 function _path_to($prog, $or_die) {
216         # relies on PHP's short-circuit mechanism
217         if(file_exists($path = "/usr/local/bin/$prog") ||
218            file_exists($path = "/usr/bin/$prog") ||
219            ($path = `which $prog` != '' && file_exists($path))) {
220                 return $path;
221         } else {
222                 if($or_die) {
223                         die("Failed to locate '$prog' executable.");
224                 }
225                 return false;
226         }
227 }
228
229
230 # returns new filename with .png extension
231 function gif_to_png($filename, $new_filename = 'just change extension') {
232         if($new_filename == 'just change extension') {
233                 $new_filename = $filename;
234                 $last_dot = strrpos($new_filename, '.');
235                 if($last_dot !== false) {
236                         $new_filename = substr($new_filename, 0, $last_dot);
237                 }
238                 $new_filename .= '.png';
239         }
240
241         imagemagick_convert($filename.'[0]', $new_filename, "-colorspace sRGB", 'GIF to PNG conversion');
242
243         unlink($filename);
244         return $new_filename;
245 }
246
247 # make a thumbnail image.
248 #
249 # Thumbnail will have the same filename, except "_thumb" will be added right
250 # before the dot preceding the extension. so foo.png yields foo_thumb.png
251 #
252 # Thumbnail will retain aspect ratio, and be either $max_width wide or
253 # $max_height tall (or, if the aspect is just right, both)
254 function make_thumbnail($filename, $max_width = '70', $max_height = '70') {
255         $last_dot = strrpos($filename, '.');
256         if($last_dot === false) {
257                 die("couldn't make thumbnail because filename has no extension.");
258         }
259
260         $thumb = substr($filename, 0, $last_dot);
261         $thumb .= '_thumb';
262         $thumb .= substr($filename, $last_dot);
263
264         $max_width = format_int_70($max_width);
265         $height_width = format_int_70($height_width);
266
267         imagemagick_convert($filename, $thumb, "-geometry ${max_width}x$max_height", 'Thumbnail creation');
268         
269         return $thumb;
270 }
271
272 function exec_or_die($command, $doing_what) {
273         exec($command, $dummy, $ret);
274         if($ret != 0) {
275                 $base = basename(preg_replace('| .*|', '', $command));
276                 die("$doing_what failed. $base called exit($ret)");
277         }
278 }
279
280 # exec convert from imagemagick.
281 function imagemagick_convert($in_filename, $out_filename, $args, $doing_what = "Image conversion") {
282         $in = escapeshellarg($in_filename);
283         $out = escapeshellarg($out_filename);
284         $command = path_to('convert') . " $in $args $out";
285
286         exec_or_die($command, $doing_what);
287 }
288
289 # exec mogrify from imagemagick.
290 function imagemagick_mogrify($in_filename, $args, $doing_what = "Image conversion") {
291         $command = path_to('mogrify') . " $args " . escapeshellarg($in_filename);
292
293         exec_or_die($command, $doing_what);
294 }
295
296 function format_int_70($str) {
297         $str = preg_replace('|[^0-9]|', '', $str);
298         if($str == '') {
299                 $str = '70';
300         }
301         return $str;
302 }
303         
304
305 # Resize image.
306 #
307 # The image will retain aspect ratio, and be either $max_width wide or
308 # $max_height tall (or, if the aspect is just right, both)
309 function resize_image($filename, $max_width = '70', $max_height = '70') {
310         $max_width = format_int_70($max_width);
311         $height_width = format_int_70($height_width);
312         
313         imagimagick_mogrify($filename, "-geometry ${max_width}x$max_height");
314 }
315
316 # Argument: path to image file
317 #
318 # Return: string in the format WIDTHxHEIGHT, or boolean false
319 #
320 # Example: image_dimensions('uploads/foo.png'); ==> "124x58"
321 function image_dimensions($image) {
322         $identify = path_to('identify');
323         $command = "$identify -format '%wx%h' " . escapeshellarg($image);
324         $dimensions = rtrim(`$command`);
325         if($dimensions == '') {
326                 return false;
327         } else {
328                 return $dimensions;
329         }
330 }
331
332 # return an array of the width and height of the image passed.
333 # calls die() if this can't be done for any reason.
334 function image_w_h_or_die($filename) {
335         $wxh = image_dimensions($filename);
336         if($wxh == false) {
337                 die("couldn't git image dimensions of $filename");
338         }
339         $wh = explode('x', $wxh);
340         if(count($wh) != 2) {
341                 die("image $filename seems to have " . count($wh) . ' dimensions');
342         }
343         return $wh;
344 }
345
346
347 # Like save_uploaded_file() (above) except that it converts all images to PNG
348 # or JPEG, converts to sRGB colorspace, and optionally scales and/or creates a
349 # thumbnail. And, if $path ends with a period, the correct extension will be
350 # appended.
351 #
352 # You are encouraged to use convert_uploaded_image() instead of this function,
353 # because it has a more useful return value.
354 #
355 # If the image_width and image_height parameters are above zero, then the image
356 # will be scaled (see below).
357 #
358 # If the thumb_width and thumb_height parameters are above zero, then a 2nd
359 # image will be created and scaled (see below) with the same name, except
360 # having "_thumb" added before the extension.
361 #
362 # Scaling: images are scaled (maintaining the aspect ratio) so they are as big
363 # as possible without either dimension being larger than what you specify.
364 #
365 # This function just returns the name of the main image. To get the dimensions
366 # and names, call convert_uploaded_image().
367 function save_uploaded_image($key, $path, $image_width = 0, $image_height = 0, $thumbnail_width = 0, $thumbnail_height = 0) {
368      $image_w_h_thumb_w_h = convert_uploaded_image($key, $path, $image_width, $image_height, $thumbnail_width, $thumbnail_height);
369      return preg_replace('| .*|', '', $image_w_h_thumb_w_h);
370 }
371
372 function ext_to_web_image_ext($in) {
373         if($in == 'png' || $in == 'gif') {
374                 return 'png';
375         } else {
376                 return 'jpg';
377         }
378 }
379
380 # this function is just like save_uploaded_image() above except that the return
381 # value is a string like "filename width height" or (if you specified both
382 # thumbnail dimensions) "image_filename image_width image_height thumb_filename
383 # thumb_width thumb_height"
384 #
385 #
386 # examples:
387 #    convert_uploaded_image('image', 'uploads/', 500, 500);
388 #           might return: "uploads/foo.jpg 500 400"
389 #    convert_uploaded_image('image', 'uploads/', 500, 500, 70, 70);
390 #           might return: "uploads/foo.jpg 500 400 uploads/foo_thumb.jpg 70 56"
391 function convert_uploaded_image($key, $path, $image_width = 0, $image_height = 0, $thumb_width = 0, $thumb_height = 0) {
392         $ret = '';
393         $tmp_filename = save_uploaded_file($key, $path . '__.');
394         $ext_rpos = strrpos($tmp_filename, '.');
395         if($ext_rpos === false) {
396                 die('save_uploaded_file() gave us a filename with no extension.');
397         }
398         $tmp_base = substr($tmp_filename, 0, $ext_rpos);
399         $tmp_ext = substr($tmp_filename, $ext_rpos + 1);
400         if(substr($path, -1) == '/') {
401                 $filename = $path . substr($tmp_base, strlen($path) + 2);
402                 $filename .= '.' . ext_to_web_image_ext($tmp_ext);
403         } elseif(substr($path, -1) == '.') {
404                 $filename = $path . ext_to_web_image_ext($tmp_ext);
405         } else {
406                 $filename = $path;
407         }
408
409         $convert_params = '-colorspace sRGB -auto-orient';
410         if($image_width > 0 && $image_height > 0) {
411                 $convert_params .= " -geometry ${image_width}x$image_height";
412         }
413         imagemagick_convert($tmp_filename.'[0]', $filename, $convert_params);
414         unlink($tmp_filename);
415         list($w, $h) = image_w_h_or_die($filename);
416         $ret = "$filename $w $h";
417         if($thumb_width > 0 && $thumb_height > 0) {
418                 $thumb_name = make_thumbnail($filename, $thumb_width, $thumb_height);
419                 list($w, $h) = image_w_h_or_die($thumb_name);
420                 $ret .= " $thumb_name $w $h";
421         }
422         return $ret;
423 }