JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Fix db_get_value after mysql->mysqli upgrade
[wfpl.git] / upload.php
index b8762b7..b21af6f 100644 (file)
@@ -1,23 +1,9 @@
 <?php
 
-#  Copyright (C) 2007 Jason Woofenden
-#
-#  This file is part of wfpl.
-#
-#  wfpl is free software; you can redistribute it and/or modify it
-#  under the terms of the GNU General Public License as published by
-#  the Free Software Foundation; either version 2, or (at your option)
-#  any later version.
-#
-#  wfpl is distributed in the hope that it will be useful, but
-#  WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-#  General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with wfpl; see the file COPYING.  If not, write to the
-#  Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#  MA 02111-1307, USA.
+# This program is in the public domain within the United States. Additionally,
+# we waive copyright and related rights in the work worldwide through the CC0
+# 1.0 Universal public domain dedication, which can be found at
+# http://creativecommons.org/publicdomain/zero/1.0/
 
 
 # This file contains functions to accept files being uplodad with the <input
@@ -38,9 +24,9 @@
 # Example:
 # 
 # <form action="foo.php" enctype="multipart/form-data" method="post">
-# <input type="hidden" name="MAX_FILE_SIZE" value="2097152" />
-# <input type="file" name="photo" />
-# <input type="submit" name="save" value="Save" />
+# <input type="hidden" name="MAX_FILE_SIZE" value="2097152">
+# <input type="file" name="photo">
+# <input type="submit" name="save" value="Save">
 # </form>
 # 
 # #######
 # the type you specified, it will convert the image for you.
 
 
-
 $GLOBALS['mime_to_ext'] = array(
        'text/plain' => 'txt',
        'text/html'  => 'html',
        'image/jpeg' => 'jpg',
+       'image/jpe' => 'jpg',
        'image/jpg'  => 'jpg',
        'image/gif'  => 'gif',
        'image/png'  => 'png',
@@ -94,11 +80,44 @@ function upload_max_filesize() {
        }
 }
 
+# return the extension this path should have WITHOUT the period
+function path_or_mime_to_ext($path, $mime = 'text/plain', $default = 'txt') {
+       $last_dot = strrpos($path, '.');
+       if($last_dot === false || $last_dot === 0 || $last_dot === (strlen($path) - 1)) {
+               # no extension
+               if(isset($GLOBALS['mime_to_ext'][$mime])) {
+                       return $GLOBALS['mime_to_ext'][$mime];
+               } else {
+                       return $default;
+               }
+       } else {
+               $ext = strtolower(substr($path, $last_dot + 1));
+               if(isset($GLOBALS['ext_to_ext'][$ext])) {
+                       return $GLOBALS['ext_to_ext'][$ext];
+               }
+               return $ext;
+       }
+}
+
+# Add or fix extension on path
+# This just does string manipulation (ie  doesn't move/open/etc any files.)
+# Mime type used to generate extension ONLY IF it doesn't have one already.
+function path_fix_ext($path, $mime = 'text/plain', $default_ext = '.txt') {
+       $last_dot = strrpos($path, '.');
+       if($last_dot === false || $last_dot === 0) { # no extension
+               $path .= '.' . path_or_mime_to_ext($path, $mime, $default_ext);
+       } else {
+               $basename = substr($path, 0, $last_dot + 1); # keep dot
+               $path = $basename . path_or_mime_to_ext($path, $mime, $default_ext);
+       }
+
+       return $path;
+}
 
 # pass in the client's path that came from an html <input type="file"/> tag
 #
 # mime time used to generate extension ONLY IF it doesn't have one already.
