Upgraded imagemagick and manually altered pdf to image module to handle changes....
[yaffs-website] / web / modules / contrib / imagemagick / src / ImagemagickExecManager.php
diff --git a/web/modules/contrib/imagemagick/src/ImagemagickExecManager.php b/web/modules/contrib/imagemagick/src/ImagemagickExecManager.php
new file mode 100644 (file)
index 0000000..7439506
--- /dev/null
@@ -0,0 +1,541 @@
+<?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;
+  }
+
+}