Commit a48f0d88 authored by alexpott's avatar alexpott

Issue #1821854 by tim.plunkett, effulgentsia, larowlan: Convert image effects into plugins.

parent 88cbedb8
......@@ -104,10 +104,10 @@ function testSchemaMapping() {
$expected['mapping']['label']['type'] = 'label';
$expected['mapping']['effects']['type'] = 'sequence';
$expected['mapping']['effects']['sequence'][0]['type'] = 'mapping';
$expected['mapping']['effects']['sequence'][0]['mapping']['name']['type'] = 'string';
$expected['mapping']['effects']['sequence'][0]['mapping']['data']['type'] = 'image.effect.[%parent.name]';
$expected['mapping']['effects']['sequence'][0]['mapping']['id']['type'] = 'string';
$expected['mapping']['effects']['sequence'][0]['mapping']['data']['type'] = 'image.effect.[%parent.id]';
$expected['mapping']['effects']['sequence'][0]['mapping']['weight']['type'] = 'integer';
$expected['mapping']['effects']['sequence'][0]['mapping']['ieid']['type'] = 'string';
$expected['mapping']['effects']['sequence'][0]['mapping']['uuid']['type'] = 'string';
$expected['mapping']['langcode']['label'] = 'Default language';
$expected['mapping']['langcode']['type'] = 'string';
......@@ -189,9 +189,9 @@ function testSchemaData() {
// The function is_array() doesn't work with ArrayAccess, so we use count().
$this->assertTrue(count($effects) == 1, 'Got an array with effects for image.style.large data');
$ieid = key($effects->getValue());
$effect = $effects[$ieid];
$this->assertTrue(count($effect['data']) && $effect['name']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
$uuid = key($effects->getValue());
$effect = $effects[$uuid];
$this->assertTrue(count($effect['data']) && $effect['id']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
$this->assertEqual($effect['data']['width']->getType(), 'integer', 'Got the right type for the scale effect width.');
$this->assertEqual($effect['data']['width']->getValue(), 480, 'Got the right value for the scale effect width.' );
......
......@@ -2,11 +2,11 @@ name: large
label: 'Large (480x480)'
effects:
ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d:
name: image_scale
id: image_scale
data:
width: '480'
height: '480'
upscale: '1'
weight: '0'
ieid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d
uuid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d
langcode: en
......@@ -2,11 +2,11 @@ name: medium
label: 'Medium (220x220)'
effects:
bddf0d06-42f9-4c75-a700-a33cafa25ea0:
name: image_scale
id: image_scale
data:
width: '220'
height: '220'
upscale: '1'
weight: '0'
ieid: bddf0d06-42f9-4c75-a700-a33cafa25ea0
uuid: bddf0d06-42f9-4c75-a700-a33cafa25ea0
langcode: en
......@@ -2,11 +2,11 @@ name: thumbnail
label: 'Thumbnail (100x100)'
effects:
1cfec298-8620-4749-b100-ccb6c4500779:
name: image_scale
id: image_scale
data:
width: '100'
height: '100'
upscale: '1'
weight: '0'
ieid: 1cfec298-8620-4749-b100-ccb6c4500779
uuid: 1cfec298-8620-4749-b100-ccb6c4500779
langcode: en
......@@ -26,13 +26,13 @@ image.style.*:
sequence:
- type: mapping
mapping:
name:
id:
type: string
data:
type: image.effect.[%parent.name]
type: image.effect.[%parent.id]
weight:
type: integer
ieid:
uuid:
type: string
langcode:
type: string
......
This diff is collapsed.
......@@ -11,58 +11,14 @@
*/
/**
* Define information about image effects provided by a module.
*
* This hook enables modules to define image manipulation effects for use with
* an image style.
*
* @return
* An array of image effects. This array is keyed on the machine-readable
* effect name. Each effect is defined as an associative array containing the
* following items:
* - "label": The human-readable name of the effect.
* - "effect callback": The function to call to perform this image effect.
* - "dimensions passthrough": (optional) Set this item if the effect doesn't
* change the dimensions of the image.
* - "dimensions callback": (optional) The function to call to transform
* dimensions for this effect.
* - "help": (optional) A brief description of the effect that will be shown
* when adding or configuring this image effect.
* - "form callback": (optional) The name of a function that will return a
* $form array providing a configuration form for this image effect.
* - "summary theme": (optional) The name of a theme function that will output
* a summary of this image effect's configuration.
*
* @see hook_image_effect_info_alter()
*/
function hook_image_effect_info() {
$effects = array();
$effects['mymodule_resize'] = array(
'label' => t('Resize'),
'help' => t('Resize an image to an exact set of dimensions, ignoring aspect ratio.'),
'effect callback' => 'mymodule_resize_effect',
'dimensions callback' => 'mymodule_resize_dimensions',
'form callback' => 'mymodule_resize_form',
'summary theme' => 'mymodule_resize_summary',
);
return $effects;
}
/**
* Alter the information provided in hook_image_effect_info().
* Alter the information provided in \Drupal\image\Annotation\ImageEffect.
*
* @param $effects
* The array of image effects, keyed on the machine-readable effect name.
*
* @see hook_image_effect_info()
*/
function hook_image_effect_info_alter(&$effects) {
// Override the Image module's crop effect with more options.
$effects['image_crop']['effect callback'] = 'mymodule_crop_effect';
$effects['image_crop']['dimensions callback'] = 'mymodule_crop_dimensions';
$effects['image_crop']['form callback'] = 'mymodule_crop_form';
// Override the Image module's 'Scale and Crop' effect label.
$effects['image_scale_and_crop']['label'] = t('Bangers and Mash');
}
/**
......
This diff is collapsed.
......@@ -126,14 +126,19 @@ function _image_update_get_style_with_effects(array $style) {
->condition('isid', $style['isid'])
->execute();
foreach ($result as $effect) {
unset($effect['isid']);
$effect['data'] = unserialize($effect['data']);
// Generate a unique image effect ID for the effect.
$uuid = new Uuid();
$effect['ieid'] = $uuid->generate();
$effect['uuid'] = $uuid->generate();
$effects[$effect['ieid']] = $effect;
// Use 'id' instead of 'name'.
$effect['id'] = $effect['name'];
// Clear out legacy keys.
unset($effect['isid'], $effect['ieid'], $effect['name']);
$effects[$effect['uuid']] = $effect;
}
return $effects;
}
......
This diff is collapsed.
......@@ -19,3 +19,16 @@ image_style_private:
requirements:
_access: 'TRUE'
image_effect_add_form:
pattern: '/admin/config/media/image-styles/manage/{image_style}/add/{image_effect}'
defaults:
_form: '\Drupal\image\Form\ImageEffectAddForm'
requirements:
_permission: 'administer image styles'
image_effect_edit_form:
pattern: '/admin/config/media/image-styles/manage/{image_style}/effects/{image_effect}'
defaults:
_form: '\Drupal\image\Form\ImageEffectEditForm'
requirements:
_permission: 'administer image styles'
......@@ -7,3 +7,6 @@ services:
class: Drupal\image\PathProcessor\PathProcessorImageStyles
tags:
- { name: path_processor_inbound, priority: 300 }
plugin.manager.image.effect:
class: Drupal\image\ImageEffectManager
arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
<?php
/**
* @file
* Contains \Drupal\image\Annotation\ImageEffect.
*/
namespace Drupal\image\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines an image effect annotation object.
*
* @see hook_image_effect_info_alter()
*
* @Annotation
*/
class ImageEffect extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the image effect.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* A brief description of the image effect.
*
* This will be shown when adding or configuring this image effect.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation (optional)
*/
public $description = '';
}
<?php
/**
* @file
* Contains \Drupal\image\ConfigurableImageEffectInterface.
*/
namespace Drupal\image;
/**
* Defines the interface for configurable image effects.
*/
interface ConfigurableImageEffectInterface extends ImageEffectInterface {
/**
* Builds the part of the image effect form specific to this image effect.
*
* This method is only responsible for the form elements specific to this
* image effect. All other aspects of the form are handled by calling code.
*
* @return array
* A render array.
*/
public function getForm();
}
<?php
/**
* @file
* Contains \Drupal\image\Form\ImageEffectAddForm.
*/
namespace Drupal\image\Form;
use Drupal\Core\Controller\ControllerInterface;
use Drupal\image\ImageEffectManager;
use Drupal\image\ImageStyleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides an add form for image effects.
*/
class ImageEffectAddForm extends ImageEffectFormBase implements ControllerInterface {
/**
* The image effect manager.
*
* @var \Drupal\image\ImageEffectManager
*/
protected $effectManager;
/**
* Constructs a new ImageEffectAddForm.
*
* @param \Drupal\image\ImageEffectManager $effect_manager
* The image effect manager.
*/
public function __construct(ImageEffectManager $effect_manager) {
$this->effectManager = $effect_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.image.effect')
);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
$form = parent::buildForm($form, $form_state, $request, $image_style, $image_effect);
drupal_set_title(t('Add %label effect', array('%label' => $this->imageEffect->label())), PASS_THROUGH);
$form['actions']['submit']['#value'] = t('Add effect');
return $form;
}
/**
* {@inheritdoc}
*/
protected function prepareImageEffect($image_effect) {
$image_effect = $this->effectManager->createInstance($image_effect);
// Set the initial weight so this effect comes last.
$image_effect->setWeight(count($this->imageStyle->getEffects()));
return $image_effect;
}
}
......@@ -8,7 +8,7 @@
namespace Drupal\image\Form;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\image\Plugin\Core\Entity\ImageStyle;
use Drupal\image\ImageStyleInterface;
use Symfony\Component\HttpFoundation\Request;
/**
......@@ -19,14 +19,14 @@ class ImageEffectDeleteForm extends ConfirmFormBase {
/**
* The image style containing the image effect to be deleted.
*
* @var \Drupal\image\Plugin\Core\Entity\ImageStyle
* @var \Drupal\image\ImageStyleInterface
*/
protected $imageStyle;
/**
* The image effect to be deleted.
*
* @var array;
* @var \Drupal\image\ImageEffectInterface
*/
protected $imageEffect;
......@@ -34,7 +34,7 @@ class ImageEffectDeleteForm extends ConfirmFormBase {
* {@inheritdoc}
*/
public function getQuestion() {
return t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $this->imageStyle->label(), '@effect' => $this->imageEffect['label']));
return t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $this->imageStyle->label(), '@effect' => $this->imageEffect->label()));
}
/**
......@@ -61,9 +61,9 @@ public function getFormID() {
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state, $image_style = NULL, $image_effect = NULL, Request $request = NULL) {
public function buildForm(array $form, array &$form_state, ImageStyleInterface $image_style = NULL, $image_effect = NULL, Request $request = NULL) {
$this->imageStyle = $image_style;
$this->imageEffect = image_effect_load($image_effect, $this->imageStyle->id());
$this->imageEffect = $this->imageStyle->getEffect($image_effect);
return parent::buildForm($form, $form_state, $request);
}
......@@ -72,8 +72,8 @@ public function buildForm(array $form, array &$form_state, $image_style = NULL,
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
image_effect_delete($this->imageStyle, $this->imageEffect);
drupal_set_message(t('The image effect %name has been deleted.', array('%name' => $this->imageEffect['label'])));
$this->imageStyle->deleteImageEffect($this->imageEffect);
drupal_set_message(t('The image effect %name has been deleted.', array('%name' => $this->imageEffect->label())));
$form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $this->imageStyle->id();
}
......
<?php
/**
* @file
* Contains \Drupal\image\Form\ImageEffectEditForm.
*/
namespace Drupal\image\Form;
use Drupal\image\ImageStyleInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides an edit form for image effects.
*/
class ImageEffectEditForm extends ImageEffectFormBase {
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
$form = parent::buildForm($form, $form_state, $request, $image_style, $image_effect);
drupal_set_title(t('Edit %label effect', array('%label' => $this->imageEffect->label())), PASS_THROUGH);
$form['actions']['submit']['#value'] = t('Update effect');
return $form;
}
/**
* {@inheritdoc}
*/
protected function prepareImageEffect($image_effect) {
return $this->imageStyle->getEffect($image_effect);
}
}
<?php
/**
* @file
* Contains \Drupal\image\Form\ImageEffectFormBase.
*/
namespace Drupal\image\Form;
use Drupal\Core\Form\FormInterface;
use Drupal\image\ConfigurableImageEffectInterface;
use Drupal\image\ImageStyleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Provides a base form for image effects.
*/
abstract class ImageEffectFormBase implements FormInterface {
/**
* The image style.
*
* @var \Drupal\image\ImageStyleInterface
*/
protected $imageStyle;
/**
* The image effect.
*
* @var \Drupal\image\ImageEffectInterface
*/
protected $imageEffect;
/**
* {@inheritdoc}
*/
public function getFormID() {
return 'image_effect_form';
}
/**
* {@inheritdoc}
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param \Drupal\image\ImageStyleInterface $image_style
* The image style.
* @param string $image_effect
* The image effect ID.
*
* @return array
* The form structure.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
$this->imageStyle = $image_style;
$this->imageEffect = $this->prepareImageEffect($image_effect);
if (!($this->imageEffect instanceof ConfigurableImageEffectInterface)) {
throw new NotFoundHttpException();
}
$form['#attached']['css'][drupal_get_path('module', 'image') . '/css/image.admin.css'] = array();
$form['uuid'] = array(
'#type' => 'value',
'#value' => $this->imageEffect->getUuid(),
);
$form['id'] = array(
'#type' => 'value',
'#value' => $this->imageEffect->getPluginId(),
);
$form['data'] = $this->imageEffect->getForm();
$form['data']['#tree'] = TRUE;
// Check the URL for a weight, then the image effect, otherwise use default.
$form['weight'] = array(
'#type' => 'hidden',
'#value' => $request->query->has('weight') ? (int) $request->query->get('weight') : $this->imageEffect->getWeight(),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#button_type' => 'primary',
);
$form['actions']['cancel'] = array(
'#type' => 'link',
'#title' => t('Cancel'),
'#href' => 'admin/config/media/image-styles/manage/' . $this->imageStyle->id(),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, array &$form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
form_state_values_clean($form_state);
$this->imageStyle->saveImageEffect($form_state['values']);
drupal_set_message(t('The image effect was successfully applied.'));
$form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $this->imageStyle->id();
}
/**
* Converts an image effect ID into an object.
*
* @param string $image_effect
* The image effect ID.
*
* @return \Drupal\image\ImageEffectInterface
* The image effect object.
*/
abstract protected function prepareImageEffect($image_effect);
}
<?php
/**
* @file
* Contains \Drupal\image\ImageEffectBag.
*/
namespace Drupal\image;
use Drupal\Component\Plugin\PluginBag;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\MapArray;
use Drupal\Component\Uuid\Uuid;
/**
* A collection of image effects.
*/
class ImageEffectBag extends PluginBag {
/**
* The manager used to instantiate the plugins.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $manager;
/**
* The initial configuration for each image effect in the bag.
*
* @var array
*/
protected $configurations = array();
/**
* Constructs a new ImageEffectBag.
*
* @param \Drupal\Component\Plugin\PluginManagerInterface $manager
* The manager to be used for instantiating plugins.
* @param array $configurations
* (optional) An associative array containing the initial configuration for
* each tour in the bag, keyed by plugin instance ID.
*/
public function __construct(PluginManagerInterface $manager, array $configurations = array()) {
$this->manager = $manager;
$this->configurations = $configurations;
if (!empty($configurations)) {
$this->instanceIDs = MapArray::copyValuesToKeys(array_keys($configurations));
}
}
/**
* {@inheritdoc}
*/
protected function initializePlugin($instance_id) {
if (!isset($this->pluginInstances[$instance_id])) {
$configuration = $this->configurations[$instance_id] + array('data' => array());
$this->pluginInstances[$instance_id] = $this->manager->createInstance($configuration['id'], $configuration);
}
}
/**
* Returns the current configuration of all image effects in this bag.
*
* @return array
* An associative array keyed by image effect UUID, whose values are image
* effect configurations.
*/
public function export() {
$instances = array();
$this->rewind();
foreach ($this as $instance_id => $instance) {
$instances[$instance_id] = $instance->export();
}
return $instances;
}
/**
* Removes an instance ID.
*
* @param string $instance_id
* An image effect instance IDs.
*/
public function removeInstanceID($instance_id) {
unset($this->instanceIDs[$instance_id], $this->configurations[$instance_id]);
$this->remove($instance_id);
}
/**
* Updates the configuration for an image effect instance.
*
* If there is no plugin instance yet, a new will be instantiated. Otherwise,
* the existing instance is updated with the new configuration.
*
* @param array $configuration
* The image effect configuration to set.
*
* @return string
*/
public function setConfig(array $configuration) {
// Derive the instance ID from the configuration.
if (empty($configuration['uuid'])) {
$uuid_generator = new Uuid();
$configuration['uuid'] = $uuid_generator->generate();
}
$instance_id = $configuration['uuid'];
$this->configurations[$instance_id] = $configuration;
$this->get($instance_id)->setPluginConfiguration($configuration);
$this->addInstanceID($instance_id);
return $instance_id;
}
/**
* Sorts all image effect instances in this bag.
*
* @return self
*/
public function sort() {
uasort($this->configurations, 'drupal_sort_weight');
$this->instanceIDs = MapArray::copyValuesToKeys(array_keys($this->configurations));
return $this;
}
}