<commit_before><?php
/**
 * Photo model.
 *
 * Something related to photos, the guts are in this file.
 * Upload, update, delete and generate, oh my!
 * @author <NAME> <<EMAIL>>
 */
class Photo extends BaseModel
{
  public function __construct($params = null)
  {
    parent::__construct();
    if(isset($params['utility']))
      $this->utility = $params['utility'];
    else
      $this->utility = new Utility;

    if(isset($params['url']))
      $this->url = $params['url'];
    else
      $this->url = new Url;

    if(isset($params['image']))
      $this->image = $params['image'];
    else
      $this->image = getImage();

    if(isset($params['user']))
      $this->user = $params['user'];
    else
      $this->user = new User;

    if(isset($params['config']))
      $this->config = $params['config'];
  }

  /**
    * Adds the urls for the photo.
    * pathWxH is a resource.
    * photoWxH is an enumerated array [path, width, height]
    *
    * @param array $photo the photo object
    * @param array $options Options for the photo such as crop (CR) and greyscale (BW)
    * @param string $protocol http or https
    * @return array
    */
  public function addApiUrls($photo, $sizes, $protocol=null)
  {
    if($protocol === null)
      $protocol = $this->utility->getProtocol(false);

    foreach($sizes as $size)
    {
      $options = $this->generateFragmentReverse($size);
      $fragment = $this->generateFragment($options['width'], $options['height'], $options['options']);
      $path = $this->generateUrlPublic($photo, $options['width'], $options['height'], $options['options'], $protocol);
      $photo["path{$size}"] = $path;
      if(strstr($fragment, 'xCR') === false)
      {
        $dimensions = $this->getRealDimensions($photo['width'], $photo['height'], $options['width'], $options['height']);
        $photo["photo{$fragment}"] = array($path, intval($dimensions['width']), intval($dimensions['height']));
      }
      else
      {
        $photo["photo{$fragment}"] = array($path, intval($options['width']), intval($options['height']));
      }
    }

    // we never need the base
    if(isset($photo['pathBase']))
      unset($photo['pathBase']);

    // the original needs to be conditionally included
    if($this->config->site->allowOriginalDownload == 1 || $this->user->isOwner())
      $photo['pathOriginal'] = $this->generateUrlOriginal($photo);
    elseif(isset($photo['pathOriginal']))
      unset($photo['pathOriginal']);

    $photo['url'] = $this->getPhotoViewUrl($photo);
    return $photo;
  }

  /**
    * Delete a photo from the remote database and remote filesystem.
    * This deletes the original photo and all versions.
    *
    * @param string $id ID of the photo
    * @return boolean
    */
  public function delete($id)
  {
    // TODO, validation
    // TODO, do not delete record from db - mark as deleted
    $photo = $this->db->getPhoto($id);
    if(!$photo)
      return false;

    $fileStatus = $this->fs->deletePhoto($photo);
    $dataStatus = $this->db->deletePhoto($photo);
    return $fileStatus && $dataStatus;
  }

  /**
    * Versions of a photo are stored by a deterministic key in the database.
    * Given $width, $height and $options this method returns that key.
    *
    * @param int $width Width of the photo to generate
    * @param int $height Height of the photo to generate
    * @param string $options Options for the photo such as crop (CR) and greyscale (BW)
    * @return string
    */
  public function generateCustomKey($width, $height, $options = null)
  {
    return sprintf('path%s', $this->generateFragment($width, $height, $options));
  }

  /**
    * Generates the "fragment" for the photo version.
    * This fragment is used in the file name as well as the database key.
    *
    * @param int $width Width of the photo to generate
    * @param int $height Height of the photo to generate
    * @param string $options Options for the photo such as crop (CR) and greyscale (BW)
    * @return string
    */
  public function generateFragment($width, $height, $options = null)
  {
    $fragment = "{$width}x{$height}";
    if(!empty($options))
      $fragment .= "x{$options}";
    return $fragment;
  }

