class ImagemagickExecManager in ImageMagick 8.3
Same name and namespace in other branches
- 8.2 src/ImagemagickExecManager.php \Drupal\imagemagick\ImagemagickExecManager
Manage execution of ImageMagick/GraphicsMagick commands.
Hierarchy
- class \Drupal\imagemagick\ImagemagickExecManager implements ImagemagickExecManagerInterface uses StringTranslationTrait
Expanded class hierarchy of ImagemagickExecManager
1 string reference to 'ImagemagickExecManager'
1 service uses ImagemagickExecManager
File
- src/
ImagemagickExecManager.php, line 16
Namespace
Drupal\imagemagickView source
class ImagemagickExecManager implements ImagemagickExecManagerInterface {
use StringTranslationTrait;
/**
* Replacement for percentage while escaping.
*/
const PERCENTAGE_REPLACE = '1357902468IMAGEMAGICKPERCENTSIGNPATTERN8642097531';
/**
* 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 format mapper service.
*
* @var \Drupal\imagemagick\ImagemagickFormatMapperInterface
*/
protected $formatMapper;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* 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\Messenger\MessengerInterface $messenger
* The messenger service.
*/
public function __construct(LoggerInterface $logger, ConfigFactoryInterface $config_factory, string $app_root, AccountProxyInterface $current_user, ImagemagickFormatMapperInterface $format_mapper, MessengerInterface $messenger) {
$this->logger = $logger;
$this->configFactory = $config_factory;
$this->appRoot = $app_root;
$this->currentUser = $current_user;
$this->formatMapper = $format_mapper;
$this->messenger = $messenger;
$this->isWindows = substr(PHP_OS, 0, 3) === 'WIN';
}
/**
* {@inheritdoc}
*/
public function getFormatMapper() : ImagemagickFormatMapperInterface {
return $this->formatMapper;
}
/**
* {@inheritdoc}
*/
public function setTimeout(int $timeout) : ImagemagickExecManagerInterface {
$this->timeout = $timeout;
return $this;
}
/**
* {@inheritdoc}
*/
public function getPackage(string $package = NULL) : string {
if ($package === NULL) {
$package = $this->configFactory
->get('imagemagick.settings')
->get('binaries');
}
return $package;
}
/**
* {@inheritdoc}
*/
public function getPackageLabel(string $package = NULL) : string {
switch ($this
->getPackage($package)) {
case 'imagemagick':
return $this
->t('ImageMagick');
case 'graphicsmagick':
return $this
->t('GraphicsMagick');
default:
return $package;
}
}
/**
* {@inheritdoc}
*/
public function checkPath(string $path, string $package = NULL) : array {
$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,
]);
}
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(string $command, ImagemagickExecArguments $arguments, string &$output = NULL, string &$error = NULL, string $path = NULL) : bool {
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) . ' ' . $source_path;
break;
case 'graphicsmagick':
// @codingStandardsIgnoreStart
// GraphicsMagick syntax:
// gm identify [arguments] source
// @codingStandardsIgnoreEnd
$cmdline = 'identify ' . $arguments
->toString(ImagemagickExecArguments::PRE_SOURCE) . ' ' . $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(string $command, string $arguments, string $id, string &$output = NULL, string &$error = NULL) : int {
$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(string $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
$this->messenger
->addMessage($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() : string {
$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(string $binary, string $path = NULL) : string {
// $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(string $arg) : string {
// Put the configured locale in a static to avoid multiple config get calls
// in the same request.
static $config_locale;
$current_locale = setlocale(LC_CTYPE, 0);
if (!isset($config_locale)) {
$config_locales = explode(' ', $this->configFactory
->get('imagemagick.settings')
->get('locale'));
$temp_locale = !empty($config_locales) ? setlocale(LC_CTYPE, $config_locales) : FALSE;
$config_locale = $temp_locale ?: $current_locale;
}
if ($this->isWindows) {
// Temporarily replace % characters.
$arg = str_replace('%', static::PERCENTAGE_REPLACE, $arg);
}
if ($current_locale !== $config_locale) {
// Temporarily swap the current locale with the configured one.
setlocale(LC_CTYPE, $config_locale);
}
$arg_escaped = escapeshellarg($arg);
if ($current_locale !== $config_locale) {
// Restore the current locale.
setlocale(LC_CTYPE, $current_locale);
}
// Get our % characters back.
if ($this->isWindows) {
$arg_escaped = str_replace(static::PERCENTAGE_REPLACE, '%', $arg_escaped);
}
return $arg_escaped;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ImagemagickExecManager:: |
protected | property | The app root. | |
ImagemagickExecManager:: |
protected | property | The configuration factory. | |
ImagemagickExecManager:: |
protected | property | The current user. | |
ImagemagickExecManager:: |
protected | property | The format mapper service. | |
ImagemagickExecManager:: |
protected | property | Whether we are running on Windows OS. | |
ImagemagickExecManager:: |
protected | property | The logger service. | |
ImagemagickExecManager:: |
protected | property | The messenger service. | |
ImagemagickExecManager:: |
protected | property | The execution timeout. | |
ImagemagickExecManager:: |
public | function |
Verifies file path of the executable binary by checking its version. Overrides ImagemagickExecManagerInterface:: |
|
ImagemagickExecManager:: |
public | function | Logs a debug message, and shows it on the screen for authorized users. | |
ImagemagickExecManager:: |
public | function | Escapes a string. | |
ImagemagickExecManager:: |
public | function |
Executes the convert executable as shell command. Overrides ImagemagickExecManagerInterface:: |
|
ImagemagickExecManager:: |
protected | function | Returns the full path to the executable. | |
ImagemagickExecManager:: |
public | function |
Returns the format mapper. Overrides ImagemagickExecManagerInterface:: |
|
ImagemagickExecManager:: |
public | function | Gets the list of locales installed on the server. | |
ImagemagickExecManager:: |
public | function |
Gets the binaries package in use. Overrides ImagemagickExecManagerInterface:: |
|
ImagemagickExecManager:: |
public | function |
Gets a translated label of the binaries package in use. Overrides ImagemagickExecManagerInterface:: |
|
ImagemagickExecManager:: |
constant | Replacement for percentage while escaping. | ||
ImagemagickExecManager:: |
public | function |
Executes a command on the operating system. Overrides ImagemagickExecManagerInterface:: |
|
ImagemagickExecManager:: |
public | function |
Sets the execution timeout (max. runtime). Overrides ImagemagickExecManagerInterface:: |
|
ImagemagickExecManager:: |
public | function | Constructs an ImagemagickExecManager object. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |