Your IP : 216.73.217.112


Current Path : /home/annegardxb/www/so-assurances/plugins/content/readlesstext/
Upload File :
Current File : /home/annegardxb/www/so-assurances/plugins/content/readlesstext/readlesstextthumb.php

<?php
/**
 * @package readlesstext
 * @copyright 2008-2014 Parvus
 * @license http://www.gnu.org/licenses/gpl-3.0.html
 * @link http://joomlacode.org/gf/project/cutoff/
 * @author Parvus
 *
 * readless is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * readless is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with readless. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version $Id$
 */

defined( '_JEXEC' ) or die;
jimport( 'joomla.plugin.plugin' );
jimport( 'joomla.utilities.date' );

class ReadLessTextThumb
{
  /**
   * Checks if the given url is still a valid thumbnail url.
   * Determines the correct url to the corresponding thumbnail.
   * If the thumbnail does not exist, it is created
   * @param string $url The url to the thumbnail image
   * @param dict $minimum Associative array as given to @c GetThumbnail().
   *   used to recreate the expected thumbnail url.
   * @param dict $crop Associative array as given to @c GetThumbnail().
   *   used to recreate the expected thumbnail url.
   * @param int $thumbWidth A number. May be zero or negative. When positive, it
   *   indicates the maximum width of the resized thumbnail. Else, there is no
   *   restriction on image width, and will be chosen in function of the
   *   height.
   *   OUT: If @c true is returned, this variable will contain the extact thumbnail width, in pixels.
   * @param int $thumbHeight A number. May be zero or negative. When positive, it
   *   indicates the maximum height of the resized thumbnail. Else, there is
   *   no restriction on image height, and will be chosen in function of the
   *   width.
   *   OUT: If @c true is returned, this variable will contain the exact thumbnail height, in pixels.
   * @return @c true if the given thumbnail url is still correct. @c false
   *   when some checks failed or could not be performed.
   * @see GetThumbnail
   */
  public static function ValidateThumbnail( $imageUrl, $thumbnailUrl, $minimum, $crop, & $thumbWidth, & $thumbHeight )
  {
    $success = false;

    $ext = strrchr( $thumbnailUrl, '.'); /* e.g.: .png */
    $expectedThumbnailPath = self::_DetermineThumbnailPath( $imageUrl, $minimum, $crop, $thumbWidth, $thumbHeight, $ext );
    $expectedThumbnailUrl = self::_DetermineUrlFromPath( $expectedThumbnailPath );

    if ( $thumbnailUrl == $expectedThumbnailUrl )
    {
      if ( @file_exists( $expectedThumbnailPath ) )
      {
        /* The given thumbnail exists, is located in the cache, and
         * the minimum configuration settings haven't been changed since.
         * we can re-use it.
         */
        $sizeArray = @getimagesize( $expectedThumbnailPath );
        $thumbWidth = $sizeArray[ 0 ];
        $thumbHeight = $sizeArray[ 1 ];

        $success = true;
      }
    }

    return $success;
  }

  private static function _DetermineImageType( $url )
  {
    $type = false;
    if ( function_exists( 'exif_imagetype' ) )
    {
      $type = @exif_imagetype( $url );
    }
    else if ( function_exists( 'getimagesize' ) )
    {
      $list = @getimagesize( $url );
      if ( $list )
      {
        $type = $list[2];
      }
    }

    if ( !/*NOT*/$type )
    {
      /* Fallback: try to determine the correct image type by checking for an extension in the url. */
      $ext = '';
      $parts = explode( '.', $url );
      if ( count( $parts ) > 1 )
      {
        $parts = explode( '?', $parts[ count( $parts ) - 1 ], 2 );
        $ext = JString::strtolower( $parts[ 0 ] );
      }
      if ( array_key_exists( $ext, ReadLessTextThumb::$_extToType ) )
      {
        $type = ReadLessTextThumb::$_extToType[ $ext ];
      }
    }

    return $type;
  }

