<?PHP
#
#   FILE:  SPTImage.php
#
#   METHODS PROVIDED:
#       SPTImage($ImageIdOrFileNameOrImageObj,
#                   $MaxPreviewWidth = NULL, $MaxPreviewHeight = NULL,
#                   $MaxThumbnailWidth = NULL, $MaxThumbnailHeight = NULL)
#           - object constructor
#       Delete()
#           - delete image and associated files and data
#       AltText($NewValue = NULL)
#           - get/set alt text attribute
#       Id()
#       Url()
#       PreviewUrl()
#       ThumbnailUrl()
#       Format()
#       Height()
#       Width()
#       PreviewHeight()
#       PreviewWidth()
#       ThumbnailHeight()
#       ThumbnailWidth()
#           - get attributes
#
#   AUTHOR:  Edward Almasy
#
#   Part of CWIS
#   Copyright 2002-2011 Internet Scout Project
#   http://scout.wisc.edu
#

class SPTImage {

    # ---- PUBLIC INTERFACE --------------------------------------------------

    /**
     * @const IMAGE_PATH base path where images are stored
     * @const PREVIEW_PATH path where preview images are stored
     * @const THUMBNAIL_PATH path where thumbnail images are stored
     */
    const IMAGE_PATH = "ImageStorage/";
    const PREVIEW_PATH = "ImageStorage/Previews/";
    const THUMBNAIL_PATH = "ImageStorage/Thumbnails/";

    # object constructor
    function SPTImage($ImageIdOrFileNameOrImageObj,
                      $MaxPreviewWidth = NULL, $MaxPreviewHeight = NULL,
                      $MaxThumbnailWidth = NULL, $MaxThumbnailHeight = NULL)
    {
        # clear error status (0 = AI_OKAY)
        $this->ErrorStatus = 0;

        # trigger the Image class file to be autoloaded since some parts of this
        # class (SPTImage) use constants defined in it but don't construct Image
        # objects
        new Image(NULL);

        # create and save a database handle for our use
        $this->DB = new SPTDatabase();

        # if image object was passed in
        if (is_object($ImageIdOrFileNameOrImageObj)
                && method_exists($ImageIdOrFileNameOrImageObj, "SPTImage"))
        {
            # create copy of image passed in
            $this->CreateCopyOfImage($ImageIdOrFileNameOrImageObj);
        }
        # else if image ID was passed in
        elseif (($ImageIdOrFileNameOrImageObj > 0)
                && preg_match("/[0-9]+/", $ImageIdOrFileNameOrImageObj))
        {
            # load info on existing image
            $this->LoadImageInfo($ImageIdOrFileNameOrImageObj);
        }
        # else assume that value passed in is file name
        else
        {
            # create new image from named file
            $this->CreateNewImage($ImageIdOrFileNameOrImageObj,
                                  $MaxPreviewWidth, $MaxPreviewHeight,
                                  $MaxThumbnailWidth, $MaxThumbnailHeight);
        }
    }

    # get attributes
    function Id() {  return $this->Id;  }
    function Url() {  return $this->FileName;  }
    function PreviewUrl() {  return $this->PreviewFileName;  }
    function ThumbnailUrl() {  return $this->ThumbnailFileName;  }
    function Format() {  return $this->Format;  }
    function Height() {  return $this->Height;  }
    function Width() {  return $this->Width;  }
    function PreviewHeight() {  return $this->PreviewHeight;  }
    function PreviewWidth() {  return $this->PreviewWidth;  }
    function ThumbnailHeight() {  return $this->ThumbnailHeight;  }
    function ThumbnailWidth() {  return $this->ThumbnailWidth;  }
    function ImageStorageDirectory() {  return self::IMAGE_PATH;  }
    function PreviewStorageDirectory() {  return self::PREVIEW_PATH;  }
    function ThumbnailStorageDirectory() {  return self::THUMBNAIL_PATH;  }
    function GetLink() {  return $this->FileName;  }

    # get/set attributes
    function AltText($NewValue = NULL)
    {
        # if new value supplied and new value differs from existing value
        if (($NewValue !== NULL) && ($NewValue != $this->AltText))
        {
            # save new value to database
            $this->DB->Query("UPDATE Images SET"
                             ." AltText = '".addslashes($NewValue)."'"
                             ." WHERE ImageId = ".$this->Id);

            # save new value locally
            $this->AltText = $NewValue;
        }

        # return attribute value to caller
        return $this->AltText;
    }

    # delete image and associated files and data
    function Delete()
    {
        # delete base image file
        if (file_exists($this->FileName)) {  unlink($this->FileName);  }

        # delete preview image file
        if (file_exists($this->PreviewFileName)) {  unlink($this->PreviewFileName);  }

        # delete thumbnail image file
        if (file_exists($this->ThumbnailFileName)) {  unlink($this->ThumbnailFileName);  }

        # delete image info record in database
        $this->DB->Query("DELETE FROM Images WHERE ImageId = ".$this->Id);
    }

    # return error status set by the constructor
    function Status()
    {
        return $this->ErrorStatus;
    }

    # check to make sure image storage directories are available
    # (returns array of error codes or NULL if no errors found)
    static function CheckDirectories()
    {
        # determine paths
        $ImagePath = self::IMAGE_PATH;
        $PreviewPath = self::PREVIEW_PATH;
        $ThumbnailPath = self::THUMBNAIL_PATH;

        # assume everything will be okay
        $ErrorsFound = NULL;

        # check base image directory
        if (!is_dir($ImagePath) || !is_writable($ImagePath))
        {
            if (!is_dir($ImagePath))
            {
                @mkdir($ImagePath, 0755);
            }
            else
            {
                @chmod($ImagePath, 0755);
            }
            if (!is_dir($ImagePath))
            {
                $ErrorsFound[] = "Image Storage Directory Not Found";
            }
            elseif (!is_writable($ImagePath))
            {
                $ErrorsFound[] = "Image Storage Directory Not Writable";
            }
        }

        # check preview directory
        if (!is_dir($PreviewPath) || !is_writable($PreviewPath))
        {
            if (!is_dir($PreviewPath))
            {
                @mkdir($PreviewPath, 0755);
            }
            else
            {
                @chmod($PreviewPath, 0755);
            }
            if (!is_dir($PreviewPath))
            {
                $ErrorsFound[] = "Preview Storage Directory Not Found";
            }
            elseif (!is_writable($PreviewPath))
            {
                $ErrorsFound[] = "Preview Storage Directory Not Writable";
            }
        }

        # check thumbnail directory
        if (!is_dir($ThumbnailPath) || !is_writable($ThumbnailPath))
        {
            if (!is_dir($ThumbnailPath))
            {
                @mkdir($ThumbnailPath, 0755);
            }
            else
            {
                @chmod($ThumbnailPath, 0755);
            }
            if (!is_dir($ThumbnailPath))
            {
                $ErrorsFound[] = "Thumbnail Storage Directory Not Found";
            }
            elseif (!is_writable($ThumbnailPath))
            {
                $ErrorsFound[] = "Thumbnail Storage Directory Not Writable";
            }
        }

        # return any errors found to caller
        return $ErrorsFound;
    }


    # ---- PRIVATE INTERFACE -------------------------------------------------

    var $Id;
    var $FileName;
    var $PreviewFileName;
    var $ThumbnailFileName;
    var $Format;
    var $AltText;
    var $Url;
    var $PreviewUrl;
    var $ThumbnailUrl;
    var $Height;
    var $Width;
    var $PreviewHeight;
    var $PreviewWidth;
    var $ThumbnailHeight;
    var $ThumbnailWidth;
    var $DB;
    var $ErrorStatus;

    function CreateNewImage($FileName, $MaxPreviewWidth, $MaxPreviewHeight,
                            $MaxThumbnailWidth, $MaxThumbnailHeight)
    {
        # if file does not exist or is not readable
        if (!is_readable($FileName))
        {
            # set error status
            $this->ErrorStatus = AI_FILEUNREADABLE;
        }
        else
        {
            # if image is invalid or unsupported type
            $SrcImage = new Image($FileName);
            if ($SrcImage->Status() != AI_OKAY)
            {
                # set error status
                $this->ErrorStatus = $SrcImage->Status();
            }
            else
            {
                # retrieve image type
                $this->Format = $SrcImage->Type();

                # generate new image ID
                $this->Id = $this->GenerateNewImageId();

                # generate and set file names
                $this->SetFileNames();

                # if our image file name differs from file name passed in
                if (realpath($this->FileName) != realpath($FileName))
                {
                    # create image file
                    $SrcImage->SaveAs($this->FileName);

                    # if create failed set error status and bail out
                    if ($SrcImage->Status() != AI_OKAY)
                    {
                        echo "create failed<br>";
                        echo "Status: ".$SrcImage->Status()."<br>";
                        echo "Failed Command: ".$SrcImage->FailedExternalCommand()."<br>";
                        echo "Missing External Executables: ";
                        print_r(Image::MissingExternalExecutables());
                        echo "<br>";
                        $this->ErrorStatus = $SrcImage->Status();
                        return;
                    }
                }

                # retrieve image width and height
                $this->Height = $SrcImage->YSize();
                $this->Width = $SrcImage->XSize();

                # generate preview image and calculate width and height
                $MaxPreviewWidth = min($MaxPreviewWidth, $this->Width);
                $MaxPreviewHeight = min($MaxPreviewHeight, $this->Height);
                $SrcImage->ScaleTo($MaxPreviewWidth, $MaxPreviewHeight, TRUE);
                $SrcImage->SaveAs($this->PreviewFileName, IMGTYPE_JPEG);
                if ($SrcImage->Status() != AI_OKAY)
                {
                    echo "preview save as failed<br>";
                    $this->ErrorStatus = $SrcImage->Status();
                    return;
                }
                if (($this->Width * $MaxPreviewHeight)
                        > ($this->Height * $MaxPreviewWidth))
                {
                    $this->PreviewWidth = $MaxPreviewWidth;
                    $this->PreviewHeight =
                            ($MaxPreviewWidth * $SrcImage->YSize()) / $SrcImage->XSize();
                }
                else
                {
                    $this->PreviewWidth =
                            ($MaxPreviewHeight * $SrcImage->XSize()) / $SrcImage->YSize();
                    $this->PreviewHeight = $MaxPreviewHeight;
                }

                # generate thumbnail image and calculate width and height
                $MaxThumbnailWidth = min($MaxThumbnailWidth, $this->Width);
                $MaxThumbnailHeight = min($MaxThumbnailHeight, $this->Height);
                $SrcImage->ScaleTo($MaxThumbnailWidth, $MaxThumbnailHeight, TRUE);
                $SrcImage->SaveAs($this->ThumbnailFileName, IMGTYPE_JPEG);
                if ($SrcImage->Status() != AI_OKAY)
                {
                    echo "thumbnail SaveAs failed.<br>";
                    $this->ErrorStatus = $SrcImage->Status();
                    return;
                }
                if (($this->Width * $MaxThumbnailHeight)
                        > ($this->Height * $MaxThumbnailWidth))
                {
                    $this->ThumbnailWidth = $MaxThumbnailWidth;
                    $this->ThumbnailHeight =
                            ($MaxThumbnailWidth * $SrcImage->YSize()) / $SrcImage->XSize();
                }
                else
                {
                    $this->ThumbnailWidth = ($MaxThumbnailHeight * $SrcImage->XSize()) / $SrcImage->YSize();
                    $this->ThumbnailHeight = $MaxThumbnailHeight;
                }

                # save image attributes to database
                $this->SaveImageInfo();
            }
        }
    }

    function LoadImageInfo($ImageId)
    {
        # save image ID
        $this->Id = $ImageId;

        # load image record from database
        $this->DB->Query("SELECT * FROM Images WHERE ImageId = ".$ImageId);

        # if the ID is invalid
        if (!$this->DB->NumRowsSelected())
        {
            $this->ErrorStatus = AI_INTERNALERROR;
            return;
        }

        $Record = $this->DB->FetchRow();

        # load in values from record
        $this->Format          = $Record["Format"];
        $this->AltText         = $Record["AltText"];
        $this->Height          = $Record["Height"];
        $this->Width           = $Record["Width"];
        $this->PreviewHeight   = $Record["PreviewHeight"];
        $this->PreviewWidth    = $Record["PreviewWidth"];
        $this->ThumbnailHeight = $Record["ThumbnailHeight"];
        $this->ThumbnailWidth  = $Record["ThumbnailWidth"];

        # generate file names
        $this->SetFileNames();
    }

