-- REQUIREMENTS --
+* PHP 5.6 or higher
+
+* Drupal 8.3.0 or higher
+
* Either ImageMagick (http://www.imagemagick.org) or GraphicsMagick
- (http://www.graphicsmagick.org) need to be installed on your server
- and the convert binary needs to be accessible and executable from PHP.
+ (http://www.graphicsmagick.org) need to be installed on your server and the
+ convert binary needs to be accessible and executable from PHP.
* The PHP configuration must allow invocation of proc_open() (which is
security-wise identical to exec()).
+* File Metadata Manager module 8.x-1.1 or higher
+
+* Composer based installation process is needed to install the module
+ dependencies, see https://www.drupal.org/node/2718229
+
Consult your server administrator or hosting provider if you are unsure about
these requirements.
-- INSTALLATION --
-* Install as usual, see https://drupal.org/node/70151 for further information.
+* Install the required module packages with Composer. From the Drupal
+ installation root directory, type
+
+ $ composer require drupal/imagemagick:^2.1
+
+ This will download both the ImageMagick module and any dependent module
+ (namely, the File Metadata Manager module).
+
+* Enable the module. Navigate to Manage > Extend. Check the box next to the
+ ImageMagick module and then click the 'Install' button at the bottom. If
+ File Metadata Manager is not already installed, the system will prompt you
+ to confirm installing it too. Just confirm and proceed.
-- CONFIGURATION --
--- /dev/null
+{
+ "name": "drupal/imagemagick",
+ "type": "drupal-module",
+ "description": "Provides an image toolkit to integrate ImageMagick with the Image API.",
+ "require": {
+ "drupal/core": "^8.3",
+ "drupal/file_mdm": "^1.1",
+ "drupal/file_mdm_exif": "^1.1"
+ }
+}
--- /dev/null
+configuration:
+ cache:
+ override: FALSE
+ settings:
+ enabled: TRUE
+ expiration: 172800
+ disallowed_paths: { }
binaries: 'imagemagick'
path_to_binaries: ''
prepend: ''
+prepend_pre_source: FALSE
log_warnings: TRUE
debug: FALSE
use_identify: TRUE
prepend:
type: string
label: 'Prepend arguments'
+ prepend_pre_source:
+ type: boolean
+ label: 'Execute the arguments before loading the source image'
log_warnings:
type: boolean
label: 'Log command executions returning with non-zero code'
profile:
type: string
label: 'Color profile path'
+
+
+imagemagick.file_metadata_plugin.imagemagick_identify:
+ type: config_object
+ label: 'imagemagick_identify file metadata plugin settings'
+ mapping:
+ configuration:
+ type: file_mdm.plugin.configuration
+ label: 'imagemagick_identify plugin settings'
* Modules can also decide to move files from remote systems to the local
* file system to allow processing.
*
- * @param \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit
- * The Imagemagick toolkit instance to alter.
+ * @param \Drupal\imagemagick\ImagemagickExecArguments $arguments
+ * The ImageMagick/GraphicsMagick execution arguments object.
*
* @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::parseFile()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getSource()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::setSourceLocalPath()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getSourceLocalPath()
+ * @see \Drupal\imagemagick\ImagemagickExecArguments::getSource()
+ * @see \Drupal\imagemagick\ImagemagickExecArguments::setSourceLocalPath()
+ * @see \Drupal\imagemagick\ImagemagickExecArguments::getSourceLocalPath()
*/
-function hook_imagemagick_pre_parse_file_alter(\Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit) {
+function hook_imagemagick_pre_parse_file_alter(\Drupal\imagemagick\ImagemagickExecArguments $arguments) {
}
/**
* to move temporary files from the local file system to remote destination
* systems.
*
- * @param \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit
- * The Imagemagick toolkit instance to alter.
+ * @param \Drupal\imagemagick\ImagemagickExecArguments $arguments
+ * The ImageMagick/GraphicsMagick execution arguments object.
*
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getDestination()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getDestinationLocalPath()
* @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::save()
+ * @see \Drupal\imagemagick\ImagemagickExecArguments::getDestination()
+ * @see \Drupal\imagemagick\ImagemagickExecArguments::getDestinationLocalPath()
*/
-function hook_imagemagick_post_save_alter(\Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit) {
+function hook_imagemagick_post_save_alter(\Drupal\imagemagick\ImagemagickExecArguments $arguments) {
}
/**
* The toolkit provides methods to prepend, add, find, get and reset
* arguments that have already been set by image effects.
*
+ * In addition to arguments that are passed to the binaries command line for
+ * execution, it is possible to push arguments to be used only by the toolkit
+ * or the hooks. You can add/get/find such arguments by specifying
+ * ImagemagickExecArguments::INTERNAL as the argument $mode in the methods.
+ *
* ImageMagick automatically converts the target image to the format denoted by
* the file extension. However, since changing the file extension is not always
* an option, you can specify an alternative image format via
- * $toolkit->setDestinationFormat('format'), where 'format' is a string
- * denoting an Imagemagick supported format.
+ * $arguments->setDestinationFormat('format'), where 'format' is a string
+ * denoting an Imagemagick supported format, or via
+ * $arguments->setDestinationFormatFromExtension('extension'), where
+ * 'extension' is a string denoting an image file extension.
+ *
* When the destination format is set, it is passed to ImageMagick's convert
* binary with the syntax "[format]:[destination]".
*
- * @param \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit
- * The Imagemagick toolkit instance to alter.
+ * @param \Drupal\imagemagick\ImagemagickExecArguments $arguments
+ * The ImageMagick/GraphicsMagick execution arguments object.
* @param string $command
- * The Imagemagick binary being called.
+ * The ImageMagick/GraphicsMagick command being called.
*
* @see http://www.imagemagick.org/script/command-line-processing.php#output
* @see http://www.imagemagick.org/Usage/files/#save
*
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getArguments()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::prependArgument()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::addArgument()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::findArgument()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::resetArguments()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getSource()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::setSourceLocalPath()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getSourceLocalPath()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getDestination()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::setDestinationLocalPath()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::getDestinationLocalPath()
+ * @see \Drupal\imagemagick\ImagemagickExecArguments
* @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::convert()
- * @see \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit::identify()
+ * @see \Drupal\imagemagick\Plugin\FileMetadata\ImagemagickIdentify::identify()
*/
-function hook_imagemagick_arguments_alter(\Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit, $command) {
+function hook_imagemagick_arguments_alter(\Drupal\imagemagick\ImagemagickExecArguments $arguments, $command) {
}
package: Media
configure: system.image_toolkit_settings
dependencies:
- - system (>=8.1.0)
+ - file_mdm:file_mdm
+ - file_mdm:file_mdm_exif
+ - drupal:system (>=8.3.0)
-# Information added by Drupal.org packaging script on 2017-03-06
-version: '8.x-1.0-alpha6'
+# Information added by Drupal.org packaging script on 2018-02-05
+version: '8.x-2.3'
core: '8.x'
project: 'imagemagick'
-datestamp: 1488787686
+datestamp: 1517855585
}
/**
- * @addtogroup updates-8.x-1.0-alpha
- * @{
+ * Enable file_mdm module.
*/
-
-/**
- * Adds the 'locale' config setting.
- */
-function imagemagick_update_8001() {
- $config_factory = \Drupal::configFactory();
- $setting = $config_factory->getEditable('imagemagick.settings')
- ->set('locale', 'en_US.UTF-8')
- ->save(TRUE);
+function imagemagick_update_8201() {
+ \Drupal::service('module_installer')->install([
+ 'file_mdm',
+ ]);
}
/**
- * Adds the 'log_warnings' config setting.
+ * Adds the 'prepend_pre_source' config setting.
*/
-function imagemagick_update_8002() {
+function imagemagick_update_8202() {
$config_factory = \Drupal::configFactory();
$setting = $config_factory->getEditable('imagemagick.settings')
- ->set('log_warnings', TRUE)
+ ->set('prepend_pre_source', FALSE)
->save(TRUE);
}
/**
- * @} End of "addtogroup updates-8.x-1.0-alpha".
+ * Clear caches to discover service changes.
*/
+function imagemagick_update_8203() {
+ // Empty function.
+}
* Provides ImageMagick integration.
*/
-use Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit;
+use Drupal\imagemagick\ImagemagickExecArguments;
/**
* Implements hook_imagemagick_pre_parse_file_alter().
*/
-function imagemagick_imagemagick_pre_parse_file_alter(ImagemagickToolkit $toolkit) {
+function imagemagick_imagemagick_pre_parse_file_alter(ImagemagickExecArguments $arguments) {
// Convert source image URI to filepath.
- $local_path = $toolkit->getSourceLocalPath();
+ $local_path = $arguments->getSourceLocalPath();
if (empty($local_path)) {
- $source = $toolkit->getSource();
+ $source = $arguments->getSource();
if (!file_valid_uri($source)) {
// The value of $source is likely a file path already.
- $toolkit->setSourceLocalPath($source);
+ $arguments->setSourceLocalPath($source);
}
else {
// If we can resolve the realpath of the file, then the file is local and
$file_system = \Drupal::service('file_system');
$path = $file_system->realpath($source);
if ($path) {
- $toolkit->setSourceLocalPath($path);
+ $arguments->setSourceLocalPath($path);
}
else {
// We are working with a remote file, copy the remote source file to a
$temp_path = $file_system->tempnam('temporary://', 'imagemagick_');
$file_system->unlink($temp_path);
$temp_path .= '.' . pathinfo($source, PATHINFO_EXTENSION);
- $path = file_unmanaged_copy($toolkit->getSource(), $temp_path, FILE_EXISTS_ERROR);
- $toolkit->setSourceLocalPath($file_system->realpath($path));
+ $path = file_unmanaged_copy($arguments->getSource(), $temp_path, FILE_EXISTS_ERROR);
+ $arguments->setSourceLocalPath($file_system->realpath($path));
}
}
}
/**
* Implements hook_imagemagick_arguments_alter().
*/
-function imagemagick_imagemagick_arguments_alter(ImagemagickToolkit $toolkit, $command) {
+function imagemagick_imagemagick_arguments_alter(ImagemagickExecArguments $arguments, $command) {
$config = \Drupal::config('imagemagick.settings');
// Add prepended arguments if needed.
if ($prepend = $config->get('prepend')) {
- $toolkit->prependArgument($prepend);
+ $arguments->add($prepend, $config->get('prepend_pre_source') ? ImagemagickExecArguments::PRE_SOURCE : ImagemagickExecArguments::POST_SOURCE, 0);
}
if ($command == 'convert') {
// Convert destination image URI to filepath.
- $local_path = $toolkit->getDestinationLocalPath();
+ $local_path = $arguments->getDestinationLocalPath();
if (empty($local_path)) {
- $destination = $toolkit->getDestination();
+ $destination = $arguments->getDestination();
if (!file_valid_uri($destination)) {
// The value of $destination is likely a file path already.
- $toolkit->setDestinationLocalPath($destination);
+ $arguments->setDestinationLocalPath($destination);
}
else {
// If we can resolve the realpath of the file, then the file is local
$file_system = \Drupal::service('file_system');
$path = $file_system->realpath($destination);
if ($path) {
- $toolkit->setDestinationLocalPath($path);
+ $arguments->setDestinationLocalPath($path);
}
else {
// We are working with a remote file, set the local destination to
$temp_path = $file_system->tempnam('temporary://', 'imagemagick_');
$file_system->unlink($temp_path);
$temp_path .= '.' . pathinfo($destination, PATHINFO_EXTENSION);
- $toolkit->setDestinationLocalPath($file_system->realpath($temp_path));
+ $arguments->setDestinationLocalPath($file_system->realpath($temp_path));
}
}
}
- // Change image density.
- if ($toolkit->findArgument('-density') === FALSE && $density = (int) $config->get('advanced.density')) {
- $toolkit->addArgument("-density {$density} -units PixelsPerInch");
+ // Change output image resolution to 72 ppi, if specified in settings.
+ if (empty($arguments->find('/^\-density/')) && $density = (int) $config->get('advanced.density')) {
+ $arguments->add("-density {$density} -units PixelsPerInch");
}
// Apply color profile.
if ($profile = $config->get('advanced.profile')) {
if (file_exists($profile)) {
- $toolkit->addArgument('-profile ' . $toolkit->escapeShellArg($profile));
+ $arguments->add('-profile ' . $arguments->escape($profile));
}
}
// Or alternatively apply colorspace.
elseif ($colorspace = $config->get('advanced.colorspace')) {
// Do not hi-jack settings made by effects.
- if ($toolkit->findArgument('-colorspace') === FALSE) {
- $toolkit->addArgument('-colorspace ' . $toolkit->escapeShellArg($colorspace));
+ if (empty($arguments->find('/^\-colorspace/'))) {
+ $arguments->add('-colorspace ' . $arguments->escape($colorspace));
}
}
// Change image quality.
- if ($toolkit->findArgument('-quality') === FALSE) {
- $toolkit->addArgument('-quality ' . \Drupal::config('imagemagick.settings')->get('quality'));
+ if (empty($arguments->find('/^\-quality/'))) {
+ $arguments->add('-quality ' . \Drupal::config('imagemagick.settings')->get('quality'));
}
}
}
/**
* Implements hook_imagemagick_post_save_alter().
*/
-function imagemagick_imagemagick_post_save_alter(ImagemagickToolkit $toolkit) {
+function imagemagick_imagemagick_post_save_alter(ImagemagickExecArguments $arguments) {
$file_system = \Drupal::service('file_system');
- $destination = $toolkit->getDestination();
+ $destination = $arguments->getDestination();
$path = $file_system->realpath($destination);
if (!$path) {
// We are working with a remote file, so move the temp file to the final
// destination, replacing any existinf file with the same name.
- file_unmanaged_move($toolkit->getDestinationLocalPath(), $toolkit->getDestination(), FILE_EXISTS_REPLACE);
+ file_unmanaged_move($arguments->getDestinationLocalPath(), $arguments->getDestination(), FILE_EXISTS_REPLACE);
}
}
services:
imagemagick.format_mapper:
class: Drupal\imagemagick\ImagemagickFormatMapper
- arguments: ['@cache.default', '@imagemagick.todo2311679', '@config.factory', '@config.typed']
- imagemagick.todo2311679:
- class: Drupal\imagemagick\Todo2311679
- arguments: ['@module_handler']
+ arguments: ['@cache.default', '@imagemagick.mime_type_mapper', '@config.factory', '@config.typed']
+ imagemagick.exec_manager:
+ class: Drupal\imagemagick\ImagemagickExecManager
+ arguments: ['@logger.channel.image', '@config.factory', '@app.root', '@current_user', '@imagemagick.format_mapper', '@module_handler']
+ imagemagick.mime_type_mapper:
+ class: Drupal\imagemagick\ImagemagickMimeTypeMapper
+ arguments: ['@file.mime_type.guesser.extension']
--- /dev/null
+<?php
+
+namespace Drupal\imagemagick;
+
+/**
+ * Stores arguments for execution of ImageMagick/GraphicsMagick commands.
+ */
+class ImagemagickExecArguments {
+
+ /**
+ * An identifier to be used for arguments internal to the toolkit.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Do not prefix arguments
+ * to mark them internal, add them with ImageMagickExecArguments::INTERNAL
+ * instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
+ */
+ const INTERNAL_ARGUMENT_IDENTIFIER = '>!>';
+
+ /**
+ * Default index for adding arguments.
+ */
+ const APPEND = -1;
+
+ /**
+ * Mode for arguments to be placed before the source path.
+ */
+ const PRE_SOURCE = 0;
+
+ /**
+ * Mode for arguments to be placed after the source path.
+ */
+ const POST_SOURCE = 1;
+
+ /**
+ * Mode for arguments not to be placed on the command line.
+ */
+ const INTERNAL = 2;
+
+ /**
+ * The ImageMagick execution manager service.
+ *
+ * @var \Drupal\imagemagick\ImagemagickExecManagerInterface
+ */
+ protected $execManager;
+
+ /**
+ * The array of command line arguments to be used by 'convert'.
+ *
+ * @var string[]
+ */
+ protected $arguments = [];
+
+ /**
+ * Path of the image file.
+ *
+ * @var string
+ */
+ protected $source = '';
+
+ /**
+ * The local filesystem path to the source image file.
+ *
+ * @var string
+ */
+ protected $sourceLocalPath = '';
+
+ /**
+ * The source image format.
+ *
+ * @var string
+ */
+ protected $sourceFormat = '';
+
+ /**
+ * The source image frames to access.
+ *
+ * @var string
+ */
+ protected $sourceFrames;
+
+ /**
+ * The image destination URI/path on saving.
+ *
+ * @var string
+ */
+ protected $destination = NULL;
+
+ /**
+ * The local filesystem path to the image destination.
+ *
+ * @var string
+ */
+ protected $destinationLocalPath = '';
+
+ /**
+ * The image destination format on saving.
+ *
+ * @var string
+ */
+ protected $destinationFormat = '';
+
+ /**
+ * Constructs an ImagemagickExecArguments object.
+ *
+ * @param \Drupal\imagemagick\ImagemagickExecManagerInterface $exec_manager
+ * The ImageMagick execution manager service.
+ */
+ public function __construct(ImagemagickExecManagerInterface $exec_manager) {
+ $this->execManager = $exec_manager;
+ }
+
+ /**
+ * Gets the command line arguments for the binary.
+ *
+ * @return string[]
+ * The array of command line arguments.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments methods to manipulate arguments directly.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
+ */
+ public function getArguments() {
+ @trigger_error('getArguments() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments methods to manipulate arguments directly. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ $ret = [];
+ foreach ($this->arguments as $i => $a) {
+ if (in_array($a['mode'], [self::POST_SOURCE, self::INTERNAL])) {
+ $ret[$i] = ($a['mode'] === self::INTERNAL ? self::INTERNAL_ARGUMENT_IDENTIFIER : '') . $a['argument'];
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Gets the command line arguments string for the binary.
+ *
+ * Removes any argument used internally within the toolkit.
+ *
+ * @return string
+ * The sring of command line arguments.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::toString()
+ * instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
+ */
+ public function getStringForBinary() {
+ @trigger_error('getStringForBinary() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::toString() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ return $this->toString(self::POST_SOURCE);
+ }
+
+ /**
+ * Gets a portion of the command line arguments string.
+ *
+ * @param int $mode
+ * The mode of the string on the command line. Can be self::PRE_SOURCE or
+ * self::POST_SOURCE.
+ *
+ * @return string
+ * The sring of command line arguments.
+ */
+ public function toString($mode) {
+ if (!$this->arguments) {
+ return '';
+ }
+ $ret = [];
+ foreach ($this->arguments as $a) {
+ if ($a['mode'] === $mode) {
+ $ret[] = $a['argument'];
+ }
+ }
+ return implode(' ', $ret);
+ }
+
+ /**
+ * Adds a command line argument.
+ *
+ * @param string $arg
+ * The command line argument to be added.
+ *
+ * @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::add() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
+ */
+ public function addArgument($arg) {
+ @trigger_error('addArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::add() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ if (strpos($arg, self::INTERNAL_ARGUMENT_IDENTIFIER) === 0) {
+ @trigger_error('Adding internal arguments prefixing them with ImagemagickExecArguments::INTERNAL_ARGUMENT_IDENTIFIER is deprecated in 8.x-2.3, will be removed in 8.x-3.0. ::add() them with ImageMagickExecArguments::INTERNAL instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ return $this->add(substr($arg, strlen(self::INTERNAL_ARGUMENT_IDENTIFIER)), self::INTERNAL);
+ }
+ return $this->add($arg);
+ }
+
+ /**
+ * Prepends a command line argument.
+ *
+ * @param string $arg
+ * The command line argument to be prepended.
+ *
+ * @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::add() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
+ */
+ public function prependArgument($arg) {
+ @trigger_error('prependArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::add() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ if (strpos($arg, self::INTERNAL_ARGUMENT_IDENTIFIER) === 0) {
+ @trigger_error('Adding internal arguments prefixing them with ImagemagickExecArguments::INTERNAL_ARGUMENT_IDENTIFIER is deprecated in 8.x-2.3, will be removed in 8.x-3.0. ::add() them with ImageMagickExecArguments::INTERNAL instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ return $this->add(substr($arg, strlen(self::INTERNAL_ARGUMENT_IDENTIFIER)), self::INTERNAL, 0);
+ }
+ return $this->add($arg, self::POST_SOURCE, 0);
+ }
+
+ /**
+ * Adds a command line argument.
+ *
+ * @param string $argument
+ * The command line argument to be added.
+ * @param int $mode
+ * (optional) The mode of the argument in the command line. Determines if
+ * the argument should be placed before or after the source image file path.
+ * Defaults to self::POST_SOURCE.
+ * @param int $index
+ * (optional) The position of the argument in the arguments array.
+ * Reflects the sequence of arguments in the command line. Defaults to
+ * self::APPEND.
+ * @param array $info
+ * (optional) An optional array with information about the argument.
+ * Defaults to an empty array.
+ *
+ * @return $this
+ */
+ public function add($argument, $mode = self::POST_SOURCE, $index = self::APPEND, array $info = []) {
+ $argument = [
+ 'argument' => $argument,
+ 'mode' => $mode,
+ 'info' => $info,
+ ];
+ if ($index === self::APPEND) {
+ $this->arguments[] = $argument;
+ }
+ elseif ($index === 0) {
+ array_unshift($this->arguments, $argument);
+ }
+ else {
+ array_splice($this->arguments, $index, 0, [$argument]);
+ }
+ return $this;
+ }
+
+ /**
+ * Finds if a command line argument exists.
+ *
+ * @param string $arg
+ * The command line argument to be found.
+ *
+ * @return bool
+ * Returns the array key for the argument if it is found in the array,
+ * FALSE otherwise.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::find() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
+ */
+ public function findArgument($arg) {
+ @trigger_error('findArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::find() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ if (strpos($arg, self::INTERNAL_ARGUMENT_IDENTIFIER) === 0) {
+ @trigger_error('Adding internal arguments prefixing them with ImagemagickExecArguments::INTERNAL_ARGUMENT_IDENTIFIER is deprecated in 8.x-2.3, will be removed in 8.x-3.0. ::add() them with ImageMagickExecArguments::INTERNAL instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ foreach ($this->getArguments() as $i => $a) {
+ if (strpos($a, $arg) === 0) {
+ return $i;
+ }
+ }
+ return FALSE;
+ }
+ $matches = $this->find('/^' . preg_quote($arg, '/') . '/', self::POST_SOURCE);
+ if (!empty($matches)) {
+ $keys = array_keys($matches);
+ return $keys[0];
+ }
+ return FALSE;
+ }
+
+ /**
+ * Returns an array of the indexes of arguments matching specific criteria.
+ *
+ * @param string $regex
+ * The regular expression pattern to be matched in the argument.
+ * @param int $mode
+ * (optional) If set, limits the search to the mode of the argument.
+ * Defaults to NULL.
+ * @param array $info
+ * (optional) If set, limits the search to the arguments whose $info array
+ * key/values match the key/values specified. Defaults to an empty array.
+ *
+ * @return array
+ * Returns an array with the matching arguments.
+ */
+ public function find($regex, $mode = NULL, array $info = []) {
+ $ret = [];
+ foreach ($this->arguments as $i => $a) {
+ if ($mode !== NULL && $a['mode'] !== $mode) {
+ continue;
+
+ }
+ if (!empty($info)) {
+ $intersect = array_intersect($info, $a['info']);
+ if ($intersect != $info) {
+ continue;
+
+ }
+ }
+ if (preg_match($regex, $a['argument']) === 1) {
+ $ret[$i] = $a;
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Removes a command line argument.
+ *
+ * @param int $index
+ * The index of the command line argument to be removed.
+ *
+ * @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::remove() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936615
+ */
+ public function removeArgument($index) {
+ @trigger_error('removeArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::remove() instead. See https://www.drupal.org/project/imagemagick/issues/2936615.', E_USER_DEPRECATED);
+ return $this->remove($index);
+ }
+
+ /**
+ * Removes a command line argument.
+ *
+ * @param int $index
+ * The index of the command line argument to be removed.
+ *
+ * @return $this
+ */
+ public function remove($index) {
+ if (isset($this->arguments[$index])) {
+ unset($this->arguments[$index]);
+ }
+ return $this;
+ }
+
+ /**
+ * Resets the command line arguments.
+ *
+ * @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::reset() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936615
+ */
+ public function resetArguments() {
+ @trigger_error('resetArguments() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::reset() instead. See https://www.drupal.org/project/imagemagick/issues/2936615.', E_USER_DEPRECATED);
+ return $this->reset();
+ }
+
+ /**
+ * Resets the command line arguments.
+ *
+ * @return $this
+ */
+ public function reset() {
+ $this->arguments = [];
+ return $this;
+ }
+
+ /**
+ * Returns the count of command line arguments.
+ *
+ * @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::find() instead,
+ * then count the result.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936615
+ */
+ public function countArguments() {
+ @trigger_error('countArguments() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::find() instead, then count the result. See https://www.drupal.org/project/imagemagick/issues/2936615.', E_USER_DEPRECATED);
+ return count($this->arguments);
+ }
+
+ /**
+ * Sets the path of the source image file.
+ *
+ * @param string $source
+ * The source path of the image file.
+ *
+ * @return $this
+ */
+ public function setSource($source) {
+ $this->source = $source;
+ return $this;
+ }
+
+ /**
+ * Gets the path of the source image file.
+ *
+ * @return string
+ * The source path of the image file, or an empty string if the source is
+ * not set.
+ */
+ public function getSource() {
+ return $this->source;
+ }
+
+ /**
+ * Sets the local filesystem path to the image file.
+ *
+ * @param string $path
+ * A filesystem path.
+ *
+ * @return $this
+ */
+ public function setSourceLocalPath($path) {
+ $this->sourceLocalPath = $path;
+ return $this;
+ }
+
+ /**
+ * Gets the local filesystem path to the image file.
+ *
+ * @return string
+ * A filesystem path.
+ */
+ public function getSourceLocalPath() {
+ return $this->sourceLocalPath;
+ }
+
+ /**
+ * Sets the source image format.
+ *
+ * @param string $format
+ * The image format.
+ *
+ * @return $this
+ */
+ public function setSourceFormat($format) {
+ $this->sourceFormat = $this->execManager->getFormatMapper()->isFormatEnabled($format) ? $format : '';
+ return $this;
+ }
+
+ /**
+ * Sets the source image format from an image file extension.
+ *
+ * @param string $extension
+ * The image file extension.
+ *
+ * @return $this
+ */
+ public function setSourceFormatFromExtension($extension) {
+ $this->sourceFormat = $this->execManager->getFormatMapper()->getFormatFromExtension($extension) ?: '';
+ return $this;
+ }
+
+ /**
+ * Gets the source image format.
+ *
+ * @return string
+ * The source image format.
+ */
+ public function getSourceFormat() {
+ return $this->sourceFormat;
+ }
+
+ /**
+ * Sets the source image frames to access.
+ *
+ * @param string $frames
+ * The frames in '[n]' string format.
+ *
+ * @return $this
+ *
+ * @see http://www.imagemagick.org/script/command-line-processing.php
+ */
+ public function setSourceFrames($frames) {
+ $this->sourceFrames = $frames;
+ return $this;
+ }
+
+ /**
+ * Gets the source image frames to access.
+ *
+ * @return string
+ * The frames in '[n]' string format.
+ *
+ * @see http://www.imagemagick.org/script/command-line-processing.php
+ */
+ public function getSourceFrames() {
+ return $this->sourceFrames;
+ }
+
+ /**
+ * Sets the image destination URI/path on saving.
+ *
+ * @param string $destination
+ * The image destination URI/path.
+ *
+ * @return $this
+ */
+ public function setDestination($destination) {
+ $this->destination = $destination;
+ return $this;
+ }
+
+ /**
+ * Gets the image destination URI/path on saving.
+ *
+ * @return string
+ * The image destination URI/path.
+ */
+ public function getDestination() {
+ return $this->destination;
+ }
+
+ /**
+ * Sets the local filesystem path to the destination image file.
+ *
+ * @param string $path
+ * A filesystem path.
+ *
+ * @return $this
+ */
+ public function setDestinationLocalPath($path) {
+ $this->destinationLocalPath = $path;
+ return $this;
+ }
+
+ /**
+ * Gets the local filesystem path to the destination image file.
+ *
+ * @return string
+ * A filesystem path.
+ */
+ public function getDestinationLocalPath() {
+ return $this->destinationLocalPath;
+ }
+
+ /**
+ * Sets the image destination format.
+ *
+ * When set, it is passed to the convert binary in the syntax
+ * "[format]:[destination]", where [format] is a string denoting an
+ * ImageMagick's image format.
+ *
+ * @param string $format
+ * The image destination format.
+ *
+ * @return $this
+ */
+ public function setDestinationFormat($format) {
+ $this->destinationFormat = $format;
+ return $this;
+ }
+
+ /**
+ * Sets the image destination format from an image file extension.
+ *
+ * When set, it is passed to the convert binary in the syntax
+ * "[format]:[destination]", where [format] is a string denoting an
+ * ImageMagick's image format.
+ *
+ * @param string $extension
+ * The destination image file extension.
+ *
+ * @return $this
+ */
+ public function setDestinationFormatFromExtension($extension) {
+ $this->destinationFormat = $this->execManager->getFormatMapper()->getFormatFromExtension($extension) ?: '';
+ return $this;
+ }
+
+ /**
+ * Gets the image destination format.
+ *
+ * When set, it is passed to the convert binary in the syntax
+ * "[format]:[destination]", where [format] is a string denoting an
+ * ImageMagick's image format.
+ *
+ * @return string
+ * The image destination format.
+ */
+ public function getDestinationFormat() {
+ return $this->destinationFormat;
+ }
+
+ /**
+ * Escapes a string.
+ *
+ * @param string $arg
+ * The string to escape.
+ *
+ * @return string
+ * An escaped string for use in the
+ * ImagemagickExecManagerInterface::execute method.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::escape()
+ * instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936680
+ */
+ public function escapeShellArg($arg) {
+ @trigger_error('escapeShellArg() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::escape() instead. See https://www.drupal.org/project/imagemagick/issues/2936680.', E_USER_DEPRECATED);
+ return $this->escape($arg);
+ }
+
+ /**
+ * Escapes a string.
+ *
+ * @param string $argument
+ * The string to escape.
+ *
+ * @return string
+ * An escaped string for use in the
+ * ImagemagickExecManagerInterface::execute method.
+ */
+ public function escape($argument) {
+ return $this->execManager->escapeShellArg($argument);
+ }
+
+}
--- /dev/null
+<?php
+
+namespace Drupal\imagemagick;
+
+use Drupal\Component\Utility\Timer;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * Manage execution of ImageMagick/GraphicsMagick commands.
+ */
+class ImagemagickExecManager implements ImagemagickExecManagerInterface {
+
+ use StringTranslationTrait;
+
+ /**
+ * Whether we are running on Windows OS.
+ *
+ * @var bool
+ */
+ protected $isWindows;
+
+ /**
+ * The app root.
+ *
+ * @var string
+ */
+ protected $appRoot;
+
+ /**
+ * The execution timeout.
+ *
+ * @var int
+ */
+ protected $timeout = 60;
+
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountProxyInterface
+ */
+ protected $currentUser;
+
+ /**
+ * The logger service.
+ *
+ * @var \Psr\Log\LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * The configuration factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * The module handler service.
+ *
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
+ /**
+ * The format mapper service.
+ *
+ * @var \Drupal\imagemagick\ImagemagickFormatMapperInterface
+ */
+ protected $formatMapper;
+
+ /**
+ * Constructs an ImagemagickExecManager object.
+ *
+ * @param \Psr\Log\LoggerInterface $logger
+ * A logger instance.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * The config factory.
+ * @param string $app_root
+ * The app root.
+ * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+ * The current user.
+ * @param \Drupal\imagemagick\ImagemagickFormatMapperInterface $format_mapper
+ * The format mapper service.
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+ * The module handler service.
+ */
+ public function __construct(LoggerInterface $logger, ConfigFactoryInterface $config_factory, $app_root, AccountProxyInterface $current_user, ImagemagickFormatMapperInterface $format_mapper, ModuleHandlerInterface $module_handler) {
+ $this->logger = $logger;
+ $this->configFactory = $config_factory;
+ $this->appRoot = $app_root;
+ $this->currentUser = $current_user;
+ $this->formatMapper = $format_mapper;
+ $this->moduleHandler = $module_handler;
+ $this->isWindows = substr(PHP_OS, 0, 3) === 'WIN';
+ }
+
+ /**
+ * Returns the format mapper.
+ *
+ * @return \Drupal\imagemagick\ImagemagickFormatMapperInterface
+ * The format mapper service.
+ *
+ * @todo in 8.x-3.0, add this method to the interface.
+ */
+ public function getFormatMapper() {
+ return $this->formatMapper;
+ }
+
+ /**
+ * Returns the module handler.
+ *
+ * @return \Drupal\Core\Extension\ModuleHandlerInterface
+ * The module handler service.
+ *
+ * @todo in 8.x-3.0, add this method to the interface.
+ */
+ public function getModuleHandler() {
+ return $this->moduleHandler;
+ }
+
+ /**
+ * Sets the execution timeout (max. runtime).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|null $timeout
+ * The timeout in seconds.
+ *
+ * @return $this
+ *
+ * @todo in 8.x-3.0, add this method to the interface.
+ */
+ public function setTimeout($timeout) {
+ $this->timeout = $timeout;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPackage($package = NULL) {
+ if ($package === NULL) {
+ $package = $this->configFactory->get('imagemagick.settings')->get('binaries');
+ }
+ return $package;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPackageLabel($package = NULL) {
+ switch ($this->getPackage($package)) {
+ case 'imagemagick':
+ return $this->t('ImageMagick');
+
+ case 'graphicsmagick':
+ return $this->t('GraphicsMagick');
+
+ default:
+ return $package;
+
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function checkPath($path, $package = NULL) {
+ $status = [
+ 'output' => '',
+ 'errors' => [],
+ ];
+
+ // Execute gm or convert based on settings.
+ $package = $package ?: $this->getPackage();
+ $binary = $package === 'imagemagick' ? 'convert' : 'gm';
+ $executable = $this->getExecutable($binary, $path);
+
+ // If a path is given, we check whether the binary exists and can be
+ // invoked.
+ if (!empty($path)) {
+ // Check whether the given file exists.
+ if (!is_file($executable)) {
+ $status['errors'][] = $this->t('The @suite executable %file does not exist.', ['@suite' => $this->getPackageLabel($package), '%file' => $executable]);
+ }
+ // If it exists, check whether we can execute it.
+ elseif (!is_executable($executable)) {
+ $status['errors'][] = $this->t('The @suite file %file is not executable.', ['@suite' => $this->getPackageLabel($package), '%file' => $executable]);
+ }
+ }
+
+ // In case of errors, check for open_basedir restrictions.
+ if ($status['errors'] && ($open_basedir = ini_get('open_basedir'))) {
+ $status['errors'][] = $this->t('The PHP <a href=":php-url">open_basedir</a> security restriction is set to %open-basedir, which may prevent to locate the @suite executable.', [
+ '@suite' => $this->getPackageLabel($package),
+ '%open-basedir' => $open_basedir,
+ ':php-url' => 'http://php.net/manual/en/ini.core.php#ini.open-basedir',
+ ]);
+ }
+
+ // Unless we had errors so far, try to invoke convert.
+ if (!$status['errors']) {
+ $error = NULL;
+ $this->runOsShell($executable, '-version', $package, $status['output'], $error);
+ if ($error !== '') {
+ $status['errors'][] = $error;
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($command, ImagemagickExecArguments $arguments, &$output = NULL, &$error = NULL, $path = NULL) {
+ switch ($command) {
+ case 'convert':
+ $binary = $this->getPackage() === 'imagemagick' ? 'convert' : 'gm';
+ break;
+
+ case 'identify':
+ $binary = $this->getPackage() === 'imagemagick' ? 'identify' : 'gm';
+ break;
+
+ }
+ $cmd = $this->getExecutable($binary, $path);
+
+ if ($source_path = $arguments->getSourceLocalPath()) {
+ if (($source_frames = $arguments->getSourceFrames()) !== NULL) {
+ $source_path .= $source_frames;
+ }
+ $source_path = $this->escapeShellArg($source_path);
+ }
+
+ if ($destination_path = $arguments->getDestinationLocalPath()) {
+ $destination_path = $this->escapeShellArg($destination_path);
+ // If the format of the derivative image has to be changed, concatenate
+ // the new image format and the destination path, delimited by a colon.
+ // @see http://www.imagemagick.org/script/command-line-processing.php#output
+ if (($format = $arguments->getDestinationFormat()) !== '') {
+ $destination_path = $format . ':' . $destination_path;
+ }
+ }
+
+ switch ($command) {
+ case 'identify':
+ switch ($this->getPackage()) {
+ case 'imagemagick':
+ // @codingStandardsIgnoreStart
+ // ImageMagick syntax:
+ // identify [arguments] source
+ // @codingStandardsIgnoreEnd
+ $cmdline = $arguments->toString(ImagemagickExecArguments::PRE_SOURCE);
+ // @todo BC layer. In 8.x-3.0, remove adding post source path
+ // arguments.
+ if (($post = $arguments->toString(ImagemagickExecArguments::POST_SOURCE)) !== '') {
+ $cmdline .= ' ' . $post;
+ }
+ $cmdline .= ' ' . $source_path;
+ break;
+
+ case 'graphicsmagick':
+ // @codingStandardsIgnoreStart
+ // GraphicsMagick syntax:
+ // gm identify [arguments] source
+ // @codingStandardsIgnoreEnd
+ $cmdline = 'identify ' . $arguments->toString(ImagemagickExecArguments::PRE_SOURCE);
+ // @todo BC layer. In 8.x-3.0, remove adding post source path
+ // arguments.
+ if (($post = $arguments->toString(ImagemagickExecArguments::POST_SOURCE)) !== '') {
+ $cmdline .= ' ' . $post;
+ }
+ $cmdline .= ' ' . $source_path;
+ break;
+
+ }
+ break;
+
+ case 'convert':
+ switch ($this->getPackage()) {
+ case 'imagemagick':
+ // @codingStandardsIgnoreStart
+ // ImageMagick syntax:
+ // convert input [arguments] output
+ // @see http://www.imagemagick.org/Usage/basics/#cmdline
+ // @codingStandardsIgnoreEnd
+ $cmdline = '';
+ if (($pre = $arguments->toString(ImagemagickExecArguments::PRE_SOURCE)) !== '') {
+ $cmdline .= $pre . ' ';
+ }
+ $cmdline .= $source_path . ' ' . $arguments->toString(ImagemagickExecArguments::POST_SOURCE) . ' ' . $destination_path;
+ break;
+
+ case 'graphicsmagick':
+ // @codingStandardsIgnoreStart
+ // GraphicsMagick syntax:
+ // gm convert [arguments] input output
+ // @see http://www.graphicsmagick.org/GraphicsMagick.html
+ // @codingStandardsIgnoreEnd
+ $cmdline = 'convert ';
+ if (($pre = $arguments->toString(ImagemagickExecArguments::PRE_SOURCE)) !== '') {
+ $cmdline .= $pre . ' ';
+ }
+ $cmdline .= $arguments->toString(ImagemagickExecArguments::POST_SOURCE) . ' ' . $source_path . ' ' . $destination_path;
+ break;
+
+ }
+ break;
+
+ }
+
+ $return_code = $this->runOsShell($cmd, $cmdline, $this->getPackage(), $output, $error);
+
+ if ($return_code !== FALSE) {
+ // If the executable returned a non-zero code, log to the watchdog.
+ if ($return_code != 0) {
+ if ($error === '') {
+ // If there is no error message, and allowed in config, log a
+ // warning.
+ if ($this->configFactory->get('imagemagick.settings')->get('log_warnings') === TRUE) {
+ $this->logger->warning("@suite returned with code @code [command: @command @cmdline]", [
+ '@suite' => $this->getPackageLabel(),
+ '@code' => $return_code,
+ '@command' => $cmd,
+ '@cmdline' => $cmdline,
+ ]);
+ }
+ }
+ else {
+ // Log $error with context information.
+ $this->logger->error("@suite error @code: @error [command: @command @cmdline]", [
+ '@suite' => $this->getPackageLabel(),
+ '@code' => $return_code,
+ '@error' => $error,
+ '@command' => $cmd,
+ '@cmdline' => $cmdline,
+ ]);
+ }
+ // Executable exited with an error code, return FALSE.
+ return FALSE;
+ }
+
+ // The shell command was executed successfully.
+ return TRUE;
+ }
+ // The shell command could not be executed.
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function runOsShell($command, $arguments, $id, &$output = NULL, &$error = NULL) {
+ $command_line = $command . ' ' . $arguments;
+ $output = '';
+ $error = '';
+
+ Timer::start('imagemagick:runOsShell');
+ $process = new Process($command_line, $this->appRoot);
+ $process->setTimeout($this->timeout);
+ try {
+ $process->run();
+ $output = utf8_encode($process->getOutput());
+ $error = utf8_encode($process->getErrorOutput());
+ $return_code = $process->getExitCode();
+ }
+ catch (\Exception $e) {
+ $error = $e->getMessage();
+ $return_code = $process->getExitCode() ? $process->getExitCode() : 1;
+ }
+ $execution_time = Timer::stop('imagemagick:runOsShell')['time'];
+
+ // Process debugging information if required.
+ if ($this->configFactory->get('imagemagick.settings')->get('debug')) {
+ $this->debugMessage('@suite command: <pre>@raw</pre> executed in @execution_timems', [
+ '@suite' => $this->getPackageLabel($id),
+ '@raw' => print_r($command_line, TRUE),
+ '@execution_time' => $execution_time,
+ ]);
+ if ($output !== '') {
+ $this->debugMessage('@suite output: <pre>@raw</pre>', [
+ '@suite' => $this->getPackageLabel($id),
+ '@raw' => print_r($output, TRUE),
+ ]);
+ }
+ if ($error !== '') {
+ $this->debugMessage('@suite error @return_code: <pre>@raw</pre>', [
+ '@suite' => $this->getPackageLabel($id),
+ '@return_code' => $return_code,
+ '@raw' => print_r($error, TRUE),
+ ]);
+ }
+ }
+
+ return $return_code;
+ }
+
+ /**
+ * Logs a debug message, and shows it on the screen for authorized users.
+ *
+ * @param string $message
+ * The debug message.
+ * @param string[] $context
+ * Context information.
+ */
+ public function debugMessage($message, array $context) {
+ $this->logger->debug($message, $context);
+ if ($this->currentUser->hasPermission('administer site configuration')) {
+ // Strips raw text longer than 10 lines to optimize displaying.
+ if (isset($context['@raw'])) {
+ $raw = explode("\n", $context['@raw']);
+ if (count($raw) > 10) {
+ $tmp = [];
+ for ($i = 0; $i < 9; $i++) {
+ $tmp[] = $raw[$i];
+ }
+ $tmp[] = (string) $this->t('[Further text stripped. The watchdog log has the full text.]');
+ $context['@raw'] = implode("\n", $tmp);
+ }
+ }
+ // @codingStandardsIgnoreLine
+ drupal_set_message($this->t($message, $context), 'status', TRUE);
+ }
+ }
+
+ /**
+ * Gets the list of locales installed on the server.
+ *
+ * @return string
+ * The string resulting from the execution of 'locale -a' in *nix systems.
+ */
+ public function getInstalledLocales() {
+ $output = '';
+ if ($this->isWindows === FALSE) {
+ $this->runOsShell('locale', '-a', 'locale', $output);
+ }
+ else {
+ $output = (string) $this->t("List not available on Windows servers.");
+ }
+ return $output;
+ }
+
+ /**
+ * Returns the full path to the executable.
+ *
+ * @param string $binary
+ * The program to execute, typically 'convert', 'identify' or 'gm'.
+ * @param string $path
+ * (optional) A custom path to the folder of the executable. When left
+ * empty, the setting imagemagick.settings.path_to_binaries is taken.
+ *
+ * @return string
+ * The full path to the executable.
+ */
+ protected function getExecutable($binary, $path = NULL) {
+ // $path is only passed from the validation of the image toolkit form, on
+ // which the path to convert is configured. @see ::checkPath()
+ if (!isset($path)) {
+ $path = $this->configFactory->get('imagemagick.settings')->get('path_to_binaries');
+ }
+
+ $executable = $binary;
+ if ($this->isWindows) {
+ $executable .= '.exe';
+ }
+
+ return $path . $executable;
+ }
+
+ /**
+ * Escapes a string.
+ *
+ * PHP escapeshellarg() drops non-ascii characters, this is a replacement.
+ *
+ * Stop-gap replacement until core issue #1561214 has been solved. Solution
+ * proposed in #1502924-8.
+ *
+ * PHP escapeshellarg() on Windows also drops % (percentage sign) characters.
+ * We prevent this by replacing it with a pattern that should be highly
+ * unlikely to appear in the string itself and does not contain any
+ * "dangerous" character at all (very wide definition of dangerous). After
+ * escaping we replace that pattern back with a % character.
+ *
+ * @param string $arg
+ * The string to escape.
+ *
+ * @return string
+ * An escaped string for use in the ::execute method.
+ */
+ public function escapeShellArg($arg) {
+ static $percentage_sign_replace_pattern = '1357902468IMAGEMAGICKPERCENTSIGNPATTERN8642097531';
+
+ // Put the configured locale in a static to avoid multiple config get calls
+ // in the same request.
+ static $config_locale;
+
+ if (!isset($config_locale)) {
+ $config_locale = $this->configFactory->get('imagemagick.settings')->get('locale');
+ if (empty($config_locale)) {
+ $config_locale = FALSE;
+ }
+ }
+
+ if ($this->isWindows) {
+ // Temporarily replace % characters.
+ $arg = str_replace('%', $percentage_sign_replace_pattern, $arg);
+ }
+
+ // If no locale specified in config, return with standard.
+ if ($config_locale === FALSE) {
+ $arg_escaped = escapeshellarg($arg);
+ }
+ else {
+ // Get the current locale.
+ $current_locale = setlocale(LC_CTYPE, 0);
+ if ($current_locale != $config_locale) {
+ // Temporarily swap the current locale with the configured one.
+ setlocale(LC_CTYPE, $config_locale);
+ $arg_escaped = escapeshellarg($arg);
+ setlocale(LC_CTYPE, $current_locale);
+ }
+ else {
+ $arg_escaped = escapeshellarg($arg);
+ }
+ }
+
+ // Get our % characters back.
+ if ($this->isWindows) {
+ $arg_escaped = str_replace($percentage_sign_replace_pattern, '%', $arg_escaped);
+ }
+
+ return $arg_escaped;
+ }
+
+}
--- /dev/null
+<?php
+
+namespace Drupal\imagemagick;
+
+/**
+ * Provides an interface for ImageMagick execution managers.
+ */
+interface ImagemagickExecManagerInterface {
+
+ /**
+ * Gets the binaries package in use.
+ *
+ * @param string $package
+ * (optional) Force the graphics package.
+ *
+ * @return string
+ * The default package ('imagemagick'|'graphicsmagick'), or the $package
+ * argument.
+ */
+ public function getPackage($package = NULL);
+
+ /**
+ * Gets a translated label of the binaries package in use.
+ *
+ * @param string $package
+ * (optional) Force the package.
+ *
+ * @return string
+ * A translated label of the binaries package in use, or the $package
+ * argument.
+ */
+ public function getPackageLabel($package = NULL);
+
+ /**
+ * Verifies file path of the executable binary by checking its version.
+ *
+ * @param string $path
+ * The user-submitted file path to the convert binary.
+ * @param string $package
+ * (optional) The graphics package to use.
+ *
+ * @return array
+ * An associative array containing:
+ * - output: The shell output of 'convert -version', if any.
+ * - errors: A list of error messages indicating if the executable could
+ * not be found or executed.
+ */
+ public function checkPath($path, $package = NULL);
+
+ /**
+ * Executes the convert executable as shell command.
+ *
+ * @param string $command
+ * The executable to run.
+ * @param \Drupal\imagemagick\ImagemagickExecArguments $arguments
+ * An ImageMagick execution arguments object.
+ * @param string &$output
+ * (optional) A variable to assign the shell stdout to, passed by
+ * reference.
+ * @param string &$error
+ * (optional) A variable to assign the shell stderr to, passed by
+ * reference.
+ * @param string $path
+ * (optional) A custom file path to the executable binary.
+ *
+ * @return bool
+ * TRUE if the command succeeded, FALSE otherwise. The error exit status
+ * code integer returned by the executable is logged.
+ */
+ public function execute($command, ImagemagickExecArguments $arguments, &$output = NULL, &$error = NULL, $path = NULL);
+
+ /**
+ * Executes a command on the operating system.
+ *
+ * This differs from ::runOsCommand in the sense that here the command to be
+ * executed and its arguments are passed separately.
+ *
+ * @param string $command
+ * The command to run.
+ * @param string $arguments
+ * The arguments of the command to run.
+ * @param string $id
+ * An identifier for the process to be spawned on the operating system.
+ * @param string &$output
+ * (optional) A variable to assign the shell stdout to, passed by
+ * reference.
+ * @param string &$error
+ * (optional) A variable to assign the shell stderr to, passed by
+ * reference.
+ *
+ * @return int|bool
+ * The operating system returned code, or FALSE if it was not possible to
+ * execute the command.
+ */
+ public function runOsShell($command, $arguments, $id, &$output = NULL, &$error = NULL);
+
+}
use Drupal\Core\Config\Schema\SchemaCheckTrait;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
-// @todo change if extension mapping service gets in, see #2311679
-use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
/**
* Provides the ImageMagick format mapper.
/**
* The MIME type guessing service.
- * @todo change if extension mapping service gets in, see #2311679
*
- * @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface
+ * @var \Drupal\imagemagick\MimeTypeMapper
*/
protected $mimeTypeMapper;
protected $typedConfig;
/**
- * Constructs an ImagemagickFormatmapper object.
+ * Constructs an ImagemagickFormatMapper object.
*
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_service
* The cache service.
- * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mime_type_mapper
+ * @param \Drupal\imagemagick\ImagemagickMimeTypeMapper $mime_type_mapper
* The MIME type mapping service.
- * @todo change if extension mapping service gets in, see #2311679
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed config service.
*/
- public function __construct(CacheBackendInterface $cache_service, MimeTypeGuesserInterface $mime_type_mapper, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config) {
+ public function __construct(CacheBackendInterface $cache_service, ImagemagickMimeTypeMapper $mime_type_mapper, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config) {
$this->cache = $cache_service;
- // @todo change if extension mapping service gets in, see #2311679
$this->mimeTypeMapper = $mime_type_mapper;
$this->configFactory = $config_factory;
$this->typedConfig = $typed_config;
$schema_errors = $this->checkConfigSchema($this->typedConfig, 'imagemagick.settings', $data);
if ($schema_errors !== TRUE) {
foreach ($schema_errors as $key => $value) {
- list($object, $path) = explode(':', $key);
+ list(, $path) = explode(':', $key);
$components = explode('.', $path);
if ($components[0] === 'image_formats') {
if (isset($components[2])) {
return $enabled_image_formats;
}
-
/**
* Returns the enabled image file extensions, processing the config map.
*
/**
* Provides an interface for ImageMagick format mappers.
- * )
*/
interface ImagemagickFormatMapperInterface {
--- /dev/null
+<?php
+
+namespace Drupal\imagemagick;
+
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
+
+/**
+ * Maps MIME types to file extensions.
+ */
+class ImagemagickMimeTypeMapper {
+
+ /**
+ * The extension MIME type guesser.
+ *
+ * @var \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser
+ */
+ protected $mimeTypeGuesser;
+
+ /**
+ * The MIME types mapping array after going through the module handler.
+ *
+ * Copied via Reflection from
+ * \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser.
+ *
+ * @var array
+ */
+ protected $mapping;
+
+ /**
+ * Constructs an ImagemagickMimeTypeMapper object.
+ *
+ * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $extension_mimetype_guesser
+ * The extension MIME type guesser.
+ */
+ public function __construct(MimeTypeGuesserInterface $extension_mimetype_guesser) {
+ $this->mimeTypeGuesser = $extension_mimetype_guesser;
+ }
+
+ /**
+ * Returns the MIME types mapping array from ExtensionMimeTypeGuesser.
+ *
+ * Copied via Reflection from
+ * \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser.
+ *
+ * @return array
+ * The MIME types mapping array.
+ */
+ protected function getMapping() {
+ if (!$this->mapping) {
+ // Guess a fake file name just to ensure the guesser loads any mapping
+ // alteration through the hooks.
+ $this->mimeTypeGuesser->guess('fake.png');
+ // Use Reflection to get a copy of the protected $mapping property in the
+ // guesser class. Get the proxied service first, then the actual mapping.
+ $reflection = new \ReflectionObject($this->mimeTypeGuesser);
+ $proxied_service = $reflection->getProperty('service');
+ $proxied_service->setAccessible(TRUE);
+ $service = $proxied_service->getValue(clone $this->mimeTypeGuesser);
+ $reflection = new \ReflectionObject($service);
+ $reflection_mapping = $reflection->getProperty('mapping');
+ $reflection_mapping->setAccessible(TRUE);
+ $this->mapping = $reflection_mapping->getValue(clone $service);
+ }
+ return $this->mapping;
+ }
+
+ /**
+ * Returns the appropriate extensions for a given MIME type.
+ *
+ * @param string $mimetype
+ * A MIME type.
+ *
+ * @return string[]
+ * An array of file extensions matching the MIME type, without leading dot.
+ */
+ public function getExtensionsForMimeType($mimetype) {
+ $mapping = $this->getMapping();
+ if (!in_array($mimetype, $mapping['mimetypes'])) {
+ return [];
+ }
+ $key = array_search($mimetype, $mapping['mimetypes']);
+ $extensions = array_keys($mapping['extensions'], $key, TRUE);
+ sort($extensions);
+ return $extensions;
+ }
+
+ /**
+ * Returns known MIME types.
+ *
+ * @return string[]
+ * An array of MIME types.
+ */
+ public function getMimeTypes() {
+ return array_values($this->getMapping()['mimetypes']);
+ }
+
+}
--- /dev/null
+<?php
+
+namespace Drupal\imagemagick\Plugin\FileMetadata;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\file_mdm\FileMetadataException;
+use Drupal\file_mdm\Plugin\FileMetadata\FileMetadataPluginBase;
+use Drupal\imagemagick\ImagemagickExecArguments;
+use Drupal\imagemagick\ImagemagickExecManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * FileMetadata plugin for ImageMagick's identify results.
+ *
+ * @FileMetadata(
+ * id = "imagemagick_identify",
+ * title = @Translation("ImageMagick identify"),
+ * help = @Translation("File metadata plugin for ImageMagick identify results."),
+ * )
+ */
+class ImagemagickIdentify extends FileMetadataPluginBase {
+
+ /**
+ * The module handler service.
+ *
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
+ /**
+ * The ImageMagick execution manager service.
+ *
+ * @var \Drupal\imagemagick\ImagemagickExecManagerInterface
+ */
+ protected $execManager;
+
+ /**
+ * Constructs an ImagemagickIdentify plugin.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ * @param array $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Cache\CacheBackendInterface $cache_service
+ * The cache service.
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * The config factory.
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+ * The module handler service.
+ * @param \Drupal\imagemagick\ImagemagickExecManagerInterface $exec_manager
+ * The ImageMagick execution manager service.
+ */
+ public function __construct(array $configuration, $plugin_id, array $plugin_definition, CacheBackendInterface $cache_service, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ImagemagickExecManagerInterface $exec_manager) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $cache_service, $config_factory);
+ $this->moduleHandler = $module_handler;
+ $this->execManager = $exec_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('cache.file_mdm'),
+ $container->get('config.factory'),
+ $container->get('module_handler'),
+ $container->get('imagemagick.exec_manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Supported keys are:
+ * 'format' - ImageMagick's image format identifier.
+ * 'width' - Image width.
+ * 'height' - Image height.
+ * 'colorspace' - Image colorspace.
+ * 'profiles' - Image profiles.
+ * 'exif_orientation' - Image EXIF orientation (only supported formats).
+ * 'source_local_path' - The local file path from where the file was
+ * parsed.
+ * 'frames_count' - Number of frames in the image.
+ */
+ public function getSupportedKeys($options = NULL) {
+ return [
+ 'format',
+ 'width',
+ 'height',
+ 'colorspace',
+ 'profiles',
+ 'exif_orientation',
+ 'source_local_path',
+ 'frames_count',
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doGetMetadataFromFile() {
+ return $this->identify();
+ }
+
+ /**
+ * Validates a file metadata key.
+ *
+ * @return bool
+ * TRUE if the key is valid.
+ *
+ * @throws \Drupal\file_mdm\FileMetadataException
+ * In case the key is invalid.
+ */
+ protected function validateKey($key, $method) {
+ if (!is_string($key)) {
+ throw new FileMetadataException("Invalid metadata key specified", $this->getPluginId(), $method);
+ }
+ if (!in_array($key, $this->getSupportedKeys(), TRUE)) {
+ throw new FileMetadataException("Invalid metadata key '{$key}' specified", $this->getPluginId(), $method);
+ }
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doGetMetadata($key = NULL) {
+ if ($key === NULL) {
+ return $this->metadata;
+ }
+ else {
+ $this->validateKey($key, __FUNCTION__);
+ switch ($key) {
+ case 'source_local_path':
+ return isset($this->metadata['source_local_path']) ? $this->metadata['source_local_path'] : NULL;
+
+ case 'frames_count':
+ return isset($this->metadata['frames']) ? count($this->metadata['frames']) : 0;
+
+ default:
+ return isset($this->metadata['frames'][0][$key]) ? $this->metadata['frames'][0][$key] : NULL;
+
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSetMetadata($key, $value) {
+ $this->validateKey($key, __FUNCTION__);
+ switch ($key) {
+ case 'source_local_path':
+ $this->metadata['source_local_path'] = $value;
+ return TRUE;
+
+ case 'frames_count':
+ return FALSE;
+
+ default:
+ $this->metadata['frames'][0][$key] = $value;
+ return TRUE;
+
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRemoveMetadata($key) {
+ $this->validateKey($key, __FUNCTION__);
+ switch ($key) {
+ case 'source_local_path':
+ if (isset($this->metadata['source_local_path'])) {
+ unset($this->metadata['source_local_path']);
+ return TRUE;
+ }
+ return FALSE;
+
+ default:
+ return FALSE;
+
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getMetadataToCache() {
+ $metadata = $this->metadata;
+ // Avoid caching the source_local_path.
+ unset($metadata['source_local_path']);
+ return $metadata;
+ }
+
+ /**
+ * Calls the identify executable on the specified file.
+ *
+ * @return array
+ * The array with identify metadata, if the file was parsed correctly.
+ * NULL otherwise.
+ */
+ protected function identify() {
+ $arguments = new ImagemagickExecArguments($this->execManager);
+
+ // Add source file.
+ $arguments->setSource($this->getLocalTempPath());
+
+ // Prepare the -format argument according to the graphics package in use.
+ switch ($this->execManager->getPackage()) {
+ case 'imagemagick':
+ $arguments->add(
+ '-format ' . $arguments->escape("format:%[magick]|width:%[width]|height:%[height]|colorspace:%[colorspace]|profiles:%[profiles]|exif_orientation:%[EXIF:Orientation]\\n"),
+ ImagemagickExecArguments::PRE_SOURCE
+ );
+ break;
+
+ case 'graphicsmagick':
+ $arguments->add(
+ '-format ' . $arguments->escape("format:%m|width:%w|height:%h|exif_orientation:%[EXIF:Orientation]\\n"),
+ ImagemagickExecArguments::PRE_SOURCE
+ );
+ break;
+
+ }
+
+ // Allow modules to alter source file and the command line parameters.
+ $command = 'identify';
+ $this->moduleHandler->alter('imagemagick_pre_parse_file', $arguments);
+ $this->moduleHandler->alter('imagemagick_arguments', $arguments, $command);
+
+ // Execute the 'identify' command.
+ $output = NULL;
+ $ret = $this->execManager->execute($command, $arguments, $output);
+
+ // Process results.
+ $data = [];
+ if ($ret) {
+ // Remove any CR character (GraphicsMagick on Windows produces such).
+ $output = str_replace("\r", '', $output);
+
+ // Builds the frames info.
+ $frames = [];
+ $frames_tmp = explode("\n", $output);
+ // Remove empty items at the end of the array.
+ while (empty($frames_tmp[count($frames_tmp) - 1])) {
+ array_pop($frames_tmp);
+ }
+ foreach ($frames_tmp as $i => $frame) {
+ $info = explode('|', $frame);
+ foreach ($info as $item) {
+ list($key, $value) = explode(':', $item);
+ if (trim($key) === 'profiles') {
+ $profiles_tmp = empty($value) ? [] : explode(',', $value);
+ $frames[$i][trim($key)] = $profiles_tmp;
+ }
+ else {
+ $frames[$i][trim($key)] = trim($value);
+ }
+ }
+ }
+ $data['frames'] = $frames;
+ // Adds the local file path that was resolved via
+ // hook_imagemagick_pre_parse_file implementations.
+ $data['source_local_path'] = $arguments->getSourceLocalPath();
+ }
+
+ return ($ret === TRUE) ? $data : NULL;
+ }
+
+}
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\ImageToolkit\ImageToolkitBase;
use Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface;
+use Drupal\Core\Link;
use Drupal\Core\Url;
+use Drupal\file_mdm\FileMetadataManagerInterface;
+use Drupal\imagemagick\ImagemagickExecArguments;
+use Drupal\imagemagick\ImagemagickExecManagerInterface;
use Drupal\imagemagick\ImagemagickFormatMapperInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ImagemagickToolkit extends ImageToolkitBase {
/**
- * Whether we are running on Windows OS.
+ * EXIF orientation not fetched.
*
- * @var bool
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * parseFileViaIdentify() to parse image files.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2941093
*/
- protected $isWindows;
+ const EXIF_ORIENTATION_NOT_FETCHED = -99;
/**
* The module handler service.
protected $formatMapper;
/**
- * The app root.
+ * The file metadata manager service.
*
- * @var string
+ * @var \Drupal\file_mdm\FileMetadataManagerInterface
*/
- protected $appRoot;
+ protected $fileMetadataManager;
/**
- * The array of command line arguments to be used by 'convert'.
+ * The ImageMagick execution manager service.
*
- * @var string[]
+ * @var \Drupal\imagemagick\ImagemagickExecManagerInterface
*/
- protected $arguments = [];
+ protected $execManager;
+
+ /**
+ * The execution arguments object.
+ *
+ * @var \Drupal\imagemagick\ImagemagickExecArguments
+ */
+ protected $arguments;
/**
* The width of the image.
protected $height;
/**
- * The number of frames of the image, for multi-frame images (e.g. GIF).
+ * The number of frames of the source image, for multi-frame images.
*
* @var int
*/
protected $frames;
/**
- * The local filesystem path to the source image file.
- *
- * @var string
- */
- protected $sourceLocalPath = '';
-
- /**
- * The source image format.
- *
- * @var string
- */
- protected $sourceFormat = '';
-
- /**
- * Keeps a copy of source image EXIF information.
+ * Image orientation retrieved from EXIF information.
*
- * @var array
- */
- protected $exifInfo = [];
-
- /**
- * The image destination URI/path on saving.
- *
- * @var string
+ * @var int
*/
- protected $destination = NULL;
+ protected $exifOrientation;
/**
- * The local filesystem path to the image destination.
+ * The source image colorspace.
*
* @var string
*/
- protected $destinationLocalPath = '';
+ protected $colorspace;
/**
- * The image destination format on saving.
+ * The source image profiles.
*
- * @var string
+ * @var string[]
*/
- protected $destinationFormat = '';
+ protected $profiles = [];
/**
* Constructs an ImagemagickToolkit object.
* The module handler service.
* @param \Drupal\imagemagick\ImagemagickFormatMapperInterface $format_mapper
* The format mapper service.
- * @param string $app_root
- * The app root.
+ * @param \Drupal\file_mdm\FileMetadataManagerInterface $file_metadata_manager
+ * The file metadata manager service.
+ * @param \Drupal\imagemagick\ImagemagickExecManagerInterface $exec_manager
+ * The ImageMagick execution manager service.
*/
- public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ImagemagickFormatMapperInterface $format_mapper, $app_root) {
+ public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ImagemagickFormatMapperInterface $format_mapper, FileMetadataManagerInterface $file_metadata_manager, ImagemagickExecManagerInterface $exec_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $operation_manager, $logger, $config_factory);
$this->moduleHandler = $module_handler;
$this->formatMapper = $format_mapper;
- $this->appRoot = $app_root;
- $this->isWindows = substr(PHP_OS, 0, 3) === 'WIN';
+ $this->fileMetadataManager = $file_metadata_manager;
+ $this->execManager = $exec_manager;
+ $this->arguments = new ImagemagickExecArguments($this->execManager);
}
/**
$container->get('config.factory'),
$container->get('module_handler'),
$container->get('imagemagick.format_mapper'),
- $container->get('app.root')
+ $container->get('file_metadata_manager'),
+ $container->get('imagemagick.exec_manager')
);
}
'#description' => $this->t('Define the image quality of processed images. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
];
+ // Settings tabs.
+ $form['imagemagick_settings'] = [
+ '#type' => 'vertical_tabs',
+ '#tree' => FALSE,
+ ];
+
// Graphics suite to use.
$form['suite'] = [
'#type' => 'details',
- '#open' => TRUE,
- '#collapsible' => FALSE,
'#title' => $this->t('Graphics package'),
+ '#group' => 'imagemagick_settings',
];
$options = [
- 'imagemagick' => $this->getPackageLabel('imagemagick'),
- 'graphicsmagick' => $this->getPackageLabel('graphicsmagick'),
+ 'imagemagick' => $this->getExecManager()->getPackageLabel('imagemagick'),
+ 'graphicsmagick' => $this->getExecManager()->getPackageLabel('graphicsmagick'),
];
$form['suite']['binaries'] = [
'#type' => 'radios',
'#title' => $this->t('Suite'),
- '#default_value' => $this->getPackage(),
+ '#default_value' => $this->getExecManager()->getPackage(),
'#options' => $options,
'#required' => TRUE,
'#description' => $this->t("Select the graphics package to use."),
'#description' => $this->t('If needed, the path to the package executables (<kbd>convert</kbd>, <kbd>identify</kbd>, <kbd>gm</kbd>, etc.), <b>including</b> the trailing slash/backslash. For example: <kbd>/usr/bin/</kbd> or <kbd>C:\Program Files\ImageMagick-6.3.4-Q16\</kbd>.'),
];
// Version information.
- $status = $this->checkPath($config->get('path_to_binaries'));
+ $status = $this->getExecManager()->checkPath($config->get('path_to_binaries'));
if (empty($status['errors'])) {
$version_info = explode("\n", preg_replace('/\r/', '', Html::escape($status['output'])));
}
$form['suite']['version'] = [
'#type' => 'details',
'#collapsible' => TRUE,
- '#collapsed' => TRUE,
+ '#open' => TRUE,
'#title' => $this->t('Version information'),
'#description' => '<pre>' . implode('<br />', $version_info) . '</pre>',
];
// Image formats.
$form['formats'] = [
'#type' => 'details',
- '#open' => TRUE,
- '#collapsible' => FALSE,
'#title' => $this->t('Image formats'),
- ];
- // Use 'identify' command.
- $form['formats']['use_identify'] = [
- '#type' => 'checkbox',
- '#title' => $this->t('Use "identify"'),
- '#default_value' => $config->get('use_identify'),
- '#description' => $this->t('Use the <kbd>identify</kbd> command to parse image files to determine image format and dimensions. If not selected, the PHP <kbd>getimagesize</kbd> function will be used, BUT this will limit the image formats supported by the toolkit.'),
+ '#group' => 'imagemagick_settings',
];
// Image formats enabled in the toolkit.
$form['formats']['enabled'] = [
'#type' => 'item',
- '#title' => $this->t('Enabled images'),
+ '#title' => $this->t('Currently enabled images'),
'#description' => $this->t("@suite formats: %formats<br />Image file extensions: %extensions", [
'%formats' => implode(', ', $this->formatMapper->getEnabledFormats()),
'%extensions' => Unicode::strtolower(implode(', ', static::getSupportedExtensions())),
- '@suite' => $this->getPackageLabel(),
+ '@suite' => $this->getExecManager()->getPackageLabel(),
]),
];
// Image formats map.
$form['formats']['mapping'] = [
'#type' => 'details',
'#collapsible' => TRUE,
- '#collapsed' => TRUE,
+ '#open' => TRUE,
'#title' => $this->t('Enable/disable image formats'),
'#description' => $this->t("Edit the map below to enable/disable image formats. Enabled image file extensions will be determined by the enabled formats, through their MIME types. More information in the module's README.txt"),
];
];
// Image formats supported by the package.
if (empty($status['errors'])) {
- $this->addArgument('-list format');
- $this->imagemagickExec('convert', $output);
- $this->resetArguments();
+ $this->arguments()->add('-list format', ImagemagickExecArguments::PRE_SOURCE);
+ $output = NULL;
+ $this->getExecManager()->execute('convert', $this->arguments(), $output);
+ $this->arguments()->reset();
$formats_info = implode('<br />', explode("\n", preg_replace('/\r/', '', Html::escape($output))));
$form['formats']['list'] = [
'#type' => 'details',
'#collapsible' => TRUE,
- '#collapsed' => TRUE,
+ '#open' => FALSE,
'#title' => $this->t('Format list'),
- '#description' => $this->t("Supported image formats returned by executing <kbd>'convert -list format'</kbd>. <b>Note:</b> these are the formats supported by the installed @suite executable, <b>not</b> by the toolkit.<br /><br />", ['@suite' => $this->getPackageLabel()]),
+ '#description' => $this->t("Supported image formats returned by executing <kbd>'convert -list format'</kbd>. <b>Note:</b> these are the formats supported by the installed @suite executable, <b>not</b> by the toolkit.<br /><br />", ['@suite' => $this->getExecManager()->getPackageLabel()]),
];
$form['formats']['list']['list'] = [
'#markup' => "<pre>" . $formats_info . "</pre>",
// Execution options.
$form['exec'] = [
'#type' => 'details',
- '#open' => TRUE,
- '#collapsible' => FALSE,
'#title' => $this->t('Execution options'),
+ '#group' => 'imagemagick_settings',
+ ];
+
+ // Use 'identify' command.
+ $form['exec']['use_identify'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Use "identify"'),
+ '#default_value' => $config->get('use_identify'),
+ '#description' => $this->t('<strong>This setting is deprecated and will be removed in the next major release of the Imagemagick module. Leave it enabled to ensure smooth transition.</strong>') . ' ' . $this->t('Use the <kbd>identify</kbd> command to parse image files to determine image format and dimensions. If not selected, the PHP <kbd>getimagesize</kbd> function will be used, BUT this will limit the image formats supported by the toolkit.'),
+ ];
+ // Cache metadata.
+ $configure_link = Link::fromTextAndUrl(
+ $this->t('Configure File Metadata Manager'),
+ Url::fromRoute('file_mdm.settings')
+ );
+ $form['exec']['metadata_caching'] = [
+ '#type' => 'item',
+ '#title' => $this->t("Cache image metadata"),
+ '#description' => $this->t("The File Metadata Manager module allows to cache image metadata. This reduces file I/O and <kbd>shell</kbd> calls. @configure.", [
+ '@configure' => $configure_link->toString(),
+ ]),
];
// Prepend arguments.
$form['exec']['prepend'] = [
- '#type' => 'textfield',
+ '#type' => 'details',
+ '#collapsible' => FALSE,
+ '#open' => TRUE,
'#title' => $this->t('Prepend arguments'),
+ '#description' => $this->t("Use this to add e.g. <kbd><a href=':limit-url'>-limit</a></kbd> or <kbd><a href=':debug-url'>-debug</a></kbd> arguments in front of the others when executing the <kbd>identify</kbd> and <kbd>convert</kbd> commands. Select 'Before source' to execute the arguments before loading the source image.", [
+ ':limit-url' => 'https://www.imagemagick.org/script/command-line-options.php#limit',
+ ':debug-url' => 'https://www.imagemagick.org/script/command-line-options.php#debug',
+ ]),
+ ];
+ $form['exec']['prepend']['container'] = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'class' => ['container-inline'],
+ ],
+ ];
+ $form['exec']['prepend']['container']['prepend'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Arguments'),
'#default_value' => $config->get('prepend'),
'#required' => FALSE,
- '#description' => $this->t('Use this to add e.g. <kbd>-limit</kbd> or <kbd>-debug</kbd> arguments in front of the others when executing the <kbd>identify</kbd> and <kbd>convert</kbd> commands.'),
];
+ $form['exec']['prepend']['container']['prepend_pre_source'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Before source'),
+ '#default_value' => $config->get('prepend_pre_source'),
+ ];
+
// Locale.
$form['exec']['locale'] = [
'#type' => 'textfield',
'#title' => $this->t('Locale'),
'#default_value' => $config->get('locale'),
'#required' => FALSE,
- '#description' => $this->t("The locale to be used to prepare the command passed to executables. The default, <kbd>'en_US.UTF-8'</kbd>, should work in most cases. If that is not available on the server, enter another locale. On *nix servers, type <kbd>'locale -a'</kbd> in a shell window to see a list of all locales available."),
+ '#description' => $this->t("The locale to be used to prepare the command passed to executables. The default, <kbd>'en_US.UTF-8'</kbd>, should work in most cases. If that is not available on the server, enter another locale. 'Installed Locales' below provides a list of locales installed on the server."),
+ ];
+ // Installed locales.
+ $locales = $this->getExecManager()->getInstalledLocales();
+ $locales_info = implode('<br />', explode("\n", preg_replace('/\r/', '', Html::escape($locales))));
+ $form['exec']['installed_locales'] = [
+ '#type' => 'details',
+ '#collapsible' => TRUE,
+ '#open' => FALSE,
+ '#title' => $this->t('Installed locales'),
+ '#description' => $this->t("This is the list of all locales available on this server. It is the output of executing <kbd>'locale -a'</kbd> on the operating system."),
+ ];
+ $form['exec']['installed_locales']['list'] = [
+ '#markup' => "<pre>" . $locales_info . "</pre>",
];
// Log warnings.
$form['exec']['log_warnings'] = [
// Advanced image settings.
$form['advanced'] = [
'#type' => 'details',
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
'#title' => $this->t('Advanced image settings'),
+ '#group' => 'imagemagick_settings',
];
$form['advanced']['density'] = [
'#type' => 'checkbox',
return $form;
}
+ /**
+ * Returns the ImageMagick execution manager service.
+ *
+ * @return \Drupal\imagemagick\ImagemagickExecManagerInterface
+ * The ImageMagick execution manager service.
+ */
+ public function getExecManager() {
+ return $this->execManager;
+ }
+
/**
* Gets the binaries package in use.
*
* @return string
* The default package ('imagemagick'|'graphicsmagick'), or the $package
* argument.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecManagerInterface::getPackage() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function getPackage($package = NULL) {
- if ($package === NULL) {
- $package = $this->configFactory->get('imagemagick.settings')->get('binaries');
- }
- return $package;
+ @trigger_error('getPackage() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecManagerInterface::getPackage() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->getExecManager()->getPackage($package);
}
/**
* @return string
* A translated label of the binaries package in use, or the $package
* argument.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecManagerInterface::getPackageLabel() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function getPackageLabel($package = NULL) {
- switch ($this->getPackage($package)) {
- case 'imagemagick':
- return $this->t('ImageMagick');
-
- case 'graphicsmagick':
- return $this->t('GraphicsMagick');
-
- default:
- return $package;
-
- }
+ @trigger_error('getPackageLabel() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecManagerInterface::getPackageLabel() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->getExecManager()->getPackageLabel($package);
}
/**
* - output: The shell output of 'convert -version', if any.
* - errors: A list of error messages indicating if the executable could
* not be found or executed.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecManagerInterface::checkPath() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function checkPath($path, $package = NULL) {
- $status = [
- 'output' => '',
- 'errors' => [],
- ];
-
- // Execute gm or convert based on settings.
- $package = $package ?: $this->getPackage();
- $binary = $package === 'imagemagick' ? 'convert' : 'gm';
- $executable = $this->getExecutable($binary, $path);
-
- // If a path is given, we check whether the binary exists and can be
- // invoked.
- if (!empty($path)) {
- // Check whether the given file exists.
- if (!is_file($executable)) {
- $status['errors'][] = $this->t('The @suite executable %file does not exist.', ['@suite' => $this->getPackageLabel($package), '%file' => $executable]);
- }
- // If it exists, check whether we can execute it.
- elseif (!is_executable($executable)) {
- $status['errors'][] = $this->t('The @suite file %file is not executable.', ['@suite' => $this->getPackageLabel($package), '%file' => $executable]);
- }
- }
-
- // In case of errors, check for open_basedir restrictions.
- if ($status['errors'] && ($open_basedir = ini_get('open_basedir'))) {
- $status['errors'][] = $this->t('The PHP <a href=":php-url">open_basedir</a> security restriction is set to %open-basedir, which may prevent to locate the @suite executable.', [
- '@suite' => $this->getPackageLabel($package),
- '%open-basedir' => $open_basedir,
- ':php-url' => 'http://php.net/manual/en/ini.core.php#ini.open-basedir',
- ]);
- }
-
- // Unless we had errors so far, try to invoke convert.
- if (!$status['errors']) {
- $error = NULL;
- $this->runOsShell($executable, '-version', $package, $status['output'], $error);
- if ($error !== '') {
- // $error normally needs check_plain(), but file system errors on
- // Windows use a unknown encoding. check_plain() would eliminate the
- // entire string.
- $status['errors'][] = $error;
- }
- }
-
- return $status;
+ @trigger_error('checkPath() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecManagerInterface::checkPath() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->getExecManager()->checkPath($path, $package);
}
/**
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
try {
// Check that the format map contains valid YAML.
- $image_formats = Yaml::decode($form_state->getValue(['imagemagick', 'formats', 'mapping', 'image_formats']));
+ $image_formats = Yaml::decode($form_state->getValue([
+ 'imagemagick', 'formats', 'mapping', 'image_formats',
+ ]));
// Validate the enabled image formats.
$errors = $this->formatMapper->validateMap($image_formats);
if ($errors) {
// it will prevent the entire image toolkit selection form from being
// submitted.
if ($form_state->getValue(['image_toolkit']) === 'imagemagick') {
- $status = $this->checkPath($form_state->getValue(['imagemagick', 'suite', 'path_to_binaries']), $form_state->getValue(['imagemagick', 'suite', 'binaries']));
+ $status = $this->getExecManager()->checkPath($form_state->getValue([
+ 'imagemagick', 'suite', 'path_to_binaries',
+ ]), $form_state->getValue(['imagemagick', 'suite', 'binaries']));
if ($status['errors']) {
$form_state->setErrorByName('imagemagick][suite][path_to_binaries', new FormattableMarkup(implode('<br />', $status['errors']), []));
}
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
- $this->configFactory->getEditable('imagemagick.settings')
- ->set('quality', $form_state->getValue(['imagemagick', 'quality']))
- ->set('binaries', $form_state->getValue(['imagemagick', 'suite', 'binaries']))
- ->set('path_to_binaries', $form_state->getValue(['imagemagick', 'suite', 'path_to_binaries']))
- ->set('use_identify', $form_state->getValue(['imagemagick', 'formats', 'use_identify']))
- ->set('image_formats', Yaml::decode($form_state->getValue(['imagemagick', 'formats', 'mapping', 'image_formats'])))
- ->set('prepend', $form_state->getValue(['imagemagick', 'exec', 'prepend']))
- ->set('locale', $form_state->getValue(['imagemagick', 'exec', 'locale']))
- ->set('log_warnings', (bool) $form_state->getValue(['imagemagick', 'exec', 'log_warnings']))
- ->set('debug', $form_state->getValue(['imagemagick', 'exec', 'debug']))
- ->set('advanced.density', $form_state->getValue(['imagemagick', 'advanced', 'density']))
- ->set('advanced.colorspace', $form_state->getValue(['imagemagick', 'advanced', 'colorspace']))
- ->set('advanced.profile', $form_state->getValue(['imagemagick', 'advanced', 'profile']))
- ->save();
+ $config = $this->configFactory->getEditable('imagemagick.settings');
+ $config
+ ->set('quality', (int) $form_state->getValue([
+ 'imagemagick', 'quality',
+ ]))
+ ->set('binaries', (string) $form_state->getValue([
+ 'imagemagick', 'suite', 'binaries',
+ ]))
+ ->set('path_to_binaries', (string) $form_state->getValue([
+ 'imagemagick', 'suite', 'path_to_binaries',
+ ]))
+ ->set('use_identify', (bool) $form_state->getValue([
+ 'imagemagick', 'exec', 'use_identify',
+ ]))
+ ->set('image_formats', Yaml::decode($form_state->getValue([
+ 'imagemagick', 'formats', 'mapping', 'image_formats',
+ ])))
+ ->set('prepend', (string) $form_state->getValue([
+ 'imagemagick', 'exec', 'prepend', 'container', 'prepend',
+ ]))
+ ->set('prepend_pre_source', (bool) $form_state->getValue([
+ 'imagemagick', 'exec', 'prepend', 'container', 'prepend_pre_source',
+ ]))
+ ->set('locale', (string) $form_state->getValue([
+ 'imagemagick', 'exec', 'locale',
+ ]))
+ ->set('log_warnings', (bool) $form_state->getValue([
+ 'imagemagick', 'exec', 'log_warnings',
+ ]))
+ ->set('debug', (bool) $form_state->getValue([
+ 'imagemagick', 'exec', 'debug',
+ ]))
+ ->set('advanced.density', (int) $form_state->getValue([
+ 'imagemagick', 'advanced', 'density',
+ ]))
+ ->set('advanced.colorspace', (string) $form_state->getValue([
+ 'imagemagick', 'advanced', 'colorspace',
+ ]))
+ ->set('advanced.profile', (string) $form_state->getValue([
+ 'imagemagick', 'advanced', 'profile',
+ ]));
+ $config->save();
}
/**
return ((bool) $this->getMimeType());
}
+ /**
+ * {@inheritdoc}
+ */
+ public function setSource($source) {
+ parent::setSource($source);
+ $this->arguments()->setSource($source);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSource() {
+ return $this->arguments()->getSource();
+ }
+
/**
* Gets the local filesystem path to the image file.
*
* @return string
* A filesystem path.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ::ensureSourceLocalPath() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function getSourceLocalPath() {
- return $this->sourceLocalPath;
+ @trigger_error('getSourceLocalPath() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::ensureSourceLocalPath() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->ensureSourceLocalPath();
+ }
+
+ /**
+ * Ensures that the local filesystem path to the image file exists.
+ *
+ * @return string
+ * A filesystem path.
+ */
+ public function ensureSourceLocalPath() {
+ // If sourceLocalPath is NULL, then ensure it is prepared. This can
+ // happen if image was identified via cached metadata: the cached data are
+ // available, but the temp file path is not resolved, or even the temp file
+ // could be missing if it was copied locally from a remote file system.
+ if (!$this->arguments()->getSourceLocalPath() && $this->getSource()) {
+ $this->moduleHandler->alter('imagemagick_pre_parse_file', $this->arguments);
+ }
+ return $this->arguments()->getSourceLocalPath();
}
/**
* A filesystem path.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::setSourceLocalPath() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function setSourceLocalPath($path) {
- $this->sourceLocalPath = $path;
+ @trigger_error('setSourceLocalPath() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::setSourceLocalPath() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ $this->arguments()->setSourceLocalPath($path);
return $this;
}
*
* @return string
* The source image format.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::getSourceFormat() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function getSourceFormat() {
- return $this->sourceFormat;
+ @trigger_error('getSourceFormat() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::getSourceFormat() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->arguments()->getSourceFormat();
}
/**
* The image format.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::setSourceFormat() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function setSourceFormat($format) {
- $this->sourceFormat = $this->formatMapper->isFormatEnabled($format) ? $format : '';
+ @trigger_error('setSourceFormat() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::setSourceFormat() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ $this->arguments()->setSourceFormat($format);
return $this;
}
* The image file extension.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::setSourceFormatFromExtension() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function setSourceFormatFromExtension($extension) {
- $format = $this->formatMapper->getFormatFromExtension($extension);
- $this->sourceFormat = $format ?: '';
+ @trigger_error('setSourceFormatFromExtension() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::setSourceFormatFromExtension() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ $this->arguments()->setSourceFormatFromExtension($extension);
return $this;
}
/**
* Gets the source EXIF orientation.
*
- * @return integer
+ * @return int
* The source EXIF orientation.
*/
public function getExifOrientation() {
- if (empty($this->exifInfo)) {
- $this->parseExifData();
+ if ($this->exifOrientation === static::EXIF_ORIENTATION_NOT_FETCHED) {
+ if ($this->getSource() !== NULL) {
+ $file_md = $this->fileMetadataManager->uri($this->getSource());
+ if ($file_md->getLocalTempPath() === NULL) {
+ $file_md->setLocalTempPath($this->ensureSourceLocalPath());
+ }
+ $orientation = $file_md->getMetadata('exif', 'Orientation');
+ $this->setExifOrientation(isset($orientation['value']) ? $orientation['value'] : NULL);
+ }
+ else {
+ $this->setExifOrientation(NULL);
+ }
}
- return isset($this->exifInfo['Orientation']) ? $this->exifInfo['Orientation'] : NULL;
+ return $this->exifOrientation;
}
/**
* Sets the source EXIF orientation.
*
- * @param integer|null $exif_orientation
+ * @param int|null $exif_orientation
* The EXIF orientation.
*
* @return $this
*/
public function setExifOrientation($exif_orientation) {
- $this->exifInfo['Orientation'] = !empty($exif_orientation) ? ((int) $exif_orientation !== 0 ? (int) $exif_orientation : NULL) : NULL;
+ $this->exifOrientation = $exif_orientation ? (int) $exif_orientation : NULL;
+ return $this;
+ }
+
+ /**
+ * Gets the source colorspace.
+ *
+ * @return string
+ * The source colorspace.
+ */
+ public function getColorspace() {
+ return $this->colorspace;
+ }
+
+ /**
+ * Sets the source colorspace.
+ *
+ * @param string $colorspace
+ * The image colorspace.
+ *
+ * @return $this
+ */
+ public function setColorspace($colorspace) {
+ $this->colorspace = Unicode::strtoupper($colorspace);
+ return $this;
+ }
+
+ /**
+ * Gets the source profiles.
+ *
+ * @return string[]
+ * The source profiles.
+ */
+ public function getProfiles() {
+ return $this->profiles;
+ }
+
+ /**
+ * Sets the source profiles.
+ *
+ * @param array $profiles
+ * The image profiles.
+ *
+ * @return $this
+ */
+ public function setProfiles(array $profiles) {
+ $this->profiles = $profiles;
return $this;
}
/**
* Gets the source image number of frames.
*
- * @return integer
+ * @return int
* The number of frames of the image.
*/
public function getFrames() {
/**
* Sets the source image number of frames.
*
- * @param integer|null $frames
+ * @param int|null $frames
* The number of frames of the image.
*
* @return $this
*
* @return string
* The image destination URI/path.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::getDestination() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function getDestination() {
- return $this->destination;
+ @trigger_error('getDestination() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::getDestination() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->arguments()->getDestination();
}
/**
* The image destination URI/path.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::setDestination() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function setDestination($destination) {
- $this->destination = $destination;
+ @trigger_error('setDestination() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::setDestination() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ $this->arguments()->setDestination($destination);
return $this;
}
*
* @return string
* A filesystem path.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::getDestinationLocalPath() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function getDestinationLocalPath() {
- return $this->destinationLocalPath;
+ @trigger_error('getDestinationLocalPath() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::getDestinationLocalPath() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->arguments()->getDestinationLocalPath();
}
/**
* A filesystem path.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::setDestinationLocalPath() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function setDestinationLocalPath($path) {
- $this->destinationLocalPath = $path;
+ @trigger_error('setDestinationLocalPath() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::setDestinationLocalPath() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ $this->arguments()->setDestinationLocalPath($path);
return $this;
}
*
* @return string
* The image destination format.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::getDestinationFormat() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function getDestinationFormat() {
- return $this->destinationFormat;
+ @trigger_error('getDestinationFormat() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::getDestinationFormat() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ return $this->arguments()->getDestinationFormat();
}
/**
* The image destination format.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::setDestinationFormat() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function setDestinationFormat($format) {
- $this->destinationFormat = $this->formatMapper->isFormatEnabled($format) ? $format : '';
+ @trigger_error('setDestinationFormat() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::setDestinationFormat() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ $this->arguments()->setDestinationFormat($this->formatMapper->isFormatEnabled($format) ? $format : '');
return $this;
}
* The destination image file extension.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImagemagickExecArguments::setDestinationFormatFromExtension() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2938375
*/
public function setDestinationFormatFromExtension($extension) {
- $format = $this->formatMapper->getFormatFromExtension($extension);
- $this->destinationFormat = $format ?: '';
+ @trigger_error('setDestinationFormatFromExtension() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImagemagickExecArguments::setDestinationFormatFromExtension() instead. See https://www.drupal.org/project/imagemagick/issues/2938375.', E_USER_DEPRECATED);
+ $this->arguments()->setDestinationFormatFromExtension($extension);
return $this;
}
* {@inheritdoc}
*/
public function getMimeType() {
- return $this->formatMapper->getMimeTypeFromFormat($this->getSourceFormat());
+ return $this->formatMapper->getMimeTypeFromFormat($this->arguments()->getSourceFormat());
+ }
+
+ /**
+ * Returns the current ImagemagickExecArguments object.
+ *
+ * @return \Drupal\imagemagick\ImagemagickExecArguments
+ * The current ImagemagickExecArguments object.
+ */
+ public function arguments() {
+ return $this->arguments;
}
/**
*
* @return string[]
* The array of command line arguments.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::arguments()
+ * instead, using ImagemagickExecArguments methods to manipulate arguments.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
*/
public function getArguments() {
- return $this->arguments ?: [];
+ @trigger_error('getArguments() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ::arguments() instead, using ImagemagickExecArguments methods to manipulate arguments. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ return $this->arguments()->getArguments();
+ }
+
+ /**
+ * Gets the command line arguments string for the binary.
+ *
+ * Removes any argument used internally within the toolkit.
+ *
+ * @return string
+ * The string of command line arguments.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::toString() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
+ */
+ public function getStringForBinary() {
+ @trigger_error('getStringForBinary() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::toString() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ return $this->arguments()->getStringForBinary();
}
/**
* The command line argument to be added.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::add() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
*/
public function addArgument($arg) {
- $this->arguments[] = $arg;
+ @trigger_error('addArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::add() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ $this->arguments()->addArgument($arg);
return $this;
}
* The command line argument to be prepended.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::add() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
*/
public function prependArgument($arg) {
- array_unshift($this->arguments, $arg);
+ @trigger_error('prependArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::add() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ $this->arguments()->prependArgument($arg);
return $this;
}
* @return bool
* Returns the array key for the argument if it is found in the array,
* FALSE otherwise.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::find() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2925780
*/
public function findArgument($arg) {
- foreach ($this->getArguments() as $i => $a) {
- if (strpos($a, $arg) === 0) {
- return $i;
- }
- }
- return FALSE;
+ @trigger_error('findArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::find() instead. See https://www.drupal.org/project/imagemagick/issues/2925780.', E_USER_DEPRECATED);
+ return $this->arguments()->findArgument($arg);
}
/**
* The index of the command line argument to be removed.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::remove() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936615
*/
public function removeArgument($index) {
- if (isset($this->arguments[$index])) {
- unset($this->arguments[$index]);
- }
+ @trigger_error('removeArgument() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::remove() instead. See https://www.drupal.org/project/imagemagick/issues/2936615.', E_USER_DEPRECATED);
+ $this->arguments()->removeArgument($index);
return $this;
}
* Resets the command line arguments.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::reset() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936615
*/
public function resetArguments() {
- $this->arguments = [];
+ @trigger_error('resetArguments() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::reset() instead. See https://www.drupal.org/project/imagemagick/issues/2936615.', E_USER_DEPRECATED);
+ $this->arguments()->resetArguments();
return $this;
}
* Returns the count of command line arguments.
*
* @return $this
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::find() instead, then count the result.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936615
*/
public function countArguments() {
- return count($this->arguments);
+ @trigger_error('countArguments() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::find() instead, then count the result. See https://www.drupal.org/project/imagemagick/issues/2936615.', E_USER_DEPRECATED);
+ return $this->arguments()->countArguments();
}
/**
* Escapes a string.
*
- * PHP escapeshellarg() drops non-ascii characters, this is a replacement.
- *
- * Stop-gap replacement until core issue #1561214 has been solved. Solution
- * proposed in #1502924-8.
- *
- * PHP escapeshellarg() on Windows also drops % (percentage sign) characters.
- * We prevent this by replacing it with a pattern that should be highly
- * unlikely to appear in the string itself and does not contain any
- * "dangerous" character at all (very wide definition of dangerous). After
- * escaping we replace that pattern back with a % character.
- *
* @param string $arg
* The string to escape.
*
* @return string
- * An escaped string for use in the ::imagemagickExec method.
+ * An escaped string for use in the
+ * ImagemagickExecManagerInterface::execute method.
+ *
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * ImageMagickExecArguments::escape() instead.
+ *
+ * @see https://www.drupal.org/project/imagemagick/issues/2936680
*/
public function escapeShellArg($arg) {
- static $percentage_sign_replace_pattern = '1357902468IMAGEMAGICKPERCENTSIGNPATTERN8642097531';
-
- // Put the configured locale in a static to avoid multiple config get calls
- // in the same request.
- static $config_locale;
-
- if (!isset($config_locale)) {
- $config_locale = $this->configFactory->get('imagemagick.settings')->get('locale');
- if (empty($config_locale)) {
- $config_locale = FALSE;
- }
- }
-
- if ($this->isWindows) {
- // Temporarily replace % characters.
- $arg = str_replace('%', $percentage_sign_replace_pattern, $arg);
- }
-
- // If no locale specified in config, return with standard.
- if ($config_locale === FALSE) {
- $arg_escaped = escapeshellarg($arg);
- }
- else {
- // Get the current locale.
- $current_locale = setlocale(LC_CTYPE, 0);
- if ($current_locale != $config_locale) {
- // Temporarily swap the current locale with the configured one.
- setlocale(LC_CTYPE, $config_locale);
- $arg_escaped = escapeshellarg($arg);
- setlocale(LC_CTYPE, $current_locale);
- }
- else {
- $arg_escaped = escapeshellarg($arg);
- }
- }
-
- // Get our % characters back.
- if ($this->isWindows) {
- $arg_escaped = str_replace($percentage_sign_replace_pattern, '%', $arg_escaped);
- }
-
- return $arg_escaped;
+ @trigger_error('escapeShellArg() is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use ImageMagickExecArguments::escape() instead. See https://www.drupal.org/project/imagemagick/issues/2936680.', E_USER_DEPRECATED);
+ return $this->getExecManager()->escapeShellArg($arg);
}
/**
* {@inheritdoc}
*/
public function save($destination) {
- $this->setDestination($destination);
+ $this->arguments()->setDestination($destination);
if ($ret = $this->convert()) {
// Allow modules to alter the destination file.
- $this->moduleHandler->alter('imagemagick_post_save', $this);
+ $this->moduleHandler->alter('imagemagick_post_save', $this->arguments);
// Reset local path to allow saving to other file.
- $this->setDestinationLocalPath('');
+ $this->arguments()->setDestinationLocalPath('');
}
return $ret;
}
* {@inheritdoc}
*/
public function parseFile() {
- // Allow modules to alter the source file.
- $this->moduleHandler->alter('imagemagick_pre_parse_file', $this);
if ($this->configFactory->get('imagemagick.settings')->get('use_identify')) {
return $this->parseFileViaIdentify();
}
* TRUE if the file could be found and is an image, FALSE otherwise.
*/
protected function parseFileViaIdentify() {
- // Prepare the -format argument according to the graphics package in use.
- switch ($this->getPackage()) {
- case 'imagemagick':
- $this->addArgument('-format ' . $this->escapeShellArg("format:%[magick]|width:%[width]|height:%[height]|exif_orientation:%[EXIF:Orientation]\\n"));
- break;
-
- case 'graphicsmagick':
- $this->addArgument('-format ' . $this->escapeShellArg("format:%m|width:%w|height:%h|exif_orientation:%[EXIF:Orientation]\\n"));
- break;
-
+ // Get 'imagemagick_identify' metadata for this image. The file metadata
+ // plugin will fetch it from the file via the ::identify() method if data
+ // is not already available.
+ $file_md = $this->fileMetadataManager->uri($this->getSource());
+ $data = $file_md->getMetadata('imagemagick_identify');
+
+ // No data, return.
+ if (!$data) {
+ return FALSE;
}
- if ($identify_output = $this->identify()) {
- $frames = explode("\n", $identify_output);
-
- // Remove empty items at the end of the array.
- while (empty($frames[count($frames) - 1])) {
- array_pop($frames);
- }
-
- // If remaining items are more than one, we have a multi-frame image.
- if (count($frames) > 1) {
- $this->setFrames(count($frames));
- }
-
- // Take information from the first frame.
- $info = explode('|', $frames[0]);
- $data = [];
- foreach ($info as $item) {
- list($key, $value) = explode(':', $item);
- $data[trim($key)] = trim($value);
- }
- $format = isset($data['format']) ? $data['format'] : NULL;
- if ($this->formatMapper->isFormatEnabled($format)) {
- $this
- ->setSourceFormat($format)
- ->setWidth((int) $data['width'])
- ->setHeight((int) $data['height'])
- ->setExifOrientation($data['exif_orientation']);
- return TRUE;
- }
+ // Sets the local file path to the one retrieved by identify if available.
+ if ($source_local_path = $file_md->getMetadata('imagemagick_identify', 'source_local_path')) {
+ $this->arguments()->setSourceLocalPath($source_local_path);
}
- return FALSE;
- }
- /**
- * Parses the image file using the PHP getimagesize() function.
- *
- * @return bool
- * TRUE if the file could be found and is an image, FALSE otherwise.
- */
- protected function parseFileViaGetImageSize() {
- if ($data = @getimagesize($this->getSourceLocalPath())) {
- $format = $this->formatMapper->getFormatFromExtension(image_type_to_extension($data[2], FALSE));
- if ($format) {
- $this
- ->setSourceFormat($format)
- ->setWidth($data[0])
- ->setHeight($data[1]);
- return TRUE;
+ // Process parsed data from the first frame.
+ $format = $file_md->getMetadata('imagemagick_identify', 'format');
+ if ($this->formatMapper->isFormatEnabled($format)) {
+ $this
+ ->setWidth((int) $file_md->getMetadata('imagemagick_identify', 'width'))
+ ->setHeight((int) $file_md->getMetadata('imagemagick_identify', 'height'))
+ ->setExifOrientation($file_md->getMetadata('imagemagick_identify', 'exif_orientation'))
+ ->setFrames($file_md->getMetadata('imagemagick_identify', 'frames_count'));
+ $this->arguments()
+ ->setSourceFormat($format);
+ // Only Imagemagick allows to get colorspace and profiles information
+ // via 'identify'.
+ if ($this->getExecManager()->getPackage() === 'imagemagick') {
+ $this->setColorspace($file_md->getMetadata('imagemagick_identify', 'colorspace'));
+ $this->setProfiles($file_md->getMetadata('imagemagick_identify', 'profiles'));
}
- };
- return FALSE;
- }
-
- /**
- * Parses the image file EXIF data using the PHP read_exif_data() function.
- *
- * @return $this
- */
- protected function parseExifData() {
- $continue = TRUE;
- // Test to see if EXIF is supported by the image format.
- $mime_type = $this->getMimeType();
- if (!in_array($mime_type, ['image/jpeg', 'image/tiff'])) {
- // Not an EXIF enabled image.
- $continue = FALSE;
- }
- $local_path = $this->getSourceLocalPath();
- if ($continue && empty($local_path)) {
- // No file path available. Most likely a new image from scratch.
- $continue = FALSE;
- }
- if ($continue && !function_exists('exif_read_data')) {
- // No PHP EXIF extension enabled, return.
- $this->logger->error('The PHP EXIF extension is not installed. The \'imagemagick\' toolkit is unable to automatically determine image orientation.');
- $continue = FALSE;
- }
- if ($continue && ($exif_data = @exif_read_data($this->getSourceLocalPath()))) {
- $this->exifInfo = $exif_data;
- return $this;
+ return TRUE;
}
- $this->setExifOrientation(NULL);
- return $this;
- }
- /**
- * Calls the identify executable on the specified file.
- *
- * @return bool
- * TRUE if the file could be identified, FALSE otherwise.
- */
- protected function identify() {
- // Allow modules to alter the command line parameters.
- $command = 'identify';
- $this->moduleHandler->alter('imagemagick_arguments', $this, $command);
-
- // Executes the command.
- $output = NULL;
- $ret = $this->imagemagickExec($command, $output);
- $this->resetArguments();
- return ($ret === TRUE) ? $output : FALSE;
+ return FALSE;
}
/**
- * Calls the convert executable with the specified arguments.
+ * Parses the image file using the file metadata 'getimagesize' plugin.
*
* @return bool
- * TRUE if the file could be converted, FALSE otherwise.
- */
- protected function convert() {
- // Allow modules to alter the command line parameters.
- $command = 'convert';
- $this->moduleHandler->alter('imagemagick_arguments', $this, $command);
-
- // Executes the command.
- return $this->imagemagickExec($command) === TRUE ? file_exists($this->getDestinationLocalPath()) : FALSE;
- }
-
- /**
- * Executes the convert executable as shell command.
+ * TRUE if the file could be found and is an image, FALSE otherwise.
*
- * @param string $command
- * The executable to run.
- * @param string $command_args
- * A string containing arguments to pass to the command, which must have
- * been passed through $this->escapeShellArg() already.
- * @param string &$output
- * (optional) A variable to assign the shell stdout to, passed by reference.
- * @param string &$error
- * (optional) A variable to assign the shell stderr to, passed by reference.
- * @param string $path
- * (optional) A custom file path to the executable binary.
+ * @deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use
+ * parseFileViaIdentify() instead.
*
- * @return mixed
- * The return value depends on the shell command result:
- * - Boolean TRUE if the command succeeded.
- * - Boolean FALSE if the shell process could not be executed.
- * - Error exit status code integer returned by the executable.
+ * @see https://www.drupal.org/project/imagemagick/issues/2938377
*/
- protected function imagemagickExec($command, &$output = NULL, &$error = NULL, $path = NULL) {
- switch ($command) {
- case 'convert':
- $binary = $this->getPackage() === 'imagemagick' ? 'convert' : 'gm';
- break;
-
- case 'identify':
- $binary = $this->getPackage() === 'imagemagick' ? 'identify' : 'gm';
- break;
-
- }
- $cmd = $this->getExecutable($binary, $path);
-
- if ($source_path = $this->getSourceLocalPath()) {
- $source_path = $this->escapeShellArg($source_path);
- }
-
- if ($destination_path = $this->getDestinationLocalPath()) {
- $destination_path = $this->escapeShellArg($destination_path);
- // If the format of the derivative image has to be changed, concatenate
- // the new image format and the destination path, delimited by a colon.
- // @see http://www.imagemagick.org/script/command-line-processing.php#output
- if (($format = $this->getDestinationFormat()) !== '') {
- $destination_path = $format . ':' . $destination_path;
- }
- }
-
- switch($command) {
- case 'identify':
- switch($this->getPackage()) {
- case 'imagemagick':
- // ImageMagick syntax:
- // identify [arguments] source
- $cmdline = implode(' ', $this->getArguments()) . ' ' . $source_path;
- break;
-
- case 'graphicsmagick':
- // GraphicsMagick syntax:
- // gm identify [arguments] source
- $cmdline = 'identify ' . implode(' ', $this->getArguments()) . ' ' . $source_path;
- break;
-
- }
- break;
-
- case 'convert':
- switch($this->getPackage()) {
- case 'imagemagick':
- // ImageMagick syntax:
- // convert input [arguments] output
- // @see http://www.imagemagick.org/Usage/basics/#cmdline
- $cmdline = $source_path . ' ' . implode(' ', $this->getArguments()) . ' ' . $destination_path;
- break;
-
- case 'graphicsmagick':
- // GraphicsMagick syntax:
- // gm convert [arguments] input output
- // @see http://www.graphicsmagick.org/GraphicsMagick.html
- $cmdline = 'convert ' . implode(' ', $this->getArguments()) . ' ' . $source_path . ' ' . $destination_path;
- break;
+ protected function parseFileViaGetImageSize() {
+ @trigger_error('Image file parsing via \'getimagesize\' is deprecated in 8.x-2.3, will be removed in 8.x-3.0. Use parsing via \'identify\' instead. See https://www.drupal.org/project/imagemagick/issues/2938377.', E_USER_DEPRECATED);
+ // Allow modules to alter the source file.
+ $this->moduleHandler->alter('imagemagick_pre_parse_file', $this->arguments);
- }
- break;
+ // Get 'getimagesize' metadata for this image.
+ $file_md = $this->fileMetadataManager->uri($this->getSource());
+ $data = $file_md->getMetadata('getimagesize');
+ // No data, return.
+ if (!$data) {
+ return FALSE;
}
- $return_code = $this->runOsShell($cmd, $cmdline, $this->getPackage(), $output, $error);
-
- if ($return_code !== FALSE) {
- // If the executable returned a non-zero code, log to the watchdog.
- if ($return_code != 0) {
- if ($error === '') {
- // If there is no error message, and allowed in config, log a
- // warning.
- if ($this->configFactory->get('imagemagick.settings')->get('log_warnings') === TRUE) {
- $this->logger->warning("@suite returned with code @code [command: @command @cmdline]", [
- '@suite' => $this->getPackageLabel(),
- '@code' => $return_code,
- '@command' => $cmd,
- '@cmdline' => $cmdline,
- ]);
- }
- }
- else {
- // Log $error with context information.
- $this->logger->error("@suite error @code: @error [command: @command @cmdline]", [
- '@suite' => $this->getPackageLabel(),
- '@code' => $return_code,
- '@error' => $error,
- '@command' => $cmd,
- '@cmdline' => $cmdline,
- ]);
- }
- // Executable exited with an error code, return it.
- return $return_code;
- }
-
- // The shell command was executed successfully.
+ // Process parsed data.
+ $format = $this->formatMapper->getFormatFromExtension(image_type_to_extension($data[2], FALSE));
+ if ($format) {
+ $this
+ ->setWidth($data[0])
+ ->setHeight($data[1])
+ // 'getimagesize' cannot provide information on number of frames in an
+ // image and EXIF orientation, so set to defaults.
+ ->setExifOrientation(static::EXIF_ORIENTATION_NOT_FETCHED)
+ ->setFrames(NULL);
+ $this->arguments()
+ ->setSourceFormat($format);
return TRUE;
}
- // The shell command could not be executed.
- return FALSE;
- }
-
- /**
- * Executes a command on the operating system.
- *
- * @param string $command
- * The command to run.
- * @param string $arguments
- * The arguments of the command to run.
- * @param string $id
- * An identifier for the process to be spawned on the operating system.
- * @param string &$output
- * (optional) A variable to assign the shell stdout to, passed by
- * reference.
- * @param string &$error
- * (optional) A variable to assign the shell stderr to, passed by
- * reference.
- *
- * @return int|bool
- * The operating system returned code, or FALSE if it was not possible to
- * execute the command.
- */
- protected function runOsShell($command, $arguments, $id, &$output = NULL, &$error = NULL) {
- if ($this->isWindows) {
- // Use Window's start command with the /B flag to make the process run in
- // the background and avoid a shell command line window from showing up.
- // @see http://us3.php.net/manual/en/function.exec.php#56599
- // Use /D to run the command from PHP's current working directory so the
- // file paths don't have to be absolute.
- $command = 'start "' . $id . '" /D ' . $this->escapeShellArg($this->appRoot) . ' /B ' . $this->escapeShellArg($command);
- }
- $command_line = $command . ' ' . $arguments;
-
- // Executes the command on the OS via proc_open().
- $descriptors = [
- // This is stdin.
- 0 => ['pipe', 'r'],
- // This is stdout.
- 1 => ['pipe', 'w'],
- // This is stderr.
- 2 => ['pipe', 'w'],
- ];
-
- if ($h = proc_open($command_line, $descriptors, $pipes, $this->appRoot)) {
- $output = '';
- while (!feof($pipes[1])) {
- $output .= fgets($pipes[1]);
- }
- $output = utf8_encode($output);
- $error = '';
- while (!feof($pipes[2])) {
- $error .= fgets($pipes[2]);
- }
- $error = utf8_encode($error);
- fclose($pipes[0]);
- fclose($pipes[1]);
- fclose($pipes[2]);
- $return_code = proc_close($h);
- }
- else {
- $return_code = FALSE;
- }
- // Process debugging information if required.
- if ($this->configFactory->get('imagemagick.settings')->get('debug')) {
- $this->debugMessage('@suite command: <pre>@raw</pre>', [
- '@suite' => $this->getPackageLabel($id),
- '@raw' => print_r($command_line, TRUE),
- ]);
- if ($output !== '') {
- $this->debugMessage('@suite output: <pre>@raw</pre>', [
- '@suite' => $this->getPackageLabel($id),
- '@raw' => print_r($output, TRUE),
- ]);
- }
- if ($error !== '') {
- $this->debugMessage('@suite error @return_code: <pre>@raw</pre>', [
- '@suite' => $this->getPackageLabel($id),
- '@return_code' => $return_code,
- '@raw' => print_r($error, TRUE),
- ]);
- }
- }
-
- return $return_code;
+ return FALSE;
}
/**
- * Logs a debug message, and shows it on the screen for authorized users.
+ * Calls the convert executable with the specified arguments.
*
- * @param string $message
- * The debug message.
- * @param string[] $context
- * Context information.
+ * @return bool
+ * TRUE if the file could be converted, FALSE otherwise.
*/
- public function debugMessage($message, array $context) {
- $this->logger->debug($message, $context);
- if (\Drupal::currentUser()->hasPermission('administer site configuration')) {
- // Strips raw text longer than 10 lines to optimize displaying.
- if (isset($context['@raw'])) {
- $raw = explode("\n", $context['@raw']);
- if (count($raw) > 10) {
- $tmp = [];
- for ($i = 0; $i < 9; $i++) {
- $tmp[] = $raw[$i];
- }
- $tmp[] = (string) $this->t('[Further text stripped. The watchdog log has the full text.]');
- $context['@raw'] = implode("\n", $tmp);
- }
- }
- drupal_set_message($this->t($message, $context), 'status', TRUE);
- }
- }
+ protected function convert() {
+ $config = $this->configFactory->get('imagemagick.settings');
- /**
- * Returns the full path to the executable.
- *
- * @param string $binary
- * The program to execute, typically 'convert', 'identify' or 'gm'.
- * @param string $path
- * (optional) A custom path to the folder of the executable. When left
- * empty, the setting imagemagick.settings.path_to_binaries is taken.
- *
- * @return string
- * The full path to the executable.
- */
- public function getExecutable($binary, $path = NULL) {
- // $path is only passed from the validation of the image toolkit form, on
- // which the path to convert is configured. @see ::checkPath()
- if (!isset($path)) {
- $path = $this->configFactory->get('imagemagick.settings')->get('path_to_binaries');
- }
+ // Ensure sourceLocalPath is prepared.
+ $this->ensureSourceLocalPath();
- $executable = $binary;
- if ($this->isWindows) {
- $executable .= '.exe';
+ // Allow modules to alter the command line parameters.
+ $command = 'convert';
+ $this->moduleHandler->alter('imagemagick_arguments', $this->arguments, $command);
+
+ // Delete any cached file metadata for the destination image file, before
+ // creating a new one, and release the URI from the manager so that
+ // metadata will not stick in the same request.
+ $this->fileMetadataManager->deleteCachedMetadata($this->arguments()->getDestination());
+ $this->fileMetadataManager->release($this->arguments()->getDestination());
+
+ // When destination format differs from source format, and source image
+ // is multi-frame, convert only the first frame.
+ $destination_format = $this->arguments()->getDestinationFormat() ?: $this->arguments()->getSourceFormat();
+ if ($this->arguments()->getSourceFormat() !== $destination_format && ($this->getFrames() === NULL || $this->getFrames() > 1)) {
+ $this->arguments()->setSourceFrames('[0]');
}
- return $path . $executable;
+ // Execute the command and return.
+ return $this->getExecManager()->execute($command, $this->arguments) && file_exists($this->arguments()->getDestinationLocalPath());
}
/**
]);
}
else {
- $status = $this->checkPath($this->configFactory->get('imagemagick.settings')->get('path_to_binaries'));
+ $status = $this->getExecManager()->checkPath($this->configFactory->get('imagemagick.settings')->get('path_to_binaries'));
if (!empty($status['errors'])) {
// Can not execute 'convert'.
$severity = REQUIREMENT_ERROR;
// No errors, report the version information.
$severity = REQUIREMENT_INFO;
$version_info = explode("\n", preg_replace('/\r/', '', Html::escape($status['output'])));
+ $value = array_shift($version_info);
$more_info_available = FALSE;
foreach ($version_info as $key => $item) {
- if (stripos($item, 'feature') !== FALSE || $key > 4) {
+ if (stripos($item, 'feature') !== FALSE || $key > 3) {
$more_info_available = TRUE;
break;
]);
}
}
- return [
+ $requirements = [
'imagemagick' => [
'title' => $this->t('ImageMagick'),
+ 'value' => isset($value) ? $value : NULL,
'description' => [
'#markup' => implode('<br />', $reported_info),
],
'severity' => $severity,
],
];
+
+ // Warn if parsing via 'getimagesize'.
+ // @todo remove in 8.x-3.0.
+ if ($this->configFactory->getEditable('imagemagick.settings')->get('use_identify') === FALSE) {
+ $requirements['imagemagick_getimagesize'] = [
+ 'title' => $this->t('ImageMagick'),
+ 'value' => $this->t('Use "identify" to parse image files'),
+ 'description' => $this->t('The toolkit is set to use the <kbd>getimagesize</kbd> PHP function to parse image files. This functionality will be dropped in the next major release of the Imagemagick module. Go to the <a href=":url">Image toolkit</a> settings page, and ensure that the \'Use "identify"\' flag in the \'Execution options\' tab is selected.', [
+ ':url' => Url::fromRoute('system.image_toolkit_settings')->toString(),
+ ]),
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+
+ return $requirements;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
- // When source image is multi-frame, convert only the first frame.
- if ($this->getToolkit()->getFrames()) {
- $path = $this->getToolkit()->getSourceLocalPath();
- if (strripos($path, '[0]', -3) === FALSE) {
- $this->getToolkit()->setSourceLocalPath($path . '[0]');
- }
- }
- $this->getToolkit()
- ->setFrames(NULL)
- ->setDestinationFormatFromExtension($arguments['extension']);
+ $this->getToolkit()->arguments()->setDestinationFormatFromExtension($arguments['extension']);
return TRUE;
}
*/
protected function execute(array $arguments) {
$this->getToolkit()
- ->resetArguments()
- ->setSourceLocalPath('')
- ->setSourceFormatFromExtension($arguments['extension'])
->setWidth($arguments['width'])
->setHeight($arguments['height'])
->setExifOrientation(NULL)
- ->setFrames(NULL);
+ ->setColorspace($this->getToolkit()->getExecManager()->getPackage() === 'imagemagick' ? 'sRGB' : NULL)
+ ->setProfiles([])
+ ->setFrames(1);
+ $this->getToolkit()->arguments()
+ ->setSourceFormatFromExtension($arguments['extension'])
+ ->setSourceLocalPath('')
+ ->reset();
$arg = '-size ' . $arguments['width'] . 'x' . $arguments['height'];
// Transparent color syntax for GIF files differs by package.
if ($arguments['extension'] === 'gif') {
- switch ($this->getToolkit()->getPackage()) {
+ switch ($this->getToolkit()->getExecManager()->getPackage()) {
case 'imagemagick':
- $arg .= ' xc:transparent -transparent-color ' . $this->getToolkit()->escapeShellArg($arguments['transparent_color']);
+ $arg .= ' xc:transparent -transparent-color ' . $this->escapeArgument($arguments['transparent_color']);
break;
case 'graphicsmagick':
- $arg .= ' xc:' . $this->getToolkit()->escapeShellArg($arguments['transparent_color']) . ' -transparent ' . $this->getToolkit()->escapeShellArg($arguments['transparent_color']);
+ $arg .= ' xc:' . $this->escapeArgument($arguments['transparent_color']) . ' -transparent ' . $this->escapeArgument($arguments['transparent_color']);
break;
}
$arg .= ' xc:transparent';
}
- $this->getToolkit()->addArgument($arg);
+ $this->addArgument($arg);
return TRUE;
}
// Even though the crop effect in Drupal core does not allow for negative
// offsets, ImageMagick supports them. Also note: if $x and $y are set to
// NULL then crop will create tiled images so we convert these to ints.
- $this->getToolkit()->addArgument(sprintf('-crop %dx%d%+d%+d!', $arguments['width'], $arguments['height'], $arguments['x'], $arguments['y']));
+ $this->addArgument(sprintf('-crop %dx%d%+d%+d!', $arguments['width'], $arguments['height'], $arguments['x'], $arguments['y']));
$this->getToolkit()->setWidth($arguments['width'])->setHeight($arguments['height']);
return TRUE;
}
* {@inheritdoc}
*/
protected function execute(array $arguments) {
- $this->getToolkit()->addArgument('-colorspace GRAY');
+ $this->addArgument('-colorspace GRAY');
return TRUE;
}
namespace Drupal\imagemagick\Plugin\ImageToolkit\Operation\imagemagick;
use Drupal\Core\ImageToolkit\ImageToolkitOperationBase;
+use Drupal\imagemagick\ImagemagickExecArguments;
+/**
+ * Base image toolkit operation class for Imagemagick.
+ */
abstract class ImagemagickImageToolkitOperationBase extends ImageToolkitOperationBase {
/**
* The correctly typed image toolkit for imagemagick operations.
*
* @return \Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit
+ * The correctly typed image toolkit for imagemagick operations.
*/
+ // @codingStandardsIgnoreStart
protected function getToolkit() {
return parent::getToolkit();
}
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Helper to add a command line argument.
+ *
+ * Adds the originating operation and plugin id to the $info array.
+ *
+ * @param string $argument
+ * The command line argument to be added.
+ * @param int $mode
+ * (optional) The mode of the argument in the command line. Determines if
+ * the argument should be placed before or after the source image file path.
+ * Defaults to ImagemagickExecArguments::POST_SOURCE.
+ * @param int $index
+ * (optional) The position of the argument in the arguments array.
+ * Reflects the sequence of arguments in the command line. Defaults to
+ * ImagemagickExecArguments::APPEND.
+ * @param array $info
+ * (optional) An optional array with information about the argument.
+ * Defaults to an empty array.
+ *
+ * @return \Drupal\imagemagick\ImagemagickExecArguments
+ * The Imagemagick arguments.
+ */
+ protected function addArgument($argument, $mode = ImagemagickExecArguments::POST_SOURCE, $index = ImagemagickExecArguments::APPEND, array $info = []) {
+ $plugin_definition = $this->getPluginDefinition();
+ $info = array_merge($info, [
+ 'image_toolkit_operation' => $plugin_definition['operation'],
+ 'image_toolkit_operation_plugin_id' => $plugin_definition['id'],
+ ]);
+ return $this->getToolkit()->arguments()->add($argument, $mode, $index, $info);
+ }
+
+ /**
+ * Helper to escape a command line argument.
+ *
+ * @param string $argument
+ * The string to escape.
+ *
+ * @return string
+ * An escaped string for use in the
+ * ImagemagickExecManagerInterface::execute method.
+ */
+ protected function escapeArgument($argument) {
+ return $this->getToolkit()->arguments()->escape($argument);
+ }
}
*/
protected function execute(array $arguments = []) {
if (!empty($arguments['filter'])) {
- $this->getToolkit()->addArgument('-filter ' . $arguments['filter']);
+ $this->addArgument('-filter ' . $arguments['filter']);
}
- $this->getToolkit()->addArgument('-resize ' . $arguments['width'] . 'x' . $arguments['height'] . '!');
+ $this->addArgument('-resize ' . $arguments['width'] . 'x' . $arguments['height'] . '!');
$this->getToolkit()->setWidth($arguments['width'])->setHeight($arguments['height']);
return TRUE;
}
*/
protected function execute(array $arguments) {
// Rotate.
- $arg = '-background ' . $this->getToolkit()->escapeShellArg($arguments['background']);
+ $arg = '-background ' . $this->escapeArgument($arguments['background']);
$arg .= ' -rotate ' . $arguments['degrees'];
$arg .= ' +repage';
- $this->getToolkit()->addArgument($arg);
+ $this->addArgument($arg);
// Need to resize the image after rotation to make sure it complies with
// the dimensions expected, calculated via the Rectangle class.
$box = new Rectangle($this->getToolkit()->getWidth(), $this->getToolkit()->getHeight());
$box = $box->rotate((float) $arguments['degrees']);
- return $this->getToolkit()->apply('resize', ['width' => $box->getBoundingWidth(), 'height' => $box->getBoundingHeight(), 'filter' => $arguments['resize_filter']]);
+ return $this->getToolkit()->apply('resize', [
+ 'width' => $box->getBoundingWidth(),
+ 'height' => $box->getBoundingHeight(),
+ 'filter' => $arguments['resize_filter'],
+ ]);
}
+
}
+++ /dev/null
-<?php
-
-/**
- * @todo #2311679, this is a stop-gap workaround
- * remove this once core has a solution in place.
- */
-
-namespace Drupal\imagemagick;
-
-use Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser;
-
-/**
- * Makes possible to guess the MIME type of a file using its extension.
- */
-class Todo2311679 extends ExtensionMimeTypeGuesser {
-
- public function getExtensionsForMimeType($mimetype) {
- if ($this->mapping === NULL) {
- $mapping = $this->defaultMapping;
- // Allow modules to alter the default mapping.
- $this->moduleHandler->alter('file_mimetype_mapping', $mapping);
- $this->mapping = $mapping;
- }
- if (!in_array($mimetype, $this->mapping['mimetypes'])) {
- return [];
- }
- $key = array_search($mimetype, $this->mapping['mimetypes']);
- $extensions = array_keys($this->mapping['extensions'], $key, TRUE);
- sort($extensions);
- return $extensions;
- }
-
- public function getMimeTypes() {
- if ($this->mapping === NULL) {
- $mapping = $this->defaultMapping;
- // Allow modules to alter the default mapping.
- $this->moduleHandler->alter('file_mimetype_mapping', $mapping);
- $this->mapping = $mapping;
- }
- return array_values($this->mapping['mimetypes']);
- }
-
-}
--- /dev/null
+<?php
+
+namespace Drupal\Tests\imagemagick\Functional;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Tests\TestFileCreationTrait;
+use Drupal\file_mdm\FileMetadataInterface;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests that Imagemagick integrates properly with File Metadata Manager.
+ *
+ * @group Imagemagick
+ */
+class ToolkitImagemagickFileMetadataTest extends BrowserTestBase {
+
+ use TestFileCreationTrait;
+
+ /**
+ * The image factory service.
+ *
+ * @var \Drupal\Core\Image\ImageFactory
+ */
+ protected $imageFactory;
+
+ /**
+ * A directory for image test file results.
+ *
+ * @var string
+ */
+ protected $testDirectory;
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ protected static $modules = [
+ 'system',
+ 'simpletest',
+ 'file_test',
+ 'imagemagick',
+ 'file_mdm',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ parent::setUp();
+
+ // Set the image factory.
+ $this->imageFactory = $this->container->get('image.factory');
+
+ // Prepare a directory for test file results.
+ $this->testDirectory = 'public://imagetest';
+
+ // Change the toolkit.
+ \Drupal::configFactory()->getEditable('system.image')
+ ->set('toolkit', 'imagemagick')
+ ->save();
+ \Drupal::configFactory()->getEditable('imagemagick.settings')
+ ->set('debug', FALSE)
+ ->set('binaries', 'imagemagick')
+ ->set('quality', 100)
+ ->save();
+
+ // Set the toolkit on the image factory.
+ $this->imageFactory->setToolkitId('imagemagick');
+ }
+
+ /**
+ * Provides data for testFileMetadata.
+ *
+ * @return array[]
+ * A simple array of simple arrays, each having the following elements:
+ * - binaries to use for testing.
+ * - parsing method to use for testing.
+ */
+ public function providerFileMetadataTest() {
+ return [
+ ['imagemagick', 'imagemagick_identify'],
+ ['graphicsmagick', 'imagemagick_identify'],
+ ];
+ }
+
+ /**
+ * Test image toolkit integration with file metadata manager.
+ *
+ * @param string $binaries
+ * The graphics package binaries to use for testing.
+ * @param string $parsing_method
+ * The parsing method to use for testing.
+ *
+ * @dataProvider providerFileMetadataTest
+ */
+ public function testFileMetadata($binaries, $parsing_method) {
+ $config = \Drupal::configFactory()->getEditable('imagemagick.settings');
+ $config_mdm = \Drupal::configFactory()->getEditable('file_mdm.settings');
+
+ // Reset file_mdm settings.
+ $config_mdm
+ ->set('metadata_cache.enabled', TRUE)
+ ->set('metadata_cache.disallowed_paths', [])
+ ->save();
+
+ // Execute tests with selected binaries.
+ // The test can only be executed if binaries are available on the shell
+ // path.
+ $config
+ ->set('binaries', $binaries)
+ ->set('use_identify', $parsing_method === 'imagemagick_identify')
+ ->save();
+ $status = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick')->getExecManager()->checkPath('');
+ if (!empty($status['errors'])) {
+ // Bots running automated test on d.o. do not have binaries installed,
+ // so the test will be skipped; it can be run locally where binaries are
+ // installed.
+ $this->markTestSkipped("Tests for '{$binaries}' cannot run because the binaries are not available on the shell path.");
+ }
+
+ // A list of files that will be tested.
+ $files = [
+ 'public://image-test.png' => [
+ 'width' => 40,
+ 'height' => 20,
+ 'frames' => 1,
+ 'mimetype' => 'image/png',
+ 'colorspace' => 'SRGB',
+ 'profiles' => [],
+ ],
+ 'public://image-test.gif' => [
+ 'width' => 40,
+ 'height' => 20,
+ 'frames' => 1,
+ 'mimetype' => 'image/gif',
+ 'colorspace' => 'SRGB',
+ 'profiles' => [],
+ ],
+ 'dummy-remote://image-test.jpg' => [
+ 'width' => 40,
+ 'height' => 20,
+ 'frames' => 1,
+ 'mimetype' => 'image/jpeg',
+ 'colorspace' => 'SRGB',
+ 'profiles' => [],
+ ],
+ 'public://test-multi-frame.gif' => [
+ 'skip_dimensions_check' => TRUE,
+ 'frames' => 13,
+ 'mimetype' => 'image/gif',
+ 'colorspace' => 'SRGB',
+ 'profiles' => [],
+ ],
+ 'public://test-exif.jpeg' => [
+ 'skip_dimensions_check' => TRUE,
+ 'frames' => 1,
+ 'mimetype' => 'image/jpeg',
+ 'colorspace' => 'SRGB',
+ 'profiles' => ['exif'],
+ ],
+ 'public://test-exif-icc.jpeg' => [
+ 'skip_dimensions_check' => TRUE,
+ 'frames' => 1,
+ 'mimetype' => 'image/jpeg',
+ 'colorspace' => 'SRGB',
+ 'profiles' => ['exif', 'icc'],
+ ],
+ ];
+
+ // Setup a list of tests to perform on each type.
+ $operations = [
+ 'resize' => [
+ 'function' => 'resize',
+ 'arguments' => ['width' => 20, 'height' => 10],
+ 'width' => 20,
+ 'height' => 10,
+ ],
+ 'scale_x' => [
+ 'function' => 'scale',
+ 'arguments' => ['width' => 20],
+ 'width' => 20,
+ 'height' => 10,
+ ],
+ // Fuchsia background.
+ 'rotate_5' => [
+ 'function' => 'rotate',
+ 'arguments' => ['degrees' => 5, 'background' => '#FF00FF'],
+ 'width' => 41,
+ 'height' => 23,
+ ],
+ 'convert_jpg' => [
+ 'function' => 'convert',
+ 'width' => 40,
+ 'height' => 20,
+ 'arguments' => ['extension' => 'jpeg'],
+ 'mimetype' => 'image/jpeg',
+ ],
+ ];
+
+ // The file metadata manager service.
+ $fmdm = $this->container->get('file_metadata_manager');
+
+ // Prepare a copy of test files.
+ $this->getTestFiles('image');
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-multi-frame.gif', 'public://', FILE_EXISTS_REPLACE);
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg', 'public://', FILE_EXISTS_REPLACE);
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-exif-icc.jpeg', 'public://', FILE_EXISTS_REPLACE);
+
+ // Perform tests without caching.
+ $config_mdm->set('metadata_cache.enabled', FALSE)->save();
+ foreach ($files as $source_uri => $source_image_data) {
+ $this->assertFalse($fmdm->has($source_uri));
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertTrue($fmdm->has($source_uri));
+ $first = TRUE;
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up a fresh image.
+ if ($first) {
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ $this->assertFalse($fmdm->has($saved_uri));
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from file.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ $fmdm->release($saved_uri);
+
+ $first = FALSE;
+ }
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+ }
+
+ // Perform tests with caching.
+ $config_mdm->set('metadata_cache.enabled', TRUE)->save();
+ foreach ($files as $source_uri => $source_image_data) {
+ $first = TRUE;
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up a fresh image.
+ $this->assertFalse($fmdm->has($source_uri));
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertTrue($fmdm->has($source_uri));
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ $source_image = $this->imageFactory->get($source_uri);
+ if ($first) {
+ // First time load, metadata loaded from file.
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ // Further loads, metadata loaded from cache.
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ $this->assertFalse($fmdm->has($saved_uri));
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service. Should be cached.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from cache.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $saved_image_md->isMetadataLoaded($parsing_method));
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ $fmdm->release($saved_uri);
+
+ // We release the source image FileMetadata at each cycle to ensure
+ // that metadata is read from cache.
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+
+ $first = FALSE;
+ }
+ }
+
+ // Open source images again after deleting the temp folder files.
+ // Source image data should now be cached, but temp files non existing.
+ // Therefore we test that the toolkit can create a new temp file copy.
+ // Note: on Windows, temp imagemagick file names have a
+ // imaNNN.tmp.[image_extension] pattern so we cannot scan for
+ // 'imagemagick'.
+ $directory_scan = file_scan_directory('temporary://', '/ima.*/');
+ $this->assertGreaterThan(0, count($directory_scan));
+ foreach ($directory_scan as $file) {
+ file_unmanaged_delete($file->uri);
+ }
+ $directory_scan = file_scan_directory('temporary://', '/ima.*/');
+ $this->assertEquals(0, count($directory_scan));
+ foreach ($files as $source_uri => $source_image_data) {
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up the source image. Parsing should be fully cached now.
+ $fmdm->release($source_uri);
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $source_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ $this->assertFalse($fmdm->has($saved_uri));
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service. Should be cached.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from cache.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $saved_image_md->isMetadataLoaded($parsing_method));
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ $fmdm->release($saved_uri);
+ }
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+ }
+
+ // Files in temporary:// must not be cached.
+ if ($parsing_method === 'imagemagick_identify') {
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-multi-frame.gif', 'temporary://', FILE_EXISTS_REPLACE);
+ $source_uri = 'temporary://test-multi-frame.gif';
+ $fmdm->release($source_uri);
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded('imagemagick_identify'));
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded('imagemagick_identify'));
+ $fmdm->release($source_uri);
+ $source_image_md = $fmdm->uri($source_uri);
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded('imagemagick_identify'));
+ }
+
+ // Invalidate cache, and open source images again. Now, all files should be
+ // parsed again.
+ Cache::InvalidateTags([
+ 'config:imagemagick.file_metadata_plugin.imagemagick_identify',
+ 'config:file_mdm.file_metadata_plugin.getimagesize',
+ ]);
+ // Disallow caching on the test results directory.
+ $config_mdm->set('metadata_cache.disallowed_paths', ['public://imagetest/*'])->save();
+ foreach ($files as $source_uri => $source_image_data) {
+ $fmdm->release($source_uri);
+ }
+ foreach ($files as $source_uri => $source_image_data) {
+ $this->assertFalse($fmdm->has($source_uri));
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertTrue($fmdm->has($source_uri));
+ $first = TRUE;
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up a fresh image.
+ if ($first) {
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ $this->assertFalse($fmdm->has($saved_uri));
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ $this->assertEquals($source_image_data['profiles'], $source_image->getToolkit()->getProfiles());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from file.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ $fmdm->release($saved_uri);
+
+ $first = FALSE;
+ }
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+ }
+ }
+
+ /**
+ * Provides data for testFileMetadataLegacy.
+ *
+ * @return array[]
+ * A simple array of simple arrays, each having the following elements:
+ * - binaries to use for testing.
+ * - parsing method to use for testing.
+ *
+ * @todo remove in 8.x-3.0.
+ */
+ public function providerFileMetadataTestLegacy() {
+ return [
+ ['imagemagick', 'getimagesize'],
+ ['graphicsmagick', 'getimagesize'],
+ ];
+ }
+
+ /**
+ * Test legacy image toolkit integration with file metadata manager.
+ *
+ * @param string $binaries
+ * The graphics package binaries to use for testing.
+ * @param string $parsing_method
+ * The parsing method to use for testing.
+ *
+ * @todo remove in 8.x-3.0.
+ *
+ * @dataProvider providerFileMetadataTestLegacy
+ *
+ * @group legacy
+ */
+ public function testFileMetadataLegacy($binaries, $parsing_method) {
+ $config = \Drupal::configFactory()->getEditable('imagemagick.settings');
+ $config_mdm = \Drupal::configFactory()->getEditable('file_mdm.settings');
+
+ // Reset file_mdm settings.
+ $config_mdm
+ ->set('metadata_cache.enabled', TRUE)
+ ->set('metadata_cache.disallowed_paths', [])
+ ->save();
+
+ // Execute tests with selected binaries.
+ // The test can only be executed if binaries are available on the shell
+ // path.
+ $config
+ ->set('binaries', $binaries)
+ ->set('use_identify', $parsing_method === 'imagemagick_identify')
+ ->save();
+ $status = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick')->getExecManager()->checkPath('');
+ if (!empty($status['errors'])) {
+ // Bots running automated test on d.o. do not have binaries installed,
+ // so the test will be skipped; it can be run locally where binaries are
+ // installed.
+ $this->markTestSkipped("Tests for '{$binaries}' cannot run because the binaries are not available on the shell path.");
+ }
+
+ // A list of files that will be tested.
+ $files = [
+ 'public://image-test.png' => [
+ 'width' => 40,
+ 'height' => 20,
+ 'frames' => 1,
+ 'mimetype' => 'image/png',
+ 'colorspace' => 'SRGB',
+ ],
+ 'public://image-test.gif' => [
+ 'width' => 40,
+ 'height' => 20,
+ 'frames' => 1,
+ 'mimetype' => 'image/gif',
+ 'colorspace' => 'SRGB',
+ ],
+ 'dummy-remote://image-test.jpg' => [
+ 'width' => 40,
+ 'height' => 20,
+ 'frames' => 1,
+ 'mimetype' => 'image/jpeg',
+ 'colorspace' => 'SRGB',
+ ],
+ 'public://test-multi-frame.gif' => [
+ 'skip_dimensions_check' => TRUE,
+ 'frames' => 13,
+ 'mimetype' => 'image/gif',
+ 'colorspace' => 'SRGB',
+ ],
+ ];
+
+ // Setup a list of tests to perform on each type.
+ $operations = [
+ 'resize' => [
+ 'function' => 'resize',
+ 'arguments' => ['width' => 20, 'height' => 10],
+ 'width' => 20,
+ 'height' => 10,
+ ],
+ 'scale_x' => [
+ 'function' => 'scale',
+ 'arguments' => ['width' => 20],
+ 'width' => 20,
+ 'height' => 10,
+ ],
+ // Fuchsia background.
+ 'rotate_5' => [
+ 'function' => 'rotate',
+ 'arguments' => ['degrees' => 5, 'background' => '#FF00FF'],
+ 'width' => 41,
+ 'height' => 23,
+ ],
+ 'convert_jpg' => [
+ 'function' => 'convert',
+ 'width' => 40,
+ 'height' => 20,
+ 'arguments' => ['extension' => 'jpeg'],
+ 'mimetype' => 'image/jpeg',
+ ],
+ ];
+
+ // The file metadata manager service.
+ $fmdm = $this->container->get('file_metadata_manager');
+
+ // Prepare a copy of test files.
+ $this->getTestFiles('image');
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-multi-frame.gif', 'public://', FILE_EXISTS_REPLACE);
+
+ // Perform tests without caching.
+ $config_mdm->set('metadata_cache.enabled', FALSE)->save();
+ foreach ($files as $source_uri => $source_image_data) {
+ $this->assertFalse($fmdm->has($source_uri));
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertTrue($fmdm->has($source_uri));
+ $first = TRUE;
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up a fresh image.
+ if ($first) {
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ // In some cases the metadata can be generated on save without need to
+ // re-read it back from the image.
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $source_image->getToolkit()->getFrames() === 1
+ ) {
+ $this->assertTrue($fmdm->has($saved_uri));
+ }
+ else {
+ $this->assertFalse($fmdm->has($saved_uri));
+ }
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ // In some cases the metadata can be generated on save without need to
+ // re-read it back from the image.
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $saved_image->getToolkit()->getFrames() === 1 &&
+ !($values['function'] === 'convert' && $source_image_data['frames'] > 1)
+ ) {
+ $this->assertIdentical(FileMetadataInterface::LOADED_BY_CODE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from file.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ switch ($parsing_method) {
+ case 'imagemagick_identify':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ break;
+
+ case 'getimagesize':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 1));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 0));
+ }
+ break;
+
+ }
+ $fmdm->release($saved_uri);
+
+ $first = FALSE;
+ }
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+ }
+
+ // Perform tests with caching.
+ $config_mdm->set('metadata_cache.enabled', TRUE)->save();
+ foreach ($files as $source_uri => $source_image_data) {
+ $first = TRUE;
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up a fresh image.
+ $this->assertFalse($fmdm->has($source_uri));
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertTrue($fmdm->has($source_uri));
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ $source_image = $this->imageFactory->get($source_uri);
+ if ($first) {
+ // First time load, metadata loaded from file.
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ // Further loads, metadata loaded from cache.
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $source_image->getToolkit()->getFrames() === 1
+ ) {
+ $this->assertTrue($fmdm->has($saved_uri));
+ }
+ else {
+ $this->assertFalse($fmdm->has($saved_uri));
+ }
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $saved_image->getToolkit()->getFrames() === 1 &&
+ !($values['function'] === 'convert' && $source_image_data['frames'] > 1)
+ ) {
+ $this->assertIdentical(FileMetadataInterface::LOADED_BY_CODE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service. Should be cached.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from cache.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $saved_image_md->isMetadataLoaded($parsing_method));
+ switch ($parsing_method) {
+ case 'imagemagick_identify':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ break;
+
+ case 'getimagesize':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 1));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 0));
+ }
+ break;
+
+ }
+ $fmdm->release($saved_uri);
+
+ // We release the source image FileMetadata at each cycle to ensure
+ // that metadata is read from cache.
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+
+ $first = FALSE;
+ }
+ }
+
+ // Open source images again after deleting the temp folder files.
+ // Source image data should now be cached, but temp files non existing.
+ // Therefore we test that the toolkit can create a new temp file copy.
+ // Note: on Windows, temp imagemagick file names have a
+ // imaNNN.tmp.[image_extension] pattern so we cannot scan for
+ // 'imagemagick'.
+ $directory_scan = file_scan_directory('temporary://', '/ima.*/');
+ $this->assertGreaterThan(0, count($directory_scan));
+ foreach ($directory_scan as $file) {
+ file_unmanaged_delete($file->uri);
+ }
+ $directory_scan = file_scan_directory('temporary://', '/ima.*/');
+ $this->assertEquals(0, count($directory_scan));
+ foreach ($files as $source_uri => $source_image_data) {
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up the source image. Parsing should be fully cached now.
+ $fmdm->release($source_uri);
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $source_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $source_image->getToolkit()->getFrames() === 1
+ ) {
+ $this->assertTrue($fmdm->has($saved_uri));
+ }
+ else {
+ $this->assertFalse($fmdm->has($saved_uri));
+ }
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $saved_image->getToolkit()->getFrames() === 1 &&
+ !($values['function'] === 'convert' && $source_image_data['frames'] > 1)
+ ) {
+ $this->assertIdentical(FileMetadataInterface::LOADED_BY_CODE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service. Should be cached.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from cache.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_CACHE, $saved_image_md->isMetadataLoaded($parsing_method));
+ switch ($parsing_method) {
+ case 'imagemagick_identify':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ break;
+
+ case 'getimagesize':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 1));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 0));
+ }
+ break;
+
+ }
+ $fmdm->release($saved_uri);
+ }
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+ }
+
+ // Files in temporary:// must not be cached.
+ if ($parsing_method === 'imagemagick_identify') {
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-multi-frame.gif', 'temporary://', FILE_EXISTS_REPLACE);
+ $source_uri = 'temporary://test-multi-frame.gif';
+ $fmdm->release($source_uri);
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded('imagemagick_identify'));
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded('imagemagick_identify'));
+ $fmdm->release($source_uri);
+ $source_image_md = $fmdm->uri($source_uri);
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded('imagemagick_identify'));
+ }
+
+ // Invalidate cache, and open source images again. Now, all files should be
+ // parsed again.
+ Cache::InvalidateTags([
+ 'config:imagemagick.file_metadata_plugin.imagemagick_identify',
+ 'config:file_mdm.file_metadata_plugin.getimagesize',
+ ]);
+ // Disallow caching on the test results directory.
+ $config_mdm->set('metadata_cache.disallowed_paths', ['public://imagetest/*'])->save();
+ foreach ($files as $source_uri => $source_image_data) {
+ $fmdm->release($source_uri);
+ }
+ foreach ($files as $source_uri => $source_image_data) {
+ $this->assertFalse($fmdm->has($source_uri));
+ $source_image_md = $fmdm->uri($source_uri);
+ $this->assertTrue($fmdm->has($source_uri));
+ $first = TRUE;
+ file_unmanaged_delete_recursive($this->testDirectory);
+ file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ foreach ($operations as $op => $values) {
+ // Load up a fresh image.
+ if ($first) {
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ }
+ $source_image = $this->imageFactory->get($source_uri);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $source_image_md->isMetadataLoaded($parsing_method));
+ $this->assertIdentical($source_image_data['mimetype'], $source_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertIdentical($source_image_data['height'], $source_image->getHeight());
+ $this->assertIdentical($source_image_data['width'], $source_image->getWidth());
+ }
+
+ // Perform our operation.
+ $source_image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $saved_uri = $this->testDirectory . '/' . $op . substr($source_uri, -4);
+ $this->assertFalse($fmdm->has($saved_uri));
+ $this->assertTrue($source_image->save($saved_uri));
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $source_image->getToolkit()->getFrames() === 1
+ ) {
+ $this->assertTrue($fmdm->has($saved_uri));
+ }
+ else {
+ $this->assertFalse($fmdm->has($saved_uri));
+ }
+
+ // Reload saved image and check data.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ $saved_image = $this->imageFactory->get($saved_uri);
+ if ($binaries === 'imagemagick' &&
+ $parsing_method === 'imagemagick_identify' &&
+ $saved_image->getToolkit()->getFrames() === 1 &&
+ !($values['function'] === 'convert' && $source_image_data['frames'] > 1)
+ ) {
+ $this->assertIdentical(FileMetadataInterface::LOADED_BY_CODE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ else {
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ }
+ $this->assertIdentical($values['function'] === 'convert' ? $values['mimetype'] : $source_image_data['mimetype'], $saved_image->getMimeType());
+ if ($binaries === 'imagemagick' && $parsing_method === 'imagemagick_identify') {
+ $this->assertIdentical($source_image_data['colorspace'], $source_image->getToolkit()->getColorspace());
+ }
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image->getHeight());
+ $this->assertEqual($values['width'], $saved_image->getWidth());
+ }
+ $fmdm->release($saved_uri);
+
+ // Get metadata via the file_mdm service.
+ $saved_image_md = $fmdm->uri($saved_uri);
+ // Should not be available at this stage.
+ $this->assertIdentical(FileMetadataInterface::NOT_LOADED, $saved_image_md->isMetadataLoaded($parsing_method));
+ // Get metadata from file.
+ $saved_image_md->getMetadata($parsing_method);
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $saved_image_md->isMetadataLoaded($parsing_method));
+ switch ($parsing_method) {
+ case 'imagemagick_identify':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 'height'));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 'width'));
+ }
+ break;
+
+ case 'getimagesize':
+ if (!isset($source_image_data['skip_dimensions_check'])) {
+ $this->assertEqual($values['height'], $saved_image_md->getMetadata($parsing_method, 1));
+ $this->assertEqual($values['width'], $saved_image_md->getMetadata($parsing_method, 0));
+ }
+ break;
+
+ }
+ $fmdm->release($saved_uri);
+
+ $first = FALSE;
+ }
+ $fmdm->release($source_uri);
+ $this->assertFalse($fmdm->has($source_uri));
+ }
+ }
+
+ /**
+ * Tests getSourceLocalPath() for re-creating local path.
+ */
+ public function testSourceLocalPath() {
+ $status = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick')->getExecManager()->checkPath('');
+ if (!empty($status['errors'])) {
+ // Bots running automated test on d.o. do not have binaries installed,
+ // so the test will be skipped; it can be run locally where binaries are
+ // installed.
+ $this->markTestSkipped("Tests for 'imagemagick' cannot run because the binaries are not available on the shell path.");
+ }
+
+ $config = \Drupal::configFactory()->getEditable('imagemagick.settings');
+ $config_mdm = \Drupal::configFactory()->getEditable('file_mdm.settings');
+
+ // The file metadata manager service.
+ $fmdm = $this->container->get('file_metadata_manager');
+
+ // The file that will be tested.
+ $source_uri = 'public://image-test.png';
+
+ // Prepare a copy of test files.
+ $this->getTestFiles('image');
+
+ // Enable metadata caching.
+ $config_mdm->set('metadata_cache.enabled', TRUE)->save();
+
+ // Parse with identify.
+ $config->set('use_identify', TRUE)->save();
+
+ // Load up the image.
+ $image = $this->imageFactory->get($source_uri);
+ $this->assertEqual($source_uri, $image->getToolkit()->getSource());
+ $this->assertEqual(drupal_realpath($source_uri), $image->getToolkit()->arguments()->getSourceLocalPath());
+
+ // Free up the URI from the file metadata manager to force reload from
+ // cache. Simulates that next imageFactory->get is from another request.
+ $fmdm->release($source_uri);
+
+ // Re-load the image, ensureLocalSourcePath should return the local path.
+ $image1 = $this->imageFactory->get($source_uri);
+ $this->assertEqual($source_uri, $image1->getToolkit()->getSource());
+ $this->assertEqual(drupal_realpath($source_uri), $image1->getToolkit()->ensureSourceLocalPath());
+ }
+
+}
namespace Drupal\Tests\imagemagick\Functional;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Image\ImageInterface;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\Tests\BrowserTestBase;
+use Drupal\file_mdm\FileMetadataInterface;
+use Drupal\imagemagick\ImagemagickExecArguments;
/**
* Tests that core image manipulations work properly through Imagemagick.
'simpletest',
'file_test',
'imagemagick',
+ 'file_mdm',
+ 'file_mdm_exif',
];
/**
}
/**
- * Provides data for testManipulations.
- *
- * @return array[]
- * A simple array of simple arrays, each having the following elements:
- * - binaries to use for testing.
- */
- public function providerManipulationTest() {
- return [
- ['imagemagick'],
- ['graphicsmagick'],
- ];
- }
-
- /**
- * Test image toolkit operations.
- *
- * Since PHP can't visually check that our images have been manipulated
- * properly, build a list of expected color values for each of the corners and
- * the expected height and widths for the final images.
+ * Helper to setup the image toolkit.
*
* @param string $binaries
* The graphics package binaries to use for testing.
- *
- * @dataProvider providerManipulationTest
+ * @param bool $check_path
+ * Whether the path to binaries should be tested.
*/
- public function testManipulations($binaries) {
+ protected function setUpToolkit($binaries, $check_path = TRUE) {
// Change the toolkit.
\Drupal::configFactory()->getEditable('system.image')
->set('toolkit', 'imagemagick')
->save();
// Execute tests with selected binaries.
- // The test can only be executed if binaries are available on the shell
- // path.
\Drupal::configFactory()->getEditable('imagemagick.settings')
->set('debug', TRUE)
->set('binaries', $binaries)
->set('quality', 100)
->save();
- $status = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick')->checkPath('');
- if (!empty($status['errors'])) {
- // Bots running automated test on d.o. do not have binaries installed,
- // so the test will be skipped; it can be run locally where binaries are
- // installed.
- $this->markTestSkipped("Tests for '{$binaries}' cannot run because the binaries are not available on the shell path.");
+
+ if ($check_path) {
+ // The test can only be executed if binaries are available on the shell
+ // path.
+ $status = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick')->getExecManager()->checkPath('');
+ if (!empty($status['errors'])) {
+ // Bots running automated test on d.o. do not have binaries installed,
+ // so the test will be skipped; it can be run locally where binaries
+ // are installed.
+ $this->markTestSkipped("Tests for '{$binaries}' cannot run because the binaries are not available on the shell path.");
+ }
}
// Set the toolkit on the image factory.
// Prepare directory.
file_unmanaged_delete_recursive($this->testDirectory);
file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+ }
+
+ /**
+ * Provides data for testManipulations.
+ *
+ * @return array[]
+ * A simple array of simple arrays, each having the following elements:
+ * - binaries to use for testing.
+ */
+ public function providerManipulationTest() {
+ return [
+ ['imagemagick'],
+ ['graphicsmagick'],
+ ];
+ }
+
+ /**
+ * Test image toolkit operations.
+ *
+ * Since PHP can't visually check that our images have been manipulated
+ * properly, build a list of expected color values for each of the corners and
+ * the expected height and widths for the final images.
+ *
+ * @param string $binaries
+ * The graphics package binaries to use for testing.
+ *
+ * @dataProvider providerManipulationTest
+ */
+ public function testManipulations($binaries) {
+ $this->setUpToolkit($binaries);
// Typically the corner colors will be unchanged. These colors are in the
// order of top-left, top-right, bottom-right, bottom-left.
$this->getTestFiles('image');
foreach ($files as $file) {
+ $image_uri = 'public://' . $file;
foreach ($operations as $op => $values) {
// Load up a fresh image.
- $image = $this->imageFactory->get('public://' . $file);
+ $image = $this->imageFactory->get($image_uri);
if (!$image->isValid()) {
$this->fail("Could not load image $file.");
continue 2;
}
// Check that no multi-frame information is set.
- $this->assertNull($image->getToolkit()->getFrames());
+ $this->assertIdentical(1, $image->getToolkit()->getFrames());
// Perform our operation.
$image->apply($values['function'], $values['arguments']);
// Save and reload image.
$file_path = $this->testDirectory . '/' . $op . substr($file, -4);
- $image->save($file_path);
+ $this->assertTrue($image->save($file_path));
$image = $this->imageFactory->get($file_path);
$this->assertTrue($image->isValid());
- // @todo GraphicsMagick specifics, temporarily adjust tests.
- $package = $image->getToolkit()->getPackage();
+ // @todo Suite specifics, temporarily adjust tests.
+ $package = $image->getToolkit()->getExecManager()->getPackage();
if ($package === 'graphicsmagick') {
- // @todo Issues with crop on GIF files, investigate.
- if (in_array($file, ['image-test.gif', 'image-test-no-transparency.gif']) && in_array($op, ['crop', 'scale_and_crop'])) {
+ // @todo Issues with crop and convert on GIF files, investigate.
+ if (in_array($file, [
+ 'image-test.gif', 'image-test-no-transparency.gif',
+ ]) && in_array($op, [
+ 'crop', 'scale_and_crop', 'convert_png',
+ ])) {
continue;
}
}
}
$color = $this->getPixelColor($image, $x, $y);
- $correct_colors = $this->colorsAreClose($color, $corner, $values['tolerance']);
+ $this->colorsAreClose($color, $corner, $values['tolerance'], $file, $op);
}
}
}
'viet "with double quotes" hình ảnh thỠnghiệm.png',
];
foreach ($file_names as $file) {
+ // On Windows, skip filenames with non-allowed characters.
+ if (substr(PHP_OS, 0, 3) === 'WIN' && preg_match('/[:*?"<>|]/', $file)) {
+ continue;
+ }
$image = $this->imageFactory->get();
- $image->createNew(50, 20, 'png');
+ $this->assertTrue($image->createNew(50, 20, 'png'));
$file_path = $this->testDirectory . '/' . $file;
- $image->save($file_path);
+ $this->assertTrue($image->save($file_path), $file);
$image_reloaded = $this->imageFactory->get($file_path, 'gd');
$this->assertTrue($image_reloaded->isValid(), "Image file '$file' loaded successfully.");
}
// Test handling a file stored through a remote stream wrapper.
$image = $this->imageFactory->get('dummy-remote://image-test.png');
// Source file should be equal to the copied local temp source file.
- $this->assertEqual(filesize('dummy-remote://image-test.png'), filesize($image->getToolkit()->getSourceLocalPath()));
+ $this->assertEqual(filesize('dummy-remote://image-test.png'), filesize($image->getToolkit()->arguments()->getSourceLocalPath()));
$image->desaturate();
- $image->save('dummy-remote://remote-image-test.png');
+ $this->assertTrue($image->save('dummy-remote://remote-image-test.png'));
// Destination file should exists, and destination local temp file should
// have been reset.
- $this->assertTrue(file_exists($image->getToolkit()->getDestination()));
- $this->assertEqual('dummy-remote://remote-image-test.png', $image->getToolkit()->getDestination());
- $this->assertIdentical('', $image->getToolkit()->getDestinationLocalPath());
+ $this->assertTrue(file_exists($image->getToolkit()->arguments()->getDestination()));
+ $this->assertEqual('dummy-remote://remote-image-test.png', $image->getToolkit()->arguments()->getDestination());
+ $this->assertIdentical('', $image->getToolkit()->arguments()->getDestinationLocalPath());
// Test retrieval of EXIF information.
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg', 'public://', FILE_EXISTS_REPLACE);
+ // The image files that will be tested.
$image_files = [
[
'path' => drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg',
'orientation' => 8,
],
+ [
+ 'path' => 'public://test-exif.jpeg',
+ 'orientation' => 8,
+ ],
+ [
+ 'path' => 'dummy-remote://test-exif.jpeg',
+ 'orientation' => 8,
+ ],
[
'path' => 'public://image-test.jpg',
'orientation' => NULL,
'orientation' => NULL,
],
];
-
foreach ($image_files as $image_file) {
// Get image using 'identify'.
\Drupal::configFactory()->getEditable('imagemagick.settings')
->save();
$image = $this->imageFactory->get($image_file['path']);
$this->assertIdentical($image_file['orientation'], $image->getToolkit()->getExifOrientation());
-
- // Get image using 'getimagesize'.
- \Drupal::configFactory()->getEditable('imagemagick.settings')
- ->set('use_identify', FALSE)
- ->save();
- $image = $this->imageFactory->get($image_file['path']);
- $this->assertIdentical($image_file['orientation'], $image->getToolkit()->getExifOrientation());
}
// Test multi-frame GIF image.
'rotated_height' => 26,
],
];
-
// Get images using 'identify'.
\Drupal::configFactory()->getEditable('imagemagick.settings')
->set('use_identify', TRUE)
// Scaling should preserve frames.
$image->scale(30);
- $image->save($image_file['destination']);
+ $this->assertTrue($image->save($image_file['destination']));
$image = $this->imageFactory->get($image_file['destination']);
$this->assertIdentical($image_file['scaled_width'], $image->getWidth());
$this->assertIdentical($image_file['scaled_height'], $image->getHeight());
// Rotating should preserve frames.
$image->rotate(24);
- $image->save($image_file['destination']);
+ $this->assertTrue($image->save($image_file['destination']));
$image = $this->imageFactory->get($image_file['destination']);
$this->assertIdentical($image_file['rotated_width'], $image->getWidth());
$this->assertIdentical($image_file['rotated_height'], $image->getHeight());
// Converting to PNG should drop frames.
$image->convert('png');
- $this->assertNull($image->getToolkit()->getFrames());
- $image->save($image_file['destination']);
+ $this->assertTrue($image->save($image_file['destination']));
$image = $this->imageFactory->get($image_file['destination']);
+ $this->assertIdentical(1, $image->getToolkit()->getFrames());
$this->assertIdentical($image_file['rotated_width'], $image->getWidth());
$this->assertIdentical($image_file['rotated_height'], $image->getHeight());
- $this->assertNull($image->getToolkit()->getFrames());
+ $this->assertIdentical(1, $image->getToolkit()->getFrames());
+ }
+ }
+
+ /**
+ * Legacy methods tests.
+ *
+ * @param string $binaries
+ * The graphics package binaries to use for testing.
+ *
+ * @dataProvider providerManipulationTest
+ *
+ * @todo remove in 8.x-3.0.
+ *
+ * @group legacy
+ */
+ public function testManipulationsLegacy($binaries) {
+ $this->setUpToolkit($binaries);
+
+ // Check package.
+ $toolkit = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick');
+ $this->assertSame($binaries, $toolkit->getPackage());
+ $this->assertNotNull($toolkit->getPackageLabel());
+ $this->assertSame([], $toolkit->checkPath('')['errors']);
+
+ // Typically the corner colors will be unchanged. These colors are in the
+ // order of top-left, top-right, bottom-right, bottom-left.
+ $default_corners = [
+ $this->red,
+ $this->green,
+ $this->blue,
+ $this->transparent,
+ ];
+
+ // A list of files that will be tested.
+ $files = [
+ 'image-test.png',
+ 'image-test.gif',
+ 'image-test-no-transparency.gif',
+ 'image-test.jpg',
+ ];
+
+ // Setup a list of tests to perform on each type.
+ $operations = [
+ 'resize' => [
+ 'function' => 'resize',
+ 'arguments' => ['width' => 20, 'height' => 10],
+ 'width' => 20,
+ 'height' => 10,
+ 'corners' => $default_corners,
+ 'tolerance' => 0,
+ ],
+ 'scale_x' => [
+ 'function' => 'scale',
+ 'arguments' => ['width' => 20],
+ 'width' => 20,
+ 'height' => 10,
+ 'corners' => $default_corners,
+ 'tolerance' => 0,
+ ],
+ 'scale_y' => [
+ 'function' => 'scale',
+ 'arguments' => ['height' => 10],
+ 'width' => 20,
+ 'height' => 10,
+ 'corners' => $default_corners,
+ 'tolerance' => 0,
+ ],
+ 'upscale_x' => [
+ 'function' => 'scale',
+ 'arguments' => ['width' => 80, 'upscale' => TRUE],
+ 'width' => 80,
+ 'height' => 40,
+ 'corners' => $default_corners,
+ 'tolerance' => 0,
+ ],
+ 'upscale_y' => [
+ 'function' => 'scale',
+ 'arguments' => ['height' => 40, 'upscale' => TRUE],
+ 'width' => 80,
+ 'height' => 40,
+ 'corners' => $default_corners,
+ 'tolerance' => 0,
+ ],
+ 'crop' => [
+ 'function' => 'crop',
+ 'arguments' => ['x' => 12, 'y' => 4, 'width' => 16, 'height' => 12],
+ 'width' => 16,
+ 'height' => 12,
+ 'corners' => array_fill(0, 4, $this->white),
+ 'tolerance' => 0,
+ ],
+ 'scale_and_crop' => [
+ 'function' => 'scale_and_crop',
+ 'arguments' => ['width' => 10, 'height' => 8],
+ 'width' => 10,
+ 'height' => 8,
+ 'corners' => array_fill(0, 4, $this->black),
+ 'tolerance' => 100,
+ ],
+ 'convert_jpg' => [
+ 'function' => 'convert',
+ 'width' => 40,
+ 'height' => 20,
+ 'arguments' => ['extension' => 'jpeg'],
+ 'mimetype' => 'image/jpeg',
+ 'corners' => $default_corners,
+ 'tolerance' => 0,
+ ],
+ 'convert_gif' => [
+ 'function' => 'convert',
+ 'width' => 40,
+ 'height' => 20,
+ 'arguments' => ['extension' => 'gif'],
+ 'mimetype' => 'image/gif',
+ 'corners' => $default_corners,
+ 'tolerance' => 15,
+ ],
+ 'convert_png' => [
+ 'function' => 'convert',
+ 'width' => 40,
+ 'height' => 20,
+ 'arguments' => ['extension' => 'png'],
+ 'mimetype' => 'image/png',
+ 'corners' => $default_corners,
+ 'tolerance' => 5,
+ ],
+ 'rotate_5' => [
+ 'function' => 'rotate',
+ 'arguments' => [
+ 'degrees' => 5,
+ 'background' => '#FF00FF',
+ 'resize_filter' => 'Box',
+ ],
+ 'width' => 41,
+ 'height' => 23,
+ 'corners' => array_fill(0, 4, $this->fuchsia),
+ 'tolerance' => 5,
+ ],
+ 'rotate_minus_10' => [
+ 'function' => 'rotate',
+ 'arguments' => [
+ 'degrees' => -10,
+ 'background' => '#FF00FF',
+ 'resize_filter' => 'Box',
+ ],
+ 'width' => 41,
+ 'height' => 26,
+ 'corners' => array_fill(0, 4, $this->fuchsia),
+ 'tolerance' => 15,
+ ],
+ 'rotate_90' => [
+ 'function' => 'rotate',
+ 'arguments' => ['degrees' => 90, 'background' => '#FF00FF'],
+ 'width' => 20,
+ 'height' => 40,
+ 'corners' => [$this->transparent, $this->red, $this->green, $this->blue],
+ 'tolerance' => 0,
+ ],
+ 'rotate_transparent_5' => [
+ 'function' => 'rotate',
+ 'arguments' => ['degrees' => 5, 'resize_filter' => 'Box'],
+ 'width' => 41,
+ 'height' => 23,
+ 'corners' => array_fill(0, 4, $this->transparent),
+ 'tolerance' => 0,
+ ],
+ 'rotate_transparent_90' => [
+ 'function' => 'rotate',
+ 'arguments' => ['degrees' => 90],
+ 'width' => 20,
+ 'height' => 40,
+ 'corners' => [$this->transparent, $this->red, $this->green, $this->blue],
+ 'tolerance' => 0,
+ ],
+ 'desaturate' => [
+ 'function' => 'desaturate',
+ 'arguments' => [],
+ 'height' => 20,
+ 'width' => 40,
+ // Grayscale corners are a bit funky. Each of the corners are a shade of
+ // gray. The values of these were determined simply by looking at the
+ // final image to see what desaturated colors end up being.
+ 'corners' => [
+ array_fill(0, 3, 76) + [3 => 0],
+ array_fill(0, 3, 149) + [3 => 0],
+ array_fill(0, 3, 29) + [3 => 0],
+ array_fill(0, 3, 225) + [3 => 127],
+ ],
+ // @todo tolerance here is too high. Check reasons.
+ 'tolerance' => 17000,
+ ],
+ ];
+
+ // Prepare a copy of test files.
+ $this->getTestFiles('image');
+
+ foreach ($files as $file) {
+ $image_uri = 'public://' . $file;
+ foreach ($operations as $op => $values) {
+ // Load up a fresh image.
+ $image = $this->imageFactory->get($image_uri);
+ if (!$image->isValid()) {
+ $this->fail("Could not load image $file.");
+ continue 2;
+ }
+
+ // Check that no multi-frame information is set.
+ $this->assertIdentical(1, $image->getToolkit()->getFrames());
+
+ // Legacy source tests.
+ $this->assertSame($image_uri, $image->getToolkit()->getSource());
+ $this->assertSame($image->getToolkit()->arguments()->getSourceLocalPath(), $image->getToolkit()->getSourceLocalPath());
+ $this->assertSame($image->getToolkit()->arguments()->getSourceFormat(), $image->getToolkit()->getSourceFormat());
+
+ // Perform our operation.
+ $image->apply($values['function'], $values['arguments']);
+
+ // Save image.
+ $file_path = $this->testDirectory . '/' . $op . substr($file, -4);
+ $this->assertTrue($image->save($file_path));
+
+ // Legacy destination tests.
+ $this->assertSame($file_path, $image->getToolkit()->getDestination());
+ $this->assertSame('', $image->getToolkit()->getDestinationLocalPath());
+ $this->assertNotNull($image->getToolkit()->arguments()->getSourceFormat(), $image->getToolkit()->getDestinationFormat());
+
+ // Reload image.
+ $image = $this->imageFactory->get($file_path);
+ $this->assertTrue($image->isValid());
+
+ // Legacy set methods.
+ $image->getToolkit()->setSourceLocalPath('bar');
+ $image->getToolkit()->setSourceFormat('PNG');
+ $image->getToolkit()->setDestination('foo');
+ $image->getToolkit()->setDestinationLocalPath('baz');
+ $image->getToolkit()->setDestinationFormat('GIF');
+ $this->assertSame('bar', $image->getToolkit()->arguments()->getSourceLocalPath());
+ $this->assertSame('PNG', $image->getToolkit()->arguments()->getSourceFormat());
+ $this->assertSame('foo', $image->getToolkit()->arguments()->getDestination());
+ $this->assertSame('baz', $image->getToolkit()->arguments()->getDestinationLocalPath());
+ $this->assertSame('GIF', $image->getToolkit()->arguments()->getDestinationFormat());
+ $image->getToolkit()->setSourceFormatFromExtension('jpg');
+ $image->getToolkit()->setDestinationFormatFromExtension('jpg');
+ $this->assertSame('JPEG', $image->getToolkit()->arguments()->getSourceFormat());
+ $this->assertSame('JPEG', $image->getToolkit()->arguments()->getDestinationFormat());
+ }
+ }
+
+ // Test retrieval of EXIF information.
+ file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg', 'public://', FILE_EXISTS_REPLACE);
+ // The image files that will be tested.
+ $image_files = [
+ [
+ 'path' => drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg',
+ 'orientation' => 8,
+ ],
+ [
+ 'path' => 'public://test-exif.jpeg',
+ 'orientation' => 8,
+ ],
+ [
+ 'path' => 'dummy-remote://test-exif.jpeg',
+ 'orientation' => 8,
+ ],
+ [
+ 'path' => 'public://image-test.jpg',
+ 'orientation' => NULL,
+ ],
+ [
+ 'path' => 'public://image-test.png',
+ 'orientation' => NULL,
+ ],
+ [
+ 'path' => 'public://image-test.gif',
+ 'orientation' => NULL,
+ ],
+ [
+ 'path' => NULL,
+ 'orientation' => NULL,
+ ],
+ ];
+
+ foreach ($image_files as $image_file) {
+ // Get image using 'getimagesize'.
+ \Drupal::configFactory()->getEditable('imagemagick.settings')
+ ->set('use_identify', FALSE)
+ ->save();
+ $image = $this->imageFactory->get($image_file['path']);
+ $this->assertIdentical($image_file['orientation'], $image->getToolkit()->getExifOrientation());
}
}
$this->assertFieldByName('image_toolkit', 'imagemagick');
$edit = [
'image_toolkit' => 'gd',
- 'imagemagick[suite][path_to_binaries]' => '/foo/bar',
+ 'imagemagick[suite][path_to_binaries]' => '/foo/bar/',
];
$this->drupalPostForm(NULL, $edit, 'Save configuration');
$this->assertFieldByName('image_toolkit', 'gd');
$transparent_index = imagecolortransparent($toolkit->getResource());
if ($color_index == $transparent_index) {
- return array(0, 0, 0, 127);
+ return [0, 0, 0, 127];
}
return array_values(imagecolorsforindex($toolkit->getResource(), $color_index));
* The expected RGBA array.
* @param int $tolerance
* The acceptable difference between the colors.
+ * @param string $file
+ * The image file being tested.
+ * @param string $op
+ * The image operation being tested.
*
* @return bool
* TRUE if the colors differences are within tolerance, FALSE otherwise.
*/
- protected function colorsAreClose(array $actual, array $expected, $tolerance) {
+ protected function colorsAreClose(array $actual, array $expected, $tolerance, $file, $op) {
// Fully transparent colors are equal, regardless of RGB.
if ($actual[3] == 127 && $expected[3] == 127) {
return TRUE;
}
$distance = pow(($actual[0] - $expected[0]), 2) + pow(($actual[1] - $expected[1]), 2) + pow(($actual[2] - $expected[2]), 2) + pow(($actual[3] - $expected[3]), 2);
- $this->assertLessThanOrEqual($tolerance, $distance, "Actual: {" . implode(',', $actual) . "}, Expected: {" . implode(',', $expected) . "}, Distance: " . $distance . ", Tolerance: " . $tolerance);
+ $this->assertLessThanOrEqual($tolerance, $distance, "Actual: {" . implode(',', $actual) . "}, Expected: {" . implode(',', $expected) . "}, Distance: " . $distance . ", Tolerance: " . $tolerance . ", File: " . $file . ", Operation: " . $op);
return TRUE;
}
+ /**
+ * Test legacy arguments handling.
+ *
+ * @todo remove in 8.x-3.0.
+ *
+ * @group legacy
+ */
+ public function testArgumentsLegacy() {
+ $this->setUpToolkit('imagemagick');
+
+ // Prepare a copy of test files.
+ $this->getTestFiles('image');
+
+ $image_uri = "public://image-test.png";
+ $image = $this->imageFactory->get($image_uri);
+ if (!$image->isValid()) {
+ $this->fail("Could not load image $image_uri.");
+ }
+
+ // Setup a list of arguments.
+ $image->getToolkit()->addArgument("-resize 100x75!");
+ // Internal argument.
+ $image->getToolkit()->addArgument(">!>INTERNAL");
+ $image->getToolkit()->addArgument("-quality 75");
+ $image->getToolkit()->prependArgument("-hoxi 76");
+
+ // Use methods introduced in 8.x-2.3.
+ $image->getToolkit()->arguments()
+ // Pre source argument.
+ ->add("-density 25", ImagemagickExecArguments::PRE_SOURCE)
+ // Another internal argument.
+ ->add("GATEAU", ImagemagickExecArguments::INTERNAL)
+ // Another pre source argument.
+ ->add("-auchocolat 90", ImagemagickExecArguments::PRE_SOURCE)
+ // Add two arguments with additional info.
+ ->add(
+ "-addz 150",
+ ImagemagickExecArguments::POST_SOURCE,
+ ImagemagickExecArguments::APPEND,
+ [
+ 'foo' => 'bar',
+ 'qux' => 'der',
+ ]
+ )
+ ->add(
+ "-addz 200",
+ ImagemagickExecArguments::POST_SOURCE,
+ ImagemagickExecArguments::APPEND,
+ [
+ 'wey' => 'lod',
+ 'foo' => 'bar',
+ ]
+ );
+
+ // Test find arguments skipping identifiers.
+ $this->assertSame([
+ 0 => '-hoxi 76',
+ 1 => '-resize 100x75!',
+ 2 => '>!>INTERNAL',
+ 3 => '-quality 75',
+ 5 => '>!>GATEAU',
+ 7 => '-addz 150',
+ 8 => '-addz 200',
+ ], $image->getToolkit()->getArguments());
+ $this->assertSame([2], array_keys($image->getToolkit()->arguments()->find('/^INTERNAL/')));
+ $this->assertSame([5], array_keys($image->getToolkit()->arguments()->find('/^GATEAU/')));
+ $this->assertSame([6], array_keys($image->getToolkit()->arguments()->find('/^\-auchocolat/')));
+ $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/^\-addz/')));
+ $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/.*/', NULL, ['foo' => 'bar'])));
+ $this->assertSame([], $image->getToolkit()->arguments()->find('/.*/', NULL, ['arw' => 'moo']));
+ $this->assertSame(2, $image->getToolkit()->findArgument('>!>INTERNAL'));
+ $this->assertSame(5, $image->getToolkit()->findArgument('>!>GATEAU'));
+ $this->assertFalse($image->getToolkit()->findArgument('-auchocolat'));
+
+ // Check resulting command line strings.
+ $this->assertSame('-density 25 -auchocolat 90', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+ $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -addz 150 -addz 200", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+ $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -addz 150 -addz 200", $image->getToolkit()->getStringForBinary());
+ }
+
+ /**
+ * Test arguments handling.
+ */
+ public function testArguments() {
+ $this->setUpToolkit('imagemagick');
+
+ // Prepare a copy of test files.
+ $this->getTestFiles('image');
+
+ $image_uri = "public://image-test.png";
+ $image = $this->imageFactory->get($image_uri);
+ if (!$image->isValid()) {
+ $this->fail("Could not load image $image_uri.");
+ }
+
+ // Setup a list of arguments.
+ $image->getToolkit()->arguments()
+ ->add("-resize 100x75!")
+ // Internal argument.
+ ->add("INTERNAL", ImagemagickExecArguments::INTERNAL)
+ ->add("-quality 75")
+ // Prepend argument.
+ ->add("-hoxi 76", ImagemagickExecArguments::POST_SOURCE, 0)
+ // Pre source argument.
+ ->add("-density 25", ImagemagickExecArguments::PRE_SOURCE)
+ // Another internal argument.
+ ->add("GATEAU", ImagemagickExecArguments::INTERNAL)
+ // Another pre source argument.
+ ->add("-auchocolat 90", ImagemagickExecArguments::PRE_SOURCE)
+ // Add two arguments with additional info.
+ ->add(
+ "-addz 150",
+ ImagemagickExecArguments::POST_SOURCE,
+ ImagemagickExecArguments::APPEND,
+ [
+ 'foo' => 'bar',
+ 'qux' => 'der',
+ ]
+ )
+ ->add(
+ "-addz 200",
+ ImagemagickExecArguments::POST_SOURCE,
+ ImagemagickExecArguments::APPEND,
+ [
+ 'wey' => 'lod',
+ 'foo' => 'bar',
+ ]
+ );
+
+ // Test find arguments skipping identifiers.
+ $this->assertSame([2], array_keys($image->getToolkit()->arguments()->find('/^INTERNAL/')));
+ $this->assertSame([5], array_keys($image->getToolkit()->arguments()->find('/^GATEAU/')));
+ $this->assertSame([6], array_keys($image->getToolkit()->arguments()->find('/^\-auchocolat/')));
+ $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/^\-addz/')));
+ $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/.*/', NULL, ['foo' => 'bar'])));
+ $this->assertSame([], $image->getToolkit()->arguments()->find('/.*/', NULL, ['arw' => 'moo']));
+
+ // Check resulting command line strings.
+ $this->assertSame('-density 25 -auchocolat 90', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+ $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -addz 150 -addz 200", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+
+ // Add arguments with a specific index.
+ $image->getToolkit()->arguments()
+ ->add("-ix aa", ImagemagickExecArguments::POST_SOURCE, 4)
+ ->add("-ix bb", ImagemagickExecArguments::POST_SOURCE, 4);
+ $this->assertSame([4, 5], array_keys($image->getToolkit()->arguments()->find('/^\-ix/')));
+ $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -ix bb -ix aa -addz 150 -addz 200", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+
+ // Create a new image and inspect the arguments.
+ $image->createNew(100, 200);
+ $this->assertSame([0], array_keys($image->getToolkit()->arguments()->find('/^./', NULL, ['image_toolkit_operation' => 'create_new'])));
+ $this->assertSame([0], array_keys($image->getToolkit()->arguments()->find('/^./', NULL, ['image_toolkit_operation_plugin_id' => 'imagemagick_create_new'])));
+ $this->assertSame("-size 100x200 xc:transparent", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+ }
+
+ /**
+ * Test module arguments alter hook.
+ */
+ public function testArgumentsAlterHook() {
+ $this->setUpToolkit('imagemagick');
+
+ $fmdm = $this->container->get('file_metadata_manager');
+
+ // Change the Advanced Colorspace setting, must be included in the command
+ // line.
+ \Drupal::configFactory()->getEditable('imagemagick.settings')
+ ->set('advanced.colorspace', 'GRAY')
+ ->save();
+
+ // Prepare a copy of test files.
+ $this->getTestFiles('image');
+ $image_uri = "public://image-test.png";
+ $image = $this->imageFactory->get($image_uri);
+ if (!$image->isValid()) {
+ $this->fail("Could not load image $image_uri.");
+ }
+
+ // Check the source colorspace.
+ $this->assertSame('SRGB', $image->getToolkit()->getColorspace());
+
+ // Setup a list of arguments.
+ $image->getToolkit()->arguments()
+ ->add("-resize 100x75!")
+ ->add("-quality 75");
+
+ // Save the derived image.
+ $image->save($image_uri . '.derived');
+
+ // Check expected command line.
+ if (substr(PHP_OS, 0, 3) === 'WIN') {
+ $expected = "-resize 100x75! -quality 75 -colorspace \"GRAY\"";
+ }
+ else {
+ $expected = "-resize 100x75! -quality 75 -colorspace 'GRAY'";
+ }
+ $this->assertSame($expected, $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+
+ // Check that the colorspace has been actually changed in the file.
+ Cache::InvalidateTags([
+ 'config:imagemagick.file_metadata_plugin.imagemagick_identify',
+ ]);
+ $fmdm->release($image_uri . '.derived');
+ $image_md = $fmdm->uri($image_uri . '.derived');
+ $image = $this->imageFactory->get($image_uri . '.derived');
+ $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $image_md->isMetadataLoaded('imagemagick_identify'));
+ $this->assertSame('GRAY', $image->getToolkit()->getColorspace());
+
+ // Change the Prepend settings, must be included in the command line.
+ // Once before the source image.
+ \Drupal::configFactory()->getEditable('imagemagick.settings')
+ ->set('prepend', '-debug All')
+ ->set('prepend_pre_source', TRUE)
+ ->save();
+ $image = $this->imageFactory->get($image_uri);
+ $image->getToolkit()->arguments()
+ ->add("-resize 100x75!")
+ ->add("-quality 75");
+ $image->save($image_uri . '.derived');
+ if (substr(PHP_OS, 0, 3) === 'WIN') {
+ $expected = "-resize 100x75! -quality 75 -colorspace \"GRAY\"";
+ }
+ else {
+ $expected = "-resize 100x75! -quality 75 -colorspace 'GRAY'";
+ }
+ $this->assertSame('-debug All', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+ $this->assertSame($expected, $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+ // Then after the source image.
+ \Drupal::configFactory()->getEditable('imagemagick.settings')
+ ->set('prepend_pre_source', FALSE)
+ ->save();
+ $image = $this->imageFactory->get($image_uri);
+ $image->getToolkit()->arguments()
+ ->add("-resize 100x75!")
+ ->add("-quality 75");
+ $image->save($image_uri . '.derived');
+ if (substr(PHP_OS, 0, 3) === 'WIN') {
+ $expected = "-debug All -resize 100x75! -quality 75 -colorspace \"GRAY\"";
+ }
+ else {
+ $expected = "-debug All -resize 100x75! -quality 75 -colorspace 'GRAY'";
+ }
+ $this->assertSame('', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+ $this->assertSame($expected, $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+ }
+
+ /**
+ * Test missing command on ExecManager.
+ */
+ public function testExecManagerCommandNotFound() {
+ $exec_manager = \Drupal::service('imagemagick.exec_manager');
+ $output = '';
+ $error = '';
+ $expected = substr(PHP_OS, 0, 3) !== 'WIN' ? 127 : 1;
+ $ret = $exec_manager->runOsShell('pinkpanther', '-inspector Clouseau', 'blake', $output, $error);
+ $this->assertEquals($expected, $ret, $error);
+ }
+
+ /**
+ * Test timeout on ExecManager.
+ */
+ public function testExecManagerTimeout() {
+ $exec_manager = \Drupal::service('imagemagick.exec_manager');
+ $output = '';
+ $error = '';
+ $expected = substr(PHP_OS, 0, 3) !== 'WIN' ? 143 : 1;
+ // Set a short timeout (1 sec.) and run a process that is expected to last
+ // longer (10 secs.). Should return a 'terminate' exit code.
+ $exec_manager->setTimeout(1);
+ $ret = $exec_manager->runOsShell('sleep', '10', 'sleep', $output, $error);
+ $this->assertEquals($expected, $ret, $error);
+ }
+
}
/**
* Implements hook_imagemagick_arguments_alter().
*/
-function pdf_to_imagefield_imagemagick_arguments_alter(\Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit, $command) {
+#function pdf_to_imagefield_imagemagick_arguments_alter(\Drupal\imagemagick\Plugin\ImageToolkit\ImagemagickToolkit $toolkit, $command) {
+function pdf_to_imagefield_imagemagick_arguments_alter(\Drupal\imagemagick\ImagemagickExecArguments $arguments_type, $arguements, $command) {
+
switch ($command) {
case 'convert':
// TODO: For some reason some versions/setups of ImageMagick do not