diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php index d3bceb8..f29320f 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php @@ -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.' ); diff --git a/core/modules/image/config/image.style.large.yml b/core/modules/image/config/image.style.large.yml index 30bb1af..cbd1c0c 100644 --- a/core/modules/image/config/image.style.large.yml +++ b/core/modules/image/config/image.style.large.yml @@ -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 diff --git a/core/modules/image/config/image.style.medium.yml b/core/modules/image/config/image.style.medium.yml index 1047f86..ba38b73 100644 --- a/core/modules/image/config/image.style.medium.yml +++ b/core/modules/image/config/image.style.medium.yml @@ -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 diff --git a/core/modules/image/config/image.style.thumbnail.yml b/core/modules/image/config/image.style.thumbnail.yml index 5834812..0368dd3 100644 --- a/core/modules/image/config/image.style.thumbnail.yml +++ b/core/modules/image/config/image.style.thumbnail.yml @@ -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 diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml index 6916f19..871c0cc 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -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 diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc index 1cd2155..1e2646f 100644 --- a/core/modules/image/image.admin.inc +++ b/core/modules/image/image.admin.inc @@ -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']); } } } @@ -262,246 +261,6 @@ function image_style_add_form_submit($form, &$form_state) { } /** - * 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. * * @param $variables diff --git a/core/modules/image/image.api.php b/core/modules/image/image.api.php index bc7ace8..7678142 100644 --- a/core/modules/image/image.api.php +++ b/core/modules/image/image.api.php @@ -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'); } /** diff --git a/core/modules/image/image.effects.inc b/core/modules/image/image.effects.inc deleted file mode 100644 index 619b09b..0000000 --- a/core/modules/image/image.effects.inc +++ /dev/null @@ -1,322 +0,0 @@ - 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', - ), - 'image_crop' => array( - 'label' => t('Crop'), - 'help' => t('Cropping will remove portions of an image to make it the specified dimensions.'), - 'effect callback' => 'image_crop_effect', - 'dimensions callback' => 'image_resize_dimensions', - 'form callback' => 'image_crop_form', - 'summary theme' => 'image_crop_summary', - ), - 'image_desaturate' => array( - 'label' => t('Desaturate'), - 'help' => t('Desaturate converts an image to grayscale.'), - 'effect callback' => 'image_desaturate_effect', - 'dimensions passthrough' => TRUE, - ), - 'image_rotate' => array( - 'label' => t('Rotate'), - 'help' => t('Rotating an image may cause the dimensions of an image to increase to fit the diagonal.'), - 'effect callback' => 'image_rotate_effect', - 'dimensions callback' => 'image_rotate_dimensions', - 'form callback' => 'image_rotate_form', - 'summary theme' => 'image_rotate_summary', - ), - ); - - return $effects; -} - -/** - * Image effect callback; Resize an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the resize effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - * @return bool - * TRUE on success. FALSE on failure to resize image. - * - * @see image_resize() - */ -function image_resize_effect($image, array $data) { - if (!image_resize($image, $data['width'], $data['height'])) { - watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image dimensions callback; Resize. - * - * @param array $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param array $data - * An array of attributes to use when performing the resize effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - */ -function image_resize_dimensions(array &$dimensions, array $data) { - // The new image will have the exact dimensions defined for the effect. - $dimensions['width'] = $data['width']; - $dimensions['height'] = $data['height']; -} - -/** - * Image effect callback; Scale an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the scale effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - "upscale": A boolean indicating that the image should be upscaled if the - * dimensions are larger than the original image. - * - * @return bool - * TRUE on success. FALSE on failure to scale image. - * - * @see image_scale() - */ -function image_scale_effect($image, array $data) { - // Set sane default values. - $data += array( - 'width' => NULL, - 'height' => NULL, - 'upscale' => FALSE, - ); - - if (!image_scale($image, $data['width'], $data['height'], $data['upscale'])) { - watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image dimensions callback; Scale. - * - * @param array $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param array $data - * An array of attributes to use when performing the scale effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - "upscale": A boolean indicating that the image should be upscaled if the - * dimensions are larger than the original image. - */ -function image_scale_dimensions(array &$dimensions, array $data) { - if ($dimensions['width'] && $dimensions['height']) { - image_dimensions_scale($dimensions, $data['width'], $data['height'], $data['upscale']); - } -} - -/** - * Image effect callback; Crop an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the crop effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - "anchor": A string describing where the crop should originate in the form - * of "XOFFSET-YOFFSET". XOFFSET is either a number of pixels or - * "left", "center", "right" and YOFFSET is either a number of pixels or - * "top", "center", "bottom". - * - * @return bool - * TRUE on success. FALSE on failure to crop image. - * - * @see image_crop() - */ -function image_crop_effect($image, array $data) { - // Set sane default values. - $data += array( - 'anchor' => 'center-center', - ); - - list($x, $y) = explode('-', $data['anchor']); - $x = image_filter_keyword($x, $image->info['width'], $data['width']); - $y = image_filter_keyword($y, $image->info['height'], $data['height']); - if (!image_crop($image, $x, $y, $data['width'], $data['height'])) { - watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image effect callback; Scale and crop an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the scale and crop effect - * with the following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - * @return bool - * TRUE on success. FALSE on failure to scale and crop image. - * - * @see image_scale_and_crop() - */ -function image_scale_and_crop_effect($image, array $data) { - if (!image_scale_and_crop($image, $data['width'], $data['height'])) { - watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image effect callback; Desaturate (grayscale) an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the desaturate effect. - * - * @return bool - * TRUE on success. FALSE on failure to desaturate image. - * - * @see image_desaturate() - */ -function image_desaturate_effect($image, $data) { - if (!image_desaturate($image)) { - watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image effect callback; Rotate an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the rotate effect containing - * the following items: - * - "degrees": The number of (clockwise) degrees to rotate the image. - * - "random": A boolean indicating that a random rotation angle should be - * used for this image. The angle specified in "degrees" is used as a - * positive and negative maximum. - * - "bgcolor": 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. - * - * @return bool - * TRUE on success. FALSE on failure to rotate image. - * - * @see image_rotate(). - */ -function image_rotate_effect($image, $data) { - // Set sane default values. - $data += array( - 'degrees' => 0, - 'bgcolor' => NULL, - 'random' => FALSE, - ); - - // Convert short #FFF syntax to full #FFFFFF syntax. - if (strlen($data['bgcolor']) == 4) { - $c = $data['bgcolor']; - $data['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3]; - } - - // Convert #FFFFFF syntax to hexadecimal colors. - if ($data['bgcolor'] != '') { - $data['bgcolor'] = hexdec(str_replace('#', '0x', $data['bgcolor'])); - } - else { - $data['bgcolor'] = NULL; - } - - if (!empty($data['random'])) { - $degrees = abs((float) $data['degrees']); - $data['degrees'] = rand(-1 * $degrees, $degrees); - } - - if (!image_rotate($image, $data['degrees'], $data['bgcolor'])) { - watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image dimensions callback; Rotate. - * - * @param array $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param array $data - * An array of attributes to use when performing the rotate effect containing - * the following items: - * - "degrees": The number of (clockwise) degrees to rotate the image. - * - "random": A boolean indicating that a random rotation angle should be - * used for this image. The angle specified in "degrees" is used as a - * positive and negative maximum. - */ -function image_rotate_dimensions(array &$dimensions, array $data) { - // If the rotate is not random and the angle is a multiple of 90 degrees, - // then the new dimensions can be determined. - if (!$data['random'] && ((int) ($data['degrees']) == $data['degrees']) && ($data['degrees'] % 90 == 0)) { - if ($data['degrees'] % 180 != 0) { - $temp = $dimensions['width']; - $dimensions['width'] = $dimensions['height']; - $dimensions['height'] = $temp; - } - } - else { - $dimensions['width'] = $dimensions['height'] = NULL; - } -} diff --git a/core/modules/image/image.install b/core/modules/image/image.install index fd440da..1617e8c 100644 --- a/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -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; } diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 4e786bd..6293033 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -6,10 +6,8 @@ */ use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Language\Language; use Drupal\field\Plugin\Core\Entity\Field; use Drupal\field\Plugin\Core\Entity\FieldInstance; -use Drupal\Component\Uuid\Uuid; use Drupal\file\Plugin\Core\Entity\File; use Drupal\image\ImageStyleInterface; use Drupal\image\Plugin\Core\Entity\ImageStyle; @@ -72,11 +70,11 @@ function image_help($path, $arg) { case 'admin/config/media/image-styles': return '

' . t('Image styles commonly provide thumbnail sizes by scaling and cropping images, but can also add various effects before an image is displayed. When an image is displayed with a style, a new file is created and the original image is left unchanged.') . '

'; case 'admin/config/media/image-styles/manage/%/add/%': - $effect = image_effect_definition_load($arg[7]); - return isset($effect['help']) ? ('

' . $effect['help'] . '

') : NULL; + $effect = Drupal::service('plugin.manager.image.effect')->getDefinition($arg[7]); + return isset($effect['description']) ? ('

' . $effect['description'] . '

') : NULL; case 'admin/config/media/image-styles/manage/%/effects/%': - $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]); - return isset($effect['help']) ? ('

' . $effect['help'] . '

') : NULL; + $effect = entity_load('image_style', $arg[5])->getEffect($arg[7])->getPluginDefinition(); + return isset($effect['description']) ? ('

' . $effect['description'] . '

') : NULL; } } @@ -164,28 +162,20 @@ function image_menu() { 'weight' => 10, 'route_name' => 'image_style_delete', ); - $items['admin/config/media/image-styles/manage/%image_style/effects/%image_effect'] = array( + $items['admin/config/media/image-styles/manage/%/effects/%'] = array( 'title' => 'Edit image effect', 'description' => 'Edit an existing effect within a style.', - 'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE), - 'page callback' => 'drupal_get_form', - 'page arguments' => array('image_effect_form', 5, 7), - 'access arguments' => array('administer image styles'), - 'file' => 'image.admin.inc', + 'route_name' => 'image_effect_edit_form', ); - $items['admin/config/media/image-styles/manage/%image_style/effects/%image_effect/delete'] = array( + $items['admin/config/media/image-styles/manage/%image_style/effects/%/delete'] = array( 'title' => 'Delete image effect', 'description' => 'Delete an existing effect from a style.', 'route_name' => 'image_effect_delete', ); - $items['admin/config/media/image-styles/manage/%image_style/add/%image_effect_definition'] = array( + $items['admin/config/media/image-styles/manage/%/add/%'] = array( 'title' => 'Add image effect', 'description' => 'Add a new effect to a style.', - 'load arguments' => array(5), - 'page callback' => 'drupal_get_form', - 'page arguments' => array('image_effect_form', 5, 7), - 'access arguments' => array('administer image styles'), - 'file' => 'image.admin.inc', + 'route_name' => 'image_effect_add_form', ); return $items; @@ -212,35 +202,45 @@ function image_theme() { // Theme functions in image.admin.inc. 'image_style_list' => array( 'variables' => array('styles' => NULL), + 'file' => 'image.admin.inc', ), 'image_style_effects' => array( 'render element' => 'form', + 'file' => 'image.admin.inc', ), 'image_style_preview' => array( 'variables' => array('style' => NULL), + 'file' => 'image.admin.inc', ), 'image_anchor' => array( 'render element' => 'element', + 'file' => 'image.admin.inc', ), 'image_resize_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), 'image_scale_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), 'image_crop_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), 'image_rotate_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), // Theme functions in image.field.inc. 'image_widget' => array( 'render element' => 'element', + 'file' => 'image.field.inc', ), 'image_formatter' => array( 'variables' => array('item' => NULL, 'path' => NULL, 'image_style' => NULL), + 'file' => 'image.field.inc', ), ); } @@ -416,169 +416,6 @@ function image_style_deliver(ImageStyleInterface $style, $scheme) { } /** - * Returns a set of image effects. - * - * These image effects are exposed by modules implementing - * hook_image_effect_info(). - * - * @return - * An array of image effects to be used when transforming images. - * @see hook_image_effect_info() - * @see image_effect_definition_load() - */ -function image_effect_definitions() { - $language_interface = language(Language::TYPE_INTERFACE); - - // hook_image_effect_info() includes translated strings, so each language is - // cached separately. - $langcode = $language_interface->id; - - $effects = &drupal_static(__FUNCTION__); - - if (!isset($effects)) { - if ($cache = cache()->get("image_effects:$langcode")) { - $effects = $cache->data; - } - else { - $effects = array(); - include_once __DIR__ . '/image.effects.inc'; - foreach (module_implements('image_effect_info') as $module) { - foreach (module_invoke($module, 'image_effect_info') as $name => $effect) { - // Ensure the current toolkit supports the effect. - $effect['module'] = $module; - $effect['name'] = $name; - $effect['data'] = isset($effect['data']) ? $effect['data'] : array(); - $effects[$name] = $effect; - } - } - uasort($effects, '_image_effect_definitions_sort'); - drupal_alter('image_effect_info', $effects); - cache()->set("image_effects:$langcode", $effects); - } - } - - return $effects; -} - -/** - * Loads the definition for an image effect. - * - * The effect definition is a set of core properties for an image effect, not - * containing any user-settings. The definition defines various functions to - * call when configuring or executing an image effect. This loader is mostly for - * internal use within image.module. Use image_effect_load() or - * entity_load() to get image effects that contain configuration. - * - * @param $effect - * The name of the effect definition to load. - * @return - * An array containing the image effect definition with the following keys: - * - "effect": The unique name for the effect being performed. Usually prefixed - * with the name of the module providing the effect. - * - "module": The module providing the effect. - * - "help": A description of the effect. - * - "function": The name of the function that will execute the effect. - * - "form": (optional) The name of a function to configure the effect. - * - "summary": (optional) The name of a theme function that will display a - * one-line summary of the effect. Does not include the "theme_" prefix. - */ -function image_effect_definition_load($effect) { - $definitions = image_effect_definitions(); - return isset($definitions[$effect]) ? $definitions[$effect] : NULL; -} - -/** - * Loads a single image effect. - * - * @param $ieid - * The image effect ID. - * @param $style_name - * The image style name. - * - * @return - * An image effect array, consisting of the following keys: - * - "ieid": The unique image effect ID. - * - "weight": The weight of this image effect within the image style. - * - "name": The name of the effect definition that powers this image effect. - * - "data": An array of configuration options for this image effect. - * Besides these keys, the entirety of the image definition is merged into - * the image effect array. Returns NULL if the specified effect cannot be - * found. - * @see image_effect_definition_load() - */ -function image_effect_load($ieid, $style_name) { - if (($style = entity_load('image_style', $style_name)) && isset($style->effects[$ieid])) { - $effect = $style->effects[$ieid]; - $definition = image_effect_definition_load($effect['name']); - $effect = array_merge($definition, $effect); - // @todo The effect's key name within the style is unknown. It *should* be - // identical to the ieid, but that is in no way guaranteed. And of course, - // the ieid key *within* the effect is senseless duplication in the first - // place. This problem can be eliminated in many places, but especially - // for loaded menu arguments like %image_effect, the actual router - // callbacks don't have access to 'ieid' anymore (unless resorting to - // dirty %index and %map tricks). - $effect['ieid'] = $ieid; - return $effect; - } - return NULL; -} - -/** - * Saves an image effect. - * - * @param ImageStyle $style - * The image style this effect belongs to. - * @param array $effect - * An image effect array. Passed by reference. - * - * @return array - * The saved image effect array. The 'ieid' key will be set for the effect. - */ -function image_effect_save($style, &$effect) { - // Remove all values that are not properties of an image effect. - // @todo Convert image effects into plugins. - $effect = array_intersect_key($effect, array_flip(array('ieid', 'module', 'name', 'data', 'weight'))); - - // Generate a unique image effect ID for a new effect. - if (empty($effect['ieid'])) { - $uuid = new Uuid(); - $effect['ieid'] = $uuid->generate(); - } - $style->effects[$effect['ieid']] = $effect; - $style->save(); -} - -/** - * Deletes an image effect. - * - * @param ImageStyle $style - * The image style this effect belongs to. - * @param $effect - * An image effect array. - */ -function image_effect_delete($style, $effect) { - unset($style->effects[$effect['ieid']]); - $style->save(); -} - -/** - * Applies an image effect to the image object. - * - * @param $image - * An image object returned by image_load(). - * @param $effect - * An image effect array. - * @return - * TRUE on success. FALSE if unable to perform the image effect on the image. - */ -function image_effect_apply($image, $effect) { - module_load_include('inc', 'image', 'image.effects'); - $function = $effect['effect callback']; - return $function($image, $effect['data']); -} - -/** * Returns HTML for an image using a specific image style. * * @param $variables @@ -656,15 +493,6 @@ function image_filter_keyword($value, $current_pixels, $new_pixels) { } /** - * Internal function for sorting image effect definitions through uasort(). - * - * @see image_effect_definitions() - */ -function _image_effect_definitions_sort($a, $b) { - return strcasecmp($a['name'], $b['name']); -} - -/** * Implements hook_entity_presave(). * * Transforms default image of image field from array into single value at save. diff --git a/core/modules/image/image.routing.yml b/core/modules/image/image.routing.yml index b178d33..01a7d53 100644 --- a/core/modules/image/image.routing.yml +++ b/core/modules/image/image.routing.yml @@ -11,3 +11,17 @@ image_effect_delete: _form: '\Drupal\image\Form\ImageEffectDeleteForm' requirements: _permission: 'administer image styles' + +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' diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml new file mode 100644 index 0000000..0658bf7 --- /dev/null +++ b/core/modules/image/image.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.image.effect: + class: Drupal\image\ImageEffectManager + arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler'] diff --git a/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php new file mode 100644 index 0000000..3a6f763 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php @@ -0,0 +1,48 @@ +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; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php index 50bf2bb..330d626 100644 --- a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\ConfirmFormBase; use Drupal\image\Plugin\Core\Entity\ImageStyle; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; /** @@ -26,7 +27,7 @@ class ImageEffectDeleteForm extends ConfirmFormBase { /** * The image effect to be deleted. * - * @var array; + * @var \Drupal\image\ImageEffectInterface */ protected $imageEffect; @@ -34,7 +35,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())); } /** @@ -63,7 +64,7 @@ public function getFormID() { */ public function buildForm(array $form, array &$form_state, $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 +73,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(); } diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php new file mode 100644 index 0000000..57fda16 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php @@ -0,0 +1,37 @@ + $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); + } + +} diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php new file mode 100644 index 0000000..705dad3 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php @@ -0,0 +1,126 @@ +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); + +} diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBag.php b/core/modules/image/lib/Drupal/image/ImageEffectBag.php new file mode 100644 index 0000000..0bdee3b --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectBag.php @@ -0,0 +1,124 @@ +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; + } + +} diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBase.php b/core/modules/image/lib/Drupal/image/ImageEffectBase.php new file mode 100644 index 0000000..a4ddeb5 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectBase.php @@ -0,0 +1,112 @@ +setPluginConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ + public function transformDimensions(array &$dimensions) { + $dimensions['width'] = $dimensions['height'] = NULL; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#markup' => '', + ); + } + + /** + * {@inheritdoc} + */ + public function label() { + return $this->pluginDefinition['label']; + } + + /** + * {@inheritdoc} + */ + public function getUuid() { + return $this->uuid; + } + + /** + * {@inheritdoc} + */ + public function setWeight($weight) { + $this->weight = $weight; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getWeight() { + return $this->weight; + } + + /** + * {@inheritdoc} + */ + public function export() { + return array( + 'uuid' => $this->getUuid(), + 'id' => $this->getPluginId(), + 'weight' => $this->getWeight(), + 'data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function setPluginConfiguration(array $configuration) { + $configuration += array( + 'data' => array(), + 'uuid' => '', + 'weight' => '', + ); + $this->configuration = $configuration['data']; + $this->uuid = $configuration['uuid']; + $this->weight = $configuration['weight']; + return $this; + } + +} diff --git a/core/modules/image/lib/Drupal/image/ImageEffectInterface.php b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php new file mode 100644 index 0000000..05d5efd --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php @@ -0,0 +1,102 @@ + $namespaces['Drupal\image']); + parent::__construct('ImageEffect', $namespaces, $annotation_namespaces, 'Drupal\image\Annotation\ImageEffect'); + + $this->alterInfo($module_handler, 'image_effect_info'); + $this->setCacheBackend($cache_backend, $language_manager, 'image_effect'); + } + +} diff --git a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php index d7803cd..730aa2c 100644 --- a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php +++ b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php @@ -8,6 +8,7 @@ namespace Drupal\image; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\image\ImageEffectInterface; /** * Provides an interface defining an image style entity. @@ -75,6 +76,9 @@ public function buildUrl($path, $clean_urls = NULL); * @param string $path * (optional) The original image path or URI. If it's supplied, only this * image derivative will be flushed. + * + * @return self + * This image style. */ public function flush($path = NULL); @@ -109,4 +113,45 @@ public function createDerivative($original_uri, $derivative_uri); */ public function transformDimensions(array &$dimensions); + /** + * Returns a specific image effect. + * + * @param string $effect + * The image effect ID. + * + * @return \Drupal\image\ImageEffectInterface + * The image effect object. + */ + public function getEffect($effect); + + /** + * Returns the image effects for this style. + * + * @return \Drupal\image\ImageEffectBag|\Drupal\image\ImageEffectInterface[] + * The image effect plugin bag. + */ + public function getEffects(); + + /** + * Saves an image effect for this style. + * + * @param array $configuration + * An array of image effect configuration. + * + * @return string + * The image effect ID. + */ + public function saveImageEffect(array $configuration); + + /** + * Deletes an image effect from this style. + * + * @param \Drupal\image\ImageEffectInterface $effect + * The image effect object. + * + * @return self + * This image style. + */ + public function deleteImageEffect(ImageEffectInterface $effect); + } diff --git a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php deleted file mode 100644 index 3f35f5b..0000000 --- a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php +++ /dev/null @@ -1,36 +0,0 @@ -effects)) { - foreach ($style->effects as $ieid => $effect) { - $definition = image_effect_definition_load($effect['name']); - $effect = array_merge($definition, $effect); - $style->effects[$ieid] = $effect; - } - // Sort effects by weight. - uasort($style->effects, 'drupal_sort_weight'); - } - } - parent::attachLoad($queried_entities, $revision_id); - } - -} diff --git a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php index 7dd36c0..e81c451 100644 --- a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php +++ b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php @@ -11,6 +11,8 @@ use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\Core\Entity\EntityStorageControllerInterface; +use Drupal\image\ImageEffectBag; +use Drupal\image\ImageEffectInterface; use Drupal\image\ImageStyleInterface; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Url; @@ -31,7 +33,7 @@ * "form" = { * "delete" = "Drupal\image\Form\ImageStyleDeleteForm" * }, - * "storage" = "Drupal\image\ImageStyleStorageController" + * "storage" = "Drupal\Core\Config\Entity\ConfigStorageController" * }, * uri_callback = "image_style_entity_uri", * config_prefix = "image.style", @@ -77,7 +79,14 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface { * * @var array */ - public $effects; + protected $effects = array(); + + /** + * Holds the collection of image effects that are used by this image style. + * + * @var \Drupal\image\ImageEffectBag + */ + protected $effectsBag; /** * Overrides Drupal\Core\Entity\Entity::id(). @@ -312,7 +321,7 @@ public function flush($path = NULL) { if (file_exists($derivative_uri)) { file_unmanaged_delete($derivative_uri); } - return; + return $this; } // Delete the style directory in each registered wrapper. @@ -322,17 +331,19 @@ public function flush($path = NULL) { } // Let other modules update as necessary on flush. - \Drupal::moduleHandler()->invokeAll('image_style_flush', array($this)); + $module_handler = \Drupal::moduleHandler(); + $module_handler->invokeAll('image_style_flush', array($this)); // Clear field caches so that formatters may be added for this style. field_info_cache_clear(); drupal_theme_rebuild(); // Clear page caches when flushing. - if (\Drupal::moduleHandler()->moduleExists('block')) { - cache('block')->deleteAll(); + if ($module_handler->moduleExists('block')) { + \Drupal::cache('block')->deleteAll(); } - cache('page')->deleteAll(); + \Drupal::cache('page')->deleteAll(); + return $this; } /** @@ -352,10 +363,8 @@ public function createDerivative($original_uri, $derivative_uri) { return FALSE; } - if (!empty($this->effects)) { - foreach ($this->effects as $effect) { - image_effect_apply($image, $effect); - } + foreach ($this->getEffects() as $effect) { + $effect->applyEffect($image); } if (!image_save($image, $derivative_uri)) { @@ -372,21 +381,8 @@ public function createDerivative($original_uri, $derivative_uri) { * {@inheritdoc} */ public function transformDimensions(array &$dimensions) { - module_load_include('inc', 'image', 'image.effects'); - - if (!empty($this->effects)) { - foreach ($this->effects as $effect) { - if (isset($effect['dimensions passthrough'])) { - continue; - } - - if (isset($effect['dimensions callback'])) { - $effect['dimensions callback']($dimensions, $effect['data']); - } - else { - $dimensions['width'] = $dimensions['height'] = NULL; - } - } + foreach ($this->getEffects() as $effect) { + $effect->transformDimensions($dimensions); } } @@ -409,4 +405,48 @@ protected function getPathToken($uri) { return substr(Crypt::hmacBase64($this->id() . ':' . $uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8); } + /** + * {@inheritdoc} + */ + public function deleteImageEffect(ImageEffectInterface $effect) { + $this->getEffects()->removeInstanceID($effect->getUuid()); + $this->save(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getEffect($effect) { + return $this->getEffects()->get($effect); + } + + /** + * {@inheritdoc} + */ + public function getEffects() { + if (!$this->effectsBag) { + $this->effectsBag = new ImageEffectBag(\Drupal::service('plugin.manager.image.effect'), $this->effects); + } + return $this->effectsBag; + } + + /** + * {@inheritdoc} + */ + public function saveImageEffect(array $configuration) { + $effect_id = $this->getEffects()->setConfig($configuration); + $this->save(); + return $effect_id; + } + + /** + * {@inheritdoc} + */ + public function getExportProperties() { + $properties = parent::getExportProperties(); + $properties['effects'] = $this->getEffects()->sort()->export(); + return $properties; + } + } diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php new file mode 100644 index 0000000..11f228e --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php @@ -0,0 +1,84 @@ +configuration += array( + 'anchor' => 'center-center', + ); + + list($x, $y) = explode('-', $this->configuration['anchor']); + $x = image_filter_keyword($x, $image->info['width'], $this->configuration['width']); + $y = image_filter_keyword($y, $image->info['height'], $this->configuration['height']); + if (!image_crop($image, $x, $y, $this->configuration['width'], $this->configuration['height'])) { + watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_crop_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $this->configuration += array( + 'width' => '', + 'height' => '', + 'anchor' => 'center-center', + ); + $form = parent::getForm(); + $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' => $this->configuration['anchor'], + '#description' => t('The part of the image that will be retained during the crop.'), + ); + return $form; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php new file mode 100644 index 0000000..816ed3e --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php @@ -0,0 +1,42 @@ + $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php new file mode 100644 index 0000000..484e6a0 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php @@ -0,0 +1,79 @@ +configuration['width'], $this->configuration['height'])) { + watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function transformDimensions(array &$dimensions) { + // The new image will have the exact dimensions defined for the effect. + $dimensions['width'] = $this->configuration['width']; + $dimensions['height'] = $this->configuration['height']; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_resize_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $form['width'] = array( + '#type' => 'number', + '#title' => t('Width'), + '#default_value' => isset($this->configuration['width']) ? $this->configuration['width'] : '', + '#field_suffix' => ' ' . t('pixels'), + '#required' => TRUE, + '#min' => 1, + ); + $form['height'] = array( + '#type' => 'number', + '#title' => t('Height'), + '#default_value' => isset($this->configuration['height']) ? $this->configuration['height'] : '', + '#field_suffix' => ' ' . t('pixels'), + '#required' => TRUE, + '#min' => 1, + ); + return $form; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php new file mode 100644 index 0000000..dee537e --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php @@ -0,0 +1,132 @@ +configuration += array( + 'degrees' => 0, + 'bgcolor' => NULL, + 'random' => FALSE, + ); + + // Convert short #FFF syntax to full #FFFFFF syntax. + if (strlen($this->configuration['bgcolor']) == 4) { + $c = $this->configuration['bgcolor']; + $this->configuration['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3]; + } + + // Convert #FFFFFF syntax to hexadecimal colors. + if ($this->configuration['bgcolor'] != '') { + $this->configuration['bgcolor'] = hexdec(str_replace('#', '0x', $this->configuration['bgcolor'])); + } + else { + $this->configuration['bgcolor'] = NULL; + } + + if (!empty($this->configuration['random'])) { + $degrees = abs((float) $this->configuration['degrees']); + $this->configuration['degrees'] = rand(-1 * $degrees, $degrees); + } + + if (!image_rotate($image, $this->configuration['degrees'], $this->configuration['bgcolor'])) { + watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function transformDimensions(array &$dimensions) { + // If the rotate is not random and the angle is a multiple of 90 degrees, + // then the new dimensions can be determined. + if (!$this->configuration['random'] && ((int) ($this->configuration['degrees']) == $this->configuration['degrees']) && ($this->configuration['degrees'] % 90 == 0)) { + if ($this->configuration['degrees'] % 180 != 0) { + $temp = $dimensions['width']; + $dimensions['width'] = $dimensions['height']; + $dimensions['height'] = $temp; + } + } + else { + $dimensions['width'] = $dimensions['height'] = NULL; + } + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_rotate_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $form['degrees'] = array( + '#type' => 'number', + '#default_value' => (isset($this->configuration['degrees'])) ? $this->configuration['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($this->configuration['bgcolor'])) ? $this->configuration['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(array($this, 'validateColorEffect')), + ); + $form['random'] = array( + '#type' => 'checkbox', + '#default_value' => (isset($this->configuration['random'])) ? $this->configuration['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; + } + + /** + * Validates to ensure a hexadecimal color value. + */ + public function validateColorEffect(array $element, array &$form_state) { + if ($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']))); + } + } + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php new file mode 100644 index 0000000..dfa5955 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php @@ -0,0 +1,35 @@ +configuration['width'], $this->configuration['height'])) { + watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php new file mode 100644 index 0000000..47ada5a --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php @@ -0,0 +1,88 @@ +configuration += array( + 'width' => NULL, + 'height' => NULL, + 'upscale' => FALSE, + ); + + if (!image_scale($image, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale'])) { + watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function transformDimensions(array &$dimensions) { + if ($dimensions['width'] && $dimensions['height']) { + Image::scaleDimensions($dimensions, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale']); + } + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_scale_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $form = parent::getForm(); + $form['#element_validate'] = array(array($this, 'validateScaleEffect')); + $form['width']['#required'] = FALSE; + $form['height']['#required'] = FALSE; + $form['upscale'] = array( + '#type' => 'checkbox', + '#default_value' => (isset($this->configuration['upscale'])) ? $this->configuration['upscale'] : 0, + '#title' => t('Allow Upscaling'), + '#description' => t('Let scale make images larger than their original size'), + ); + return $form; + } + + /** + * Validates to ensure that either a height or a width is specified. + */ + public function validateScaleEffect(array $element, array &$form_state) { + if (empty($element['width']['#value']) && empty($element['height']['#value'])) { + form_error($element, t('Width and height can not both be blank.')); + } + } + +} diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php index 88eb3ad..f34b246 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php @@ -129,23 +129,23 @@ function testStyle() { // Confirm that all effects on the image style have settings on the effect // edit form that match what was saved. - $ieids = array(); - foreach ($style->effects as $ieid => $effect) { - // Store the ieid for later use. - $ieids[$effect['name']] = $ieid; - $this->drupalGet($style_path . '/effects/' . $ieid); - foreach ($effect_edits[$effect['name']] as $field => $value) { - $this->assertFieldByName($field, $value, format_string('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect['name'], '%value' => $value))); + $uuids = array(); + foreach ($style->getEffects() as $uuid => $effect) { + // Store the uuid for later use. + $uuids[$effect->getPluginId()] = $uuid; + $this->drupalGet($style_path . '/effects/' . $uuid); + foreach ($effect_edits[$effect->getPluginId()] as $field => $value) { + $this->assertFieldByName($field, $value, format_string('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect->getPluginId(), '%value' => $value))); } } // Assert that every effect was saved. foreach (array_keys($effect_edits) as $effect_name) { - $this->assertTrue(isset($ieids[$effect_name]), format_string( - 'A %effect_name effect was saved with ID %ieid', + $this->assertTrue(isset($uuids[$effect_name]), format_string( + 'A %effect_name effect was saved with ID %uuid', array( '%effect_name' => $effect_name, - '%ieid' => $ieids[$effect_name], + '%uuid' => $uuids[$effect_name], ))); } @@ -154,12 +154,13 @@ function testStyle() { // Confirm the order of effects is maintained according to the order we // added the fields. $effect_edits_order = array_keys($effect_edits); - $effects_order = array_values($style->effects); $order_correct = TRUE; - foreach ($effects_order as $index => $effect) { - if ($effect_edits_order[$index] != $effect['name']) { + $index = 0; + foreach ($style->getEffects()->sort() as $effect) { + if ($effect_edits_order[$index] != $effect->getPluginId()) { $order_correct = FALSE; } + $index++; } $this->assertTrue($order_correct, 'The order of the effects is correctly set by default.'); @@ -172,8 +173,8 @@ function testStyle() { 'name' => $style_name, 'label' => $style_label, ); - foreach ($style->effects as $ieid => $effect) { - $edit['effects[' . $ieid . '][weight]'] = $weight; + foreach ($style->getEffects() as $uuid => $effect) { + $edit['effects[' . $uuid . '][weight]'] = $weight; $weight--; } @@ -200,12 +201,13 @@ function testStyle() { // Confirm the new style order was saved. $effect_edits_order = array_reverse($effect_edits_order); - $effects_order = array_values($style->effects); $order_correct = TRUE; - foreach ($effects_order as $index => $effect) { - if ($effect_edits_order[$index] != $effect['name']) { + $index = 0; + foreach ($style->getEffects()->sort() as $effect) { + if ($effect_edits_order[$index] != $effect->getPluginId()) { $order_correct = FALSE; } + $index++; } $this->assertTrue($order_correct, 'The order of the effects is correctly set by default.'); @@ -216,19 +218,20 @@ function testStyle() { $this->assertEqual($this->getImageCount($style), 1, format_string('Image style %style image %file successfully generated.', array('%style' => $style->label(), '%file' => $image_path))); // Delete the 'image_crop' effect from the style. - $this->drupalPost($style_path . '/effects/' . $ieids['image_crop'] . '/delete', array(), t('Delete')); + $this->drupalPost($style_path . '/effects/' . $uuids['image_crop'] . '/delete', array(), t('Delete')); // Confirm that the form submission was successful. $this->assertResponse(200); - $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $style->effects[$ieids['image_crop']]['label']))); + $image_crop_effect = $style->getEffect($uuids['image_crop']); + $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $image_crop_effect->label()))); // Confirm that there is no longer a link to the effect. - $this->assertNoLinkByHref($style_path . '/effects/' . $ieids['image_crop'] . '/delete'); + $this->assertNoLinkByHref($style_path . '/effects/' . $uuids['image_crop'] . '/delete'); // Refresh the image style information and verify that the effect was // actually deleted. $style = entity_load_unchanged('image_style', $style->id()); - $this->assertFalse(isset($style->effects[$ieids['image_crop']]), format_string( - 'Effect with ID %ieid no longer found on image style %style', + $this->assertFalse($style->getEffects()->has($uuids['image_crop']), format_string( + 'Effect with ID %uuid no longer found on image style %style', array( - '%ieid' => $ieids['image_crop'], + '%uuid' => $uuids['image_crop'], '%style' => $style->label, ))); diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php index 8a08dee..0ff6738 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php @@ -59,7 +59,7 @@ function testImageDimensions() { // Scale an image that is wider than it is high. $effect = array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array( 'width' => 120, 'height' => 90, @@ -68,7 +68,7 @@ function testImageDimensions() { 'weight' => 0, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -81,7 +81,7 @@ function testImageDimensions() { // Rotate 90 degrees anticlockwise. $effect = array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => -90, 'random' => FALSE, @@ -89,7 +89,7 @@ function testImageDimensions() { 'weight' => 1, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -102,7 +102,7 @@ function testImageDimensions() { // Scale an image that is higher than it is wide (rotated by previous effect). $effect = array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array( 'width' => 120, 'height' => 90, @@ -111,7 +111,7 @@ function testImageDimensions() { 'weight' => 2, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -124,7 +124,7 @@ function testImageDimensions() { // Test upscale disabled. $effect = array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array( 'width' => 400, 'height' => 200, @@ -133,7 +133,7 @@ function testImageDimensions() { 'weight' => 3, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -146,12 +146,12 @@ function testImageDimensions() { // Add a desaturate effect. $effect = array( - 'name' => 'image_desaturate', + 'id' => 'image_desaturate', 'data' => array(), 'weight' => 4, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -164,7 +164,7 @@ function testImageDimensions() { // Add a random rotate effect. $effect = array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => 180, 'random' => TRUE, @@ -172,7 +172,7 @@ function testImageDimensions() { 'weight' => 5, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -183,7 +183,7 @@ function testImageDimensions() { // Add a crop effect. $effect = array( - 'name' => 'image_crop', + 'id' => 'image_crop', 'data' => array( 'width' => 30, 'height' => 30, @@ -192,7 +192,7 @@ function testImageDimensions() { 'weight' => 6, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -205,7 +205,7 @@ function testImageDimensions() { // Rotate to a non-multiple of 90 degrees. $effect = array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => 57, 'random' => FALSE, @@ -213,7 +213,7 @@ function testImageDimensions() { 'weight' => 7, ); - image_effect_save($style, $effect); + $effect_id = $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -221,17 +221,18 @@ function testImageDimensions() { $this->assertResponse(200, 'Image was generated at the URL.'); $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.'); - image_effect_delete($style, $effect); + $effect_plugin = $style->getEffect($effect_id); + $style->deleteImageEffect($effect_plugin); // Ensure that an effect with no dimensions callback unsets the dimensions. // This ensures compatibility with 7.0 contrib modules. $effect = array( - 'name' => 'image_module_test_null', + 'id' => 'image_module_test_null', 'data' => array(), 'weight' => 8, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php index 7f24796..74f41ec 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php @@ -22,6 +22,13 @@ class ImageEffectsTest extends ToolkitTestBase { */ public static $modules = array('image', 'image_test', 'image_module_test'); + /** + * The image effect manager. + * + * @var \Drupal\image\ImageEffectManager + */ + protected $manager; + public static function getInfo() { return array( 'name' => 'Image effects', @@ -30,17 +37,19 @@ public static function getInfo() { ); } - function setUp() { + public function setUp() { parent::setUp(); - - module_load_include('inc', 'image', 'image.effects'); + $this->manager = $this->container->get('plugin.manager.image.effect'); } /** * Test the image_resize_effect() function. */ function testResizeEffect() { - $this->assertTrue(image_resize_effect($this->image, array('width' => 1, 'height' => 2)), 'Function returned the expected value.'); + $this->assertImageEffect('image_resize', array( + 'width' => 1, + 'height' => 2, + )); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. @@ -54,7 +63,10 @@ function testResizeEffect() { */ function testScaleEffect() { // @todo: need to test upscaling. - $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 10)), 'Function returned the expected value.'); + $this->assertImageEffect('image_scale', array( + 'width' => 10, + 'height' => 10, + )); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. @@ -68,7 +80,11 @@ function testScaleEffect() { */ function testCropEffect() { // @todo should test the keyword offsets. - $this->assertTrue(image_crop_effect($this->image, array('anchor' => 'top-1', 'width' => 3, 'height' => 4)), 'Function returned the expected value.'); + $this->assertImageEffect('image_crop', array( + 'anchor' => 'top-1', + 'width' => 3, + 'height' => 4, + )); $this->assertToolkitOperationsCalled(array('crop')); // Check the parameters. @@ -83,7 +99,10 @@ function testCropEffect() { * Test the image_scale_and_crop_effect() function. */ function testScaleAndCropEffect() { - $this->assertTrue(image_scale_and_crop_effect($this->image, array('width' => 5, 'height' => 10)), 'Function returned the expected value.'); + $this->assertImageEffect('image_scale_and_crop', array( + 'width' => 5, + 'height' => 10, + )); $this->assertToolkitOperationsCalled(array('resize', 'crop')); // Check the parameters. @@ -98,7 +117,7 @@ function testScaleAndCropEffect() { * Test the image_desaturate_effect() function. */ function testDesaturateEffect() { - $this->assertTrue(image_desaturate_effect($this->image, array()), 'Function returned the expected value.'); + $this->assertImageEffect('image_desaturate', array()); $this->assertToolkitOperationsCalled(array('desaturate')); // Check the parameters. @@ -111,7 +130,10 @@ function testDesaturateEffect() { */ function testRotateEffect() { // @todo: need to test with 'random' => TRUE - $this->assertTrue(image_rotate_effect($this->image, array('degrees' => 90, 'bgcolor' => '#fff')), 'Function returned the expected value.'); + $this->assertImageEffect('image_rotate', array( + 'degrees' => 90, + 'bgcolor' => '#fff', + )); $this->assertToolkitOperationsCalled(array('rotate')); // Check the parameters. @@ -127,15 +149,32 @@ function testImageEffectsCaching() { $image_effect_definitions_called = &drupal_static('image_module_test_image_effect_info_alter'); // First call should grab a fresh copy of the data. - $effects = image_effect_definitions(); + $manager = $this->container->get('plugin.manager.image.effect'); + $effects = $manager->getDefinitions(); $this->assertTrue($image_effect_definitions_called === 1, 'image_effect_definitions() generated data.'); // Second call should come from cache. - drupal_static_reset('image_effect_definitions'); drupal_static_reset('image_module_test_image_effect_info_alter'); - $cached_effects = image_effect_definitions(); - $this->assertTrue(is_null($image_effect_definitions_called), 'image_effect_definitions() returned data from cache.'); + $cached_effects = $manager->getDefinitions(); + $this->assertTrue($image_effect_definitions_called === 0, 'image_effect_definitions() returned data from cache.'); $this->assertTrue($effects == $cached_effects, 'Cached effects are the same as generated effects.'); } + + /** + * Asserts the effect processing of an image effect plugin. + * + * @param string $effect_name + * The name of the image effect to test. + * @param array $data + * The data to pass to the image effect. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertImageEffect($effect_name, array $data) { + $effect = $this->manager->createInstance($effect_name, array('data' => $data)); + return $this->assertTrue($effect->applyEffect($this->image), 'Function returned the expected value.'); + } + } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php index b1d1716..7d0f054 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php @@ -20,10 +20,6 @@ * image.module: * image_style_options() * \Drupal\image\ImageStyleInterface::flush() - * image_effect_definition_load() - * image_effect_load() - * image_effect_save() - * image_effect_delete() * image_filter_keyword() */ diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php index 101171b..b5c3776 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php @@ -100,11 +100,11 @@ function testFlush() { // Remove the 'image_scale' effect and updates the style, which in turn // forces an image style flush. $style_path = 'admin/config/media/image-styles/manage/' . $style->id(); - $ieids = array(); - foreach ($style->effects as $ieid => $effect) { - $ieids[$effect['name']] = $ieid; + $uuids = array(); + foreach ($style->getEffects() as $uuid => $effect) { + $uuids[$effect->getPluginId()] = $uuid; } - $this->drupalPost($style_path . '/effects/' . $ieids['image_scale'] . '/delete', array(), t('Delete')); + $this->drupalPost($style_path . '/effects/' . $uuids['image_scale'] . '/delete', array(), t('Delete')); $this->assertResponse(200); $this->drupalPost($style_path, array(), t('Update style')); $this->assertResponse(200); diff --git a/core/modules/image/tests/image_module_test.module b/core/modules/image/tests/image_module_test.module deleted file mode 100644 index 7cc6cdd..0000000 --- a/core/modules/image/tests/image_module_test.module +++ /dev/null @@ -1,51 +0,0 @@ -get('image.test_file_download') ?: FALSE; - if ($default_uri == $uri) { - return array('X-Image-Owned-By' => 'image_module_test'); - } -} - -/** - * Implements hook_image_effect_info(). - */ -function image_module_test_image_effect_info() { - $effects = array( - 'image_module_test_null' => array( - 'effect callback' => 'image_module_test_null_effect', - ), - ); - - return $effects; -} - -/** - * Image effect callback; Null. - * - * @param $image - * An image object returned by image_load(). - * @param $data - * An array with no attributes. - * - * @return - * TRUE - */ -function image_module_test_null_effect(array &$image, array $data) { - return TRUE; -} - -/** - * Implements hook_image_effect_info_alter(). - * - * Used to keep a count of cache misses in image_effect_definitions(). - */ -function image_module_test_image_effect_info_alter(&$effects) { - $image_effects_definition_called = &drupal_static(__FUNCTION__, 0); - $image_effects_definition_called++; -} diff --git a/core/modules/image/tests/image_module_test.info.yml b/core/modules/image/tests/modules/image_module_test/image_module_test.info.yml similarity index 100% rename from core/modules/image/tests/image_module_test.info.yml rename to core/modules/image/tests/modules/image_module_test/image_module_test.info.yml diff --git a/core/modules/image/tests/modules/image_module_test/image_module_test.module b/core/modules/image/tests/modules/image_module_test/image_module_test.module new file mode 100644 index 0000000..7d64d2c --- /dev/null +++ b/core/modules/image/tests/modules/image_module_test/image_module_test.module @@ -0,0 +1,23 @@ +get('image.test_file_download') ?: FALSE; + if ($default_uri == $uri) { + return array('X-Image-Owned-By' => 'image_module_test'); + } +} + +/** + * Implements hook_image_effect_info_alter(). + * + * Used to keep a count of cache misses in \Drupal\image\ImageEffectManager. + */ +function image_module_test_image_effect_info_alter(&$effects) { + $image_effects_definition_called = &drupal_static(__FUNCTION__, 0); + $image_effects_definition_called++; +} diff --git a/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php new file mode 100644 index 0000000..23b1931 --- /dev/null +++ b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php @@ -0,0 +1,31 @@ + 'test-custom', 'effects' => array( 'image_rotate' => array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => '90', 'bgcolor' => '#FFFFFF', @@ -47,7 +47,7 @@ public function testImageStyleUpgrade() { 'weight' => '1', ), 'image_desaturate' => array( - 'name' => 'image_desaturate', + 'id' => 'image_desaturate', 'data' => array(), 'weight' => '2', ), @@ -57,7 +57,7 @@ public function testImageStyleUpgrade() { 'name' => 'thumbnail', 'effects' => array ( 'image_scale' => array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array ( 'width' => '177', 'height' => '177', @@ -73,11 +73,11 @@ public function testImageStyleUpgrade() { // during by the image style upgrade functions. foreach ($config->get('effects') as $uuid => $effect) { // Copy placeholder data. - $style['effects'][$uuid] = $style['effects'][$effect['name']]; - // Set the missing ieid key as this is unknown because it is a UUID. - $style['effects'][$uuid]['ieid'] = $uuid; + $style['effects'][$uuid] = $style['effects'][$effect['id']]; + // Set the missing uuid key as this is unknown because it is a UUID. + $style['effects'][$uuid]['uuid'] = $uuid; // Remove the placeholder data. - unset($style['effects'][$effect['name']]); + unset($style['effects'][$effect['id']]); } $this->assertEqual($this->sortByKey($style), $config->get(), format_string('@first is equal to @second.', array( '@first' => var_export($this->sortByKey($style), TRUE),