    function CreateCopyOfImage($SrcImage)
    {
        $Image = new Image($SrcImage->Url());
        if ($Image->Status() != AI_OKAY)
        {
            # set error status
            $this->ErrorStatus = $Image->Status();
            return;
        }

        # generate new image ID
        $this->Id = $this->GenerateNewImageId();

        # generate file names
        $this->SetFileNames();

        # copy attributes from source image
        $this->Format = $SrcImage->Format();
        $this->AltText = $SrcImage->AltText();
        $this->Width = $SrcImage->Width();
        $this->Height = $SrcImage->Height();
        $this->PreviewWidth = $SrcImage->PreviewWidth();
        $this->PreviewHeight = $SrcImage->PreviewHeight();
        $this->ThumbnailWidth = $SrcImage->ThumbnailWidth();
        $this->ThumbnailHeight = $SrcImage->ThumbnailHeight();

        # copy source image files
        copy($SrcImage->Url(), $this->FileName);
        copy($SrcImage->PreviewUrl(), $this->PreviewFileName);
        copy($SrcImage->ThumbnailUrl(), $this->ThumbnailFileName);

        # save image attributes to database
        $this->SaveImageInfo();
    }

    # generate and save image, preview, and thumnail file names
    # (requires image ID and format to be set beforehand)
    function SetFileNames()
    {
        if (Image::Extension($this->Format))
        {
            $FileExtension = Image::Extension($this->Format);
        }
        else
        {
            $FileExtension = "";
        }

        $this->FileName = self::IMAGE_PATH . "Img--"
                .sprintf("%08d.", $this->Id).$FileExtension;
        $this->PreviewFileName = self::PREVIEW_PATH . "Preview--"
                .sprintf("%08d.", $this->Id).$FileExtension;
        $this->ThumbnailFileName = self::THUMBNAIL_PATH . "Thumb--"
                .sprintf("%08d.", $this->Id).$FileExtension;
    }

    # retrieve next image ID
    function GenerateNewImageId()
    {
        # look up highest image ID in database
        $CurrentHighestId = $this->DB->Query("SELECT ImageId FROM Images"
                                             ." ORDER BY ImageId DESC LIMIT 1",
                                             "ImageId");

        # return next highest ID or 1 if no ID yet used
        return ($CurrentHighestId > 0) ? ($CurrentHighestId + 1) : 1;
    }

    # store image attributes to database
    function SaveImageInfo()
    {
        # look for existing image record with matching ID
        $RecordCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM Images"
                                        ." WHERE ImageId = ".$this->Id,
                                        "RecordCount");

        # if matching ID found
        if ($RecordCount > 0)
        {
            # update existing image record
            $this->DB->Query("UPDATE Images SET"
                             ." Format = '"         .$this->Format."',"
                             ." AltText = '"        .addslashes($this->AltText)."',"
                             ." Height = '"         .$this->Height."',"
                             ." Width = '"          .$this->Width."',"
                             ." PreviewHeight = '"  .$this->PreviewHeight."',"
                             ." PreviewWidth = '"   .$this->PreviewWidth."',"
                             ." ThumbnailHeight = '".$this->ThumbnailHeight."',"
                             ." ThumbnailWidth = '" .$this->ThumbnailWidth."'"
                             ." WHERE ImageId = ".$this->Id);
        }
        else
        {
            # add new image record
            $this->DB->Query("INSERT INTO Images SET"
                             ." ImageId = '"        .$this->Id."',"
                             ." Format = '"         .$this->Format."',"
                             ." AltText = '"        .addslashes($this->AltText)."',"
                             ." Height = '"         .$this->Height."',"
                             ." Width = '"          .$this->Width."',"
                             ." PreviewHeight = '"  .$this->PreviewHeight."',"
                             ." PreviewWidth = '"   .$this->PreviewWidth."',"
                             ." ThumbnailHeight = '".$this->ThumbnailHeight."',"
                             ." ThumbnailWidth = '" .$this->ThumbnailWidth."'");
        }
    }
}
