<?php

// prints debug info on TRUE
define("DEBUG"FALSE);
define("SHOW_MSGS"FALSE);

//set tabs to 4 spaces in your editor to make this look nice
/* vim: set ts=4: */

/*
    FIXME:
        * the pnm stuff doesn't work
        * currently geared only towards *nix machines... should work fine on
          windows with a few tweaks
        * thumbnails are currently required to be generated as the same type of
          file as the original file. i think perhaps they should be all
          JPEGs...
*/

//////////////////////////// CONFIGURATION OPTIONS ////////////////////////////

// path to image directory. no trailing slash
// the image directory must be web-accessible, because we'll link directly to
// it
$PATHS["images"] = "pics";

// width of the page in pixels. default 770
$CFG["WIDTH"] = 770;

// largest possible thumbnail height and width
$CFG["THUMB_SIZE"] = 50;

// stop downloading and refuse image if it is larger than this value
// default is 100k
$CFG["MAX_BYTES"] = 110 1024;

// show up to this many images
// set to 0 for unlimited
$CFG["MAX_IMAGES"] = 100;

// if these are present, chown, chgrp and chmod files to match these perms
$CFG["USER"] = "pizza";
$CFG["GROUP"] = "dorks";
$CFG["PERMS"] = 0644;

// if this is set to TRUE we output the gallery with as little overhead as
// possible. thumbnail sizes are not validated (which means if you want to alter
// the size of the thumbnails you'll need to turn this on or delete existing
// thumbnails)
// this option can save a significant percent of load time for a gallery of a
// few hundred images
$CFG["ASAP"] = TRUE;

// should we delete an image if we cannot create a thumbnail of it?
$CFG["DEL_IMG_ON_ERR"] = FALSE;

///////////////////////// END CONFIGURATION OPTIONS ///////////////////////////

// how many images to display per row
$CFG["IMG_PER_ROW"] = floor($CFG["WIDTH"] / $CFG["THUMB_SIZE"]);
    
$CFG["IMG_PER_ROW"] -= ceil($CFG["IMG_PER_ROW"] / $CFG["THUMB_SIZE"]);

$IMG_EXTS = array(
    
"jpg"    => "image/jpg",
    
"jpeg"    => "image/jpeg",
    
"gif"    => "image/gif",
    
"png"    => "image/png"
);

$PATHS["thumbs"] = $PATHS["images"] . DIRECTORY_SEPARATOR "thumbs";
$PATHS["checksum"] = $PATHS["images"] . DIRECTORY_SEPARATOR "checksum";

$START timestamp();

////////////////////// display functions

function debug ($msg)
{
    if (
DEBUG)
    {
        
print_debug(nl2br("$msg\n"));
    }
}

function 
debug_dump ($variable)
{
    if (
DEBUG)
    {
        echo 
"<pre>";
        
var_dump($variable);
        echo 
"</pre>";
    }
}

function 
print_debug($msg)
{
    
printf('<a class="debug">%s</a>'$msg);
    
flush();
}

function 
print_error($msg)
{
    
print_class ($msg"err");
}

function 
print_msg ($msg)
{
    if (
DEBUG || SHOW_MSGS)
    {
        
print_class ($msg"msg");
    }
}

function 
print_ok ($msg)
{
    
print_class ($msg"ok");
}

function 
print_class ($msg$class)
{
    echo 
'<a class="' $class '">' $msg .'</a><br />' "\n";
    
flush();
}

function 
format_bytes ($bytes)
{
    
//$postfix = array(" bytes", "Kb", "Mb", "Gb", "Tb");
    //$entry = floor(log10($bytes) / 3);
    //return sprintf("%5.2f%s", $bytes / 1024, $postfix[$entry]);
    
return sprintf("%.2fKb"$bytes 1024);
}

////////////////////////////////////// path functions

function image_path ($img)
{
    global 
$PATHS;
    return 
$PATHS["images"] . DIRECTORY_SEPARATOR $img;
}

