Commit 0c1eb090 authored by Steven Jones's avatar Steven Jones Committed by Steven Jones
Browse files

Issue #2990670 by Steven Jones: Split optimizations into separate modules

parent 74408538
{
"name": "drupal/imageapi_optimize",
"description": "Define pipelines for image optimization and provide integration with core image styles.",
"type": "drupal-module",
"license": "GPL-2.0+",
"minimum-stability": "dev",
"require": {
"tinify/tinify": "~1.5"
}
}
langcode: en
status: true
dependencies: { }
name: local_binaries
label: 'Local Binaries'
processors:
ad260c90-e845-452d-b5dd-d20d9db687fe:
uuid: ad260c90-e845-452d-b5dd-d20d9db687fe
id: advdef
weight: 1
data:
manual_executable_path: ''
recompress: false
mode: 3
0a8a85c1-2ccc-4cde-844c-97a7588f006c:
uuid: 0a8a85c1-2ccc-4cde-844c-97a7588f006c
id: advpng
weight: 2
data:
manual_executable_path: ''
recompress: false
mode: 3
2987bbcc-6a38-4c82-a9f5-86df39c67935:
uuid: 2987bbcc-6a38-4c82-a9f5-86df39c67935
id: jfifremove
weight: 3
data:
manual_executable_path: ''
2c1a727a-d7d5-4355-842a-519554d46647:
uuid: 2c1a727a-d7d5-4355-842a-519554d46647
id: jpegoptim
weight: 4
data:
manual_executable_path: ''
progressive:
quality:
size:
74c9f4a7-e5c8-4ddd-8698-4d940f73fea9:
uuid: 74c9f4a7-e5c8-4ddd-8698-4d940f73fea9
id: jpegtran
weight: 5
data:
manual_executable_path: ''
progressive: false
fb380dd6-14f0-4b5f-8615-1ed40147ae17:
uuid: fb380dd6-14f0-4b5f-8615-1ed40147ae17
id: optipng
weight: 6
data:
manual_executable_path: ''
level: 5
interlace:
f1390b39-5a83-46a5-ba88-4f671ee0e941:
uuid: f1390b39-5a83-46a5-ba88-4f671ee0e941
id: pngcrush
weight: 7
data:
manual_executable_path: ''
f8156232-0825-4655-9d53-5333120387df:
uuid: f8156232-0825-4655-9d53-5333120387df
id: pngout
weight: 8
data:
manual_executable_path: ''
7645ce49-49f7-4a53-a781-4c7929e7d11c:
uuid: 7645ce49-49f7-4a53-a781-4c7929e7d11c
id: pngquant
weight: 9
data:
manual_executable_path: ''
langcode: en
status: true
dependencies: { }
name: resmushit
label: reSmush.it
processors:
b160a7b4-debf-494c-89e6-049a7f787859:
uuid: b160a7b4-debf-494c-89e6-049a7f787859
id: resmushit
weight: 1
data:
quality:
......@@ -27,106 +27,6 @@ imageapi_optimize.processor.*:
type: mapping
label: 'Processor settings'
imageapi_optimize.processor.advdef:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
recompress:
label: 'Recompress'
type: boolean
mode:
label: 'Compression mode'
type: integer
imageapi_optimize.processor.advpng:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
recompress:
label: 'Recompress'
type: boolean
mode:
label: 'Compression mode'
type: integer
imageapi_optimize.processor.jfifremove:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
imageapi_optimize.processor.jpegoptim:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
progressive:
label: 'Progressive'
type: boolean
quality:
label: 'Quality'
type: integer
size:
label: 'Target file size'
type: integer
imageapi_optimize.processor.jpegtran:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
progressive:
label: 'Progressive'
type: boolean
imageapi_optimize.processor.optipng:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
interlace:
label: 'Interlace'
type: boolean
level:
label: 'Optimization level'
type: integer
imageapi_optimize.processor.pngcrush:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
imageapi_optimize.processor.pngout:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
imageapi_optimize.processor.pngquant:
type: mapping
mapping:
manual_executable_path:
label: 'Binary executable path'
type: string
imageapi_optimize.processor.resmushit:
type: mapping
mapping:
quality:
label: 'Quality'
type: integer
imageapi_optimize.settings:
type: config_object
mapping:
......
name: Image Optimize (or ImageAPI Optimize)
type: module
package: Image Optimize
description: 'Define pipelines for image optimization and provide integration with core image styles.'
core: 8.x
dependencies:
......
......@@ -5,5 +5,3 @@ services:
imageapi_optimize.hooks:
class: Drupal\imageapi_optimize\ImageAPIOptimizeHookImplementations
arguments: ['@string_translation']
imageapi_optimize.shell_operations:
class: Drupal\imageapi_optimize\ShellOperations
<?php
namespace Drupal\imageapi_optimize;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Image\ImageFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for image optimizations that run local binaries.
*/
abstract class ImageAPIOptimizeProcessorBinaryBase extends ConfigurableImageAPIOptimizeProcessorBase {
/**
* The file system service.
*
* @var \Drupal\core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The imageapi shell operation service.
*
* @var \Drupal\imageapi_optimize\ImageAPIOptimizeShellOperationsInterface
*/
protected $shellOperations;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, ImageFactory $image_factory, FileSystemInterface $file_system, ImageAPIOptimizeShellOperationsInterface $shell_operations) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $logger, $image_factory);
$this->fileSystem = $file_system;
$this->shellOperations = $shell_operations;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('logger.factory')->get('imageapi_optimize'),
$container->get('image.factory'),
$container->get('file_system'),
$container->get('imageapi_optimize.shell_operations')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'manual_executable_path' => '',
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
if (!$this->findExecutablePath()) {
$form['executable'] = array(
'#type' => 'item',
'#title' => $this->t('Executable'),
'#markup' => $this->t('The @binary binary must be installed, please install or specify the path to the @binary executable directly.', array('@binary' => $this->executableName())),
);
}
else {
$form['executable'] = array(
'#type' => 'item',
'#title' => $this->t('Executable'),
'#markup' => $this->t('The @binary executable has been automatically located: @path. To override, set a different executate path below.', array('@path' => $this->findExecutablePath(), '@binary' => $this->executableName())),
);
}
$form['manual_executable_path'] = array(
'#type' => 'textfield',
'#title' => $this->t('Manually set path'),
'#description' => $this->t('Specify the full path to the @binary executable.', array('@binary' => $this->executableName())),
'#default_value' => $this->configuration['manual_executable_path'],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['manual_executable_path'] = $form_state->getValue('manual_executable_path');
}
/**
* Search the local system for the given executable binary.
*
* @param null $executable
* The name of the executable binary to find on the local system. If not
* specified the default executeable name for this class will be used.
*
* @return string|false
* The path to the binary on the local system, or FALSE if it could not be
* located.
*/
protected function findExecutablePath($executable = NULL) {
if (is_null($executable)) {
$executable = $this->executableName();
}
return $this->shellOperations->findExecutablePath($executable);
}
/**
* Execute a shell command on the local system.
*
* @param $command
* The command to execute.
* @param $options
* An array of options for the command. This will not be escaped before executing.
* @param $arguments
* An array of arguments for the command. These will be escaped.
*
* @return bool
* Returns TRUE if the command completed successfully, FALSE otherwise.
*/
protected function execShellCommand($command, $options, $arguments) {
return $this->shellOperations->execShellCommand($command, $options, $arguments);
}
/**
* Sanitize the given path for binary processors.
*
* @param $drupal_filepath
* The file path to sanitize.
*
* @return string
* The sanitized file path.
*/
protected function sanitizeFilename($drupal_filepath) {
return $this->fileSystem->realpath($drupal_filepath);
}
protected function saveCommandStdoutToFile($cmd, $dst) {
return $this->shellOperations->saveCommandStdoutToFile($cmd, $dst);
}
public function getFullPathToBinary() {
if ($this->configuration['manual_executable_path']) {
return $this->configuration['manual_executable_path'];
}
elseif ($this->findExecutablePath()) {
return $this->findExecutablePath();
}
}
public function getSummary() {
$description = '';
if (!$this->getFullPathToBinary()) {
$description .= $this->t('<strong>Command not found</strong>');
}
$summary = array(
'#markup' => $description,
);
$summary += parent::getSummary();
return $summary;
}
abstract protected function executableName();
/**
* {@inheritdoc}
*/
abstract public function applyToImage($image_uri);
}
<?php
namespace Drupal\imageapi_optimize;
interface ImageAPIOptimizeShellOperationsInterface {
public function findExecutablePath($executable = NULL);
public function execShellCommand($command, $options, $arguments);
public function saveCommandStdoutToFile($cmd, $dst);
}
<?php
namespace Drupal\imageapi_optimize\Plugin\ImageAPIOptimizeProcessor;
use Drupal\Core\Form\FormStateInterface;
use Drupal\imageapi_optimize\ImageAPIOptimizeProcessorBinaryBase;
/**
* Uses the AdvDef binary to optimize images.
*
* @ImageAPIOptimizeProcessor(
* id = "advdef",
* label = @Translation("AdvDef"),
* description = @Translation("Uses the AdvDef binary to optimize images.")
* )
*/
class AdvDef extends ImageAPIOptimizeProcessorBinaryBase {
/**
* {@inheritdoc}
*/
protected function executableName() {
return 'advdef';
}
public function applyToImage($image_uri) {
if ($cmd = $this->getFullPathToBinary()) {
if ($this->getMimeType($image_uri) == 'image/png') {
$options = array(
'--quiet',
);
$arguments = array(
$this->sanitizeFilename($image_uri),
);
if ($this->configuration['recompress']) {
$options[] = '--recompress';
}
$options[] = '-' . $this->configuration['mode'];
return $this->execShellCommand($cmd, $options, $arguments);
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'recompress' => TRUE,
'mode' => 3,
];
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['recompress'] = array(
'#title' => $this->t('Recompress'),
'#type' => 'checkbox',
'#default_value' => $this->configuration['recompress'],
);
$form['mode'] = array(
'#title' => $this->t('Compression mode'),
'#type' => 'select',
'#options' => array(
0 => $this->t('Disabled'),
1 => $this->t('Fast'),
2 => $this->t('Normal'),
3 => $this->t('Extra'),
4 => $this->t('Insane'),
),
'#default_value' => $this->configuration['mode'],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['recompress'] = $form_state->getValue('recompress');
$this->configuration['mode'] = $form_state->getValue('mode');
}
}
<?php
namespace Drupal\imageapi_optimize\Plugin\ImageAPIOptimizeProcessor;
use Drupal\Core\Form\FormStateInterface;
use Drupal\imageapi_optimize\ImageAPIOptimizeProcessorBinaryBase;
/**
* Uses the AdvPng binary to optimize images.
*
* @ImageAPIOptimizeProcessor(
* id = "advpng",
* label = @Translation("AdvPng"),
* description = @Translation("Uses the AdvPng binary to optimize images.")
* )
*/
class AdvPng extends ImageAPIOptimizeProcessorBinaryBase {
/**
* {@inheritdoc}
*/
protected function executableName() {
return 'advpng';
}
public function applyToImage($image_uri) {
if ($cmd = $this->getFullPathToBinary()) {
if ($this->getMimeType($image_uri) == 'image/png') {
$options = array(
'--quiet',
);
$arguments = array(
$this->sanitizeFilename($image_uri),
);
if ($this->configuration['recompress']) {
$options[] = '--recompress';
}
$options[] = '-' . $this->configuration['mode'];
return $this->execShellCommand($cmd, $options, $arguments);
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'recompress' => TRUE,
'mode' => 3,
];
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['recompress'] = array(
'#title' => $this->t('Recompress'),
'#type' => 'checkbox',
'#default_value' => $this->configuration['recompress'],
);
$form['mode'] = array(
'#title' => $this->t('Compression mode'),
'#type' => 'select',
'#options' => array(
0 => $this->t('Disabled'),
1 => $this->t('Fast'),
2 => $this->t('Normal'),
3 => $this->t('Extra'),
4 => $this->t('Insane'),
),
'#default_value' => $this->configuration['mode'],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['recompress'] = $form_state->getValue('recompress');
$this->configuration['mode'] = $form_state->getValue('mode');
}
}
<?php
namespace Drupal\imageapi_optimize\Plugin\ImageAPIOptimizeProcessor;