Commit a48f0d88 authored by alexpott's avatar alexpott
Browse files

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
......
......@@ -5,6 +5,9 @@
* Administration pages for image settings.
*/
use Drupal\Component\Utility\String;
use Drupal\image\ConfigurableImageEffectInterface;
use Drupal\image\ImageStyleInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
......@@ -35,7 +38,7 @@ function image_style_list() {
* @ingroup forms
* @see image_style_form_submit()
*/
function image_style_form($form, &$form_state, $style) {
function image_style_form($form, &$form_state, ImageStyleInterface $style) {
$title = t('Edit style %name', array('%name' => $style->label()));
drupal_set_title($title, PASS_THROUGH);
......@@ -69,54 +72,56 @@ function image_style_form($form, &$form_state, $style) {
$form['effects'] = array(
'#theme' => 'image_style_effects',
);
if (!empty($style->effects)) {
foreach ($style->effects as $key => $effect) {
$form['effects'][$key]['#weight'] = isset($form_state['input']['effects']) ? $form_state['input']['effects'][$key]['weight'] : NULL;
$form['effects'][$key]['label'] = array(
'#markup' => check_plain($effect['label']),
);
$form['effects'][$key]['summary'] = array(
'#markup' => isset($effect['summary theme']) ? theme($effect['summary theme'], array('data' => $effect['data'])) : '',
);
$form['effects'][$key]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight for @title', array('@title' => $effect['label'])),
'#title_display' => 'invisible',
'#default_value' => $effect['weight'],
);
foreach ($style->getEffects()->sort() as $effect) {
$key = $effect->getUuid();
$form['effects'][$key]['#weight'] = isset($form_state['input']['effects']) ? $form_state['input']['effects'][$key]['weight'] : NULL;
$form['effects'][$key]['label'] = array(
'#markup' => String::checkPlain($effect->label()),
);
$form['effects'][$key]['summary'] = $effect->getSummary();
$form['effects'][$key]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight for @title', array('@title' => $effect->label())),
'#title_display' => 'invisible',
'#default_value' => $effect->getWeight(),
);
$links = array();
if (isset($effect['form callback'])) {
$links['edit'] = array(
'title' => t('edit'),
'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
);
}
$links['delete'] = array(
'title' => t('delete'),
'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
);
$form['effects'][$key]['operations'] = array(
'#type' => 'operations',
'#links' => $links,
);
$form['effects'][$key]['configure'] = array(
'#type' => 'link',
'#title' => t('edit'),
'#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
'#access' => isset($effect['form callback']),
);
$form['effects'][$key]['remove'] = array(
'#type' => 'link',
'#title' => t('delete'),
'#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
$links = array();
$is_configurable = $effect instanceof ConfigurableImageEffectInterface;
if ($is_configurable) {
$links['edit'] = array(
'title' => t('edit'),
'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
);
}
$links['delete'] = array(
'title' => t('delete'),
'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
);
$form['effects'][$key]['operations'] = array(
'#type' => 'operations',
'#links' => $links,
);
$form['effects'][$key]['configure'] = array(
'#type' => 'link',
'#title' => t('edit'),
'#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
'#access' => $is_configurable,
);
$form['effects'][$key]['remove'] = array(
'#type' => 'link',
'#title' => t('delete'),
'#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
);
}
// Build the new image effect addition form and add it to the effect list.
$new_effect_options = array();
foreach (image_effect_definitions() as $effect => $definition) {
$effects = Drupal::service('plugin.manager.image.effect')->getDefinitions();
uasort($effects, function ($a, $b) {
return strcasecmp($a['id'], $b['id']);
});
foreach ($effects as $effect => $definition) {
$new_effect_options[$effect] = $definition['label'];
}
$form['effects']['new'] = array(
......@@ -168,21 +173,21 @@ function image_style_form_add_validate($form, &$form_state) {
function image_style_form_add_submit($form, &$form_state) {
$style = $form_state['image_style'];
// Check if this field has any configuration options.
$effect = image_effect_definition_load($form_state['values']['new']);
$effect = Drupal::service('plugin.manager.image.effect')->getDefinition($form_state['values']['new']);
// Load the configuration form for this option.
if (isset($effect['form callback'])) {
if (is_subclass_of($effect['class'], '\Drupal\image\ConfigurableImageEffectInterface')) {
$path = 'admin/config/media/image-styles/manage/' . $style->id() . '/add/' . $form_state['values']['new'];
$form_state['redirect'] = array($path, array('query' => array('weight' => $form_state['values']['weight'])));
}
// If there's no form, immediately add the image effect.
else {
$effect = array(
'name' => $effect['name'],
'id' => $effect['id'],
'data' => array(),
'weight' => $form_state['values']['weight'],
);
image_effect_save($style, $effect);
$style->saveImageEffect($effect);
drupal_set_message(t('The image effect was successfully applied.'));
}
}
......@@ -195,15 +200,9 @@ function image_style_form_submit($form, &$form_state) {
// Update image effect weights.
if (!empty($form_state['values']['effects'])) {
foreach ($form_state['values']['effects'] as $ieid => $effect_data) {
if (isset($style->effects[$ieid])) {
$effect = array(
'name' => $style->effects[$ieid]['name'],
'data' => $style->effects[$ieid]['data'],
'weight' => $effect_data['weight'],
'ieid' => $ieid,
);
$style->effects[$ieid] = $effect;
foreach ($form_state['values']['effects'] as $uuid => $effect_data) {
if ($style->getEffects()->has($uuid)) {
$style->getEffect($uuid)->setWeight($effect_data['weight']);
}
}
}
......@@ -261,246 +260,6 @@ function image_style_add_form_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $style->id();
}
/**
* Form builder; Form for adding and editing image effects.
*
* This form is used universally for editing all image effects. Each effect adds
* its own custom section to the form by calling the 'form callback' specified
* in hook_image_effect_info().
*
* @param $form_state
* An associative array containing the current state of the form.
* @param $style
* An image style array.
* @param $effect
* An image effect array.
*
* @ingroup forms
* @see image_resize_form()
* @see image_scale_form()
* @see image_rotate_form()
* @see image_crop_form()
* @see image_effect_form_submit()
*/
function image_effect_form($form, &$form_state, $style, $effect) {
// If there's no configuration for this image effect, return to
// the image style page.
if (!isset($effect['form callback'])) {
return new RedirectResponse(url('admin/config/media/image-styles/manage/' . $style->id(), array('absolute' => TRUE)));
}
$form_state['image_style'] = $style;
$form_state['image_effect'] = $effect;
if (!empty($effect['ieid'])) {
$title = t('Edit %label effect', array('%label' => $effect['label']));
}
else{
$title = t('Add %label effect', array('%label' => $effect['label']));
}
drupal_set_title($title, PASS_THROUGH);
$form['#attached']['css'][drupal_get_path('module', 'image') . '/css/image.admin.css'] = array();
$form['ieid'] = array(
'#type' => 'value',
'#value' => !empty($effect['ieid']) ? $effect['ieid'] : NULL,
);
$form['name'] = array(
'#type' => 'value',
'#value' => $effect['name'],
);
$form['data'] = call_user_func($effect['form callback'], $effect['data']);
$form['data']['#tree'] = TRUE;
// Check the URL for a weight, then the image effect, otherwise use default.
$weight = Drupal::request()->query->get('weight');
$form['weight'] = array(
'#type' => 'hidden',
'#value' => isset($weight) ? intval($weight) : (isset($effect['weight']) ? $effect['weight'] : count($style->effects)),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => !empty($effect['ieid']) ? t('Update effect') : t('Add effect'),
);
$form['actions']['cancel'] = array(
'#type' => 'link',
'#title' => t('Cancel'),
'#href' => 'admin/config/media/image-styles/manage/' . $style->id(),
);
return $form;
}
/**
* Submit handler for updating an image effect.
*/
function image_effect_form_submit($form, &$form_state) {
form_state_values_clean($form_state);
$effect = $form_state['values'];
$style = $form_state['image_style'];
image_effect_save($style, $effect);
drupal_set_message(t('The image effect was successfully applied.'));
$form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $style->id();
}
/**
* Element validate handler to ensure a hexadecimal color value.
*/
function image_effect_color_validate($element, &$form_state) {
if ($element['#value'] != '') {
$hex_value = preg_replace('/^#/', '', $element['#value']);
if (!preg_match('/^#[0-9A-F]{3}([0-9A-F]{3})?$/', $element['#value'])) {
form_error($element, t('!name must be a hexadecimal color value.', array('!name' => $element['#title'])));
}
}
}
/**
* Element validate handler to ensure that either a height or a width is
* specified.
*/
function image_effect_scale_validate($element, &$form_state) {
if (empty($element['width']['#value']) && empty($element['height']['#value'])) {
form_error($element, t('Width and height can not both be blank.'));
}
}
/**
* Form structure for the image resize form.
*
* Note that this is not a complete form, it only contains the portion of the
* form for configuring the resize options. Therefore it does not not need to
* include metadata about the effect, nor a submit button.
*
* @param $data
* The current configuration for this resize effect.
*/
function image_resize_form($data) {
$form['width'] = array(
'#type' => 'number',
'#title' => t('Width'),
'#default_value' => isset($data['width']) ? $data['width'] : '',
'#field_suffix' => ' ' . t('pixels'),
'#required' => TRUE,
'#min' => 1,
);
$form['height'] = array(
'#type' => 'number',
'#title' => t('Height'),
'#default_value' => isset($data['height']) ? $data['height'] : '',
'#field_suffix' => ' ' . t('pixels'),
'#required' => TRUE,
'#min' => 1,
);
return $form;
}
/**
* Form structure for the image scale form.
*
* Note that this is not a complete form, it only contains the portion of the
* form for configuring the scale options. Therefore it does not not need to
* include metadata about the effect, nor a submit button.
*
* @param $data
* The current configuration for this scale effect.
*/
function image_scale_form($data) {
$form = image_resize_form($data);
$form['#element_validate'] = array('image_effect_scale_validate');
$form['width']['#required'] = FALSE;
$form['height']['#required'] = FALSE;
$form['upscale'] = array(
'#type' => 'checkbox',
'#default_value' => (isset($data['upscale'])) ? $data['upscale'] : 0,
'#title' => t('Allow Upscaling'),
'#description' => t('Let scale make images larger than their original size'),
);
return $form;
}
/**
* Form structure for the image crop form.
*
* Note that this is not a complete form, it only contains the portion of the
* form for configuring the crop options. Therefore it does not not need to
* include metadata about the effect, nor a submit button.
*
* @param $data
* The current configuration for this crop effect.
*/
function image_crop_form($data) {
$data += array(
'width' => '',
'height' => '',
'anchor' => 'center-center',
);
$form = image_resize_form($data);
$form['anchor'] = array(
'#type' => 'radios',
'#title' => t('Anchor'),
'#options' => array(
'left-top' => t('Top') . ' ' . t('Left'),
'center-top' => t('Top') . ' ' . t('Center'),
'right-top' => t('Top') . ' ' . t('Right'),
'left-center' => t('Center') . ' ' . t('Left'),
'center-center' => t('Center'),
'right-center' => t('Center') . ' ' . t('Right'),
'left-bottom' => t('Bottom') . ' ' . t('Left'),
'center-bottom' => t('Bottom') . ' ' . t('Center'),
'right-bottom' => t('Bottom') . ' ' . t('Right'),
),
'#theme' => 'image_anchor',
'#default_value' => $data['anchor'],
'#description' => t('The part of the image that will be retained during the crop.'),
);
return $form;
}
/**
* Form structure for the image rotate form.
*
* Note that this is not a complete form, it only contains the portion of the
* form for configuring the rotate options. Therefore it does not not need to
* include metadata about the effect, nor a submit button.
*
* @param $data
* The current configuration for this rotate effect.
*/
function image_rotate_form($data) {
$form['degrees'] = array(
'#type' => 'number',
'#default_value' => (isset($data['degrees'])) ? $data['degrees'] : 0,
'#title' => t('Rotation angle'),
'#description' => t('The number of degrees the image should be rotated. Positive numbers are clockwise, negative are counter-clockwise.'),
'#field_suffix' => '°',
'#required' => TRUE,
);
$form['bgcolor'] = array(
'#type' => 'textfield',
'#default_value' => (isset($data['bgcolor'])) ? $data['bgcolor'] : '#FFFFFF',
'#title' => t('Background color'),
'#description' => t('The background color to use for exposed areas of the image. Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave blank for transparency on image types that support it.'),
'#size' => 7,
'#maxlength' => 7,
'#element_validate' => array('image_effect_color_validate'),
);
$form['random'] = array(
'#type' => 'checkbox',
'#default_value' => (isset($data['random'])) ? $data['random'] : 0,
'#title' => t('Randomize'),
'#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'),
);
return $form;
}
/**
* Returns HTML for the page containing the list of image styles.
*
......
......@@ -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');
}
/**
......
<?php
/**
* @file
* Functions needed to execute image effects provided by Image module.
*/
/**
* Implements hook_image_effect_info().
*/
function image_image_effect_info() {
$effects = array(
'image_resize' => array(
'label' => t('Resize'),
'help' => t('Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.'),
'effect callback' => 'image_resize_effect',
'dimensions callback' => 'image_resize_dimensions',
'form callback' => 'image_resize_form',
'summary theme' => 'image_resize_summary',
),
'image_scale' => array(
'label' => t('Scale'),
'help' => t('Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.'),
'effect callback' => 'image_scale_effect',
'dimensions callback' => 'image_scale_dimensions',
'form callback' => 'image_scale_form',
'summary theme' => 'image_scale_summary',
),
'image_scale_and_crop' => array(
'label' => t('Scale and crop'),
'help' => t('Scale and crop will maintain the aspect-ratio of the original image, then crop the larger dimension. This is most useful for creating perfectly square thumbnails without stretching the image.'),
'effect callback' => 'image_scale_and_crop_effect',
'dimensions callback' => 'image_resize_dimensions',
'form callback' => 'image_resize_form',
'summary theme' => 'image_resize_summary',
),
'imag