-function generate_filename($path, $mime = 'text/plain') {
+function generate_filename($path, $mime = 'text/plain', $default_ext = '.txt') {
        # lower case
        $filename = strtolower($path);
 
@@ -115,26 +134,23 @@ function generate_filename($path, $mime = 'text/plain') {
        }
 
        # replace symbols with underscores
-       $filename = ereg_replace('[^a-z0-9_.]', '_', $filename);
+       $filename = preg_replace('|[^a-z0-9_.]|', '_', $filename);
 
-       # remove dots from the begning (no invisible files)
-       $filename = ereg_replace('^\.*', '', $filename);
+       # limit length
+       if(strlen($filename > 80)) {
+               $filename = substr($filename, -80);
+       }
 
-       # fix extension
-       $last_dot = strrpos($filename, '.');
-       if($last_dot === false) {
-               #no extension
-               if(isset($GLOBALS['mime_to_ext'][$mime])) {
-                       $filename .= '.' . $GLOBALS['mime_to_ext'][$mime];
-               }
-       } else {
-               $basename = substr($filename, 0, $last_dot);
-               $ext = substr($filename, $last_dot + 1);
-               if(isset($GLOBALS['ext_to_ext'][$ext])) {
-                       $ext .= $GLOBALS['ext_to_ext'][$ext];
-               }
-               $filename = $basename . '.' . $ext;
+       # remove dots from the beginning (no invisible files)
+       $filename = preg_replace('|^\.*|', '', $filename);
+
+       # make sure there's something before the extension
+       if ($filename == '') {
+               return '_';
        }
+
+       $filename = path_fix_ext($filename, $mime, $default_ext);
+
        return $filename;
 }
 
@@ -142,27 +158,74 @@ function generate_filename($path, $mime = 'text/plain') {
 
 # Move uploaded file, and return the new filename.
 #
-# Pass in the index into the $_FILES array (the name of the html input tag) and
-# the path to the folder you'd like it saved to. If path ends with a slash this
-# function will generate a filename based on the client's name, otherwise it'll
-# name the file that.
+# $key: Pass in the index into the $_FILES array (the name of the html input tag) and
+# the path to the folder you'd like it saved to.
 #
-# example: save_uploaded_file('pdf', 'uploaded_pdfs/');
-# example: save_uploaded_file('resume', "/www/example.com/remumes/$user_id.txt");
+# $path: If path ends with a slash this function will generate a filename based
+# on the client's name. If it ends with a period, the dot will be removed and
+# the client's name appended. Otherwise $path will be used as the filename
+# exactly as is, even if extensions differ between the client's name and $path.
+#
+# where user uploads "c:\foo\Bar baz.PDF" at <input name="in" type="file">
+#    save_uploaded_file('in', 'uploaded_pdfs/'); yeilds:
+#       "uploaded_pdfs/bar_baz.pdf"
+#    save_uploaded_file('in', 'uploaded_pdfs/prefix.'); yeilds:
+#       "uploaded_pdfs/prefixbar_baz.pdf"
+#    save_uploaded_file('in', 'uploaded_pdfs/qux.pdf'); yeilds:
+#       "uploaded_pdfs/qux.pdf"
 function save_uploaded_file($key, $path) {
-       if(substr($path, -1) == '/') {
+       $end = substr($path, -1);
+       if($end == '.' || $end == '/') {
+               if($end == '.') {
+                       $path = substr($path, 0, -1);
+               }
                $filename = $path . generate_filename($_FILES[$key]['name'], $_FILES[$key]['type']);
        } else {
                $filename = $path;
        }
 
        if(!move_uploaded_file($_FILES[$key]['tmp_name'], $filename)) {
-               die('file upload failed');
+               return false;
        }
 
        return $filename;
 }
 
+# this function exists to deal with cases where binaries are installed in very
+# standard places (like /usr/bin or /usr/local bin) and PHP's PATH environment
+# variable is not set appropriately.
+function path_to($prog, $or_die = true) {
+       $prog = preg_replace('|[^a-z0-9_.-]|i', '', $prog);
+       $prog = preg_replace('|^[-.]*|', '', $prog);
+       if($prog == '') {
+               die('Invalid argument to path_to()');
+       }
+
+       if(!isset($GLOBALS["path_to_$prog"])) {
+               $ret = _path_to($prog, $or_die);
+               if($ret == false) {
+                       return false;
+               }
+               $GLOBALS["path_to_$prog"] = $ret;
+       }
+
+       return $GLOBALS["path_to_$prog"];
+}
+       
+function _path_to($prog, $or_die) {
+       # relies on PHP's short-circuit mechanism
+       if(file_exists($path = "/usr/local/bin/$prog") ||
+          file_exists($path = "/usr/bin/$prog") ||
+          ($path = `which $prog` != '' && file_exists($path))) {
+               return $path;
+       } else {
+               if($or_die) {
+                       die("Failed to locate '$prog' executable.");
+               }
+               return false;
+       }
+}
+
 
 # returns new filename with .png extension
 function gif_to_png($filename, $new_filename = 'just change extension') {
@@ -175,40 +238,186 @@ function gif_to_png($filename, $new_filename = 'just change extension') {
                $new_filename .= '.png';
        }
 
-       $convert = '/usr/local/bin/convert';
-       if(!file_exists($convert)) {
-               $convert = '/usr/bin/convert';
-       }
-       if(!file_exists($convert)) {
-               $convert = `which convert`;
-       }
-       if(!file_exists($convert)) {
-               die("can't find imagemagick's 'convert' program");
+       imagemagick_convert($filename.'[0]', $new_filename, "-colorspace sRGB", 'GIF to PNG conversion');
+
+       unlink($filename);
+       return $new_filename;
+}
+
+# make a thumbnail image.
+#
+# Thumbnail will have the same filename, except "_thumb" will be added right
+# before the dot preceding the extension. so foo.png yields foo_thumb.png
+#
+# Thumbnail will retain aspect ratio, and be either $max_width wide or
+# $max_height tall (or, if the aspect is just right, both)
+function make_thumbnail($filename, $max_width = '70', $max_height = '70') {
+       $last_dot = strrpos($filename, '.');
+       if($last_dot === false) {
+               die("couldn't make thumbnail because filename has no extension.");
        }
-               
-       $command = "$convert " . escapeshellarg($filename) . ' ' . escapeshellarg($new_filename);
 
+       $thumb = substr($filename, 0, $last_dot);
+       $thumb .= '_thumb';
+       $thumb .= substr($filename, $last_dot);
+
+       $max_width = format_int_70($max_width);
+       $height_width = format_int_70($height_width);
+
+       imagemagick_convert($filename, $thumb, "-geometry ${max_width}x$max_height", 'Thumbnail creation');
+       
+       return $thumb;
+}
+
+function exec_or_die($command, $doing_what) {
        exec($command, $dummy, $ret);
        if($ret != 0) {
-               die("image conversion failed. convert did exit($ret)");
+               $base = basename(preg_replace('| .*|', '', $command));
+               die("$doing_what failed. $base called exit($ret)");
        }
-       unlink($filename);
-       return $new_filename;
 }
 
-# like save_uploaded_file() (above) except it converts gifs to pngs.
+# exec convert from imagemagick.
+function imagemagick_convert($in_filename, $out_filename, $args, $doing_what = "Image conversion") {
+       $in = escapeshellarg($in_filename);
+       $out = escapeshellarg($out_filename);
+       $command = path_to('convert') . " $in $args $out";
+
+       exec_or_die($command, $doing_what);
+}
+
+# exec mogrify from imagemagick.
+function imagemagick_mogrify($in_filename, $args, $doing_what = "Image conversion") {
+       $command = path_to('mogrify') . " $args " . escapeshellarg($in_filename);
+
+       exec_or_die($command, $doing_what);
+}
+
+function format_int_70($str) {
+       $str = preg_replace('|[^0-9]|', '', $str);
+       if($str == '') {
+               $str = '70';
+       }
+       return $str;
+}
+       
+
+# Resize image.
 #
-# FIXME: if a filename is passed in the end of path, we should check if the file type matches, and if not run convert.
-function save_uploaded_image($key, $path) {
-       if(substr($path, -1) == '/') {
-               $filename = save_uploaded_file($key, $path);
-               if(substr($filename, -4) == '.gif') {
-                       $filename = gif_to_png($filename);
-               }
-               return $filename;
+# The image will retain aspect ratio, and be either $max_width wide or
+# $max_height tall (or, if the aspect is just right, both)
+function resize_image($filename, $max_width = '70', $max_height = '70') {
+       $max_width = format_int_70($max_width);
+       $height_width = format_int_70($height_width);
+       
+       imagimagick_mogrify($filename, "-geometry ${max_width}x$max_height");
+}
+
+# Argument: path to image file
+#
+# Return: string in the format WIDTHxHEIGHT, or boolean false
+#
+# Example: image_dimensions('uploads/foo.png'); ==> "124x58"
+function image_dimensions($image) {
+       $identify = path_to('identify');
+       $command = "$identify -format '%wx%h' " . escapeshellarg($image);
+       $dimensions = rtrim(`$command`);
+       if($dimensions == '') {
+               return false;
+       } else {
+               return $dimensions;
+       }
+}
+
+# return an array of the width and height of the image passed.
+# calls die() if this can't be done for any reason.
+function image_w_h_or_die($filename) {
+       $wxh = image_dimensions($filename);
+       if($wxh == false) {
+               die("couldn't git image dimensions of $filename");
+       }
+       $wh = explode('x', $wxh);
+       if(count($wh) != 2) {
+               die("image $filename seems to have " . count($wh) . ' dimensions');
+       }
+       return $wh;
+}
+
+
+# Like save_uploaded_file() (above) except that it converts all images to PNG
+# or JPEG, converts to sRGB colorspace, and optionally scales and/or creates a
+# thumbnail. And, if $path ends with a period, the correct extension will be
+# appended.
+#
+# You are encouraged to use convert_uploaded_image() instead of this function,
+# because it has a more useful return value.
+#
+# If the image_width and image_height parameters are above zero, then the image
+# will be scaled (see below).
+#
+# If the thumb_width and thumb_height parameters are above zero, then a 2nd
+# image will be created and scaled (see below) with the same name, except
+# having "_thumb" added before the extension.
+#
+# Scaling: images are scaled (maintaining the aspect ratio) so they are as big
+# as possible without either dimension being larger than what you specify.
+#
+# This function just returns the name of the main image. To get the dimensions
+# and names, call convert_uploaded_image().
+function save_uploaded_image($key, $path, $image_width = 0, $image_height = 0, $thumbnail_width = 0, $thumbnail_height = 0) {
+     $image_w_h_thumb_w_h = convert_uploaded_image($key, $path, $image_width, $image_height, $thumbnail_width, $thumbnail_height);
+     return preg_replace('| .*|', '', $image_w_h_thumb_w_h);
+}
+
+function ext_to_web_image_ext($in) {
+       if($in == 'png' || $in == 'gif') {
+               return 'png';
        } else {
-               return save_uploaded_file($key, $path);
+               return 'jpg';
        }
 }
 
-?>
+# this function is just like save_uploaded_image() above except that the return
+# value is a string like "filename width height" or (if you specified both
+# thumbnail dimensions) "image_filename image_width image_height thumb_filename
+# thumb_width thumb_height"
+#
+#
+# examples:
+#    convert_uploaded_image('image', 'uploads/', 500, 500);
+#          might return: "uploads/foo.jpg 500 400"
+#    convert_uploaded_image('image', 'uploads/', 500, 500, 70, 70);
+#          might return: "uploads/foo.jpg 500 400 uploads/foo_thumb.jpg 70 56"
+function convert_uploaded_image($key, $path, $image_width = 0, $image_height = 0, $thumb_width = 0, $thumb_height = 0) {
+       $ret = '';
+       $tmp_filename = save_uploaded_file($key, $path . '__.');
+       $ext_rpos = strrpos($tmp_filename, '.');
+       if($ext_rpos === false) {
+               die('save_uploaded_file() gave us a filename with no extension.');
+       }
+       $tmp_base = substr($tmp_filename, 0, $ext_rpos);
+       $tmp_ext = substr($tmp_filename, $ext_rpos + 1);
+       if(substr($path, -1) == '/') {
+               $filename = $path . substr($tmp_base, strlen($path) + 2);
+               $filename .= '.' . ext_to_web_image_ext($tmp_ext);
+       } elseif(substr($path, -1) == '.') {
+               $filename = $path . ext_to_web_image_ext($tmp_ext);
+       } else {
+               $filename = $path;
+       }
+
+       $convert_params = '-colorspace sRGB -auto-orient';
+       if($image_width > 0 && $image_height > 0) {
+               $convert_params .= " -geometry ${image_width}x$image_height";
+       }
+       imagemagick_convert($tmp_filename.'[0]', $filename, $convert_params);
+       unlink($tmp_filename);
+       list($w, $h) = image_w_h_or_die($filename);
+       $ret = "$filename $w $h";
+       if($thumb_width > 0 && $thumb_height > 0) {
+               $thumb_name = make_thumbnail($filename, $thumb_width, $thumb_height);
+               list($w, $h) = image_w_h_or_die($thumb_name);
+               $ret .= " $thumb_name $w $h";
+       }
+       return $ret;
+}