  private static function _DetermineThumbnailPath( $imageUrl, $minimum, $crop, $thumbWidth, $thumbHeight, $ext )
  {
    $path = JPATH_CACHE . '/plg_readlesstext/';
    if ( !/*NOT*/@file_exists( $path ) )
    {
      @mkdir( $path );
    }

    if ( !/*NOT*/@is_dir( $path ) or !/*NOT*/@is_writable( $path ) )
    {
      /* Insufficient write permissions. Use fall-back. */
      $thumbnailUrl = $imageUrl;
    }
    else
    {
      $string = $imageUrl . $minimum[ 'width' ] . $minimum[ 'height' ] . $minimum[ 'ratio' ]
        . $crop[ 'horizontal_position' ] . $crop[ 'vertical_position' ]
        . $thumbWidth . $thumbHeight . 'v5.2 (r274)';
      $thumbnailPath = $path . md5( $string ) . $ext;
    }
    return $thumbnailPath;
  }

  private static function _DetermineUrlFromPath( $path )
  {
    $host = parse_url( $path, PHP_URL_HOST );
    if ( $host )
    {
      /* $path is already a url. */
      $url = false;
    }
    else if ( JString::strpos( $path, JPATH_BASE . '/' ) === 0 )
    {
      $url = JString::str_ireplace( JPATH_BASE . '/', JURI::base(), $path );
    }
    else
    {
      $url = JURI::base() . $path;
    }
    return $url;
  }

  private static function _DeterminePathFromUrl( $url )
  {
    $host = parse_url( $url, PHP_URL_HOST );
    if ( $host )
    {
      if ( JString::strpos( $url, JURI::base() . '/' ) === 0 )
      {
        $url = JString::str_ireplace( JURI::base(), JPATH_BASE . '/', $path );
      }
      else
      {
        $path = false; /* Can not be converted to a local path. */
      }
    }
    else
    {
      $path = JPATH_BASE . '/' . $url;
      $path = JString::str_ireplace( '//', '/', $path );
    }
    return $path;
  }

  /**
   * Find the resized dimensions, keeping the proportions.
   * @param uint $width
   * @param uint $height
   * @param uint $thumbWidth
   * @param uint $thumbHeight
   * @param dict $crop Associative array, with keys 'horizontal_position',
   *   'vertical_position' and values 'left', 'right', 'center' or 'no'.
   */
  private static function _DetermineResizeFactor( $width, $height, $thumbWidth, $thumbHeight, $crop )
  {
    /* There are four different ways to resize:
     * A: resize full width to thumbnail width,
     *     resize height with same ratio,
     *      crop height to thumbnail height (top, bottom, evenly both)
     * B: resize full height to thumbnail height,
     *     resize width with same ratio,
     *      crop width to thumbnail width (left, right, evenly both)
     * C: resize full width to thumbnail width,
     *     resize height with same ratio,
     *      resized height <= thumbnail height
     * D: resize full height to thumbnail height,
     *     resize width with same ratio,
     *      resized width <= thumbnail width
     *
     *  Based on
     * - the actual image dimensions: ix, iy
     * - the desired thumbnail dimensions: tx, ty
     *  - the crop options:
     *     crop horizontal: yes (left/right/evenly) or no (do not crop horizontally)
     *      crop vertical: yes (left/right/evenly) or no (do not crop vertically)
     * we need to determine the resize factor.
     *
     * Determine the horizontal and vertical ratio's: rx, ry
     * - If rx < ry: If resized using ry, the horizontal width will be
     *     greater than the thumbnail width. So either the width must
     *     be cropped (if allowed), either the image must be resized
     *     using rx (and thus the resized height will be less than the
     *     desired thumbnail height ty).
     * - If rx == ry: highly unlikely. Crop options are not needed here.
     *     Just resize using the single resize factor that was calculated.
     * - If rx > ry: Similar to the first case.
     *     Replace width <> height, rx <> ry and ty <> tx
     * Thus:
     * rx, ry = tx/ix, ty/iy
     * rx < ry
     *   ? crop horizontal ? r = ry : r = rx
     *   : crop vertical ? r = rx : r = ry
     */

    if ( $thumbWidth > 0 )
    {
      $resizeFactorWidth = min( 1, $thumbWidth / $width );
    }
    else
    {
      $thumbWidth = $width;
      $resizeFactorWidth = 1;
    }
    if ( $thumbHeight > 0 )
    {
      $resizeFactorHeight = min( 1, $thumbHeight / $height );
    }
    else
    {
      $thumbHeight = $height;
      $resizeFactorHeight = 1;
    }
    $resizeFactor = 1; /* Default value */
    if ( $resizeFactorWidth < $resizeFactorHeight )
    {
      /* Width is (relatively) greater than the height. */
      if ( in_array( $crop[ 'horizontal_position' ], array( 'left', 'right', 'center' ) ) )
      {
        /* Horizontal cropping is allowed. We may resize less,
         * and crop the extraneous part.
        */
        $resizeFactor = $resizeFactorHeight;
      }
      else
      {
        /* Horizontal cropping is not allowed. The full width must be
         * resized: the resized height will be less than the intended
        * thumbnail height.
        */
        $resizeFactor = $resizeFactorWidth;
      }
    }
    else
    {
      /* Vertical cropping is allowed. We may resize less,
       * and crop the extraneous part.
      */
      if ( in_array( $crop[ 'vertical_position' ], array( 'top', 'bottom', 'center' ) ) )
      {
        $resizeFactor = $resizeFactorWidth;
      }
      else
      {
        /* Vertical cropping is not allowed. The full height must be
         * resized: the resized width will be less than the intended
        * thumbnail width.
        */
        $resizeFactor = $resizeFactorHeight;
      }
    }
    return $resizeFactor;
  }

