View source
<?php
namespace Drupal\imagemagick;
use Drupal\Component\Utility\Timer;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\Process;
class ImagemagickExecManager implements ImagemagickExecManagerInterface {
use StringTranslationTrait;
const PERCENTAGE_REPLACE = '1357902468IMAGEMAGICKPERCENTSIGNPATTERN8642097531';
protected $isWindows;
protected $appRoot;
protected $timeout = 60;
protected $currentUser;
protected $logger;
protected $configFactory;
protected $formatMapper;
protected $messenger;
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';
}
public function getFormatMapper() : ImagemagickFormatMapperInterface {
return $this->formatMapper;
}
public function setTimeout(int $timeout) : ImagemagickExecManagerInterface {
$this->timeout = $timeout;
return $this;
}
public function getPackage(string $package = NULL) : string {
if ($package === NULL) {
$package = $this->configFactory
->get('imagemagick.settings')
->get('binaries');
}
return $package;
}
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;
}
}
public function checkPath(string $path, string $package = NULL) : array {
$status = [
'output' => '',
'errors' => [],
];
$package = $package ?: $this
->getPackage();
$binary = $package === 'imagemagick' ? 'convert' : 'gm';
$executable = $this
->getExecutable($binary, $path);
if (!empty($path)) {
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,
]);
}
}
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',
]);
}
if (!$status['errors']) {
$error = NULL;
$this
->runOsShell($executable, '-version', $package, $status['output'], $error);
if ($error !== '') {
$status['errors'][] = $error;
}
}
return $status;
}
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 (($format = $arguments
->getDestinationFormat()) !== '') {
$destination_path = $format . ':' . $destination_path;
}
}
switch ($command) {
case 'identify':
switch ($this
->getPackage()) {
case 'imagemagick':
$cmdline = $arguments
->toString(ImagemagickExecArguments::PRE_SOURCE) . ' ' . $source_path;
break;
case 'graphicsmagick':
$cmdline = 'identify ' . $arguments
->toString(ImagemagickExecArguments::PRE_SOURCE) . ' ' . $source_path;
break;
}
break;
case 'convert':
switch ($this
->getPackage()) {
case 'imagemagick':
$cmdline = '';
if (($pre = $arguments
->toString(ImagemagickExecArguments::PRE_SOURCE)) !== '') {
$cmdline .= $pre . ' ';
}
$cmdline .= $source_path . ' ' . $arguments
->toString(ImagemagickExecArguments::POST_SOURCE) . ' ' . $destination_path;
break;
case 'graphicsmagick':
$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 ($return_code != 0) {
if ($error === '') {
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 {
$this->logger
->error("@suite error @code: @error [command: @command @cmdline]", [
'@suite' => $this
->getPackageLabel(),
'@code' => $return_code,
'@error' => $error,
'@command' => $cmd,
'@cmdline' => $cmdline,
]);
}
return FALSE;
}
return TRUE;
}
return FALSE;
}
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'];
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;
}
public function debugMessage(string $message, array $context) {
$this->logger
->debug($message, $context);
if ($this->currentUser
->hasPermission('administer site configuration')) {
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);
}
}
$this->messenger
->addMessage($this
->t($message, $context), 'status', TRUE);
}
}
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;
}
protected function getExecutable(string $binary, string $path = NULL) : string {
if (!isset($path)) {
$path = $this->configFactory
->get('imagemagick.settings')
->get('path_to_binaries');
}
$executable = $binary;
if ($this->isWindows) {
$executable .= '.exe';
}
return $path . $executable;
}
public function escapeShellArg(string $arg) : string {
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) {
$arg = str_replace('%', static::PERCENTAGE_REPLACE, $arg);
}
if ($current_locale !== $config_locale) {
setlocale(LC_CTYPE, $config_locale);
}
$arg_escaped = escapeshellarg($arg);
if ($current_locale !== $config_locale) {
setlocale(LC_CTYPE, $current_locale);
}
if ($this->isWindows) {
$arg_escaped = str_replace(static::PERCENTAGE_REPLACE, '%', $arg_escaped);
}
return $arg_escaped;
}
}