  /**
    * Generate a version of the photo as specified by the width, height and options.
    * This method requres the $hash ve validated to keep random versions of images to be created.
    * The photo is generated, uploaded to the remote file system and added to the database.
    * Operations are done in place on a downloaded version of the base photo and this file name is returned.
    *
    * @param string $id The id of the photo.
    * @param int $width The width of the requested photo.
    * @param int $height The height of the requested photo.
    * @param string $options Optional options to be applied on the photo
    * @return mixed string on success, false on failure
    */
  public function generate($id, $hash, $width, $height, $options = null)
  {
    if(!$this->isValidHash($hash, $id, $width, $height, $options))
      return false;

    $photo = $this->db->getPhoto($id);
    if(!$photo)
    {
      $this->logger->crit('Could not get photo from db in generate method');
      return false;
    }

    $filename = $this->fs->getPhoto($photo['pathBase']);
    if(!$filename)
    {
      $this->logger->crit('Could not get photo from fs in generate method');
      return false;
    }

    try
    {
      $this->image->load($filename);
    }
    catch(OPInvalidImageException $e)
    {
      $this->logger->crit('Could not get image from image adapter in generate method', $e);
      return false;
    }

    $maintainAspectRatio = true;
    if(!empty($options))
    {
      $optionsArray = (array)explode('x', $options);
      foreach($optionsArray as $option)
      {
        switch($option)
        {
          case 'BW':
            $this->image->greyscale();
            break;
          case 'CR':
            $maintainAspectRatio = false;
            break;
        }
      }
    }

    $this->image->scale($width, $height, $maintainAspectRatio);

    $this->image->write($filename);
    $customPath = $this->generateCustomUrl($photo['pathBase'], $width, $height, $options);
    $key = $this->generateCustomKey($width, $height, $options);
    $resFs = $this->fs->putPhoto($filename, $customPath);
    $resDb = $this->db->postPhoto($id, array($key => $customPath));
    // TODO unlink $filename
    if($resFs && $resDb)
      return $filename;

    return false;
  }

  /**
    * Does the opposite of $this->generateFragment.
    * Given a string fragment this will return it's parts as an array.
    * The $options must start with a width and height (i.e. 800x600)
    *
    * @param string $options Options for the photo such as crop (CR) and greyscale (BW)
    * @return array
    */
  public function generateFragmentReverse($options)
  {
    $options = explode('x', $options);
    $width = array_shift($options);
    $height = array_shift($options);
    $options = implode('x', $options);
    return array('width' => $width, 'height' => $height, 'options' => $options);
  }

  /**
    * When a custom version of a photo needs to be generated it must be accompanied by a hash.
    * This method generates that hash based on a secret and normalization of parameters.
    * The method takes any number of arguments and processes them all.
    *
    * @param string $param1 any parameter value
    * ...
    * @param string $paramN any parameter value
    * @return mixed string on success, FALSE on error
    */
  public function generateHash(/*$args1, $args2, ...*/)
  {
    $args = func_get_args();
    if(count($args) === 0)
      return false;

    foreach($args as $k => $v)
    {
      if(strlen($v) == 0)
        unset($args[$k]);
    }
    $args[] = $this->config->secrets->secret;
    return substr(sha1(implode('.', $args)), 0, 5);
  }