  /**
   * Determine the start positions sx, sy: everything lower and
   * everything higher than that plus the image width/height will be
   * thrown away (cropped).
   * @param uint $width
   * @param uint $height
   * @param uint $thumbWidth
   * @param uint $thumbHeight
   * @param dict $crop Associative array, with keys 'horizontal_position',
   *   'vertical_position' and values 'left', 'right', 'center' or 'no'.
   * @param unknown_type $resizeFactor
   */
  private static function _DetermineCroppedRectangle( $width, $height, & $thumbWidth, & $thumbHeight, $crop, $resizeFactor )
  {
    /*
     * Default value is 0, 0, to be used when cutting on the
     * right/bottom, or when cropping is disabled.
     *
     * If there is something to be thrown away, i.e.
     * if r * ix > tx
     *   cut left, retain right ? sx = ix - tx / r
     *   cut right, retain left ? sx = 0
     *   cut evenly ? sx = (ix - tx / r) / 2
     *
     * Likewise for sy
     */

    $horizontalStart = 0; /* Default value */
    $usedWidth = min( $width, $thumbWidth / $resizeFactor );
    $thumbWidth = intval( ( $usedWidth * $resizeFactor ) + 0.01 );
    if ( $usedWidth + 1 < $width )
    {
      switch ( $crop[ 'horizontal_position' ] )
      {
        case 'center':
          $horizontalStart = max( 0, ( $width - $usedWidth ) / 2 );
          break;

        case 'right':
          $horizontalStart = max( 0, $width - $usedWidth );
          break;

        case 'left':
          /* $horizontalStart remains 0 */
          break;

        default:
          /* Do not crop the width after all. We should never get here! */
          break;
      }
    }

    $verticalStart = 0; /* Default value */
    $usedHeight = min( $height, $thumbHeight / $resizeFactor );
    $thumbHeight = intval( ( $usedHeight * $resizeFactor ) + 0.01 );
    if ( $usedHeight + 1 < $height )
    {
      switch ( $crop[ 'vertical_position' ] )
      {
        case 'center':
          $verticalStart = max( 0, ( $height - $usedHeight ) / 2 );
          break;

        case 'bottom':
          $verticalStart = max( 0, $height - $usedHeight );
          break;

        case 'top':
          /* $verticalStart remains 0 */
          break;

        default:
          /* Do not crop the height after all. We should never get here! */
          break;
      }
    }

    return array( $horizontalStart, $verticalStart, $usedWidth, $usedHeight );
  }

