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