function 
checksum_path ($data)
{
    global 
$PATHS;
    return 
$PATHS["checksum"] . DIRECTORY_SEPARATOR md5($data);
}

function 
thumb_path ($img)
{
    global 
$PATHS;
    return 
$PATHS["thumbs"] . DIRECTORY_SEPARATOR $img;
}

//////////////////////////////////////// general miscellansous functions

function image_del ($filename)
{
    global 
$CFG;
    
// if the file was modified very recently, it's likely it's a file we have
    // just tried fetching and maybe the url didn't work. if not, it may be that
    // we applied this to a whole bunch of pre-fetched images; if this is the case
    // we don't want to be deleting the whole set of images if something doesn't
    // work
    
if ($CFG["DEL_IMG_ON_ERR"] || time() - filemtime(image_path($filename)) < 2)
    {
        
unlink(image_path($filename));
        
unlink(checksum_path($filename));
    }
    
thumb_del($filename);
}

function 
thumb_del ($filename)
{
    @
unlink(thumb_path($filename));
}

function 
detect_gd2 ()
{
    return (
detect_gd() && function_exists("gd_info"));
}

function 
detect_gd ()
{
    return (
extension_loaded("gd") || @dl("gd"));
}

function 
detect_convert ()
{
    return (
strpos(`convert 2>&1`, "imagemagick") !== FALSE);
}

function 
detect_pnm ()
{
    
$giftopnm shell_exec("giftopnm -h 2>&1");
    
$pnmtopng shell_exec("pnmtopng --help 2>&1");

    
debug("giftopnm:$giftopnm:");
    
debug("pnmtopng:$pnmtopng:");

    return (
        (
strpos($giftopnm"unrecognized option") !== FALSE) &&
        (
strpos($pnmtopng"man pnmtopng") !== FALSE)
    );
}

function 
timestamp ()
{
    return 
array_sum(explode(' 'microtime()));
}

function 
current_user ()
{
    
//FIXME: figure out how to do this in windows too
    
return `whoami`;
}

