<?PHP

#
#   FILE:  File.php
#
#   Copyright 2010 Edward Almasy and Internet Scout
#   http://scout.wisc.edu
#


class File {

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

    # status codes (set by constructor and returned by File::Status())
    const FILESTAT_OK =             0;
    const FILESTAT_COPYERROR =      1;
    const FILESTAT_PARAMERROR =     2;
    const FILESTAT_ZEROLENGTH =     3;
    const FILESTAT_DOESNOTEXIST =   4;
    const FILESTAT_UNREADABLE =     5;

    # object constructor
    function File($IdOrFileName, $ResourceId = NULL, $FieldId = NULL,
            $DesiredFileName = NULL, $CheckFileLength = TRUE)
    {
        # assume constructor will succeed
        $this->Status = self::FILESTAT_OK;

        # get our own database handle
        $DB = new Database();
        $this->DB = $DB;

        # if ID supplied
        if (is_int($IdOrFileName))
        {
            # set file ID from supplied value
            $this->Id = intval($IdOrFileName);

            # load file info from database
            $DB->Query("SELECT * FROM Files WHERE FileId = ".$this->Id);
            $this->DBFields = $DB->FetchRow();

            # if the image wasn't found in the database
            if (!$DB->NumRowsSelected())
            {
                $this->Status = self::FILESTAT_DOESNOTEXIST;
            }
        }
        # else if file name and resource ID and field ID supplied
        elseif (strlen($IdOrFileName) && ($ResourceId != NULL) && ($FieldId != NULL))
        {
            # if file does not exist
            $TempFileName = $IdOrFileName;
            if (!file_exists($TempFileName) || !is_readable($TempFileName))
            {
                # set status indicating appropriate error
                $this->Status = file_exists($TempFileName)
                        ? self::FILESTAT_DOESNOTEXIST : self::FILESTAT_UNREADABLE;
            }
            else
            {
                # if we were asked to check file length and file was zero length
                $FileLength = filesize($TempFileName);
                if ($CheckFileLength && !$FileLength)
                {
                    # set status indicating zero length file
                    $this->Status = self::FILESTAT_ZEROLENGTH;
                }
                else
                {
                    # generate secret string (used to protect from unauthorized download)
                    srand((double)microtime() * 1000000);
                    $SecretString = sprintf("%04X", rand(1, 30000));

                    # attempt to get file type
                    $FileType = "";
                    if (function_exists("finfo_open"))
                    {
                        $FInfoHandle = finfo_open(FILEINFO_MIME);

                        if ($FInfoHandle)
                        {
                            $FInfoMime = finfo_file($FInfoHandle, $TempFileName);
                            finfo_close($FInfoHandle);

                            if ($FInfoMime)
                            {
                                $FileType = $FInfoMime;
                            }
                        }
                    }
                    else if (function_exists("mime_content_type"))
                    {
                        # mime_content_type has been deprecated, but it may be
                        # the only way to get the mimetype for PHP < 5.3
                        $MimeType = mime_content_type($TempFileName);

                        if ($MimeType)
                        {
                            $FileType = $MimeType;
                        }
                    }

                    # add file info to database
                    $BaseFileName = $DesiredFileName
                            ? basename($DesiredFileName) : basename($TempFileName);
                    $DB->Query("INSERT INTO Files"
                            ." (ResourceId, FieldId, FileName, FileLength, FileType,"
                                ." SecretString)"
                            ." VALUES ("
                                .intval($ResourceId).", "
                                .intval($FieldId).", "
                                ."'".addslashes($BaseFileName)."', "
                                .$FileLength.", "
                                ."'".$FileType."', "
                                ."'".$SecretString."')");

                    # retrieve ID of new file
                    $this->Id = $DB->LastInsertId("Files");

                    # load file info back in from database
                    $DB->Query("SELECT * FROM Files WHERE FileId = ".$this->Id);
                    $this->DBFields = $DB->FetchRow();

                    # copy file to storage
                    $CopySucceeded = copy($IdOrFileName, $this->GetNameOfStoredFile());

                    # if copy failed
                    if (!$CopySucceeded)
                    {
                        # remove file info from database
                        $DB->Query("DELETE FROM Files WHERE FileId = ".$this->Id);

                        # set status indicating constructor failed
                        $this->Status = self::FILESTAT_COPYERROR;
                    }
                }
            }
        }
        else
        {
            # set status indicating constructor failed
            $this->Status = self::FILESTAT_PARAMERROR;
        }
    }

    # return object status (used to report errors occurring in constructor)
    function Status() {  return $this->Status;  }

    # get various attributes
    function Id() {  return $this->Id;  }
    function Name() {  return $this->DBFields["FileName"];  }
    function GetLength() {  return $this->DBFields["FileLength"];  }
    function GetType() {  return $this->DBFields["FileType"];  }

    # get/set various attributes
    function Comment($NewValue = DB_NOVALUE)
            {  return $this->UpdateValue("FileComment", $NewValue);  }
    function FieldId($NewValue = DB_NOVALUE)
            {  return $this->UpdateValue("FieldId", $NewValue);  }
    function ResourceId($NewValue = DB_NOVALUE)
            {  return $this->UpdateValue("ResourceId", $NewValue);  }

    # get MIME type (defaults to "application/octet-stream" if not available)
    function GetMimeType()
    {
        return strlen($this->GetType())
                ? $this->GetType() : "application/octet-stream";
    }

    # get link for downloading file
    function GetLink()
    {
        global $AF;

        # if .htaccess files are supported, use the redirect that includes
        # the file name so that browsers don't use index.php as the name
        # for the downloaded file
        if ($AF->HtaccessSupport())
        {
            return "downloads/".$this->Id."/".rawurlencode($this->Name());
        }

        # otherwise use the download portal
        else
        {
            return "index.php?P=DownloadFile&Id=".$this->Id;
        }
    }

    # delete file (other methods are invalid after calling this!)
    function Delete()
    {
        # remove file entry from DB
        $this->DB->Query("DELETE FROM Files WHERE FileId = ".$this->Id);

        # delete file
        $FileName = $this->GetNameOfStoredFile();
        if (file_exists($FileName))
        {
            unlink($FileName);
        }
    }

    # retrieve actual name of stored file
    function GetNameOfStoredFile()
    {
        return sprintf("FileStorage/%06d-%s-%s",
                $this->Id, $this->DBFields["SecretString"], $this->Name());
    }


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

    private $DB;
    private $Status;
    private $Id;
    private $DBFields;

    # convenience function to supply parameters to Database->UpdateValue()
    private function UpdateValue($FieldName, $NewValue)
    {
        return $this->DB->UpdateValue("Files", $FieldName, $NewValue,
                               "FileId = ".intval($this->Id),
                               $this->DBFields, TRUE);
    }
}


?>
