&1'; $cmd = sprintf( $cmd, $device, $resolution, ( $frame + 1 ), escapeshellarg( $output_file ), escapeshellarg( $file ) ); exec( $cmd, $stdout, $return ); if ( 0 != $return ) { self::_mla_debug_add( "ERROR: _ghostscript_convert exec returned '{$return}, cmd = " . var_export( $cmd, true ) ); self::_mla_debug_add( "ERROR: _ghostscript_convert exec returned '{$return}, details = " . var_export( $stdout, true ) ); return false; } try { self::$image->readImage( $output_file ); } catch ( Exception $e ) { self::_mla_debug_add( "ERROR: _ghostscript_convert readImage Exception = " . var_export( $e->getMessage(), true ) ); return false; } @unlink( $output_file ); return true; } // found Ghostscript self::_mla_debug_add( 'MLAImageProcessor::_ghostscript_convert Ghostscript detection failure' ); return false; } // _ghostscript_convert /** * Prepare the image for output, scaling and flattening as required * * @since 2.10 * @uses self::$image updates the image in this Imagick object * * @param integer zero or new width * @param integer zero or new height * @param boolean proportional fit (true) or exact fit (false) * @param string output MIME type * @param integer compression quality; 1 - 100 * * @return void */ private static function _prepare_image( $width, $height, $best_fit, $type, $quality ) { //error_log( __LINE__ . " MLAImageProcessor::_prepare_image( {$width}, {$height}, {$best_fit}, {$type}, {$quality} )", 0 ); if ( 'WordPress' == $type ) { $default_width = 0; $type = 'image/jpeg'; } else { $default_width = 150; } if ( is_callable( array( self::$image, 'scaleImage' ) ) ) { if ( 0 < $width && 0 < $height ) { // Both are set; use them as-is self::$image->scaleImage( $width, $height, $best_fit ); } elseif ( 0 < $width || 0 < $height ) { // One is set; scale the other one proportionally if reducing $image_size = self::$image->getImageGeometry(); if ( $width && isset( $image_size['width'] ) && $width < $image_size['width'] ) { self::$image->scaleImage( $width, 0 ); } elseif ( $height && isset( $image_size['height'] ) && $height < $image_size['height'] ) { self::$image->scaleImage( 0, $height ); } } else { // Neither is specified, apply defaults; ( 0, 0 ) is invalid. if ( $default_width ) { self::$image->scaleImage( $default_width, 0 ); } } } if ( 0 < $quality && 101 > $quality ) { if ( 'image/jpeg' == $type ) { self::$image->setImageCompressionQuality( $quality ); self::$image->setImageCompression( imagick::COMPRESSION_JPEG ); } else { self::$image->setImageCompressionQuality( $quality ); } } if ( 'image/jpeg' == $type ) { if ( is_callable( array( self::$image, 'setImageBackgroundColor' ) ) ) { self::$image->setImageBackgroundColor('white'); } if ( is_callable( array( self::$image, 'mergeImageLayers' ) ) ) { self::$image = self::$image->mergeImageLayers( imagick::LAYERMETHOD_FLATTEN ); } elseif ( is_callable( array( self::$image, 'flattenImages' ) ) ) { self::$image = self::$image->flattenImages(); } } } // _prepare_image /** * Log debug information * * @since 2.12 * * @param string $message Error message. */ private static function _mla_debug_add( $message ) { if ( self::$mla_debug ) { if ( class_exists( 'MLACore' ) ) { MLACore::mla_debug_add( $message ); } else { error_log( $message, 0); } } } /** * Abort the operation and exit * * @since 2.10 * * @param string $message Error message. * @param string $title Optional. Error title. Default empty. * @param integer $response Optional. HTML response code. Default 500. * @return void echos page content and calls exit(); */ private static function _mla_die( $message, $title = '', $response = 500 ) { self::_mla_debug_add( __LINE__ . " _mla_die( '{$message}', '{$title}', '{$response}' )" ); exit(); } /** * Log the message and return error message array * * @since 2.10 * * @param string $message Error message. * @param string $line Optional. Line number in the caller. * * @return array( 'error' => message ) */ private static function _mla_error_return( $message, $line = '' ) { self::_mla_debug_add( $line . " MLAImageProcessor::_mla_error_return '{$message}'" ); return array( 'error' => $message ); } /** * Process Imagick thumbnail conversion request, e.g., for a PDF thumbnail * * Replaces download_url() in the Codex "Function Reference/wp handle sideload" example. * * @since 2.13 * * @param string $input_file Path and name of the source file relative to upload directory * @param array $args Generation parameters * * @return array file attributes ( 'file', 'url', 'type' ) on success, ( 'error' ) on failure */ public static function mla_handle_thumbnail_sideload( $input_file, $args ) { MLACore::mla_debug_add( __LINE__ . " MLAImageProcessor::mla_handle_thumbnail_sideload( {$input_file} ) args = " . var_export( $args, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL ); if ( ! class_exists( 'Imagick' ) ) { return self::_mla_error_return( 'Imagick not installed', __LINE__ ); } if( ini_get( 'zlib.output_compression' ) ) { ini_set( 'zlib.output_compression', 'Off' ); } if ( ! is_file( $input_file ) ) { return self::_mla_error_return( 'File not found: ' . $input_file, __LINE__ ); } // Process generation parameters and supply defaults $width = isset( $args['width'] ) ? abs( intval( $args['width'] ) ) : 0; $height = isset( $args['height'] ) ? abs( intval( $args['height'] ) ) : 0; $type = isset( $args['type'] ) ? $args['type'] : 'image/jpeg'; $quality = isset( $args['quality'] ) ? abs( intval( $args['quality'] ) ) : 0; $frame = isset( $args['frame'] ) ? abs( intval( $args['frame'] ) ) : 0; $resolution = isset( $args['resolution'] ) ? abs( intval( $args['resolution'] ) ) : 72; $best_fit = isset( $args['best_fit'] ) ? (boolean) $args['best_fit'] : false; $ghostscript_path = isset( $args['ghostscript_path'] ) ? $args['ghostscript_path'] : ''; // Retain WordPress type for _prepare_image and adjust defaults if ( 'WordPress' === $type ) { $mime_type = 'image/jpeg'; $resolution = isset( $args['resolution'] ) ? abs( intval( $args['resolution'] ) ) : 128; } else { $mime_type = $type; } // Convert the file to an image format and load it try { $try_step = __LINE__ . ' new Imagick()'; self::$image = new Imagick(); /* * this must be called before reading the image, otherwise has no effect - * "-density {$x_resolution}x{$y_resolution}" * this is important to give good quality output, otherwise text might be unclear * default resolution is 72,72 or 128,128 for WordPress thumbnails */ $try_step = __LINE__ . ' setResolution'; self::$image->setResolution( $resolution, $resolution ); $try_step = __LINE__ . ' _ghostscript_convert'; $result = self::_ghostscript_convert( $input_file, $frame, $resolution, $mime_type, $ghostscript_path ); if ( false === $result ) { try { $try_step = __LINE__ . " readImage [{$frame}]"; self::$image->readImage( $input_file . '[' . $frame . ']' ); } catch ( Exception $e ) { $try_step = __LINE__ . ' readImage [0]'; self::$image->readImage( $input_file . '[0]' ); } if ( 'image/jpeg' == $mime_type ) { $extension = 'JPG'; } else { $extension = 'PNG'; } $try_step = __LINE__ . " setImageFormat( {$extension} )"; self::$image->setImageFormat( $extension ); } if ( ! self::$image->valid() ) { self::_mla_die( 'File not loaded', __LINE__, 404 ); } } catch ( Throwable $e ) { // PHP 7 return self::_mla_error_return( 'Image load Throwable: ' . $e->getMessage() . ' from step ' . $try_step, __LINE__ ); } catch ( Exception $e ) { // PHP 5 return self::_mla_error_return( 'Image load Exception: ' . $e->getMessage() . ' from step ' . $try_step, __LINE__ ); } /* * Prepare the output image; resize and flatten, if necessary. * $type retains "WordPress" selection */ try { self::_prepare_image( $width, $height, $best_fit, $type, $quality ); } catch ( Throwable $e ) { // PHP 7 return self::_mla_error_return( '_prepare_image Throwable: ' . $e->getMessage(), __LINE__ ); } catch ( Exception $e ) { // PHP 5 return self::_mla_error_return( '_prepare_image Exception: ' . $e->getMessage(), __LINE__ ); } // Write the image to an appropriately-named file try { $output_file = wp_tempnam( $input_file ); self::$image->writeImage( $output_file ); $dimensions = self::$image->getImageGeometry(); } catch ( Throwable $e ) { // PHP 7 return self::_mla_error_return( 'Image write Throwable: ' . $e->getMessage(), __LINE__ ); } catch ( Exception $e ) { // PHP 5 @unlink( $output_file ); return self::_mla_error_return( 'Image write Exception: ' . $e->getMessage(), __LINE__ ); } // array based on $_FILE as seen in PHP file uploads $results = array( 'name' => basename( $input_file ), 'type' => $mime_type, 'tmp_name' => $output_file, 'error' => 0, 'size' => filesize( $output_file ), 'width' => $dimensions['width'], 'height' => $dimensions['height'], ); MLACore::mla_debug_add( __LINE__ . " MLAImageProcessor::mla_handle_thumbnail_sideload( {$input_file} ) results = " . var_export( $results, true ), MLACore::MLA_DEBUG_CATEGORY_THUMBNAIL ); return $results; } /** * Process Imagick image stream request, e.g., for a PDF thumbnail * * Requires mla_stream_file (relative to wp_upload_dir ) in $_REQUEST; * optional $_REQUEST parameters are: * mla_stream_width, mla_stream_height, mla_stream_frame, mla_stream_resolution, * mla_stream_quality, mla_stream_type, mla_stream_fit, mla_ghostscript_path * * @since 2.10 * * @return void echos image content and calls exit(); */ public static function mla_process_stream_image() { self::_mla_debug_add( 'MLAImageProcessor::mla_process_stream_image REQUEST = ' . var_export( $_REQUEST, true ) ); if ( ! class_exists( 'Imagick' ) ) { self::_mla_die( 'Imagick not installed', __LINE__, 500 ); } if( ini_get( 'zlib.output_compression' ) ) { ini_set( 'zlib.output_compression', 'Off' ); } $file = $_REQUEST['mla_stream_file']; if ( ! is_file( $file ) ) { self::_mla_die( 'File not found', __LINE__, 404 ); } $use_mutex = isset( $_REQUEST['mla_single_thread'] ); $width = isset( $_REQUEST['mla_stream_width'] ) ? abs( intval( $_REQUEST['mla_stream_width'] ) ) : 0; $height = isset( $_REQUEST['mla_stream_height'] ) ? abs( intval( $_REQUEST['mla_stream_height'] ) ) : 0; $type = isset( $_REQUEST['mla_stream_type'] ) ? $_REQUEST['mla_stream_type'] : 'image/jpeg'; $quality = isset( $_REQUEST['mla_stream_quality'] ) ? abs( intval( $_REQUEST['mla_stream_quality'] ) ) : 0; $frame = isset( $_REQUEST['mla_stream_frame'] ) ? abs( intval( $_REQUEST['mla_stream_frame'] ) ) : 0; $resolution = isset( $_REQUEST['mla_stream_resolution'] ) ? abs( intval( $_REQUEST['mla_stream_resolution'] ) ) : 72; /* * If mla_ghostscript_path is present, a non-standard GS location can be found in a file written by * the [mla_gallery] shortcode processor. */ $ghostscript_path = isset( $_REQUEST['mla_ghostscript_path'] ) ? $_REQUEST['mla_ghostscript_path'] : ''; if ( ! empty( $ghostscript_path ) ) { $ghostscript_path = @file_get_contents( dirname( __FILE__ ) . '/' . 'mla-ghostscript-path.txt' ); } if ( $use_mutex ) { $temp_file = self::_get_temp_file(); @unlink( $temp_file ); $temp_file = pathinfo( $temp_file, PATHINFO_DIRNAME ) . '/mla-mutex.txt'; $mutex = new MLAMutex(); $mutex->init( 1, $temp_file ); $mutex->acquire(); self::_mla_debug_add( 'MLAImageProcessor::mla_process_stream_image begin file = ' . var_export( $file, true ) ); } /* * Convert the file to an image format and load it */ try { self::$image = new Imagick(); /* * this must be called before reading the image, otherwise has no effect - * "-density {$x_resolution}x{$y_resolution}" * this is important to give good quality output, otherwise text might be unclear * default resolution is 72,72 */ self::$image->setResolution( $resolution, $resolution ); //$result = false; $result = self::_ghostscript_convert( $file, $frame, $resolution, $type, $ghostscript_path ); if ( false === $result ) { try { self::$image->readImage( $file . '[' . $frame . ']' ); } catch ( Exception $e ) { self::$image->readImage( $file . '[0]' ); } if ( 'image/jpeg' == $type ) { $extension = 'JPG'; } else { $extension = 'PNG'; } self::$image->setImageFormat( $extension ); } if ( ! self::$image->valid() ) { self::_mla_die( 'File not loaded', __LINE__, 404 ); } } catch ( Exception $e ) { self::_mla_die( 'Image load exception: ' . $e->getMessage(), __LINE__, 404 ); } /* * Prepare the output image; resize and flatten, if necessary */ try { if ( isset( $_REQUEST['mla_stream_fit'] ) ) { $best_fit = ( '1' == $_REQUEST['mla_stream_fit'] ); } else { $best_fit = false; } self::_prepare_image( $width, $height, $best_fit, $type, $quality ); } catch ( Exception $e ) { self::_mla_die( '_prepare_image exception: ' . $e->getMessage(), __LINE__, 500 ); } /* * Stream the image back to the requestor */ try { header( "Content-Type: $type" ); echo self::$image->getImageBlob(); } catch ( Exception $e ) { self::_mla_die( 'Image stream exception: ' . $e->getMessage(), __LINE__, 500 ); } if ( $use_mutex ) { $mutex->release(); } exit(); } // mla_process_stream_image } // Class MLAImageProcessor /** * Class MLA (Media Library Assistant) Mutex provides a simple "mutual exclusion" semaphore * for the [mla_gallery] mla_viewer=single option * * Adapted from the example by mr.smaon@gmail.com in the PHP Manual "Semaphore Functions" page. * * @package Media Library Assistant * @since 2.10 */ class MLAMutex { /** * Semaphore identifier returned by sem_get() * * @since 2.10 * * @var resource */ private $sem_id; /** * True if the semaphore has been acquired * * @since 2.10 * * @var boolean */ private $is_acquired = false; /** * True if using a file lock instead of a semaphore * * @since 2.10 * * @var boolean */ private $use_file_lock = false; /** * Name of the (locked) file used as a semaphore * * @since 2.10 * * @var string */ private $filename = ''; /** * File system pointer resource of the (locked) file used as a semaphore * * @since 2.10 * * @var resource */ private $filepointer; /** * Initializes the choice of semaphore Vs file lock * * @since 2.10 * * @param boolean $use_lock True to force use of file locking * * @return void */ function __construct( $use_lock = false ) { /* * If sem_ functions are not available require file locking */ if ( ! is_callable( 'sem_get' ) ) { $use_lock = true; } if ( $use_lock || 'WIN' == substr( PHP_OS, 0, 3 ) ) { $this->use_file_lock = true; } } /** * Creates the semaphore or sets the (lock) file name * * @since 2.10 * * @param integer $id Key to identify the semaphore * @param string $filename Absolute path and name of the file for locking * * @return boolean True if the initialization succeeded */ public function init( $id, $filename = '' ) { if( $this->use_file_lock ) { if( empty( $filename ) ) { return false; } else { $this->filename = $filename; } } else { if( ! ( $this->sem_id = sem_get( $id, 1) ) ) { return false; } } return true; } /** * Acquires the semaphore or opens and locks the file * * @since 2.10 * * @return boolean True if the acquisition succeeded */ public function acquire() { if( $this->use_file_lock ) { if ( empty( $this->filename ) ) { return true; } if( false == ( $this->filepointer = @fopen( $this->filename, "w+" ) ) ) { return false; } if( false == flock( $this->filepointer, LOCK_EX ) ) { return false; } } else { if ( ! sem_acquire( $this->sem_id ) ) { return false; } } $this->is_acquired = true; return true; } /** * Releases the semaphore or unlocks and closes (but does not unlink) the file * * @since 2.10 * * @return boolean True if the release succeeded */ public function release() { if( ! $this->is_acquired ) { return true; } if( $this->use_file_lock ) { if( false == flock( $this->filepointer, LOCK_UN ) ) { return false; } fclose( $this->filepointer ); } else { if ( ! sem_release( $this->sem_id ) ) { return false; } } $this->is_acquired = false; return true; } /** * Returns the semaphore identifier, if it exists, else NULL * * @since 2.10 * * @return resource Semaphore identifier or NULL */ public function getId() { return $this->sem_id; } } // MLAMutex ?>