// set things right as far as the path is concerned
function check_dir ($dir)
{
    global 
$PATHS;
    
$err FALSE;
    if (!
file_exists($dir)){
        if (!@
mkdir($dir0755))
        {
            
$user current_user();
            
$err =
                
"could not create sub-directory '$dir'. do either of the
                following:
                <ul>
                    <li>give user '$user' ownership of the directory containing
                    this file (<i>chown -R $user " 
getcwd() . "</i>)</li>
                    <li>create directory '$dir' manually and give user '$user'
                    ownership of it (<i>mkdir $dir; chown -R $user $dir</i>)
                    </li>
                </ul>
                
                If you do not have access to the <i>chown</i> command then you
                need to create the '$dir' directory manually and then make it
                world-writeable (<i>chmod -R 0777 $dir</i>). This is not a real
                great solution, but it does work."
;
        }
    }
    else if (!
is_dir($dir))
    {
        
$err "file '$dir' exists, but is not a directory. please " .
            
"change the \$PATHS[\"images\"] setting";
    }
    else if (!
is_writeable($dir))
    {
        
$err "'$dir' exists and is a directory, but i cannot write " .
                
"to it. make it writeable for user '" .
                
current_user() . "'";
    }
    return 
$err;
}


////////////////////////////////////////////// thumbnail / image functions

function thumb_exists ($img)
{
    return 
file_exists(thumb_path($img));
}

/////////////////////////// functions relating to checking for requirements

function ensure_version ()
{

    
$v explode('.'phpversion());

    if (
$v[0] < || ($v[0] == && $v[1] < 2))
    {
        
print_error(
            
"Sorry, this script requires PHP 4.2 or higher, you're running " .
            
phpversion() . ". Download the latest and greatest from
            <a href=\"http://www.php.net/\">php.net</a>"
        
);
        die(
"</body></html>");
    }
}

function 
ensure_dirs ()
{
    global 
$PATHS;

    foreach (
array_keys($PATHS) as $dir)
    {
        if ((
$msg check_dir($PATHS[$dir])) !== FALSE)
        {
            
print_error($msg);
            die(
"</body></html>");
        }
    }
}

// ensure we have the necessary elements to run this script
function ensure_requirements ()
{

    
ensure_version();

    
ensure_dirs();

    if (!
detect_gd() && !detect_convert())
    {
        
print_error(
            
"Neither the PHP GD extension nor the 'convert' program supplied
            by ImageMagick could be detected. You need one of these installed
            in order to generate thumbnail images"
        
);
        return;
    }
}

function 
write_file ($dest$data)
{
    global 
$CFG;

    
debug("fopen($dest)");

    
$fp fopen($dest'wb');
    if (!
$fp)
    {
        
print_error("could not open '$dest' to write img data");
        return;
    }
    
fwrite($fp$data);
    
fclose($fp);

    if (!empty(
$CFG["USER"]))
    {
        
chown($dest$CFG["USER"]);
    }

    if (!empty(
$CFG["GROUP"]))
    {
        
chmod($dest$CFG["GROUP"]);
    }

    if (!empty(
$CFG["PERMS"]))
    {
        
chmod($dest$CFG["PERMS"]);
    }
}


function 
has_img_ext ($path)
{
    global 
$IMG_EXTS;
    
$ext get_filename_ext($path);
    
debug("ext: $ext");
    return 
in_array(strtolower($ext), array_keys($IMG_EXTS));
}

function 
get_filename_ext ($filename)
{
    return 
substr($filenamestrrpos($filename'.') + 1);
}

function 
get_img_ext ($content_type)
{
    global 
$IMG_EXTS;
    foreach (
$IMG_EXTS as $ext => $ctype)
    {
        if (
strtolower($content_type) == $ctype)
        {
            return 
$ext;
        }
    }
    return 
FALSE;
}

function 
already_have_file ($data)
{
    
$path checksum_path($data);

    if (
file_exists($path))
    {
        return 
TRUE;
    }
    else
    {
        
debug("touch($path)");
        
touch($path);
        return 
FALSE;
    }
}

function 
deal_with_headers ($fp)
{
    
$headers = array();

    do {

        
$line rtrim(fgets($fp4096));
        
debug("headerline:$line:");

        if (
$line == '')
            break; 
// received \r\n\r\n

        
list($header$val) = preg_split('/:\s+/'$line);
        
debug("header($header)val($val)");

        
$headers[strtolower($header)] = $val;

    } while (
TRUE);

    return 
$headers;
}

function 
download ($url)
{

    global 
$CFG$IMG_EXTS;

    
$dl["error"] = FALSE;

    
$uri parse_url($url);

    
debug_dump($uri);

    switch (
strtoupper($uri["scheme"]))
    {
    case 
"HTTP":

        
$img_ext has_img_ext($uri["path"]);
        
debug("img_ext: $img_ext");

        if (
$img_ext)
        {
            
# deal with urlencoding and spaces in filenames
            
$dl["filename"] = str_replace(" ""_"urldecode(basename($uri["path"])));
        }

        
// do a raw HTTP GET
        
if (isset($uri["query"]) || !$img_ext)
        {
            
$fp = @fsockopen(
                
$uri["host"],
                (
$uri["port"] ? $uri["port"] : 80),
                
$errno,
                
$errstr
            
);
            
debug("fso: $fp");
            if (!
$fp)
            {
                
$dl["error"] = "Could not connect to '" $uri["domain"] .
                    
"': $errstr";
                return 
$dl;
            }

            
$request "GET " $uri["path"] . ($uri["query"] ? "?" $uri["query"] : "") . " HTTP/1.1\r\n" .
                
"Host: " $uri["host"] . "\r\n" .
                
"\r\n\r\n";
            
debug("request:$request:");

            
fwrite($fp$request);
            
fflush($fp);

            
$dl["headers"] = deal_with_headers($fp);

            if (!
in_array($dl["headers"]["content-type"], array_values($IMG_EXTS)))
            {
                
$dl["error"] = "URL returned Content-Type '" .
                    
$dl["content-type"] . "', which is not an image";
                return 
$dl;
            }
            elseif (@
$dl["headers"]["content-length"] > $CFG["MAX_BYTES"])
            {
                
$dl["error"] = "Image is larger than " format_bytes() . " bytes... too big";
                return 
$dl;
            }
        }
        else 
// do a simple fopen
        
{
            
debug("fopen: $fp");
            
$fp fopen($url ,'rb');
            if (!
$fp)
            {
                
$dl["error"] = "could not open file '$url'";
                return 
$dl;
            }
        }

        
$recvd 0;
        
$buf NULL;
        
$bufsize 4096;
        
$CL 0;
        if (isset(
$dl["headers"]["content-length"]))
        {
            
$CL $dl["headers"]["content-length"];
        }
        
$dl["data"] = "";

        
// read file data
        
while (!feof($fp))
        {
            
// adjust buffer size based on how much we've read
            // depends on Content-Length header
            
if ($CL)
            {
                if (
$recvd >= $CL)
                {
                    
// all done, finish up function
                    
break;
                }
                else if (
$CL $recvd $bufsize)
                {
                    
$bufsize $CL $recvd;
                }
            }
            
$buf fread($fp$bufsize);
            
$recvd += strlen($buf);
            
$dl["data"] .= $buf;
            
debug("received $recvd bytes total");
            if (
$recvd $CFG["MAX_BYTES"])
            {
                
$dl["error"] = "Image exceeds " .
                    
format_bytes($CFG["MAX_BYTES"]);
                
$dl["data"] = FALSE;
                return 
$dl;
            }
        }
        
debug("read all data!");
        break;
    case 
"FTP":
        
print_error("this script should support ftp, but it doesn't yet");
        return;
        break;
    default:
        
print_error("Unsupported protocol '" $info["scheme"] . "'");
        return;
        break;
    }

    
//debug_dump($dl);

    
return $dl;
}

function 
fetch_file ()
{
    global 
$_FILES$PATHS$IMG_EXTS;

    
debug_dump($_FILES);

    if (
$_FILES["image_file"]["error"] !== 0)
    {
        
// deal with upload errors
        
switch ($_FILES["image_file"]["error"])
        {
        case 
UPLOAD_ERR_INI_SIZE:
        case 
UPLOAD_ERR_FORM_SIZE:
            
print_error("The image was too large");
            return;
            break;
        case 
UPLOAD_ERR_PARTIAL:
            
print_error("The image was only partially uploaded. Try again");
            
// do nothing... the temporary file will be automatically deleted by
            // php
            
return;
            break;
        case 
UPLOAD_ERR_NO_FILE:
            
print_error("No file provided. Doing nothing.");
            return;
            break;
        }
    }

    
// file was uploaded without errors

    // is file named like an image?
    
if (!has_img_ext($_FILES["image_file"]["name"]))
    {
        
print_error(
            
"Only images with the following extensions are supported: " .
                
join(","array_keys($IMG_EXTS))
        );
        return;
    }


    
// ensure a unique filename
    
$filename unique_filename($_FILES["image_file"]["name"]);

    
// everything looks good
    
    //move image from temp dir to image dir
    
if (
        !
move_uploaded_file(
            
$_FILES["image_file"]["tmp_name"],
            
image_path($filename)
        )
    )
    {
        
print_error("Error moving tmp upload file.");
        return;
    }

    
print_msg("Creating thumbnail...");

    if (!
thumb_create($filename))
    {
        
print_error("Could not create thumbnail for '$filename'.");
        
//image_del($filename);
        
return;
    }

    
// ensure the file does not already exist in the gallery
    
if (already_have_file(join(""file(image_path($filename)))))
    {
        
print_error("This image already exists in the gallery");
        
thumb_del($filename);
        
image_del($filename);
        return;
    }

    
print_ok("Image successfully uploaded");

}

// fetch an image file by url
function fetch_url ($url)
{
    global 
$PATHS;

    
$data download($url);
    
flush();

    
// if we couldn't fetch the file, print error and go on with our lives
    
if ($data["error"])
    {
        
print_error($data["error"]);
        return;
    }
    
// if the file was fetched via a download script, invent a name for it
    
if (!$data["filename"])
    {
        
$data["filename"] =
            
unique_filename("img." get_img_ext($data["headers"]["content-type"]));
    }
    else
    {
        
$data["filename"] = unique_filename($data["filename"]);
    }

    
debug("data[filename]: " $data["filename"] . ":");
    
flush();

    if (
already_have_file($data["data"]))
    {
        
print_msg("that image already exists in the gallery");
        return;
    }

    
// write data to file
    
write_file(image_path($data["filename"]), $data["data"]);

    
debug("data[filename]:" $data["filename"] . ":");

    
print_msg("Creating thumbnail...");

    if (!
thumb_create($data["filename"]))
    {
        return;
    }

    
print_ok("Image fetched successfully.");
}

function 
thumb_create ($filename)
{

    
$success FALSE;

    
// we may replacing an already-existing thumbnail. the image name is
    // guarenteed to be unique by this point, so if this file exist it is the
    // thumbnail we'll need to replace
    
if (file_exists(thumb_path($filename)))
    {
        
thumb_del($filename);
    }
    
    if (
detect_gd2()) // try using GD2 extension first
    
{
        
debug("gd2 detected");
        
$success thumb_create_gd($filename);
        if (
$success !== FALSE)
        {
            return 
$success;
        }
    }
    else
    {
        
debug("gd2 not detected");
    }

    if (
detect_convert()) // imagemagick's convert is installed
    
{
        
debug("convert detected");
        
$success thumb_create_convert($filename);
        if (
$success !== FALSE)
        {
            return 
$success;
        }
    }
    else
    {
        
debug("convert not detected");
    }

    if (
detect_gd()) // try using GD1
    
{
        
debug("gd detected");
        
$success thumb_create_gd($filename);
        if (
$success !== FALSE)
        {
            return 
$success;
        }
    }
    else
    {
        
debug("gd not detected");
    }

    return 
$success;
    
}

function 
thumb_create_convert ($filename)
{

    global 
$CFG;

    
$cmd sprintf(
        
'convert -resize "%dx%d" %s %s',
        
$CFG["THUMB_SIZE"],
        
$CFG["THUMB_SIZE"],
        
escapeshellcmd(image_path($filename)),
        
escapeshellcmd(thumb_path($filename))
    );

    
debug($cmd);
    
system($cmd);

    return 
thumb_exists($filename);
}

function 
thumb_create_gd ($filename)
{

    global 
$CFG;

    
$fullpath image_path($filename);

    
// gd is available
    
$ext strtoupper(get_filename_ext($filename));
    switch (
$ext)
    {
    case 
"GIF":
        if (
function_exists("imagegif"))
        {
            
$src = @imagecreatefromgif($fullpath);
            break;
        }
        else
        {
            return 
FALSE;
            
//GIF not supported, convert image to jpeg
            /*
            if (detect_pnm())
            {
                debug("converting to png via pnm");
                gif_to_png_via_pnm($filename);
                debug("filename:$filename:");
                $fullpath = image_path($filename);
            }
            else
            {
                debug("pnm not detected");
                return FALSE;
            }
            */
        
}
        
        
// NO BREAK, FALLS THROUGH

    
case "PNG":
        
$src = @imagecreatefrompng($fullpath);
        break;

    case 
"JPG":
    case 
"JPEG":
        
$src = @imagecreatefromjpeg($fullpath);
        break;

    default:
        
print_error("Unsupported file extension");
        return 
FALSE;
    }

    if (!
$src)
    {
        return 
FALSE;
    }
    
    
$src_x imageSX($src);
    
$src_y imageSY($src);

    
debug("src_x: $src_x, src_y: $src_y");

    
// figure out the dest's size
    
$max = ($src_x $src_y $src_x $src_y);
    
$dst_x = ($CFG["THUMB_SIZE"] * ($src_x $max));
    
$dst_y = ($CFG["THUMB_SIZE"] * ($src_y $max));

    
debug("max: $max, dst_x: $dst_x, dst_y: $dst_y");

    
// create destination for thumbnail data
    
switch ($ext)
    {
    case 
'JPG':
    case 
'JPEG':
    default:
        
// this requires GD 2+
        
$dst = @imagecreatetruecolor($dst_x$dst_y);

        
// fallback on crappier function
        
if (!$dst)
        {
            
$dst = @imagecreate($dst_x$dst_y);
        }

        break;
    
/*
    case 'GIF':
    case 'PNG':
        $dst = @imagecreate($dst_x, $dst_y);
        break;
    */
    
}

    if (!
$dst)
    {
        
print_error("Unable to run the imagecreate() function in GD");
        return 
FALSE;
    }

    
$copied = @imagecopyresampled(
        
$dst$src0000$dst_x$dst_y$src_x$src_y);

    if (!
$copied)
    {
        
debug("imagecopyresampled failed, trying imagecopyresized");
        
// fallback on crappier function if GD 2 isn't installed
        
$copied = @imagecopyresized(
            
$dst$src0000$dst_x$dst_y$src_x$src_y);
    }

    
// resize src to dst
    
if (!$copied)
    {
        
print_error("imagecopyresized failed");
        
imagedestroy($src);
        
imagedestroy($dst);
        return 
FALSE;
    }

    
$outfile thumb_path($filename);

    switch (
$ext)
    {
    case 
"JPG":
    case 
"JPEG":
        
$success imagejpeg($dst$outfile);
        break;
    
/*
    case "GIF":
        $success = @imagegif($dst, $outfile);
        // fallback on
        if (!$success)
        {
            $success = imagejpeg($dst, $outfile);
        }
        break;
    case "PNG":
        $success = imagepng($dst, $outfile);
        break;
    */
    
}

    if (!
$success)
    {
        
print_error("Error saving thumbnail file");
        
imagedestroy($src);
        
imagedestroy($dst);
        return 
FALSE;
    }

    
// worked if we got here
    
imagedestroy($src);
    
imagedestroy($dst);

    return 
thumb_exists($filename);

}

function 
gif_to_png_via_pnm (&$filename)
{

    
$fullpath image_path($filename);
    
$pngpath preg_replace('/gif$/i''png'$fullpath);

    
$cmd sprintf(
        
'giftopnm %s | pnmtopng > %s 2>&1',
        
escapeshellcmd($fullpath),
        
escapeshellcmd($pngpath)
    );

    
debug($cmd);
    
system($cmd);

    
$filename $pngpath;

    return 
thumb_exists($filename);
}


function 
unique_filename ($filename)
{
        
// if the original filename doesn't exist, use it
        
if (!file_exists(image_path($filename)))
        {
            return 
$filename;
        }
        
// get filename and extension
        
list ($file$ext) = preg_split('/\.(?=[^.]+)/'$filename);
        
$c 0;
        do {
            
// format filename
            
$filename sprintf("%s%d.%s"$file$c++, $ext);
        } while (
file_exists(image_path($filename)));
        
// we finally found one that doesn't exist!
        
return $filename;
}

function 
list_image_files ()
{

    global 
$PATHS$CFG;

    
$images = array();

    
$dir = @opendir($PATHS["images"]);

    if (!
$dir)
    {
        
print_error("Cannot open image path '" $PATHS["images"] . "'");
       &n