  private static function _LoadImageUsingCurl( $url, $maxImageLoadTime )
  {
    $image = false;
    $curl = false;
    if ( function_exists( 'curl_init' ) )
    {
      $curl = curl_init();
    }
    if ( $curl )
    {
      curl_setopt( $curl, CURLOPT_URL, $url );
      curl_setopt( $curl, CURLOPT_HEADER, false );
      curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
      curl_setopt( $curl, CURLOPT_CONNECTTIMEOUT, $maxImageLoadTime );
      if ( ( ini_get( 'open_basedir' ) == '') and
          ( ( ini_get( 'safe_mode' ) == 'Off' ) or ( !/*NOT*/ini_get( 'safe_mode' ) ) ) )
      {
        /* The follow location option can not be activated when either dafe_mode or open_basedir
         * is set in php.ini - as a security measure. Trying to set it anyway results in a warning.
         * @todo If this is not set, redirection is not possible. A workaround for this is
         *   described here: http://stackoverflow.com/a/6918742/911550
         *   To implement?
         */
        curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
      }
      curl_setopt( $curl, CURLOPT_MAXREDIRS, 11/*just a number that seems plenty enough*/ );
      curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER,  FALSE );
      $contents = curl_exec( $curl );
      curl_close( $curl );
      $image = @imagecreatefromstring( $contents );
    }
    return $image;
  }

  /**
   * Tries various ways to open an image and read its contents.
   * @param string $url References a local image with relative or full path, or a remote image.
   * @param function $loadFunction
   * @return resource image upon success, false on failure.
   */
  private static function _LoadImage( $url, $loadFunction, $maxImageLoadTime )
  {
    $originalDefaultSocketTimeoutValue = false;

    /* Always first try the url as given.
     * If that fails, try to convert the url to a local absolute path.
     * If that can not be done, or loading fails again, try to convert to a full url.
     * Try loading both using imagecreatefrom.+, and using curl combined with imagecreatefromstring.
     * If that all fails, give up.
     */

    $fullPath = self::_DeterminePathFromUrl( $url );
    $fullUrl = self::_DetermineUrlFromPath( $url );

    $tries = array();
    $tries[] = 'curl';
    $tries[] = 'normal';
    if ( $fullPath )
    {
      $tries[] = 'fullPath';
    }
    /* Always include at least one way to fetch an image based on the full url. I had a report from a user who
     * apparently couldn't load images referenced locally, but could load images referenced via a url (???).
     */
    if ( $fullUrl and ini_get( 'allow_url_fopen' ) )
    {
      $tries[] = 'fullUrl';
      if ( $maxImageLoadTime > 0 )
      {
        $originalDefaultSocketTimeoutValue = ini_set( 'default_socket_timeout', $maxImageLoadTime );
      }
    }
    $tries[] = 'curl';

    $image = false;
    foreach ( $tries as $try )
    {
      if ( !/*NOT*/$image )
      {
        switch ( $try )
        {
          case 'normal':
            $image = @call_user_func( $loadFunction, $url );
            break;

          case 'fullPath':
            $image = @call_user_func( $loadFunction, $fullPath );
            break;

          case 'fullUrl':
            $image = @call_user_func( $loadFunction, $fullUrl );
            break;

          case 'curl':
            if ( $fullUrl )
            {
              $image = self::_LoadImageUsingCurl( $fullUrl, $maxImageLoadTime );
            }
            else
            {
              $image = self::_LoadImageUsingCurl( $url, $maxImageLoadTime );
            }
            break;

          default:
            /* May never come here. */
            break;
        }
      }
    }

    if ( $originalDefaultSocketTimeoutValue /* is FALSE when ini_set failed or was not executed */ )
    {
      ini_set( 'default_socket_timeout', $originalDefaultSocketTimeoutValue );
    }

    return $image;
  }

  /**
   * Determines the correct url to the corresponding thumbnail.
   * If the thumbnail does not exist, it is created
   * @param string $url The path to the image
   * @param dict $minimum Associative array, with keys 'width', 'height', 'ratio',
   *   and values 0 or positive numbers, expressed in pixels.
   *   Looked at both to find a previously created thumbnail; and when the
   *   thumbnail does not exist yet and has to be created.
   * @param dict $crop Associative array, with keys 'horizontal_position',
   *   'vertical_position' and values 'left', 'right', 'center' or 'no'.
   * @param int $thumbWidth A number. May be zero or negative. When positive, it
   *   indicates the maximum width of the resized thumbnail. Else, there is no
   *   restriction on image width, and will be chosen in function of the
   *   height.
   *   OUT: If the path to the thumbnail is returned, this variable will contain
   *   the extact thumbnail width, in pixels.
   * @param int $thumbHeight A number. May be zero or negative. When positive, it
   *   indicates the maximum height of the resized thumbnail. Else, there is
   *   no restriction on image height, and will be chosen in function of the
   *   width.
   *   OUT: If the path to the thumbnail is returned, this variable will contain
   *   the extact thumbnail height, in pixels.
   * @param int $lifetime The lifetime of the thumbnail in seconds to set when it
   *   is created by calling this function. Not used to check if the existing
   *   thumbnail is still valid. Default: 4 weeks (2419200 seconds).
   * @return false if an error occurred or if the given $url is incorrect. The
   *   path to the thumbnail otherwise.
   */
  public static function GetThumbnail( $url, $minimum, $crop, & $thumbWidth, & $thumbHeight, $lifetime = 2419200, $maxImageLoadTime = 60 )
  {
    $type = self::_DetermineImageType( $url );
    if ( $type and array_key_exists( $type, ReadLessTextThumb::$_image ) )
    {
      $ext = ReadLessTextThumb::$_image[ $type ][ 'ext' ];
      $thumbnailPath = self::_DetermineThumbnailPath( $url, $minimum, $crop, $thumbWidth, $thumbHeight, $ext );
      $thumbnailUrl = self::_DetermineUrlFromPath( $thumbnailPath );

      if ( @file_exists( $thumbnailPath ) )
      {
        /* Thumbnail already exists.
         * The image resource $url has been examined during a previous execution;
         * and according to the settings, it is fit to serve as a thumbnail.
         */
        $sizeArray = @getimagesize( $thumbnailPath );
        $thumbWidth = $sizeArray[ 0 ];
        $thumbHeight = $sizeArray[ 1 ];
      }
      else
      {
        $image = self::_LoadImage( $url, ReadLessTextThumb::$_image[ $type ][ 'load' ], $maxImageLoadTime );
        $width = -1;
        $height = -1;
        if ( $image )
        {
          $width = max( 1, @imagesx( $image ) ); /* Ensure a division is possible. */
          $height = max( 1, @imagesy( $image ) ); /* Ensure a division is possible. */
        }
        $ratio = min( $width / $height, $height / $width );
        if ( !/*NOT*/$image
                or ( $width < $minimum[ 'width' ] )
                or ( $height < $minimum[ 'height' ] )
                or ( $ratio < $minimum[ 'ratio' ] ) )
        {
          /* Thumbnail may not be created.
           * According to the settings, it is not fit to serve as a thumbnail.
          */
          $thumbnailUrl = false;
        }
        else
        {
          $resizeFactor = self::_DetermineResizeFactor( $width, $height, $thumbWidth, $thumbHeight, $crop );
          /* $thumbWidth and $thumbHeight is now updated as well. */

          /* The image width to use is equal to r * tx
           * The image height to use is equal to r * ty
           */

          $start = self::_DetermineCroppedRectangle( $width, $height, $thumbWidth, $thumbHeight, $crop, $resizeFactor );
          $horizontalStart = $start[0];
          $verticalStart = $start[1];
          $usedWidth = $start[2];
          $usedHeight = $start[3];
          /* $thumbWidth and $thumbHeight is now updated as well. */

          /* Create thumbnail */
          $thumbnail = call_user_func( ReadLessTextThumb::$_image[ $type ][ 'create' ], $thumbWidth, $thumbHeight );
          if ( $type == 1 /* IMAGETYPE_GIF */ )
          {
            /* Make the thumbnail initially transparent if the original was transparent too.
             * Otherwise, fill it initially up with all white.
             */
            $transparentColorIdentifier = @imagecolortransparent( $image );
            if ( $transparentColorIdentifier >= 0 )
            {
              $colors = @imagecolorsforindex( $image, $transparentColorIdentifier );
              $transcolorindex = @imagecolorallocate( $thumbnail, $colors[ 'red' ], $colors[ 'green' ], $colors[ 'blue' ] );
              @imagefill( $thumbnail, 0, 0, $transcolorindex );
              @imagecolortransparent( $thumbnail, $transcolorindex ); /* Needed? */
            }
            else
            {
              $whiteColorIdentifier = @imagecolorallocate( $thumbnail, 255, 255, 255 );
              @imagefill( $thumbnail, 0, 0, $whitecolorindex);
            }
          }

          if ( ReadLessTextThumb::$_image[ $type ][ 'create_alpha' ] )
          {
            call_user_func( ReadLessTextThumb::$_image[ $type ][ 'create_alpha' ], $thumbnail, false );
          }
          call_user_func( ReadLessTextThumb::$_image[ $type ][ 'copy' ], $thumbnail, $image,
              0, 0, $horizontalStart, $verticalStart,
              $thumbWidth, $thumbHeight, $usedWidth, $usedHeight );
          if ( ReadLessTextThumb::$_image[ $type ][ 'save_alpha' ] )
          {
            call_user_func( ReadLessTextThumb::$_image[ $type ][ 'save_alpha' ], $thumbnail, true );
          }
          call_user_func( ReadLessTextThumb::$_image[ $type ][ 'save' ], $thumbnail, $thumbnailPath );

          /* The expiration information is not used directly, but it is still
           * added to allow Joomla's core garbage collection functionality to work.
           */
          $expirePath = $thumbnailPath . '_expire';
          @file_put_contents( $expirePath, ( time() + $lifetime) );
        }
      }
    }
    else
    {
      /* To me, the remaining image types are esoteric. Some of them I never
       * even heard of.
       * OR
       * Determining the image type failed.
       */
      $thumbnailUrl = false;
    }

    return $thumbnailUrl;
  }

  private static $_extToType = array(
      'gif' => 1 /* IMAGETYPE_GIF */,
      'jpg' => 2 /* IMAGETYPE_JPEG */,
      'jpeg' => 2 /* IMAGETYPE_JPEG */,
      'png' => 3 /* IMAGETYPE_PNG */,
      'bmp' => 6 /* IMAGETYPE_PNG */
  );

  private static $_image = array(
      1 /* IMAGETYPE_GIF */ => array(
          'ext' => '.gif',
          'load' => 'imagecreatefromgif',
          'create' => 'imagecreate',
          'create_alpha' => '',
          'copy' => 'imagecopyresampled',
          'save_alpha' => '',
          'save' => 'imagegif'
      ),
      2 /* IMAGETYPE_JPEG */ => array(
          'ext' => '.jpg',
          'load' => 'imagecreatefromjpeg',
          'create' => 'imagecreatetruecolor',
          'create_alpha' => '',
          'copy' => 'imagecopyresampled',
          'save_alpha' => '',
          'save' => 'imagejpeg'
      ),
      3 /* IMAGETYPE_PNG */ => array(
          'ext' => '.png',
          'load' => 'imagecreatefrompng',
          'create' => 'imagecreatetruecolor',
          'create_alpha' => 'imagealphablending',
          'copy' => 'imagecopyresampled',
          'save_alpha' => 'imagesavealpha',
          'save' => 'imagepng'
      ),
      6000 /* IMAGETYPE_BMP */ => array(
          'ext' => '.bmp',
          'load' => 'imagecreatefromwbmp',
          'create' => 'imagecreate',
          'create_alpha' => '',
          'copy' => 'imagecopyresampled',
          'save_alpha' => '',
          'save' => 'imagewbmp'
      ) );
}
?>