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 02af74e..5111177 100644 --- a/core/modules/image/image.admin.inc +++ b/core/modules/image/image.admin.inc @@ -5,6 +5,8 @@ * Administration pages for image settings. */ +use Drupal\Component\Utility\String; +use Drupal\image\ImageStyleInterface; use Symfony\Component\HttpFoundation\RedirectResponse; /** @@ -35,7 +37,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 +71,55 @@ 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(); + if ($effect->hasForm()) { + $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' => $effect->hasForm(), + ); + $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 +171,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 (!$effect['no_form']) { $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 +198,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 +259,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 @@ -608,7 +365,7 @@ function theme_image_style_effects($variables) { * * @param $variables * An associative array containing: - * - style: The image style array being previewed. + * - style: \Drupal\image\ImageStyleInterface image style being previewed. * * @ingroup themeable */ @@ -634,9 +391,9 @@ function theme_image_style_preview($variables) { $original_attributes['style'] = 'width: ' . $original_width . 'px; height: ' . $original_height . 'px;'; // Set up preview file information. - $preview_file = image_style_path($style->id(), $original_path); + $preview_file = $style->buildUri($original_path); if (!file_exists($preview_file)) { - image_style_create_derivative($style, $original_path, $preview_file); + $style->createDerivative($original_path, $preview_file); } $preview_image = image_get_info($preview_file); if ($preview_image['width'] > $preview_image['height']) { diff --git a/core/modules/image/image.api.php b/core/modules/image/image.api.php index d2990f5..2909507 100644 --- a/core/modules/image/image.api.php +++ b/core/modules/image/image.api.php @@ -11,58 +11,16 @@ */ /** - * 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'; + $effects['image_crop']['effect_callback'] = 'mymodule_crop_effect'; + $effects['image_crop']['dimensions_callback'] = 'mymodule_crop_dimensions'; + $effects['image_crop']['form_callback'] = 'mymodule_crop_form'; } /** @@ -74,8 +32,8 @@ function hook_image_effect_info_alter(&$effects) { * be cleared using this hook. This hook is called whenever a style is updated, * deleted, or any effect associated with the style is update or deleted. * - * @param Drupal\image\Plugin\Core\Entity\ImageStyle $style - * The image style array that is being flushed. + * @param \Drupal\image\ImageStyleInterface $style + * The image style object that is being flushed. */ function hook_image_style_flush($style) { // Empty cached data that contains information about the style. 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 9a3dd64..71bc42c 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -9,6 +9,8 @@ use Drupal\Core\Language\Language; use Drupal\field\Plugin\Core\Entity\Field; use Drupal\field\Plugin\Core\Entity\FieldInstance; +use Drupal\image\ImageEffectInterface; +use Drupal\image\ImageStyleInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -76,10 +78,10 @@ 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]); + $effect = Drupal::service('plugin.manager.image.effect')->getDefinition($arg[7]); return isset($effect['help']) ? ('

' . $effect['help'] . '

') : 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]); + $effect = entity_load('image_style', $arg[5])->getEffect($arg[7])->getPluginDefinition(); return isset($effect['help']) ? ('

' . $effect['help'] . '

') : NULL; } } @@ -168,28 +170,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; @@ -216,35 +210,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', ), ); } @@ -357,10 +361,7 @@ function image_file_predelete(File $file) { function image_path_flush($path) { $styles = entity_load_multiple('image_style'); foreach ($styles as $style) { - $image_path = image_style_path($style->id(), $path); - if (file_exists($image_path)) { - file_unmanaged_delete($image_path); - } + $style->flush($path); } } @@ -404,471 +405,16 @@ function image_style_options($include_empty = TRUE) { * After generating an image, transfer it to the requesting agent. * * @param $style - * The image style + * The image style. + * + * @todo: Remove this wrapper in https://drupal.org/node/1987712. */ function image_style_deliver($style, $scheme) { $args = func_get_args(); array_shift($args); array_shift($args); $target = implode('/', $args); - - // Check that the style is defined, the scheme is valid, and the image - // derivative token is valid. (Sites which require image derivatives to be - // generated without a token can set the - // 'image.settings:allow_insecure_derivatives' configuration to TRUE to bypass - // the latter check, but this will increase the site's vulnerability to - // denial-of-service attacks.) - $valid = !empty($style) && file_stream_wrapper_valid_scheme($scheme); - if (!config('image.settings')->get('allow_insecure_derivatives')) { - $image_derivative_token = Drupal::request()->query->get(IMAGE_DERIVATIVE_TOKEN); - $valid = $valid && isset($image_derivative_token) && $image_derivative_token === image_style_path_token($style->name, $scheme . '://' . $target); - } - if (!$valid) { - throw new AccessDeniedHttpException(); - } - - $image_uri = $scheme . '://' . $target; - $derivative_uri = image_style_path($style->id(), $image_uri); - - // If using the private scheme, let other modules provide headers and - // control access to the file. - if ($scheme == 'private') { - if (file_exists($derivative_uri)) { - file_download($scheme, file_uri_target($derivative_uri)); - } - else { - $headers = module_invoke_all('file_download', $image_uri); - if (in_array(-1, $headers) || empty($headers)) { - throw new AccessDeniedHttpException(); - } - if (count($headers)) { - foreach ($headers as $name => $value) { - drupal_add_http_header($name, $value); - } - } - } - } - - // Don't try to generate file if source is missing. - if (!file_exists($image_uri)) { - watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri)); - return new Response(t('Error generating image, missing source file.'), 404); - } - - // Don't start generating the image if the derivative already exists or if - // generation is in progress in another thread. - $lock_name = 'image_style_deliver:' . $style->id() . ':' . Crypt::hashBase64($image_uri); - if (!file_exists($derivative_uri)) { - $lock_acquired = lock()->acquire($lock_name); - if (!$lock_acquired) { - // Tell client to retry again in 3 seconds. Currently no browsers are known - // to support Retry-After. - throw new ServiceUnavailableHttpException(3, t('Image generation in progress. Try again shortly.')); - } - } - - // Try to generate the image, unless another thread just did it while we were - // acquiring the lock. - $success = file_exists($derivative_uri) || image_style_create_derivative($style, $image_uri, $derivative_uri); - - if (!empty($lock_acquired)) { - lock()->release($lock_name); - } - - if ($success) { - $image = image_load($derivative_uri); - $uri = $image->source; - $headers = array( - 'Content-Type' => $image->info['mime_type'], - 'Content-Length' => $image->info['file_size'], - ); - return new BinaryFileResponse($uri, 200, $headers); - } - else { - watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri)); - return new Response(t('Error generating image.'), 500); - } -} - -/** - * Creates a new image derivative based on an image style. - * - * Generates an image derivative by creating the destination folder (if it does - * not already exist), applying all image effects defined in $style->effects, - * and saving a cached version of the resulting image. - * - * @param $style - * An image style array. - * @param $source - * Path of the source file. - * @param $destination - * Path or URI of the destination file. - * - * @return - * TRUE if an image derivative was generated, or FALSE if the image derivative - * could not be generated. - * - * @see image_style_load() - */ -function image_style_create_derivative($style, $source, $destination) { - // Get the folder for the final location of this style. - $directory = drupal_dirname($destination); - - // Build the destination folder tree if it doesn't already exist. - if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { - watchdog('image', 'Failed to create style directory: %directory', array('%directory' => $directory), WATCHDOG_ERROR); - return FALSE; - } - - if (!$image = image_load($source)) { - return FALSE; - } - - if (!empty($style->effects)) { - foreach ($style->effects as $effect) { - image_effect_apply($image, $effect); - } - } - - if (!image_save($image, $destination)) { - if (file_exists($destination)) { - watchdog('image', 'Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $destination), WATCHDOG_ERROR); - } - return FALSE; - } - - return TRUE; -} - -/** - * Determines the dimensions of the styled image. - * - * Applies all of an image style's effects to $dimensions. - * - * @param $style_name - * The name of the style to be applied. - * @param $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - */ -function image_style_transform_dimensions($style_name, array &$dimensions) { - module_load_include('inc', 'image', 'image.effects'); - $style = entity_load('image_style', $style_name); - - if (!empty($style->effects)) { - foreach ($style->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; - } - } - } -} - -/** - * Flushes cached media for a style. - * - * @param $style - * An image style array. - */ -function image_style_flush($style) { - // Delete the style directory in each registered wrapper. - $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE); - foreach ($wrappers as $wrapper => $wrapper_data) { - file_unmanaged_delete_recursive($wrapper . '://styles/' . $style->id()); - } - - // Let other modules update as necessary on flush. - module_invoke_all('image_style_flush', $style); - - // 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 (module_exists('block')) { - cache('block')->deleteAll(); - } - cache('page')->deleteAll(); -} - -/** - * Returns the URL for an image derivative given a style and image path. - * - * @param $style_name - * The name of the style to be used with this image. - * @param $path - * The path to the image. - * @param $clean_urls - * (optional) Whether clean URLs are in use. - * @return - * The absolute URL where a style image can be downloaded, suitable for use - * in an tag. Requesting the URL will cause the image to be created. - * @see image_style_deliver() - */ -function image_style_url($style_name, $path, $clean_urls = NULL) { - $uri = image_style_path($style_name, $path); - // The token query is added even if the - // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so that - // the emitted links remain valid if it is changed back to the default FALSE. - // However, sites which need to prevent the token query from being emitted at - // all can additionally set the 'image.settings:suppress_itok_output' - // configuration to TRUE to achieve that (if both are set, the security token - // will neither be emitted in the image derivative URL nor checked for in - // image_style_deliver()). - $token_query = array(); - if (!config('image.settings')->get('suppress_itok_output')) { - $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, file_stream_wrapper_uri_normalize($path))); - } - - if ($clean_urls === NULL) { - // Assume clean URLs unless the request tells us otherwise. - $clean_urls = TRUE; - try { - $request = Drupal::request(); - $clean_urls = $request->attributes->get('clean_urls'); - } - catch (ServiceNotFoundException $e) { - } - } - - // If not using clean URLs, the image derivative callback is only available - // with the script path. If the file does not exist, use url() to ensure - // that it is included. Once the file exists it's fine to fall back to the - // actual file path, this avoids bootstrapping PHP once the files are built. - if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { - $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath(); - return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query)); - } - - $file_url = file_create_url($uri); - // Append the query string with the token, if necessary. - if ($token_query) { - $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query); - } - - return $file_url; -} - -/** - * Generates a token to protect an image style derivative. - * - * This prevents unauthorized generation of an image style derivative, - * which can be costly both in CPU time and disk space. - * - * @param string $style_name - * The name of the image style. - * @param string $uri - * The URI of the image for this style, for example as returned by - * image_style_path(). - * - * @return string - * An eight-character token which can be used to protect image style - * derivatives against denial-of-service attacks. - */ -function image_style_path_token($style_name, $uri) { - // Return the first eight characters. - return substr(Crypt::hmacBase64($style_name . ':' . $uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8); -} - -/** - * Returns the URI of an image when using a style. - * - * The path returned by this function may not exist. The default generation - * method only creates images when they are requested by a user's browser. - * - * @param $style_name - * The name of the style to be used with this image. - * @param $uri - * The URI or path to the image. - * @return - * The URI to an image style image. - * @see image_style_url() - */ -function image_style_path($style_name, $uri) { - $scheme = file_uri_scheme($uri); - if ($scheme) { - $path = file_uri_target($uri); - } - else { - $path = $uri; - $scheme = file_default_scheme(); - } - return $scheme . '://styles/' . $style_name . '/' . $scheme . '/' . $path; -} - -/** - * 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(); - - // Flush all derivatives that exist for this style, so they are regenerated - // with the new or updated effect. - image_style_flush($style); -} - -/** - * 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(); - image_style_flush($style); -} - -/** - * 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']); + return $style->deliver($scheme, $target); } /** @@ -892,13 +438,15 @@ function image_effect_apply($image, $effect) { * @ingroup themeable */ function theme_image_style($variables) { + $style = entity_load('image_style', $variables['style_name']); + // Determine the dimensions of the styled image. $dimensions = array( 'width' => $variables['width'], 'height' => $variables['height'], ); - image_style_transform_dimensions($variables['style_name'], $dimensions); + $style->transformDimensions($dimensions); // Add in the image style name as an HTML class. $variables['attributes']['class'][] = 'image-style-' . drupal_html_class($variables['style_name']); @@ -908,7 +456,7 @@ function theme_image_style($variables) { '#width' => $dimensions['width'], '#height' => $dimensions['height'], '#attributes' => $variables['attributes'], - '#uri' => image_style_url($variables['style_name'], $variables['uri']), + '#uri' => $style->buildUrl($variables['uri']), ); if (isset($variables['alt'])) { @@ -945,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..efe1358 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php @@ -0,0 +1,53 @@ +effectManager = $effect_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.image.effect') + ); + } + + /** + * {@inheritdoc} + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\image\ImageStyleInterface $image_style + * The image style. + * @param string $image_effect + * The image effect ID. + * + * @return array|\Symfony\Component\HttpFoundation\RedirectResponse + * The form structure. + */ + 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..671c065 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php @@ -0,0 +1,47 @@ + $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..c472f58 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php @@ -0,0 +1,125 @@ +imageStyle = $image_style; + $this->imageEffect = $this->prepareImageEffect($image_effect); + + if (!$this->imageEffect->hasForm()) { + 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') ? intval($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..82901c9 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectBase.php @@ -0,0 +1,126 @@ +setPluginConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ + public function processDimensions(array &$dimensions) { + $dimensions['width'] = $dimensions['height'] = NULL; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#markup' => '', + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function hasForm() { + return !$this->pluginDefinition['no_form']; + } + + /** + * {@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..2c13129 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php @@ -0,0 +1,121 @@ + $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 87367d3..717be6a 100644 --- a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php +++ b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php @@ -2,16 +2,156 @@ /** * @file - * Contains \Drupal\image\Plugin\Core\Entity\ImageStyleInterface. + * Contains \Drupal\image\ImageStyleInterface. */ namespace Drupal\image; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\image\ImageEffectInterface; /** * Provides an interface defining an image style entity. */ interface ImageStyleInterface extends ConfigEntityInterface { + /** + * Deliver an image derivative. + * + * Transfers a generated image derivative to the requesting agent. Modules may + * implement this method to set different serve different image derivatives + * from different stream wrappers or to customize different permissions on + * each image style. + * + * @param string $scheme + * The scheme name of the original image file stream wrapper ('public', + * 'private', 'temporary', etc.). + * @param string $target + * The target part of the uri. + * + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response + * The image to be delivered. + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException + * + * @todo: Move to controller after https://drupal.org/node/1987712. + */ + public function deliver($scheme, $target); + + /** + * Returns the URI of this image when using this style. + * + * The path returned by this function may not exist. The default generation + * method only creates images when they are requested by a user's browser. + * Modules may implement this method to decide where to place derivatives. + * + * @param string $uri + * The URI or path to the original image. + * + * @return string + * The URI to the image derivative for this style. + */ + public function buildUri($uri); + + /** + * Returns the URL of this image derivative for an original image path or URI. + * + * @param string $path + * The path or URI to the original image. + * @param mixed $clean_urls + * (optional) Whether clean URLs are in use. + * + * @return string + * The absolute URL where a style image can be downloaded, suitable for use + * in an tag. Requesting the URL will cause the image to be created. + * + * @see \Drupal\image\ImageStyleInterface::deliver() + */ + public function buildUrl($path, $clean_urls = NULL); + + /** + * Flushes cached media for this style. + * + * @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); + + /** + * Creates a new image derivative based on this image style. + * + * Generates an image derivative applying all image effects and saving the + * resulting image. + * + * @param string $original_uri + * Original image file URI. + * @param string $derivative_uri + * Derivative image file URI. + * + * @return bool + * TRUE if an image derivative was generated, or FALSE if the image + * derivative could not be generated. + */ + public function createDerivative($original_uri, $derivative_uri); + + /** + * Determines the dimensions of this image style. + * + * Stores the dimensions of this image style into $dimensions associative + * array. Implementations have to provide at least values to next keys: + * - width: Integer with the derivative image width. + * - height: Integer with the derivative image height. + * + * @param array $dimensions + * Associative array passed by reference. Implementations have to store the + * resulting width and height, in pixels. + */ + 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 2e99768..71ef480 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,7 +11,15 @@ 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; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; /** * Defines an image style configuration entity. @@ -24,7 +32,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", @@ -68,9 +76,14 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface { /** * The array of image effects for this image style. * - * @var string + * @var array */ - public $effects; + protected $effects = array(); + + /** + * @var \Drupal\image\ImageEffectBag + */ + protected $effectsBag; /** * Overrides Drupal\Core\Entity\Entity::id(). @@ -83,11 +96,17 @@ public function id() { * {@inheritdoc} */ public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { - if ($update && !empty($this->original) && $this->id() !== $this->original->id()) { - // The old image style name needs flushing after a rename. - image_style_flush($this->original); - // Update field instance settings if necessary. - static::replaceImageStyle($this); + if ($update) { + if (!empty($this->original) && $this->id() !== $this->original->id()) { + // The old image style name needs flushing after a rename. + $this->original->flush(); + // Update field instance settings if necessary. + static::replaceImageStyle($this); + } + else { + // Flush image style when updating without changing the name. + $this->flush(); + } } } @@ -97,7 +116,7 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $ public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { foreach ($entities as $style) { // Flush cached media for the deleted style. - image_style_flush($style); + $style->flush(); // Check whether field instance settings need to be updated. // In case no replacement style was specified, all image fields that are // using the deleted style are left in a broken state. @@ -112,7 +131,7 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont /** * Update field instance settings if the image style name is changed. * - * @param \Drupal\image\Plugin\Core\Entity\ImageStyle $style + * @param \Drupal\image\ImageStyleInterface $style * The image style. */ protected static function replaceImageStyle(ImageStyle $style) { @@ -148,4 +167,289 @@ protected static function replaceImageStyle(ImageStyle $style) { } } + /** + * {@inheritdoc} + * + * @todo: Move to controller after https://drupal.org/node/1987712. + */ + public function deliver($scheme, $target) { + + // Check that the scheme is valid, and the image derivative token is valid. + // (Sites which require image derivatives to be generated without a token + // can set the 'image.settings:allow_insecure_derivatives' configuration to + // TRUE to bypass the latter check, but this will increase the site's + // vulnerability to denial-of-service attacks.) + $valid = file_stream_wrapper_valid_scheme($scheme); + if (!\Drupal::config('image.settings')->get('allow_insecure_derivatives')) { + $image_derivative_token = \Drupal::request()->query->get(IMAGE_DERIVATIVE_TOKEN); + $valid = $valid && isset($image_derivative_token) && $image_derivative_token === $this->getPathToken($scheme . '://' . $target); + } + if (!$valid) { + throw new AccessDeniedHttpException(); + } + + $original_uri = $scheme . '://' . $target; + $derivative_uri = $this->buildUri($original_uri); + $headers_to_send = array(); + // If using the private scheme, let other modules provide headers and + // control access to the file. + if ($scheme == 'private') { + if (file_exists($derivative_uri)) { + file_download($scheme, file_uri_target($derivative_uri)); + } + else { + $headers = \Drupal::moduleHandler()->invokeAll('file_download', array($original_uri)); + if (in_array(-1, $headers) || empty($headers)) { + throw new AccessDeniedHttpException(); + } + if (count($headers)) { + foreach ($headers as $name => $value) { + $headers_to_send[$name] = $value; + } + } + } + } + + // Don't try to generate file if source is missing. + if (!file_exists($original_uri)) { + watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', array('%source_image_path' => $original_uri, '%derivative_path' => $derivative_uri)); + return new Response(t('Error generating image, missing source file.'), 404); + } + + // Don't start generating the image if the derivative already exists or if + // generation is in progress in another thread. + $lock_name = 'image_style_deliver:' . $this->id() . ':' . Crypt::hashBase64($original_uri); + if (!file_exists($derivative_uri)) { + $lock_acquired = \Drupal::lock()->acquire($lock_name); + if (!$lock_acquired) { + // Tell client to retry again in 3 seconds. Currently no browsers are + // known to support Retry-After. + throw new ServiceUnavailableHttpException(3, t('Image generation in progress. Try again shortly.')); + } + } + + // Try to generate the image, unless another thread just did it while we + // were acquiring the lock. + $success = file_exists($derivative_uri) || $this->createDerivative($original_uri, $derivative_uri); + + if (!empty($lock_acquired)) { + \Drupal::lock()->release($lock_name); + } + + if ($success) { + $image = image_load($derivative_uri); + $uri = $image->source; + $headers = $headers_to_send + array( + 'Content-Type' => $image->info['mime_type'], + 'Content-Length' => $image->info['file_size'], + ); + return new BinaryFileResponse($uri, 200, $headers); + } + else { + watchdog('image', 'Unable to generate the derived image located at %path.', array('%path' => $derivative_uri)); + return new Response(t('Error generating image.'), 500); + } + } + + /** + * {@inheritdoc} + */ + public function buildUri($uri) { + $scheme = file_uri_scheme($uri); + if ($scheme) { + $path = file_uri_target($uri); + } + else { + $path = $uri; + $scheme = file_default_scheme(); + } + return $scheme . '://styles/' . $this->id() . '/' . $scheme . '/' . $path; + } + + /** + * {@inheritdoc} + */ + public function buildUrl($path, $clean_urls = NULL) { + $uri = $this->buildUri($path); + // The token query is added even if the + // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so + // that the emitted links remain valid if it is changed back to the default + // FALSE. However, sites which need to prevent the token query from being + // emitted at all can additionally set the + // 'image.settings:suppress_itok_output' configuration to TRUE to achieve + // that (if both are set, the security token will neither be emitted in the + // image derivative URL nor checked for in + // \Drupal\image\ImageStyleInterface::deliver()). + $token_query = array(); + if (!\Drupal::config('image.settings')->get('suppress_itok_output')) { + $token_query = array(IMAGE_DERIVATIVE_TOKEN => $this->getPathToken(file_stream_wrapper_uri_normalize($path))); + } + + if ($clean_urls === NULL) { + // Assume clean URLs unless the request tells us otherwise. + $clean_urls = TRUE; + try { + $request = \Drupal::request(); + $clean_urls = $request->attributes->get('clean_urls'); + } + catch (ServiceNotFoundException $e) { + } + } + + // If not using clean URLs, the image derivative callback is only available + // with the script path. If the file does not exist, use url() to ensure + // that it is included. Once the file exists it's fine to fall back to the + // actual file path, this avoids bootstrapping PHP once the files are built. + if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { + $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath(); + return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query)); + } + + $file_url = file_create_url($uri); + // Append the query string with the token, if necessary. + if ($token_query) { + $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . Url::buildQuery($token_query); + } + + return $file_url; + } + + /** + * {@inheritdoc} + */ + public function flush($path = NULL) { + // A specific image path has been provided. Flush only that derivative. + if (isset($path)) { + $derivative_uri = $this->buildUri($path); + if (file_exists($derivative_uri)) { + file_unmanaged_delete($derivative_uri); + } + return $this; + } + + // Delete the style directory in each registered wrapper. + $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE); + foreach ($wrappers as $wrapper => $wrapper_data) { + file_unmanaged_delete_recursive($wrapper . '://styles/' . $this->id()); + } + + // Let other modules update as necessary on flush. + $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 ($module_handler->moduleExists('block')) { + \Drupal::cache('block')->deleteAll(); + } + \Drupal::cache('page')->deleteAll(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function createDerivative($original_uri, $derivative_uri) { + // Get the folder for the final location of this style. + $directory = drupal_dirname($derivative_uri); + + // Build the destination folder tree if it doesn't already exist. + if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { + watchdog('image', 'Failed to create style directory: %directory', array('%directory' => $directory), WATCHDOG_ERROR); + return FALSE; + } + + if (!$image = image_load($original_uri)) { + return FALSE; + } + + foreach ($this->getEffects() as $effect) { + $effect->processEffect($image); + } + + if (!image_save($image, $derivative_uri)) { + if (file_exists($derivative_uri)) { + watchdog('image', 'Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $derivative_uri), WATCHDOG_ERROR); + } + return FALSE; + } + + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function transformDimensions(array &$dimensions) { + foreach ($this->getEffects() as $effect) { + $effect->processDimensions($dimensions); + } + } + + /** + * Generates a token to protect an image style derivative. + * + * This prevents unauthorized generation of an image style derivative, + * which can be costly both in CPU time and disk space. + * + * @param string $uri + * The URI of the image for this style, for example as returned by + * \Drupal\image\ImageStyleInterface::buildUri(). + * + * @return string + * An eight-character token which can be used to protect image style + * derivatives against denial-of-service attacks. + */ + protected function getPathToken($uri) { + // Return the first eight characters. + 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..ae06d37 --- /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..b378f26 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php @@ -0,0 +1,43 @@ + $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..a40f7d3 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php @@ -0,0 +1,78 @@ +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 processDimensions(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..b676d8d --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php @@ -0,0 +1,131 @@ +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 processDimensions(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..9ee30f0 --- /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..45a6724 --- /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 processDimensions(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/FileMoveTest.php b/core/modules/image/lib/Drupal/image/Tests/FileMoveTest.php index f085f2d..2fc0b68 100644 --- a/core/modules/image/lib/Drupal/image/Tests/FileMoveTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/FileMoveTest.php @@ -39,8 +39,9 @@ function testNormal() { // Create derivative image. $styles = entity_load_multiple('image_style'); $style = image_style_load(key($styles)); - $derivative_uri = image_style_path($style->id(), $file->getFileUri()); - image_style_create_derivative($style, $file->getFileUri(), $derivative_uri); + $original_uri = $file->getFileUri(); + $derivative_uri = $style->buildUri($original_uri); + $style->createDerivative($original_uri, $derivative_uri); // Check if derivative image exists. $this->assertTrue(file_exists($derivative_uri), 'Make sure derivative image is generated successfully.'); diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php index 973a8ef..f69f9b2 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php @@ -36,7 +36,7 @@ function createSampleImage($style) { $file_path = file_unmanaged_copy($file->uri); } - return image_style_url($style->id(), $file_path) ? $file_path : FALSE; + return $style->buildUrl($file_path) ? $file_path : FALSE; } /** @@ -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, ))); @@ -271,9 +274,13 @@ function testStyleReplacement() { $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); $node = node_load($nid); + // Get node field original image URI. + $fid = $node->get($field_name)->target_id; + $original_uri = file_load($fid)->getFileUri(); + // Test that image is displayed using newly created style. $this->drupalGet('node/' . $nid); - $this->assertRaw(image_style_url($style_name, file_load($node->{$field_name}[Language::LANGCODE_NOT_SPECIFIED][0]['target_id'])->getFileUri()), format_string('Image displayed using style @style.', array('@style' => $style_name))); + $this->assertRaw($style->buildUrl($original_uri), format_string('Image displayed using style @style.', array('@style' => $style_name))); // Rename the style and make sure the image field is updated. $new_style_name = strtolower($this->randomName(10)); @@ -285,7 +292,10 @@ function testStyleReplacement() { $this->drupalPost($style_path . $style_name, $edit, t('Update style')); $this->assertText(t('Changes to the style have been saved.'), format_string('Style %name was renamed to %new_name.', array('%name' => $style_name, '%new_name' => $new_style_name))); $this->drupalGet('node/' . $nid); - $this->assertRaw(image_style_url($new_style_name, file_load($node->{$field_name}[Language::LANGCODE_NOT_SPECIFIED][0]['target_id'])->getFileUri()), 'Image displayed using style replacement style.'); + + // Reload the image style using the new name. + $style = entity_load('image_style', $new_style_name); + $this->assertRaw($style->buildUrl($original_uri), 'Image displayed using style replacement style.'); // Delete the style and choose a replacement style. $edit = array( @@ -295,8 +305,9 @@ function testStyleReplacement() { $message = t('Style %name was deleted.', array('%name' => $new_style_label)); $this->assertRaw($message); + $replacement_style = entity_load('image_style', 'thumbnail'); $this->drupalGet('node/' . $nid); - $this->assertRaw(image_style_url('thumbnail', file_load($node->{$field_name}[Language::LANGCODE_NOT_SPECIFIED][0]['target_id'])->getFileUri()), 'Image displayed using style replacement style.'); + $this->assertRaw($replacement_style->buildUrl($original_uri), 'Image displayed using style replacement style.'); } /** @@ -360,9 +371,13 @@ function testConfigImport() { $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); $node = node_load($nid); + // Get node field original image URI. + $fid = $node->get($field_name)->target_id; + $original_uri = file_load($fid)->getFileUri(); + // Test that image is displayed using newly created style. $this->drupalGet('node/' . $nid); - $this->assertRaw(image_style_url($style_name, file_load($node->{$field_name}[Language::LANGCODE_NOT_SPECIFIED][0]['target_id'])->getFileUri()), format_string('Image displayed using style @style.', array('@style' => $style_name))); + $this->assertRaw($style->buildUrl($original_uri), format_string('Image displayed using style @style.', array('@style' => $style_name))); // Copy config to staging, and delete the image style. $staging = $this->container->get('config.storage.staging'); @@ -374,4 +389,5 @@ function testConfigImport() { $this->assertFalse(entity_load('image_style', $style_name), 'Style deleted after config import.'); $this->assertEqual($this->getImageCount($style), 0, 'Image style was flushed after being deleted by config import.'); } + } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php index e7552d2..0ff6738 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php @@ -44,7 +44,7 @@ function testImageDimensions() { $style = entity_create('image_style', array('name' => 'test', 'label' => 'Test')); $style->save(); $generated_uri = 'public://styles/test/public/'. drupal_basename($original_uri); - $url = image_style_url('test', $original_uri); + $url = $style->buildUrl($original_uri); $variables = array( 'style_name' => 'test', @@ -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..c3c3c47 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->processEffect($this->image), 'Function returned the expected value.'); + } + } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php index c0f0cfd..e7775ad 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php @@ -112,7 +112,7 @@ function _testImageFieldFormatters($scheme) { // Ensure the derivative image is generated so we do not have to deal with // image style callback paths. - $this->drupalGet(image_style_url('thumbnail', $image_uri)); + $this->drupalGet(entity_load('image_style', 'thumbnail')->buildUrl($image_uri)); $image_info['uri'] = $image_uri; $image_info['width'] = 100; $image_info['height'] = 50; @@ -124,7 +124,7 @@ function _testImageFieldFormatters($scheme) { if ($scheme == 'private') { // Log out and try to access the file. $this->drupalLogout(); - $this->drupalGet(image_style_url('thumbnail', $image_uri)); + $this->drupalGet(entity_load('image_style', 'thumbnail')->buildUrl($image_uri)); $this->assertResponse('403', 'Access denied to image style thumbnail as anonymous user.'); } } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php index 3b9be9f..7d0f054 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php @@ -15,15 +15,11 @@ * * image.effects.inc: * image_style_generate() - * image_style_create_derivative() + * \Drupal\image\ImageStyleInterface::createDerivative() * * image.module: * image_style_options() - * image_style_flush() - * image_effect_definition_load() - * image_effect_load() - * image_effect_save() - * image_effect_delete() + * \Drupal\image\ImageStyleInterface::flush() * 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 298819a..b5c3776 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php @@ -34,8 +34,8 @@ function createSampleImage($style, $wrapper) { // Make sure we have an image in our wrapper testing file directory. $source_uri = file_unmanaged_copy($file->uri, $wrapper . '://'); // Build the derivative image. - $derivative_uri = image_style_path($style->id(), $source_uri); - $derivative = image_style_create_derivative($style, $source_uri, $derivative_uri); + $derivative_uri = $style->buildUri($source_uri); + $derivative = $style->createDerivative($source_uri, $derivative_uri); return $derivative ? $derivative_uri : FALSE; } @@ -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/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php index 1b9e2db..8629dfb 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php @@ -9,6 +9,7 @@ use Drupal\simpletest\WebTestBase; use Symfony\Component\HttpFoundation\Request; +use Drupal\image\Plugin\Core\Entity\ImageStyle; /** * Tests the functions for generating paths and URLs for image styles. @@ -22,7 +23,7 @@ class ImageStylesPathAndUrlTest extends WebTestBase { */ public static $modules = array('image', 'image_module_test'); - protected $style_name; + protected $style; protected $image_info; protected $image_filepath; @@ -37,55 +38,59 @@ public static function getInfo() { function setUp() { parent::setUp(); - $this->style_name = 'style_foo'; - $style = entity_create('image_style', array('name' => $this->style_name, 'label' => $this->randomString())); - $style->save(); + $this->style = entity_create('image_style', array('name' => 'style_foo', 'label' => $this->randomString())); + $this->style->save(); } /** - * Test image_style_path(). + * Test \Drupal\image\Plugin\Core\Entity\ImageStyle::buildUri(). */ function testImageStylePath() { $scheme = 'public'; - $actual = image_style_path($this->style_name, "$scheme://foo/bar.gif"); - $expected = "$scheme://styles/" . $this->style_name . "/$scheme/foo/bar.gif"; + $actual = $this->style->buildUri("$scheme://foo/bar.gif"); + $expected = "$scheme://styles/" . $this->style->id() . "/$scheme/foo/bar.gif"; $this->assertEqual($actual, $expected, 'Got the path for a file URI.'); - $actual = image_style_path($this->style_name, 'foo/bar.gif'); - $expected = "$scheme://styles/" . $this->style_name . "/$scheme/foo/bar.gif"; + $actual = $this->style->buildUri('foo/bar.gif'); + $expected = "$scheme://styles/" . $this->style->id() . "/$scheme/foo/bar.gif"; $this->assertEqual($actual, $expected, 'Got the path for a relative file path.'); } /** - * Test image_style_url() with a file using the "public://" scheme. + * Test \Drupal\image\Plugin\Core\Entity\ImageStyle::buildUrl() with a file + * using the "public://" scheme. */ function testImageStyleUrlAndPathPublic() { $this->_testImageStyleUrlAndPath('public'); } /** - * Test image_style_url() with a file using the "private://" scheme. + * Test \Drupal\image\Plugin\Core\Entity\ImageStyle::buildUrl() with a file + * using the "private://" scheme. */ function testImageStyleUrlAndPathPrivate() { $this->_testImageStyleUrlAndPath('private'); } /** - * Test image_style_url() with the "public://" scheme and unclean URLs. + * Test \Drupal\image\Plugin\Core\Entity\ImageStyle::buildUrl() with the + * "public://" scheme and unclean URLs. */ function testImageStylUrlAndPathPublicUnclean() { $this->_testImageStyleUrlAndPath('public', FALSE); } /** - * Test image_style_url() with the "private://" schema and unclean URLs. + * Test \Drupal\image\Plugin\Core\Entity\ImageStyle::buildUrl() with the + * "private://" schema and unclean URLs. */ function testImageStyleUrlAndPathPrivateUnclean() { $this->_testImageStyleUrlAndPath('private', FALSE); } /** - * Tests image_style_url() with a file URL that has an extra slash in it. + * Tests \Drupal\image\Plugin\Core\Entity\ImageStyle::buildUrl() with a file + * URL that has an extra slash in it. */ function testImageStyleUrlExtraSlash() { $this->_testImageStyleUrlAndPath('public', TRUE, TRUE); @@ -96,13 +101,13 @@ function testImageStyleUrlExtraSlash() { */ function testImageStyleUrlForMissingSourceImage() { $non_existent_uri = 'public://foo.png'; - $generated_url = image_style_url($this->style_name, $non_existent_uri); + $generated_url = $this->style->buildUrl($non_existent_uri); $this->drupalGet($generated_url); $this->assertResponse(404, 'Accessing an image style URL with a source image that does not exist provides a 404 error response.'); } /** - * Tests image_style_url(). + * Tests \Drupal\image\Plugin\Core\Entity\ImageStyle::buildUrl(). */ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FALSE) { $request = $this->prepareRequestForGenerator($clean_url); @@ -112,7 +117,7 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA config('system.file')->set('default_scheme', 'temporary')->save(); // Create the directories for the styles. - $directory = $scheme . '://styles/' . $this->style_name; + $directory = $scheme . '://styles/' . $this->style->id(); $status = file_prepare_directory($directory, FILE_CREATE_DIRECTORY); $this->assertNotIdentical(FALSE, $status, 'Created the directory for the generated images for the test style.'); @@ -127,9 +132,9 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA $this->assertNotIdentical(FALSE, $original_uri, 'Created the generated image file.'); // Get the URL of a file that has not been generated and try to create it. - $generated_uri = image_style_path($this->style_name, $original_uri); + $generated_uri = $this->style->buildUri($original_uri); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); - $generate_url = image_style_url($this->style_name, $original_uri, $clean_url); + $generate_url = $this->style->buildUrl($original_uri, $clean_url); // Ensure that the tests still pass when the file is generated by accessing // a poorly constructed (but still valid) file URL that has an extra slash @@ -137,7 +142,7 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA if ($extra_slash) { $modified_uri = str_replace('://', ':///', $original_uri); $this->assertNotEqual($original_uri, $modified_uri, 'An extra slash was added to the generated file URI.'); - $generate_url = image_style_url($this->style_name, $modified_uri, $clean_url); + $generate_url = $this->style->buildUrl($modified_uri, $clean_url); } if (!$clean_url) { $this->assertTrue(strpos($generate_url, 'index.php/') !== FALSE, 'When using non-clean URLS, the system path contains the script name.'); @@ -177,9 +182,9 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA // make sure that access is denied. $file_noaccess = array_shift($files); $original_uri_noaccess = file_unmanaged_copy($file_noaccess->uri, $scheme . '://', FILE_EXISTS_RENAME); - $generated_uri_noaccess = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/'. drupal_basename($original_uri_noaccess); + $generated_uri_noaccess = $scheme . '://styles/' . $this->style->id() . '/' . $scheme . '/'. drupal_basename($original_uri_noaccess); $this->assertFalse(file_exists($generated_uri_noaccess), 'Generated file does not exist.'); - $generate_url_noaccess = image_style_url($this->style_name, $original_uri_noaccess); + $generate_url_noaccess = $this->style->buildUrl($original_uri_noaccess); $this->drupalGet($generate_url_noaccess); $this->assertResponse(403, 'Confirmed that access is denied for the private image style.'); @@ -216,9 +221,9 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA // has not been created and try to create it. Check that the security token // is not present in the URL but that the image is still accessible. config('image.settings')->set('suppress_itok_output', TRUE)->save(); - $generated_uri = image_style_path($this->style_name, $original_uri); + $generated_uri = $this->style->buildUri($original_uri); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); - $generate_url = image_style_url($this->style_name, $original_uri, $clean_url); + $generate_url = $this->style->buildUrl($original_uri, $clean_url); $this->assertIdentical(strpos($generate_url, IMAGE_DERIVATIVE_TOKEN . '='), FALSE, 'The security token does not appear in the image style URL.'); $this->drupalGet($generate_url); $this->assertResponse(200, 'Image was accessible at the URL with a missing token.'); diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php index cd00cb1..f0d540f 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php @@ -41,7 +41,7 @@ function testImageFormatterTheme() { // Create a style. $style = entity_create('image_style', array('name' => 'test', 'label' => 'Test')); $style->save(); - $url = image_style_url('test', $original_uri); + $url = $style->buildUrl($original_uri); // Test using theme_image_formatter() without an image title, alt text, or // link options. @@ -84,7 +84,7 @@ function testImageStyleTheme() { // Create a style. $style = entity_create('image_style', array('name' => 'image_test', 'label' => 'Test')); $style->save(); - $url = image_style_url('image_test', $original_uri); + $url = $style->buildUrl($original_uri); $path = $this->randomName(); $element = array( 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..158afb1 --- /dev/null +++ b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php @@ -0,0 +1,32 @@ +setComponent($field_name, $display_options) ->save(); - $this->drupalGet(image_style_url('large', $image_uri)); + $large_style = entity_load('image_style', 'large'); + $this->drupalGet($large_style->buildUrl($image_uri)); $image_info['uri'] = $image_uri; $image_info['width'] = 480; $image_info['height'] = 240; @@ -194,7 +195,7 @@ public function _testPictureFieldFormatters($scheme) { if ($scheme == 'private') { // Log out and try to access the file. $this->drupalLogout(); - $this->drupalGet(image_style_url('large', $image_uri)); + $this->drupalGet($large_style->buildUrl($image_uri)); $this->assertResponse('403', 'Access denied to image style thumbnail as anonymous user.'); } } diff --git a/core/modules/picture/picture.module b/core/modules/picture/picture.module index deba3b0..680f05c 100644 --- a/core/modules/picture/picture.module +++ b/core/modules/picture/picture.module @@ -225,7 +225,7 @@ function theme_picture($variables) { // Fallback image, output as source with media query. $sources[] = array( - 'src' => image_style_url($variables['style_name'], $variables['uri']), + 'src' => entity_load('image_style', $variables['style_name'])->buildUrl($variables['uri']), 'dimensions' => picture_get_image_dimensions($variables), ); @@ -244,7 +244,7 @@ function theme_picture($variables) { // Only one image, use src. if (count($new_sources) == 1) { $sources[] = array( - 'src' => image_style_url($new_sources[0]['style_name'], $new_sources[0]['uri']), + 'src' => entity_load('image_style', $new_sources[0]['style_name'])->buildUrl($new_sources[0]['uri']), 'dimensions' => picture_get_image_dimensions($new_sources[0]), 'media' => $breakpoint->mediaQuery, ); @@ -253,7 +253,7 @@ function theme_picture($variables) { // Multiple images, use srcset. $srcset = array(); foreach ($new_sources as $new_source) { - $srcset[] = image_style_url($new_source['style_name'], $new_source['uri']) . ' ' . $new_source['#multiplier']; + $srcset[] = entity_load('image_style', $new_source['style_name'])->buildUrl($new_source['uri']) . ' ' . $new_source['#multiplier']; } $sources[] = array( 'srcset' => implode(', ', $srcset), @@ -338,7 +338,7 @@ function picture_get_image_dimensions($variables) { 'height' => $variables['height'], ); - image_style_transform_dimensions($variables['style_name'], $dimensions); + entity_load('image_style', $variables['style_name'])->transformDimensions($dimensions); return $dimensions; } diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/ImageFieldAttributesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/ImageFieldAttributesTest.php index 338ddfb..a622a8c 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/ImageFieldAttributesTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/ImageFieldAttributesTest.php @@ -101,7 +101,7 @@ function testNodeTeaser() { // Construct the node and image URIs for testing. $node_uri = url('node/' . $this->node->id(), array('absolute' => TRUE)); - $image_uri = image_style_url('medium', $this->file->getFileUri()); + $image_uri = entity_load('image_style', 'medium')->buildUrl($this->file->getFileUri()); // Test relations from node to image. $expected_value = array( diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index 3c03d80..95c21f3 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -344,7 +344,7 @@ function rdf_preprocess_field(&$variables) { // this field has a URI. if (isset($item['entity']->uri)) { if (!empty($element[$delta]['#image_style'])) { - $variables['item_attributes'][$delta]['resource'] = image_style_url($element[$delta]['#image_style'], $item['entity']->getFileUri()); + $variables['item_attributes'][$delta]['resource'] = entity_load('image_style', $element[$delta]['#image_style'])->buildUrl($item['entity']->getFileUri()); } else { $variables['item_attributes'][$delta]['resource'] = file_create_url($item['entity']->getFileUri()); diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php index fc24813..cc5979a 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php @@ -38,7 +38,7 @@ public function testImageStyleUpgrade() { 'name' => '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),