Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/recipe-fiddle.git
<?php
namespace com\whitemagicsoftware;

require "constants.php";

/**
 * Handles image uploads, securely, into the /tmp/recipefiddle/uploads
 * directory.
 */
class SecureImageUpload extends Obj {
  /** @see http://www.phpclasses.org/browse/file/2743.html */
  private $extensions = array(
   "image/bmp"  => "bmp",
   "image/gif"  => "gif",
   "image/jpeg" => "jpg",
   "image/png"  => "png",
   "image/tif"  => "tif",

   "image/svg+xml"   => "svg",
   "application/pdf" => "pdf",

   "applciation/postscript" => "eps",
   "image/eps"              => "eps"
  ); 

  // Website where the image can be retrieved.
  private $url = "";

  // Canonical file path.
  private $filename = "";

  /**
   * Saves a file into a user's account.
   *
   * TODO: Provide error messages.
   *
   * @param $formElement Name of the form's input file element.
   * @param $id Unique identifier for the user uploading the file.
   * @param $appId Identifier for the application that uses images.
   *
   * @return true iff the file was uploaded successfully.
   */
  public function handle( $formElement, $id, $appId = "" ) {
    global $FILE_MAX_UPLOAD_TIME;

    // Assume the upload failed.
    $result = false;

    // Let the file be sent, within a limited amount of time.
    set_time_limit( $FILE_MAX_UPLOAD_TIME );

    $file = $_FILES[ $formElement ];

    if( !empty( $file ) ) {
      $error = $file[ "error" ];

      if( $error === UPLOAD_ERR_OK ) {
        $result = $this->relocate( $file, $id, $appId );
      }
      else if( $error === UPLOAD_ERR_INI_SIZE ) {
        $this->log( "Upload exceeds maximum: $id/$appId" );
      }
    }

    return $result;
  }

  /**
   * Called to store the user's file upload. The appId is used to
   * organize the files in an easily identifiable way.
   *
   * @param $id Unique identifier for the user uploading the file.
   * @param $appId Identifier for the application that uses images.
   * @return true iff the file was saved successfully.
   */
  private function relocate( $file, $id, $appId ) {
    global $IMAGE_DIRECTORY_ROOT;
    global $SERVICE_IMAGE;

    // Assume processing the file failed.
    $result = false;

    // The directory is used for the local system path and URL path.
    $directory = "$id/$appId/";
    $localPath = "$IMAGE_DIRECTORY_ROOT$directory";
    $this->createDirectory( "$localPath" );

    // Create a new filename.
    $tempFilename  = $file[ "tmp_name" ];
    $ext           = $this->getExtension( $tempFilename );

    // Ensure the filename is always unique.
    // @see http://stackoverflow.com/a/460168/59087
    // @see http://www.php.net/manual/en/function.uniqid.php
    $hashFilename  = md5( uniqid( $file[ "name" ] ) ) . ".$ext";

    // The odds of overwriting the filename are astronomical.
    $localFilename = "$localPath$hashFilename";

    //$this->log( "Moving $tempFilename to $localFilename ..." );

    // Safely store the file until the upload has been processed.
    if( move_uploaded_file( $tempFilename, $localFilename ) ) {
      if( ($result = chmod( $localFilename, 0644 )) === true ) {
        $this->setFilename( $localFilename );
        $this->setURL( "$SERVICE_IMAGE$directory$hashFilename" );
      }
    }
    else {
      $this->log( "Create failed: $localFilename" );
    }

    return $result;
  }

  /**
   * Returns the extension for the file's mime type (using EXIF data).
   *
   * @return The empty string if the file's mime type could not be
   * ascertained.
   */
  private function getExtension( $filename ) {
    $mimeType = $this->getMimeType( $filename );

    return empty( $this->extensions[ $mimeType ] ) ?
      "" : $this->extensions[ $mimeType ];
  }

  /**
   * Returns the mime type associated with the file by examining the
   * file contents (rather than the file's name).
   */
  private function getMimeType( $filename ) {
    $path = realpath( $filename );
    return finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $path );
  }

  /**
   * Returns the URL for the file that was uploaded.
   *
   * @param $u The fully qualified file reference [usually HTTP(s)].
   */
  private function setURL( $u ) {
    if( !empty( $u ) ) {
      $this->url = $u;
    }
  }

  /**
   * Sets the path to the safe filename that was uploaded and moved into
   * the web directory.
   *
   * @param $f The canonical path to the file.
   */
  private function setFilename( $f ) {
    if( !empty( $f ) ) {
      $this->filename = $f;
    }
  }

  /**
   * Returns the safe name for the file that was just uploaded.
   *
   * @return A string.
   */
  public function getURL() {
    return $this->url;
  }

  /**
   * Returns the canonical path to the file.
   *
   * @return A string.
   */
  public function getFilename() {
    return $this->filename;
  }
}