  /**
    * Generates the default paths given a photo name.
    * These paths will also be the initial versions of the photo that are stored in the file system and database.
    *
    * @param string $photoName File name of the photo
    * @return array
    */
  public function generatePaths($photoName)
  {
    $baseName = time() . '-' . preg_replace('/[^a-zA-Z0-9.-_]/', '-', $photoName);
    $originalName = substr(uniqid(), 0, 6) . '-' . preg_replace('/[^a-zA-Z0-9.-_]/', '-', $photoName);<commit_msg>Adding a more random prefix for base photo name. Closes #539<commit_after><?php
/**
 * Photo model.
 *
 * Something related to photos, the guts are in this file.
 * Upload, update, delete and generate, oh my!
 * @author <NAME> <<EMAIL>>
 */
class Photo extends BaseModel
{
  public function __construct($params = null)
  {
    parent::__construct();
    if(isset($params['utility']))
      $this->utility = $params['utility'];
    else
      $this->utility = new Utility;

    if(isset($params['url']))
      $this->url = $params['url'];
    else
      $this->url = new Url;

    if(isset($params['image']))
      $this->image = $params['image'];
    else
      $this->image = getImage();

    if(isset($params['user']))
      $this->user = $params['user'];
    else
      $this->user = new User;

    if(isset($params['config']))
      $this->config = $params['config'];
  }

  /**
    * Adds the urls for the photo.
    * pathWxH is a resource.
    * photoWxH is an enumerated array [path, width, height]
    *
    * @param array $photo the photo object
    * @param array $options Options for the photo such as crop (CR) and greyscale (BW)
    * @param string $protocol http or https
    * @return array
    */
  public function addApiUrls($photo, $sizes, $protocol=null)
  {
    if($protocol === null)
      $protocol = $this->utility->getProtocol(false);

    foreach($sizes as $size)
    {
      $options = $this->generateFragmentReverse($size);
      $fragment = $this->generateFragment($options['width'], $options['height'], $options['options']);
      $path = $this->generateUrlPublic($photo, $options['width'], $options['height'], $options['options'], $protocol);
      $photo["path{$size}"] = $path;
      if(strstr($fragment, 'xCR') === false)
      {
        $dimensions = $this->getRealDimensions($photo['width'], $photo['height'], $options['width'], $options['height']);
        $photo["photo{$fragment}"] = array($path, intval($dimensions['width']), intval($dimensions['height']));
      }
      else
      {
        $photo["photo{$fragment}"] = array($path, intval($options['width']), intval($options['height']));
      }
    }

    // we never need the base
    if(isset($photo['pathBase']))
      unset($photo['pathBase']);

    // the original needs to be conditionally included
    if($this->config->site->allowOriginalDownload == 1 || $this->user->isOwner())
      $photo['pathOriginal'] = $this->generateUrlOriginal($photo);
    elseif(isset($photo['pathOriginal']))
      unset($photo['pathOriginal']);

    $photo['url'] = $this->getPhotoViewUrl($photo);
    return $photo;
  }

  /**
    * Delete a photo from the remote database and remote filesystem.
    * This deletes the original photo and all versions.
    *
    * @param string $id ID of the photo
    * @return boolean
    */
  public function delete($id)
  {
    // TODO, validation
    // TODO, do not delete record from db - mark as deleted
    $photo = $this->db->getPhoto($id);
    if(!$photo)
      return false;

    $fileStatus = $this->fs->deletePhoto($photo);
    $dataStatus = $this->db->deletePhoto($photo);
    return $fileStatus && $dataStatus;
  }

  /**
    * Versions of a photo are stored by a deterministic key in the database.
    * Given $width, $height and $options this method returns that key.
    *
    * @param int $width Width of the photo to generate
    * @param int $height Height of the photo to generate
    * @param string $options Options for the photo such as crop (CR) and greyscale (BW)
    * @return string
    */
  public function generateCustomKey($width, $height, $options = null)
  {
    return sprintf('path%s', $this->generateFragment($width, $height, $options));
  }

  /**
    * Generates the "fragment" for the photo version.
    * This fragment is used in the file name as well as the database key.
    *
    * @param int $width Width of the photo to generate
    * @param int $height Height of the photo to generate
    * @param string $options Options for the photo such as crop (CR) and greyscale (BW)
    * @return string
    */
  public function generateFragment($width, $height, $options = null)
  {
    $fragment = "{$width}x{$height}";
    if(!empty($options))
      $fragment .= "x{$options}";
    return $fragment;
  }

  /**
    * Generate a version of the photo as specified by the width, height and options.
    * This method requres the $hash ve validated to keep random versions of images to be created.
    * The photo is generated, uploaded to the remote file system and added to the database.
    * Operations are done in place on a downloaded version of the base photo and this file name is returned.
    *
    * @param string $id The id of the photo.
    * @param int $width The width of the requested photo.
    * @param int $height The height of the requested photo.
    * @param string $options Optional options to be applied on the photo
    * @return mixed string on success, false on failure
    */
  public function generate($id, $hash, $width, $height, $options = null)
  {
    if(!$this->isValidHash($hash, $id, $width, $height, $options))
      return false;

    $photo = $this->db->getPhoto($id);
    if(!$photo)
    {
      $this->logger->crit('Could not get photo from db in generate method');
      return false;
    }

    $filename = $this->fs->getPhoto($photo['pathBase']);
    if(!$filename)
    {
      $this->logger->crit('Could not get photo from fs in generate method');
      return false;
    }

    try
    {
      $this->image->load($filename);
    }
    catch(OPInvalidImageException $e)
    {
      $this->logger->crit('Could not get image from image adapter in generate method', $e);
      return false;
    }

    $maintainAspectRatio = true;
    if(!empty($options))
    {
      $optionsArray = (array)explode('x', $options);
      foreach($optionsArray as $option)
      {
        switch($option)
        {
          case 'BW':
            $this->image->greyscale();
            break;
          case 'CR':
            $maintainAspectRatio = false;
            break;
        }
      }
    }

    $this->image->scale($width, $height, $maintainAspectRatio);

    $this->image->write($filename);
    $customPath = $this->generateCustomUrl($photo['pathBase'], $width, $height, $options);
    $key = $this->generateCustomKey($width, $height, $options);
    $resFs = $this->fs->putPhoto($filename, $customPath);
    $resDb = $this->db->postPhoto($id, array($key => $customPath));
    // TODO unlink $filename
    if($resFs && $resDb)
      return $filename;

    return false;
  }

  /**
    * Does the opposite of $this->generateFragment.
    * Given a string fragment this will return it's parts as an array.
    * The $options must start with a width and height (i.e. 800x600)
    *
    * @param string $options Options for the photo such as crop (CR) and greyscale (BW)
    * @return array
    */
  public function generateFragmentReverse($options)
  {
    $options = explode('x', $options);
    $width = array_shift($options);
    $height = array_shift($options);
    $options = implode('x', $options);
    return array('width' => $width, 'height' => $height, 'options' => $options);
  }

  /**
    * When a custom version of a photo needs to be generated it must be accompanied by a hash.
    * This method generates that hash based on a secret and normalization of parameters.
    * The method takes any number of arguments and processes them all.
    *
    * @param string $param1 any parameter value
    * ...
    * @param string $paramN any parameter value
    * @return mixed string on success, FALSE on error
    */
  public function generateHash(/*$args1, $args2, ...*/)
  {
    $args = func_get_args();
    if(count($args) === 0)
      return false;

    foreach($args as $k => $v)
    {
      if(strlen($v) == 0)
        unset($args[$k]);
    }
    $args[] = $this->config->secrets->secret;
    return substr(sha1(implode('.', $args)), 0, 5);
  }

  /**
    * Generates the default paths given a photo name.
    * These paths will also be the initial versions of the photo that are stored in the file system and database.
    * We need the prefix for the original to be different from the base
    * A random number between 1,000,000 and 9,999,999 is sufficient when paired with the filename
    *
    * @param string $photoName File name of the photo
    * @return array
    */
  public function generatePaths($photoName)
  {
    $baseName = dechex(rand(1000000,9999999)) . '-' . preg_replace('/[^a-zA-Z0-9.-_]/', '-', $photoName);
    $originalName = uniqid() . '-' . preg_replace('/[^a-zA-Z0-9.-_]/', '-', $photoName);
{'fraction_non_alphanumeric': 0.14741855815594496, 'fraction_numerical': 0.004174997059861225, 'mean_word_length': 2.9377170641352164, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 135, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4114606', 'n_tokens_mistral': 5137, 'n_tokens_neox': 5001, 'n_words': 1602}