Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
3
web/core/modules/image/config/install/image.settings.yml
Normal file
3
web/core/modules/image/config/install/image.settings.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
preview_image: core/modules/image/sample.png
|
||||
allow_insecure_derivatives: false
|
||||
suppress_itok_output: false
|
14
web/core/modules/image/config/install/image.style.large.yml
Normal file
14
web/core/modules/image/config/install/image.style.large.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
name: large
|
||||
label: 'Large (480×480)'
|
||||
effects:
|
||||
ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d:
|
||||
uuid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d
|
||||
id: image_scale
|
||||
weight: 0
|
||||
data:
|
||||
width: 480
|
||||
height: 480
|
||||
upscale: false
|
14
web/core/modules/image/config/install/image.style.medium.yml
Normal file
14
web/core/modules/image/config/install/image.style.medium.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
name: medium
|
||||
label: 'Medium (220×220)'
|
||||
effects:
|
||||
bddf0d06-42f9-4c75-a700-a33cafa25ea0:
|
||||
uuid: bddf0d06-42f9-4c75-a700-a33cafa25ea0
|
||||
id: image_scale
|
||||
weight: 0
|
||||
data:
|
||||
width: 220
|
||||
height: 220
|
||||
upscale: false
|
|
@ -0,0 +1,14 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
name: thumbnail
|
||||
label: 'Thumbnail (100×100)'
|
||||
effects:
|
||||
1cfec298-8620-4749-b100-ccb6c4500779:
|
||||
uuid: 1cfec298-8620-4749-b100-ccb6c4500779
|
||||
id: image_scale
|
||||
weight: 0
|
||||
data:
|
||||
width: 100
|
||||
height: 100
|
||||
upscale: false
|
|
@ -0,0 +1,30 @@
|
|||
# Basic data types for image.
|
||||
|
||||
image_size:
|
||||
type: mapping
|
||||
mapping:
|
||||
width:
|
||||
type: integer
|
||||
label: 'Width'
|
||||
height:
|
||||
type: integer
|
||||
label: 'Height'
|
||||
|
||||
field_default_image:
|
||||
type: mapping
|
||||
mapping:
|
||||
uuid:
|
||||
type: string
|
||||
label: 'Image'
|
||||
alt:
|
||||
type: label
|
||||
label: 'Alternative text'
|
||||
title:
|
||||
type: label
|
||||
label: 'Title'
|
||||
width:
|
||||
type: integer
|
||||
label: 'Width'
|
||||
height:
|
||||
type: integer
|
||||
label: 'Height'
|
151
web/core/modules/image/config/schema/image.schema.yml
Normal file
151
web/core/modules/image/config/schema/image.schema.yml
Normal file
|
@ -0,0 +1,151 @@
|
|||
# Schema for configuration files of the Image module.
|
||||
|
||||
image.style.*:
|
||||
type: config_entity
|
||||
label: 'Image style'
|
||||
mapping:
|
||||
name:
|
||||
type: string
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
effects:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: mapping
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
data:
|
||||
type: image.effect.[%parent.id]
|
||||
weight:
|
||||
type: integer
|
||||
uuid:
|
||||
type: string
|
||||
|
||||
image.effect.*:
|
||||
type: mapping
|
||||
label: 'Effect settings'
|
||||
|
||||
image.effect.image_crop:
|
||||
type: image_size
|
||||
label: 'Image crop'
|
||||
mapping:
|
||||
anchor:
|
||||
label: 'Anchor'
|
||||
type: string
|
||||
|
||||
image.effect.image_convert:
|
||||
type: mapping
|
||||
label: 'Convert'
|
||||
mapping:
|
||||
extension:
|
||||
label: 'Extension'
|
||||
type: string
|
||||
|
||||
image.effect.image_resize:
|
||||
type: image_size
|
||||
label: 'Image resize'
|
||||
|
||||
image.effect.image_rotate:
|
||||
type: mapping
|
||||
label: 'Image rotate'
|
||||
mapping:
|
||||
degrees:
|
||||
type: integer
|
||||
label: 'Rotation angle'
|
||||
bgcolor:
|
||||
label: 'Background color'
|
||||
type: color_hex
|
||||
random:
|
||||
type: boolean
|
||||
label: 'Randomize'
|
||||
|
||||
image.effect.image_scale:
|
||||
type: image_size
|
||||
label: 'Image scale'
|
||||
mapping:
|
||||
upscale:
|
||||
type: boolean
|
||||
label: 'Upscale'
|
||||
|
||||
# The image desaturate effect has no settings.
|
||||
image.effect.image_desaturate:
|
||||
type: sequence
|
||||
|
||||
image.effect.image_scale_and_crop:
|
||||
type: image_size
|
||||
label: 'Image scale and crop'
|
||||
|
||||
image.settings:
|
||||
type: config_object
|
||||
mapping:
|
||||
preview_image:
|
||||
type: string
|
||||
label: 'Preview image'
|
||||
allow_insecure_derivatives:
|
||||
type: boolean
|
||||
label: 'Allow insecure image derivatives'
|
||||
suppress_itok_output:
|
||||
type: boolean
|
||||
label: 'Suppress the itok query string for image derivatives'
|
||||
|
||||
field.storage_settings.image:
|
||||
type: field.storage_settings.file
|
||||
label: 'Image settings'
|
||||
mapping:
|
||||
default_image:
|
||||
type: field_default_image
|
||||
label: 'Default value'
|
||||
|
||||
field.field_settings.image:
|
||||
type: base_file_field_field_settings
|
||||
label: 'Image settings'
|
||||
mapping:
|
||||
max_resolution:
|
||||
type: string
|
||||
label: 'Maximum image resolution'
|
||||
min_resolution:
|
||||
type: string
|
||||
label: 'Minimum image resolution'
|
||||
alt_field:
|
||||
type: boolean
|
||||
label: 'Enable Alt field'
|
||||
alt_field_required:
|
||||
type: boolean
|
||||
label: 'Alt field required'
|
||||
title_field:
|
||||
type: boolean
|
||||
label: 'Enable Title field'
|
||||
title_field_required:
|
||||
type: boolean
|
||||
label: 'Title field required'
|
||||
default_image:
|
||||
type: field_default_image
|
||||
label: 'Default value'
|
||||
|
||||
field.value.image:
|
||||
type: field_default_image
|
||||
label: 'Default value'
|
||||
|
||||
field.formatter.settings.image:
|
||||
type: mapping
|
||||
label: 'Image field display format settings'
|
||||
mapping:
|
||||
image_link:
|
||||
type: string
|
||||
label: 'Link image to'
|
||||
image_style:
|
||||
type: string
|
||||
label: 'Image style'
|
||||
|
||||
field.widget.settings.image_image:
|
||||
type: mapping
|
||||
label: 'Image field display format settings'
|
||||
mapping:
|
||||
progress_indicator:
|
||||
type: string
|
||||
label: 'Progress indicator'
|
||||
preview_image_style:
|
||||
type: string
|
||||
label: 'Preview image style'
|
74
web/core/modules/image/css/image.admin.css
Normal file
74
web/core/modules/image/css/image.admin.css
Normal file
|
@ -0,0 +1,74 @@
|
|||
|
||||
/**
|
||||
* Image style configuration pages.
|
||||
*/
|
||||
.image-style-new,
|
||||
.image-style-new div {
|
||||
display: inline;
|
||||
}
|
||||
.image-style-preview .preview-image-wrapper {
|
||||
float: left;
|
||||
padding-bottom: 2em;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 48%;
|
||||
}
|
||||
.image-style-preview .preview-image {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
.image-style-preview .preview-image .width {
|
||||
border: 1px solid #666;
|
||||
border-top: none;
|
||||
bottom: -6px;
|
||||
height: 2px;
|
||||
left: -1px;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.image-style-preview .preview-image .width span {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
.image-style-preview .preview-image .height {
|
||||
border: 1px solid #666;
|
||||
border-left: none;
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
top: -1px;
|
||||
width: 2px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.image-style-preview .preview-image .height span {
|
||||
height: 2em;
|
||||
left: 10px;
|
||||
margin-top: -1em;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve image style preview on narrow viewports.
|
||||
*/
|
||||
@media screen and (max-width: 470px) {
|
||||
.image-style-preview .preview-image-wrapper {
|
||||
float: none;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.image-style-preview .preview-image-wrapper:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Image anchor element.
|
||||
*/
|
||||
.image-anchor {
|
||||
width: auto;
|
||||
}
|
||||
.image-anchor tr {
|
||||
background: none;
|
||||
}
|
||||
.image-anchor td {
|
||||
border: 1px solid #ccc;
|
||||
}
|
134
web/core/modules/image/image.admin.inc
Normal file
134
web/core/modules/image/image.admin.inc
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Administration pages for image settings.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
* Prepares variables for image style preview templates.
|
||||
*
|
||||
* Default template: image-style-preview.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - style: \Drupal\image\ImageStyleInterface image style being previewed.
|
||||
*/
|
||||
function template_preprocess_image_style_preview(&$variables) {
|
||||
// Style information.
|
||||
$style = $variables['style'];
|
||||
$variables['style_id'] = $style->id();
|
||||
$variables['style_name'] = $style->label();
|
||||
|
||||
// Cache bypass token.
|
||||
$variables['cache_bypass'] = REQUEST_TIME;
|
||||
|
||||
// Sample image info.
|
||||
$sample_width = 160;
|
||||
$sample_height = 160;
|
||||
$image_factory = \Drupal::service('image.factory');
|
||||
|
||||
// Set up original file information.
|
||||
$original_path = \Drupal::config('image.settings')->get('preview_image');
|
||||
$original_image = $image_factory->get($original_path);
|
||||
$variables['original'] = array(
|
||||
'url' => file_url_transform_relative(file_create_url($original_path)),
|
||||
'width' => $original_image->getWidth(),
|
||||
'height' => $original_image->getHeight(),
|
||||
);
|
||||
if ($variables['original']['width'] > $variables['original']['height']) {
|
||||
$variables['preview']['original']['width'] = min($variables['original']['width'], $sample_width);
|
||||
$variables['preview']['original']['height'] = round($variables['preview']['original']['width'] / $variables['original']['width'] * $variables['original']['height']);
|
||||
}
|
||||
else {
|
||||
$variables['preview']['original']['height'] = min($variables['original']['height'], $sample_height);
|
||||
$variables['preview']['original']['width'] = round($variables['preview']['original']['height'] / $variables['original']['height'] * $variables['original']['width']);
|
||||
}
|
||||
|
||||
// Set up derivative file information.
|
||||
$preview_file = $style->buildUri($original_path);
|
||||
// Create derivative if necessary.
|
||||
if (!file_exists($preview_file)) {
|
||||
$style->createDerivative($original_path, $preview_file);
|
||||
}
|
||||
$preview_image = $image_factory->get($preview_file);
|
||||
$variables['derivative'] = array(
|
||||
'url' => file_url_transform_relative(file_create_url($preview_file)),
|
||||
'width' => $preview_image->getWidth(),
|
||||
'height' => $preview_image->getHeight(),
|
||||
);
|
||||
if ($variables['derivative']['width'] > $variables['derivative']['height']) {
|
||||
$variables['preview']['derivative']['width'] = min($variables['derivative']['width'], $sample_width);
|
||||
$variables['preview']['derivative']['height'] = round($variables['preview']['derivative']['width'] / $variables['derivative']['width'] * $variables['derivative']['height']);
|
||||
}
|
||||
else {
|
||||
$variables['preview']['derivative']['height'] = min($variables['derivative']['height'], $sample_height);
|
||||
$variables['preview']['derivative']['width'] = round($variables['preview']['derivative']['height'] / $variables['derivative']['height'] * $variables['derivative']['width']);
|
||||
}
|
||||
|
||||
// Build the preview of the original image.
|
||||
$variables['original']['rendered'] = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $original_path,
|
||||
'#alt' => t('Sample original image'),
|
||||
'#title' => '',
|
||||
'#attributes' => array(
|
||||
'width' => $variables['original']['width'],
|
||||
'height' => $variables['original']['height'],
|
||||
'style' => 'width: ' . $variables['preview']['original']['width'] . 'px; height: ' . $variables['preview']['original']['height'] . 'px;',
|
||||
),
|
||||
);
|
||||
|
||||
// Build the preview of the image style derivative. Timestamps are added
|
||||
// to prevent caching of images on the client side.
|
||||
$variables['derivative']['rendered'] = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $variables['derivative']['url'] . '?cache_bypass=' . $variables['cache_bypass'],
|
||||
'#alt' => t('Sample modified image'),
|
||||
'#title' => '',
|
||||
'#attributes' => array(
|
||||
'width' => $variables['derivative']['width'],
|
||||
'height' => $variables['derivative']['height'],
|
||||
'style' => 'width: ' . $variables['preview']['derivative']['width'] . 'px; height: ' . $variables['preview']['derivative']['height'] . 'px;',
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for image anchor templates.
|
||||
*
|
||||
* Default template: image-anchor.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the image.
|
||||
*/
|
||||
function template_preprocess_image_anchor(&$variables) {
|
||||
$element = $variables['element'];
|
||||
|
||||
$rows = array();
|
||||
$row = array();
|
||||
foreach (Element::children($element) as $n => $key) {
|
||||
$element[$key]['#attributes']['title'] = $element[$key]['#title'];
|
||||
unset($element[$key]['#title']);
|
||||
$row[] = array(
|
||||
'data' => $element[$key],
|
||||
);
|
||||
if ($n % 3 == 3 - 1) {
|
||||
$rows[] = $row;
|
||||
$row = array();
|
||||
}
|
||||
}
|
||||
|
||||
$variables['table'] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => array(),
|
||||
'#rows' => $rows,
|
||||
'#attributes' => array(
|
||||
'class' => array('image-anchor'),
|
||||
),
|
||||
);
|
||||
}
|
43
web/core/modules/image/image.api.php
Normal file
43
web/core/modules/image/image.api.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks related to image styles and effects.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter the information provided in \Drupal\image\Annotation\ImageEffect.
|
||||
*
|
||||
* @param $effects
|
||||
* The array of image effects, keyed on the machine-readable effect name.
|
||||
*/
|
||||
function hook_image_effect_info_alter(&$effects) {
|
||||
// Override the Image module's 'Scale and Crop' effect label.
|
||||
$effects['image_scale_and_crop']['label'] = t('Bangers and Mash');
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to image style flushing.
|
||||
*
|
||||
* This hook enables modules to take effect when a style is being flushed (all
|
||||
* images are being deleted from the server and regenerated). Any
|
||||
* module-specific caches that contain information related to the style should
|
||||
* 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\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.
|
||||
\Drupal::cache('mymodule')->deleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
81
web/core/modules/image/image.field.inc
Normal file
81
web/core/modules/image/image.field.inc
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implement an image field, based on the file module's file field.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
* Prepares variables for image widget templates.
|
||||
*
|
||||
* Default template: image-widget.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: A render element representing the image field widget.
|
||||
*/
|
||||
function template_preprocess_image_widget(&$variables) {
|
||||
$element = $variables['element'];
|
||||
|
||||
$variables['attributes'] = array('class' => array('image-widget', 'js-form-managed-file', 'form-managed-file', 'clearfix'));
|
||||
|
||||
if (!empty($element['fids']['#value'])) {
|
||||
$file = reset($element['#files']);
|
||||
$element['file_' . $file->id()]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
|
||||
}
|
||||
|
||||
$variables['data'] = array();
|
||||
foreach (Element::children($element) as $child) {
|
||||
$variables['data'][$child] = $element[$child];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for image formatter templates.
|
||||
*
|
||||
* Default template: image-formatter.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - item: An ImageItem object.
|
||||
* - item_attributes: An optional associative array of html attributes to be
|
||||
* placed in the img tag.
|
||||
* - image_style: An optional image style.
|
||||
* - url: An optional \Drupal\Core\Url object.
|
||||
*/
|
||||
function template_preprocess_image_formatter(&$variables) {
|
||||
if ($variables['image_style']) {
|
||||
$variables['image'] = array(
|
||||
'#theme' => 'image_style',
|
||||
'#style_name' => $variables['image_style'],
|
||||
);
|
||||
}
|
||||
else {
|
||||
$variables['image'] = array(
|
||||
'#theme' => 'image',
|
||||
);
|
||||
}
|
||||
$variables['image']['#attributes'] = $variables['item_attributes'];
|
||||
|
||||
$item = $variables['item'];
|
||||
|
||||
// Do not output an empty 'title' attribute.
|
||||
if (Unicode::strlen($item->title) != 0) {
|
||||
$variables['image']['#title'] = $item->title;
|
||||
}
|
||||
|
||||
if (($entity = $item->entity) && empty($item->uri)) {
|
||||
$variables['image']['#uri'] = $entity->getFileUri();
|
||||
}
|
||||
else {
|
||||
$variables['image']['#uri'] = $item->uri;
|
||||
}
|
||||
|
||||
foreach (array('width', 'height', 'alt') as $key) {
|
||||
$variables['image']["#$key"] = $item->$key;
|
||||
}
|
||||
}
|
9
web/core/modules/image/image.info.yml
Normal file
9
web/core/modules/image/image.info.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name: Image
|
||||
type: module
|
||||
description: 'Defines an image field type and provides image manipulation tools.'
|
||||
package: Field types
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- file
|
||||
configure: entity.image_style.collection
|
63
web/core/modules/image/image.install
Normal file
63
web/core/modules/image/image.install
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the image module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function image_install() {
|
||||
// Create the styles directory and ensure it's writable.
|
||||
$directory = file_default_scheme() . '://styles';
|
||||
file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function image_uninstall() {
|
||||
// Remove the styles directory and generated images.
|
||||
file_unmanaged_delete_recursive(file_default_scheme() . '://styles');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_requirements() to check the PHP GD Library.
|
||||
*
|
||||
* @param $phase
|
||||
*/
|
||||
function image_requirements($phase) {
|
||||
if ($phase != 'runtime') {
|
||||
return array();
|
||||
}
|
||||
|
||||
$toolkit = \Drupal::service('image.toolkit.manager')->getDefaultToolkit();
|
||||
if ($toolkit) {
|
||||
$plugin_definition = $toolkit->getPluginDefinition();
|
||||
$requirements = array(
|
||||
'image.toolkit' => array(
|
||||
'title' => t('Image toolkit'),
|
||||
'value' => $toolkit->getPluginId(),
|
||||
'description' => $plugin_definition['title'],
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($toolkit->getRequirements() as $key => $requirement) {
|
||||
$namespaced_key = 'image.toolkit.' . $toolkit->getPluginId() . '.' . $key;
|
||||
$requirements[$namespaced_key] = $requirement;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$requirements = array(
|
||||
'image.toolkit' => array(
|
||||
'title' => t('Image toolkit'),
|
||||
'value' => t('None'),
|
||||
'description' => t("No image toolkit is configured on the site. Check PHP installed extensions or add a contributed toolkit that doesn't require a PHP extension. Make sure that at least one valid image toolkit is enabled."),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
5
web/core/modules/image/image.libraries.yml
Normal file
5
web/core/modules/image/image.libraries.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
admin:
|
||||
version: VERSION
|
||||
css:
|
||||
theme:
|
||||
css/image.admin.css: {}
|
5
web/core/modules/image/image.links.action.yml
Normal file
5
web/core/modules/image/image.links.action.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
image_style_add_action:
|
||||
route_name: image.style_add
|
||||
title: 'Add image style'
|
||||
appears_on:
|
||||
- entity.image_style.collection
|
5
web/core/modules/image/image.links.menu.yml
Normal file
5
web/core/modules/image/image.links.menu.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
entity.image_style.collection:
|
||||
title: 'Image styles'
|
||||
description: 'Configure styles that can be used for resizing or adjusting images on display.'
|
||||
parent: system.admin_config_media
|
||||
route_name: entity.image_style.collection
|
9
web/core/modules/image/image.links.task.yml
Normal file
9
web/core/modules/image/image.links.task.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
entity.image_style.edit_form:
|
||||
title: 'Edit'
|
||||
route_name: entity.image_style.edit_form
|
||||
base_route: entity.image_style.edit_form
|
||||
|
||||
entity.image_style.collection:
|
||||
title: List
|
||||
route_name: entity.image_style.collection
|
||||
base_route: entity.image_style.collection
|
485
web/core/modules/image/image.module
Normal file
485
web/core/modules/image/image.module
Normal file
|
@ -0,0 +1,485 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Exposes global functionality for creating image styles.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
use Drupal\field\FieldConfigInterface;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
|
||||
/**
|
||||
* Image style constant for user presets in the database.
|
||||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0.
|
||||
*/
|
||||
const IMAGE_STORAGE_NORMAL = 1;
|
||||
|
||||
/**
|
||||
* Image style constant for user presets that override module-defined presets.
|
||||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0.
|
||||
*/
|
||||
const IMAGE_STORAGE_OVERRIDE = 2;
|
||||
|
||||
/**
|
||||
* Image style constant for module-defined presets in code.
|
||||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0.
|
||||
*/
|
||||
const IMAGE_STORAGE_DEFAULT = 4;
|
||||
|
||||
/**
|
||||
* Image style constant to represent an editable preset.
|
||||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0.
|
||||
*/
|
||||
define('IMAGE_STORAGE_EDITABLE', IMAGE_STORAGE_NORMAL | IMAGE_STORAGE_OVERRIDE);
|
||||
|
||||
/**
|
||||
* Image style constant to represent any module-based preset.
|
||||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0.
|
||||
*/
|
||||
define('IMAGE_STORAGE_MODULE', IMAGE_STORAGE_OVERRIDE | IMAGE_STORAGE_DEFAULT);
|
||||
|
||||
/**
|
||||
* The name of the query parameter for image derivative tokens.
|
||||
*/
|
||||
define('IMAGE_DERIVATIVE_TOKEN', 'itok');
|
||||
|
||||
// Load all Field module hooks for Image.
|
||||
require_once __DIR__ . '/image.field.inc';
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function image_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.image':
|
||||
$field_ui_url = \Drupal::moduleHandler()->moduleExists('field_ui') ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#';
|
||||
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Image module allows you to create fields that contain image files and to configure <a href=":image_styles">Image styles</a> that can be used to manipulate the display of images. See the <a href=":field">Field module help</a> and the <a href=":field_ui">Field UI help</a> pages for terminology and general information on entities, fields, and how to create and manage fields. For more information, see the <a href=":image_documentation">online documentation for the Image module</a>.', array(':image_styles' => \Drupal::url('entity.image_style.collection'), ':field' => \Drupal::url('help.page', array('name' => 'field')), ':field_ui' => $field_ui_url, ':image_documentation' => 'https://www.drupal.org/documentation/modules/image')) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dt>' . t('Defining image styles') . '</dt>';
|
||||
$output .= '<dd>' . t('The concept of image styles is that you can upload a single image but display it in several ways; each display variation, or <em>image style</em>, is the result of applying one or more <em>effects</em> to the original image. As an example, you might upload a high-resolution image with a 4:3 aspect ratio, and display it scaled down, square cropped, or black-and-white (or any combination of these effects). The Image module provides a way to do this efficiently: you configure an image style with the desired effects on the <a href=":image">Image styles page</a>, and the first time a particular image is requested in that style, the effects are applied. The resulting image is saved, and the next time that same style is requested, the saved image is retrieved without the need to recalculate the effects. Drupal core provides several effects that you can use to define styles; others may be provided by contributed modules.', array(':image' => \Drupal::url('entity.image_style.collection')));
|
||||
$output .= '<dt>' . t('Naming image styles') . '</dt>';
|
||||
$output .= '<dd>' . t('When you define an image style, you will need to choose a displayed name and a machine name. The displayed name is shown in administrative pages, and the machine name is used to generate the URL for accessing an image processed in that style. There are two common approaches to naming image styles: either based on the effects being applied (for example, <em>Square 85x85</em>), or based on where you plan to use it (for example, <em>Profile picture</em>).') . '</dd>';
|
||||
$output .= '<dt>' . t('Configuring image fields') . '</dt>';
|
||||
$output .= '<dd>' . t('A few of the settings for image fields are defined once when you create the field and cannot be changed later; these include the choice of public or private file storage and the number of images that can be stored in the field. The rest of the settings can be edited later; these settings include the field label, help text, allowed file extensions, image resolution restrictions, and the subdirectory in the public or private file storage where the images will be stored. The editable settings can also have different values for different entity sub-types; for instance, if your image field is used on both Page and Article content types, you can store the files in a different subdirectory for the two content types.') . '</dd>';
|
||||
$output .= '<dd>' . t('For accessibility and search engine optimization, all images that convey meaning on web sites should have alternate text. Drupal also allows entry of title text for images, but it can lead to confusion for screen reader users and its use is not recommended. Image fields can be configured so that alternate and title text fields are enabled or disabled; if enabled, the fields can be set to be required. The recommended setting is to enable and require alternate text and disable title text.') . '</dd>';
|
||||
$output .= '<dd>' . t('When you create an image field, you will need to choose whether the uploaded images will be stored in the public or private file directory defined in your settings.php file and shown on the <a href=":file-system">File system page</a>. This choice cannot be changed later. You can also configure your field to store files in a subdirectory of the public or private directory; this setting can be changed later and can be different for each entity sub-type using the field. For more information on file storage, see the <a href=":system-help">System module help page</a>.', array(':file-system' => \Drupal::url('system.file_system_settings'), ':system-help' => \Drupal::url('help.page', array('name' => 'system')))) . '</dd>';
|
||||
$output .= '<dd>' . t('The maximum file size that can be uploaded is limited by PHP settings of the server, but you can restrict it further by configuring a <em>Maximum upload size</em> in the field settings (this setting can be changed later). The maximum file size, either from PHP server settings or field configuration, is automatically displayed to users in the help text of the image field.') . '</dd>';
|
||||
$output .= '<dd>' . t('You can also configure a minimum and/or maximum resolution for uploaded images. Images that are too small will be rejected. Images that are to large will be resized. During the resizing the <a href="http://wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image will be lost.') . '</dd>';
|
||||
$output .= '<dd>' . t('You can also configure a default image that will be used if no image is uploaded in an image field. This default can be defined for all instances of the field in the field storage settings when you create a field, and the setting can be overridden for each entity sub-type that uses the field.') . '</dd>';
|
||||
$output .= '<dt>' . t('Configuring displays and form displays') . '</dt>';
|
||||
$output .= '<dd>' . t('On the <em>Manage display</em> page, you can choose the image formatter, which determines the image style used to display the image in each display mode and whether or not to display the image as a link. On the <em>Manage form display</em> page, you can configure the image upload widget, including setting the preview image style shown on the entity edit form.') . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'entity.image_style.collection':
|
||||
return '<p>' . 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.') . '</p>';
|
||||
|
||||
case 'image.effect_add_form':
|
||||
$effect = \Drupal::service('plugin.manager.image.effect')->getDefinition($route_match->getParameter('image_effect'));
|
||||
return isset($effect['description']) ? ('<p>' . $effect['description'] . '</p>') : NULL;
|
||||
|
||||
case 'image.effect_edit_form':
|
||||
$effect = $route_match->getParameter('image_style')->getEffect($route_match->getParameter('image_effect'));
|
||||
$effect_definition = $effect->getPluginDefinition();
|
||||
return isset($effect_definition['description']) ? ('<p>' . $effect_definition['description'] . '</p>') : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function image_theme() {
|
||||
return array(
|
||||
// Theme functions in image.module.
|
||||
'image_style' => array(
|
||||
// HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft
|
||||
// allows the alt attribute to be omitted in some cases. Therefore,
|
||||
// default the alt attribute to an empty string, but allow code using
|
||||
// '#theme' => 'image_style' to pass explicit NULL for it to be omitted.
|
||||
// Usually, neither omission nor an empty string satisfies accessibility
|
||||
// requirements, so it is strongly encouraged for code using '#theme' =>
|
||||
// 'image_style' to pass a meaningful value for the alt variable.
|
||||
// - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
|
||||
// - http://www.w3.org/TR/xhtml1/dtds.html
|
||||
// - http://dev.w3.org/html5/spec/Overview.html#alt
|
||||
// The title attribute is optional in all cases, so it is omitted by
|
||||
// default.
|
||||
'variables' => array(
|
||||
'style_name' => NULL,
|
||||
'uri' => NULL,
|
||||
'width' => NULL,
|
||||
'height' => NULL,
|
||||
'alt' => '',
|
||||
'title' => NULL,
|
||||
'attributes' => array(),
|
||||
),
|
||||
),
|
||||
|
||||
// Theme functions in 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, 'effect' => array()),
|
||||
),
|
||||
'image_scale_summary' => array(
|
||||
'variables' => array('data' => NULL, 'effect' => array()),
|
||||
),
|
||||
'image_crop_summary' => array(
|
||||
'variables' => array('data' => NULL, 'effect' => array()),
|
||||
),
|
||||
'image_rotate_summary' => array(
|
||||
'variables' => array('data' => NULL, 'effect' => array()),
|
||||
),
|
||||
|
||||
// Theme functions in image.field.inc.
|
||||
'image_widget' => array(
|
||||
'render element' => 'element',
|
||||
'file' => 'image.field.inc',
|
||||
),
|
||||
'image_formatter' => array(
|
||||
'variables' => array('item' => NULL, 'item_attributes' => NULL, 'url' => NULL, 'image_style' => NULL),
|
||||
'file' => 'image.field.inc',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_file_download().
|
||||
*
|
||||
* Control the access to files underneath the styles directory.
|
||||
*/
|
||||
function image_file_download($uri) {
|
||||
$path = file_uri_target($uri);
|
||||
|
||||
// Private file access for image style derivatives.
|
||||
if (strpos($path, 'styles/') === 0) {
|
||||
$args = explode('/', $path);
|
||||
|
||||
// Discard "styles", style name, and scheme from the path
|
||||
$args = array_slice($args, 3);
|
||||
|
||||
// Then the remaining parts are the path to the image.
|
||||
$original_uri = file_uri_scheme($uri) . '://' . implode('/', $args);
|
||||
|
||||
// Check that the file exists and is an image.
|
||||
$image = \Drupal::service('image.factory')->get($uri);
|
||||
if ($image->isValid()) {
|
||||
// Check the permissions of the original to grant access to this image.
|
||||
$headers = \Drupal::moduleHandler()->invokeAll('file_download', array($original_uri));
|
||||
// Confirm there's at least one module granting access and none denying access.
|
||||
if (!empty($headers) && !in_array(-1, $headers)) {
|
||||
return array(
|
||||
// Send headers describing the image's size, and MIME-type.
|
||||
'Content-Type' => $image->getMimeType(),
|
||||
'Content-Length' => $image->getFileSize(),
|
||||
// By not explicitly setting them here, this uses normal Drupal
|
||||
// Expires, Cache-Control and ETag headers to prevent proxy or
|
||||
// browser caching of private images.
|
||||
);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_file_move().
|
||||
*/
|
||||
function image_file_move(File $file, File $source) {
|
||||
// Delete any image derivatives at the original image path.
|
||||
image_path_flush($source->getFileUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_predelete() for file entities.
|
||||
*/
|
||||
function image_file_predelete(File $file) {
|
||||
// Delete any image derivatives of this image.
|
||||
image_path_flush($file->getFileUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cached versions of a specific file in all styles.
|
||||
*
|
||||
* @param $path
|
||||
* The Drupal file path to the original image.
|
||||
*/
|
||||
function image_path_flush($path) {
|
||||
$styles = ImageStyle::loadMultiple();
|
||||
foreach ($styles as $style) {
|
||||
$style->flush($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of image styles suitable for using as select list options.
|
||||
*
|
||||
* @param $include_empty
|
||||
* If TRUE a '- None -' option will be inserted in the options array.
|
||||
* @return
|
||||
* Array of image styles both key and value are set to style name.
|
||||
*/
|
||||
function image_style_options($include_empty = TRUE) {
|
||||
$styles = ImageStyle::loadMultiple();
|
||||
$options = array();
|
||||
if ($include_empty && !empty($styles)) {
|
||||
$options[''] = t('- None -');
|
||||
}
|
||||
foreach ($styles as $name => $style) {
|
||||
$options[$name] = $style->label();
|
||||
}
|
||||
|
||||
if (empty($options)) {
|
||||
$options[''] = t('No defined styles');
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for image style templates.
|
||||
*
|
||||
* Default template: image-style.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - width: The width of the image.
|
||||
* - height: The height of the image.
|
||||
* - style_name: The name of the image style to be applied.
|
||||
* - attributes: Additional attributes to apply to the image.
|
||||
* - uri: URI of the source image before styling.
|
||||
* - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0
|
||||
* always require an alt attribute. The HTML 5 draft allows the alt
|
||||
* attribute to be omitted in some cases. Therefore, this variable defaults
|
||||
* to an empty string, but can be set to NULL for the attribute to be
|
||||
* omitted. Usually, neither omission nor an empty string satisfies
|
||||
* accessibility requirements, so it is strongly encouraged for code using
|
||||
* '#theme' => 'image_style' to pass a meaningful value for this variable.
|
||||
* - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
|
||||
* - http://www.w3.org/TR/xhtml1/dtds.html
|
||||
* - http://dev.w3.org/html5/spec/Overview.html#alt
|
||||
* - title: The title text is displayed when the image is hovered in some
|
||||
* popular browsers.
|
||||
* - attributes: Associative array of attributes to be placed in the img tag.
|
||||
*/
|
||||
function template_preprocess_image_style(&$variables) {
|
||||
$style = ImageStyle::load($variables['style_name']);
|
||||
|
||||
// Determine the dimensions of the styled image.
|
||||
$dimensions = array(
|
||||
'width' => $variables['width'],
|
||||
'height' => $variables['height'],
|
||||
);
|
||||
|
||||
$style->transformDimensions($dimensions, $variables['uri']);
|
||||
|
||||
$variables['image'] = array(
|
||||
'#theme' => 'image',
|
||||
'#width' => $dimensions['width'],
|
||||
'#height' => $dimensions['height'],
|
||||
'#attributes' => $variables['attributes'],
|
||||
'#uri' => $style->buildUrl($variables['uri']),
|
||||
'#style_name' => $variables['style_name'],
|
||||
);
|
||||
|
||||
if (isset($variables['alt']) || array_key_exists('alt', $variables)) {
|
||||
$variables['image']['#alt'] = $variables['alt'];
|
||||
}
|
||||
if (isset($variables['title']) || array_key_exists('title', $variables)) {
|
||||
$variables['image']['#title'] = $variables['title'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a keyword (center, top, left, etc) and returns it as a pixel offset.
|
||||
*
|
||||
* @param $value
|
||||
* @param $current_pixels
|
||||
* @param $new_pixels
|
||||
*/
|
||||
function image_filter_keyword($value, $current_pixels, $new_pixels) {
|
||||
switch ($value) {
|
||||
case 'top':
|
||||
case 'left':
|
||||
return 0;
|
||||
|
||||
case 'bottom':
|
||||
case 'right':
|
||||
return $current_pixels - $new_pixels;
|
||||
|
||||
case 'center':
|
||||
return $current_pixels / 2 - $new_pixels / 2;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_presave().
|
||||
*
|
||||
* Transforms default image of image field from array into single value at save.
|
||||
*/
|
||||
function image_entity_presave(EntityInterface $entity) {
|
||||
// Get the default image settings, return if not saving an image field storage
|
||||
// or image field entity.
|
||||
$default_image = [];
|
||||
if (($entity instanceof FieldStorageConfigInterface || $entity instanceof FieldConfigInterface) && $entity->getType() == 'image') {
|
||||
$default_image = $entity->getSetting('default_image');
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity->isSyncing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uuid = $default_image['uuid'];
|
||||
if ($uuid) {
|
||||
$original_uuid = isset($entity->original) ? $entity->original->getSetting('default_image')['uuid'] : NULL;
|
||||
if ($uuid != $original_uuid) {
|
||||
$file = \Drupal::entityManager()->loadEntityByUuid('file', $uuid);
|
||||
if ($file) {
|
||||
$image = \Drupal::service('image.factory')->get($file->getFileUri());
|
||||
$default_image['width'] = $image->getWidth();
|
||||
$default_image['height'] = $image->getHeight();
|
||||
}
|
||||
else {
|
||||
$default_image['uuid'] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Both FieldStorageConfigInterface and FieldConfigInterface have a
|
||||
// setSetting() method.
|
||||
$entity->setSetting('default_image', $default_image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_update() for 'field_storage_config'.
|
||||
*/
|
||||
function image_field_storage_config_update(FieldStorageConfigInterface $field_storage) {
|
||||
if ($field_storage->getType() != 'image') {
|
||||
// Only act on image fields.
|
||||
return;
|
||||
}
|
||||
|
||||
$prior_field_storage = $field_storage->original;
|
||||
|
||||
// The value of a managed_file element can be an array if #extended == TRUE.
|
||||
$uuid_new = $field_storage->getSetting('default_image')['uuid'];
|
||||
$uuid_old = $prior_field_storage->getSetting('default_image')['uuid'];
|
||||
|
||||
$file_new = $uuid_new ? \Drupal::entityManager()->loadEntityByUuid('file', $uuid_new) : FALSE;
|
||||
|
||||
if ($uuid_new != $uuid_old) {
|
||||
|
||||
// Is there a new file?
|
||||
if ($file_new) {
|
||||
$file_new->status = FILE_STATUS_PERMANENT;
|
||||
$file_new->save();
|
||||
\Drupal::service('file.usage')->add($file_new, 'image', 'default_image', $field_storage->uuid());
|
||||
}
|
||||
|
||||
// Is there an old file?
|
||||
if ($uuid_old && ($file_old = \Drupal::entityManager()->loadEntityByUuid('file', $uuid_old))) {
|
||||
\Drupal::service('file.usage')->delete($file_old, 'image', 'default_image', $field_storage->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
// If the upload destination changed, then move the file.
|
||||
if ($file_new && (file_uri_scheme($file_new->getFileUri()) != $field_storage->getSetting('uri_scheme'))) {
|
||||
$directory = $field_storage->getSetting('uri_scheme') . '://default_images/';
|
||||
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
|
||||
file_move($file_new, $directory . $file_new->filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_update() for 'field_config'.
|
||||
*/
|
||||
function image_field_config_update(FieldConfigInterface $field) {
|
||||
$field_storage = $field->getFieldStorageDefinition();
|
||||
if ($field_storage->getType() != 'image') {
|
||||
// Only act on image fields.
|
||||
return;
|
||||
}
|
||||
|
||||
$prior_instance = $field->original;
|
||||
|
||||
$uuid_new = $field->getSetting('default_image')['uuid'];
|
||||
$uuid_old = $prior_instance->getSetting('default_image')['uuid'];
|
||||
|
||||
// If the old and new files do not match, update the default accordingly.
|
||||
$file_new = $uuid_new ? \Drupal::entityManager()->loadEntityByUuid('file', $uuid_new) : FALSE;
|
||||
if ($uuid_new != $uuid_old) {
|
||||
// Save the new file, if present.
|
||||
if ($file_new) {
|
||||
$file_new->status = FILE_STATUS_PERMANENT;
|
||||
$file_new->save();
|
||||
\Drupal::service('file.usage')->add($file_new, 'image', 'default_image', $field->uuid());
|
||||
}
|
||||
// Delete the old file, if present.
|
||||
if ($uuid_old && ($file_old = \Drupal::entityManager()->loadEntityByUuid('file', $uuid_old))) {
|
||||
\Drupal::service('file.usage')->delete($file_old, 'image', 'default_image', $field->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
// If the upload destination changed, then move the file.
|
||||
if ($file_new && (file_uri_scheme($file_new->getFileUri()) != $field_storage->getSetting('uri_scheme'))) {
|
||||
$directory = $field_storage->getSetting('uri_scheme') . '://default_images/';
|
||||
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
|
||||
file_move($file_new, $directory . $file_new->filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_delete() for 'field_storage_config'.
|
||||
*/
|
||||
function image_field_storage_config_delete(FieldStorageConfigInterface $field) {
|
||||
if ($field->getType() != 'image') {
|
||||
// Only act on image fields.
|
||||
return;
|
||||
}
|
||||
|
||||
// The value of a managed_file element can be an array if #extended == TRUE.
|
||||
$uuid = $field->getSetting('default_image')['uuid'];
|
||||
if ($uuid && ($file = \Drupal::entityManager()->loadEntityByUuid('file', $uuid))) {
|
||||
\Drupal::service('file.usage')->delete($file, 'image', 'default_image', $field->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_delete() for 'field_config'.
|
||||
*/
|
||||
function image_field_config_delete(FieldConfigInterface $field) {
|
||||
$field_storage = $field->getFieldStorageDefinition();
|
||||
if ($field_storage->getType() != 'image') {
|
||||
// Only act on image fields.
|
||||
return;
|
||||
}
|
||||
|
||||
// The value of a managed_file element can be an array if #extended == TRUE.
|
||||
$uuid = $field->getSetting('default_image')['uuid'];
|
||||
|
||||
// Remove the default image when the instance is deleted.
|
||||
if ($uuid && ($file = \Drupal::entityManager()->loadEntityByUuid('file', $uuid))) {
|
||||
\Drupal::service('file.usage')->delete($file, 'image', 'default_image', $field->uuid());
|
||||
}
|
||||
}
|
2
web/core/modules/image/image.permissions.yml
Normal file
2
web/core/modules/image/image.permissions.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
administer image styles:
|
||||
title: 'Administer image styles'
|
22
web/core/modules/image/image.post_update.php
Normal file
22
web/core/modules/image/image.post_update.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post-update functions for Image.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
|
||||
/**
|
||||
* Saves the image style dependencies into form and view display entities.
|
||||
*/
|
||||
function image_post_update_image_style_dependencies() {
|
||||
// Merge view and form displays. Use array_values() to avoid key collisions.
|
||||
$displays = array_merge(array_values(EntityViewDisplay::loadMultiple()), array_values(EntityFormDisplay::loadMultiple()));
|
||||
/** @var \Drupal\Core\Entity\Display\EntityDisplayInterface[] $displays */
|
||||
foreach ($displays as $display) {
|
||||
// Re-save each config entity to add missed dependencies.
|
||||
$display->save();
|
||||
}
|
||||
}
|
73
web/core/modules/image/image.routing.yml
Normal file
73
web/core/modules/image/image.routing.yml
Normal file
|
@ -0,0 +1,73 @@
|
|||
image.style_add:
|
||||
path: '/admin/config/media/image-styles/add'
|
||||
defaults:
|
||||
_entity_form: image_style.add
|
||||
_title: 'Add image style'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
entity.image_style.edit_form:
|
||||
path: '/admin/config/media/image-styles/manage/{image_style}'
|
||||
defaults:
|
||||
_entity_form: image_style.edit
|
||||
_title: 'Edit style'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
entity.image_style.delete_form:
|
||||
path: '/admin/config/media/image-styles/manage/{image_style}/delete'
|
||||
defaults:
|
||||
_entity_form: 'image_style.delete'
|
||||
_title: 'Delete'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
entity.image_style.flush_form:
|
||||
path: '/admin/config/media/image-styles/manage/{image_style}/flush'
|
||||
defaults:
|
||||
_entity_form: 'image_style.flush'
|
||||
_title: 'Flush'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
image.effect_delete:
|
||||
path: '/admin/config/media/image-styles/manage/{image_style}/effects/{image_effect}/delete'
|
||||
defaults:
|
||||
_form: '\Drupal\image\Form\ImageEffectDeleteForm'
|
||||
_title: 'Delete image effect'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
entity.image_style.collection:
|
||||
path: '/admin/config/media/image-styles'
|
||||
defaults:
|
||||
_entity_list: 'image_style'
|
||||
_title: 'Image styles'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
image.style_private:
|
||||
path: '/system/files/styles/{image_style}/{scheme}'
|
||||
defaults:
|
||||
_controller: '\Drupal\image\Controller\ImageStyleDownloadController::deliver'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
image.effect_add_form:
|
||||
path: '/admin/config/media/image-styles/manage/{image_style}/add/{image_effect}'
|
||||
defaults:
|
||||
_form: '\Drupal\image\Form\ImageEffectAddForm'
|
||||
_title: 'Add image effect'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
image.effect_edit_form:
|
||||
path: '/admin/config/media/image-styles/manage/{image_style}/effects/{image_effect}'
|
||||
defaults:
|
||||
_form: '\Drupal\image\Form\ImageEffectEditForm'
|
||||
_title: 'Edit image effect'
|
||||
requirements:
|
||||
_permission: 'administer image styles'
|
||||
|
||||
route_callbacks:
|
||||
- '\Drupal\image\Routing\ImageStyleRoutes::routes'
|
15
web/core/modules/image/image.services.yml
Normal file
15
web/core/modules/image/image.services.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
services:
|
||||
path_processor.image_styles:
|
||||
class: Drupal\image\PathProcessor\PathProcessorImageStyles
|
||||
arguments: ['@stream_wrapper_manager']
|
||||
tags:
|
||||
- { name: path_processor_inbound, priority: 300 }
|
||||
plugin.manager.image.effect:
|
||||
class: Drupal\image\ImageEffectManager
|
||||
parent: default_plugin_manager
|
||||
image.page_cache_response_policy.deny_private_image_style_download:
|
||||
class: Drupal\image\PageCache\DenyPrivateImageStyleDownload
|
||||
arguments: ['@current_route_match']
|
||||
public: false
|
||||
tags:
|
||||
- { name: page_cache_response_policy }
|
70
web/core/modules/image/image.views.inc
Normal file
70
web/core/modules/image/image.views.inc
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provide views data for image.module.
|
||||
*/
|
||||
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_field_views_data().
|
||||
*
|
||||
* Views integration for image fields. Adds an image relationship to the default
|
||||
* field data.
|
||||
*
|
||||
* @see views_field_default_views_data()
|
||||
*/
|
||||
function image_field_views_data(FieldStorageConfigInterface $field_storage) {
|
||||
$data = views_field_default_views_data($field_storage);
|
||||
foreach ($data as $table_name => $table_data) {
|
||||
// Add the relationship only on the target_id field.
|
||||
$data[$table_name][$field_storage->getName() . '_target_id']['relationship'] = array(
|
||||
'id' => 'standard',
|
||||
'base' => 'file_managed',
|
||||
'entity type' => 'file',
|
||||
'base field' => 'fid',
|
||||
'label' => t('image from @field_name', array('@field_name' => $field_storage->getName())),
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_views_data_views_data_alter().
|
||||
*
|
||||
* Views integration to provide reverse relationships on image fields.
|
||||
*/
|
||||
function image_field_views_data_views_data_alter(array &$data, FieldStorageConfigInterface $field_storage) {
|
||||
$entity_type_id = $field_storage->getTargetEntityTypeId();
|
||||
$field_name = $field_storage->getName();
|
||||
$entity_manager = \Drupal::entityManager();
|
||||
$entity_type = $entity_manager->getDefinition($entity_type_id);
|
||||
$pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type_id;
|
||||
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
|
||||
$table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping();
|
||||
|
||||
list($label) = views_entity_field_label($entity_type_id, $field_name);
|
||||
|
||||
$data['file_managed'][$pseudo_field_name]['relationship'] = array(
|
||||
'title' => t('@entity using @field', array('@entity' => $entity_type->getLabel(), '@field' => $label)),
|
||||
'label' => t('@field_name', array('@field_name' => $field_name)),
|
||||
'help' => t('Relate each @entity with a @field set to the image.', array('@entity' => $entity_type->getLabel(), '@field' => $label)),
|
||||
'group' => $entity_type->getLabel(),
|
||||
'id' => 'entity_reverse',
|
||||
'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
|
||||
'entity_type' => $entity_type_id,
|
||||
'base field' => $entity_type->getKey('id'),
|
||||
'field_name' => $field_name,
|
||||
'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
|
||||
'field field' => $field_name . '_target_id',
|
||||
'join_extra' => array(
|
||||
0 => array(
|
||||
'field' => 'deleted',
|
||||
'value' => 0,
|
||||
'numeric' => TRUE,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
id: d6_imagecache_presets
|
||||
label: ImageCache Presets
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
plugin: d6_imagecache_presets
|
||||
process:
|
||||
name:
|
||||
-
|
||||
plugin: machine_name
|
||||
source: presetname
|
||||
-
|
||||
plugin: dedupe_entity
|
||||
entity_type: image_style
|
||||
field: name
|
||||
length: 32
|
||||
label: presetname
|
||||
effects:
|
||||
plugin: d6_imagecache_actions
|
||||
source:
|
||||
- '@plugin'
|
||||
- data
|
||||
destination:
|
||||
plugin: entity:image_style
|
|
@ -0,0 +1,17 @@
|
|||
id: d7_image_settings
|
||||
label: Image configuration
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- allow_insecure_derivatives
|
||||
- suppress_itok_output
|
||||
- image_style_preview_image
|
||||
process:
|
||||
suppress_itok_output: suppress_itok_output
|
||||
allow_insecure_derivatives: allow_insecure_derivatives
|
||||
preview_image: image_style_preview_image
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: image.settings
|
|
@ -0,0 +1,18 @@
|
|||
id: d7_image_styles
|
||||
label: Image styles
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: d7_image_styles
|
||||
process:
|
||||
name: name
|
||||
label: label
|
||||
effects:
|
||||
plugin: iterator
|
||||
source: effects
|
||||
process:
|
||||
id: name
|
||||
weight: weight
|
||||
data: data
|
||||
destination:
|
||||
plugin: entity:image_style
|
BIN
web/core/modules/image/sample.png
Normal file
BIN
web/core/modules/image/sample.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
55
web/core/modules/image/src/Annotation/ImageEffect.php
Normal file
55
web/core/modules/image/src/Annotation/ImageEffect.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an image effect annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\ImageEffect
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\image\Plugin\ImageEffect\ResizeImageEffect
|
||||
*
|
||||
* @see hook_image_effect_info_alter()
|
||||
* @see \Drupal\image\ConfigurableImageEffectInterface
|
||||
* @see \Drupal\image\ConfigurableImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectInterface
|
||||
* @see \Drupal\image\ImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectManager
|
||||
* @see \Drupal\Core\ImageToolkit\Annotation\ImageToolkitOperation
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class ImageEffect extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the image effect.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* A brief description of the image effect.
|
||||
*
|
||||
* This will be shown when adding or configuring this image effect.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation (optional)
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
}
|
31
web/core/modules/image/src/ConfigurableImageEffectBase.php
Normal file
31
web/core/modules/image/src/ConfigurableImageEffectBase.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a base class for configurable image effects.
|
||||
*
|
||||
* @see \Drupal\image\Annotation\ImageEffect
|
||||
* @see \Drupal\image\ConfigurableImageEffectInterface
|
||||
* @see \Drupal\image\ImageEffectInterface
|
||||
* @see \Drupal\image\ImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class ConfigurableImageEffectBase extends ImageEffectBase implements ConfigurableImageEffectInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
|
||||
/**
|
||||
* Defines the interface for configurable image effects.
|
||||
*
|
||||
* @see \Drupal\image\Annotation\ImageEffect
|
||||
* @see \Drupal\image\ConfigurableImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectInterface
|
||||
* @see \Drupal\image\ImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ConfigurableImageEffectInterface extends ImageEffectInterface, PluginFormInterface {
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Image\ImageFactory;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
use Drupal\system\FileDownloadController;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
|
||||
/**
|
||||
* Defines a controller to serve image styles.
|
||||
*/
|
||||
class ImageStyleDownloadController extends FileDownloadController {
|
||||
|
||||
/**
|
||||
* The lock backend.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||
*/
|
||||
protected $lock;
|
||||
|
||||
/**
|
||||
* The image factory.
|
||||
*
|
||||
* @var \Drupal\Core\Image\ImageFactory
|
||||
*/
|
||||
protected $imageFactory;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a ImageStyleDownloadController object.
|
||||
*
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend.
|
||||
* @param \Drupal\Core\Image\ImageFactory $image_factory
|
||||
* The image factory.
|
||||
*/
|
||||
public function __construct(LockBackendInterface $lock, ImageFactory $image_factory) {
|
||||
$this->lock = $lock;
|
||||
$this->imageFactory = $image_factory;
|
||||
$this->logger = $this->getLogger('image');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('lock'),
|
||||
$container->get('image.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a derivative, given a style and image path.
|
||||
*
|
||||
* After generating an image, transfer it to the requesting agent.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
* @param string $scheme
|
||||
* The file scheme, defaults to 'private'.
|
||||
* @param \Drupal\image\ImageStyleInterface $image_style
|
||||
* The image style to deliver.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response
|
||||
* The transferred file as response or some error response.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* Thrown when the user does not have access to the file.
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException
|
||||
* Thrown when the file is still being generated.
|
||||
*/
|
||||
public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) {
|
||||
$target = $request->query->get('file');
|
||||
$image_uri = $scheme . '://' . $target;
|
||||
|
||||
// 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. To prevent this variable from leaving the
|
||||
// site vulnerable to the most serious attacks, a token is always required
|
||||
// when a derivative of a style is requested.
|
||||
// The $target variable for a derivative of a style has
|
||||
// styles/<style_name>/... as structure, so we check if the $target variable
|
||||
// starts with styles/.
|
||||
$valid = !empty($image_style) && file_stream_wrapper_valid_scheme($scheme);
|
||||
if (!$this->config('image.settings')->get('allow_insecure_derivatives') || strpos(ltrim($target, '\/'), 'styles/') === 0) {
|
||||
$valid &= $request->query->get(IMAGE_DERIVATIVE_TOKEN) === $image_style->getPathToken($image_uri);
|
||||
}
|
||||
if (!$valid) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$derivative_uri = $image_style->buildUri($image_uri);
|
||||
$headers = array();
|
||||
|
||||
// If using the private scheme, let other modules provide headers and
|
||||
// control access to the file.
|
||||
if ($scheme == 'private') {
|
||||
$headers = $this->moduleHandler()->invokeAll('file_download', array($image_uri));
|
||||
if (in_array(-1, $headers) || empty($headers)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
}
|
||||
|
||||
// Don't try to generate file if source is missing.
|
||||
if (!file_exists($image_uri)) {
|
||||
// If the image style converted the extension, it has been added to the
|
||||
// original file, resulting in filenames like image.png.jpeg. So to find
|
||||
// the actual source image, we remove the extension and check if that
|
||||
// image exists.
|
||||
$path_info = pathinfo($image_uri);
|
||||
$converted_image_uri = $path_info['dirname'] . DIRECTORY_SEPARATOR . $path_info['filename'];
|
||||
if (!file_exists($converted_image_uri)) {
|
||||
$this->logger->notice('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($this->t('Error generating image, missing source file.'), 404);
|
||||
}
|
||||
else {
|
||||
// The converted file does exist, use it as the source.
|
||||
$image_uri = $converted_image_uri;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't start generating the image if the derivative already exists or if
|
||||
// generation is in progress in another thread.
|
||||
if (!file_exists($derivative_uri)) {
|
||||
$lock_name = 'image_style_deliver:' . $image_style->id() . ':' . Crypt::hashBase64($image_uri);
|
||||
$lock_acquired = $this->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, $this->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->createDerivative($image_uri, $derivative_uri);
|
||||
|
||||
if (!empty($lock_acquired)) {
|
||||
$this->lock->release($lock_name);
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$image = $this->imageFactory->get($derivative_uri);
|
||||
$uri = $image->getSource();
|
||||
$headers += array(
|
||||
'Content-Type' => $image->getMimeType(),
|
||||
'Content-Length' => $image->getFileSize(),
|
||||
);
|
||||
// \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()
|
||||
// sets response as not cacheable if the Cache-Control header is not
|
||||
// already modified. We pass in FALSE for non-private schemes for the
|
||||
// $public parameter to make sure we don't change the headers.
|
||||
return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');
|
||||
}
|
||||
else {
|
||||
$this->logger->notice('Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));
|
||||
return new Response($this->t('Error generating image.'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
523
web/core/modules/image/src/Entity/ImageStyle.php
Normal file
523
web/core/modules/image/src/Entity/ImageStyle.php
Normal file
|
@ -0,0 +1,523 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
|
||||
use Drupal\Core\Routing\RequestHelper;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\image\ImageEffectPluginCollection;
|
||||
use Drupal\image\ImageEffectInterface;
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
/**
|
||||
* Defines an image style configuration entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "image_style",
|
||||
* label = @Translation("Image style"),
|
||||
* handlers = {
|
||||
* "form" = {
|
||||
* "add" = "Drupal\image\Form\ImageStyleAddForm",
|
||||
* "edit" = "Drupal\image\Form\ImageStyleEditForm",
|
||||
* "delete" = "Drupal\image\Form\ImageStyleDeleteForm",
|
||||
* "flush" = "Drupal\image\Form\ImageStyleFlushForm"
|
||||
* },
|
||||
* "list_builder" = "Drupal\image\ImageStyleListBuilder",
|
||||
* "storage" = "Drupal\image\ImageStyleStorage",
|
||||
* },
|
||||
* admin_permission = "administer image styles",
|
||||
* config_prefix = "style",
|
||||
* entity_keys = {
|
||||
* "id" = "name",
|
||||
* "label" = "label"
|
||||
* },
|
||||
* links = {
|
||||
* "flush-form" = "/admin/config/media/image-styles/manage/{image_style}/flush",
|
||||
* "edit-form" = "/admin/config/media/image-styles/manage/{image_style}",
|
||||
* "delete-form" = "/admin/config/media/image-styles/manage/{image_style}/delete",
|
||||
* "collection" = "/admin/config/media/image-styles",
|
||||
* },
|
||||
* config_export = {
|
||||
* "name",
|
||||
* "label",
|
||||
* "effects",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, EntityWithPluginCollectionInterface {
|
||||
|
||||
/**
|
||||
* The name of the image style.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The image style label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The array of image effects for this image style.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $effects = array();
|
||||
|
||||
/**
|
||||
* Holds the collection of image effects that are used by this image style.
|
||||
*
|
||||
* @var \Drupal\image\ImageEffectPluginCollection
|
||||
*/
|
||||
protected $effectsCollection;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
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 settings if necessary.
|
||||
if (!$this->isSyncing()) {
|
||||
static::replaceImageStyle($this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Flush image style when updating without changing the name.
|
||||
$this->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
|
||||
/** @var \Drupal\image\ImageStyleInterface[] $entities */
|
||||
foreach ($entities as $style) {
|
||||
// Flush cached media for the deleted style.
|
||||
$style->flush();
|
||||
// Clear the replacement ID, if one has been previously stored.
|
||||
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
|
||||
$storage->clearReplacementId($style->id());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update field settings if the image style name is changed.
|
||||
*
|
||||
* @param \Drupal\image\ImageStyleInterface $style
|
||||
* The image style.
|
||||
*/
|
||||
protected static function replaceImageStyle(ImageStyleInterface $style) {
|
||||
if ($style->id() != $style->getOriginalId()) {
|
||||
// Loop through all entity displays looking for formatters / widgets using
|
||||
// the image style.
|
||||
foreach (EntityViewDisplay::loadMultiple() as $display) {
|
||||
foreach ($display->getComponents() as $name => $options) {
|
||||
if (isset($options['type']) && $options['type'] == 'image' && $options['settings']['image_style'] == $style->getOriginalId()) {
|
||||
$options['settings']['image_style'] = $style->id();
|
||||
$display->setComponent($name, $options)
|
||||
->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (EntityViewDisplay::loadMultiple() as $display) {
|
||||
foreach ($display->getComponents() as $name => $options) {
|
||||
if (isset($options['type']) && $options['type'] == 'image_image' && $options['settings']['preview_image_style'] == $style->getOriginalId()) {
|
||||
$options['settings']['preview_image_style'] = $style->id();
|
||||
$display->setComponent($name, $options)
|
||||
->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildUri($uri) {
|
||||
$source_scheme = $scheme = $this->fileUriScheme($uri);
|
||||
$default_scheme = $this->fileDefaultScheme();
|
||||
|
||||
if ($source_scheme) {
|
||||
$path = $this->fileUriTarget($uri);
|
||||
// The scheme of derivative image files only needs to be computed for
|
||||
// source files not stored in the default scheme.
|
||||
if ($source_scheme != $default_scheme) {
|
||||
$class = $this->getStreamWrapperManager()->getClass($source_scheme);
|
||||
$is_writable = $class::getType() & StreamWrapperInterface::WRITE;
|
||||
|
||||
// Compute the derivative URI scheme. Derivatives created from writable
|
||||
// source stream wrappers will inherit the scheme. Derivatives created
|
||||
// from read-only stream wrappers will fall-back to the default scheme.
|
||||
$scheme = $is_writable ? $source_scheme : $default_scheme;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$path = $uri;
|
||||
$source_scheme = $scheme = $default_scheme;
|
||||
}
|
||||
return "$scheme://styles/{$this->id()}/$source_scheme/{$this->addExtension($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')) {
|
||||
// The passed $path variable can be either a relative path or a full URI.
|
||||
$original_uri = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path);
|
||||
$token_query = array(IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($original_uri));
|
||||
}
|
||||
|
||||
if ($clean_urls === NULL) {
|
||||
// Assume clean URLs unless the request tells us otherwise.
|
||||
$clean_urls = TRUE;
|
||||
try {
|
||||
$request = \Drupal::request();
|
||||
$clean_urls = RequestHelper::isCleanUrl($request);
|
||||
}
|
||||
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::fromUri() 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 = $this->getStreamWrapperManager()->getViaUri($uri)->getDirectoryPath();
|
||||
return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query))->toString();
|
||||
}
|
||||
|
||||
$file_url = file_create_url($uri);
|
||||
// Append the query string with the token, if necessary.
|
||||
if ($token_query) {
|
||||
$file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . UrlHelper::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 = $this->getStreamWrapperManager()->getWrappers(StreamWrapperInterface::WRITE_VISIBLE);
|
||||
foreach ($wrappers as $wrapper => $wrapper_data) {
|
||||
if (file_exists($directory = $wrapper . '://styles/' . $this->id())) {
|
||||
file_unmanaged_delete_recursive($directory);
|
||||
}
|
||||
}
|
||||
|
||||
// Let other modules update as necessary on flush.
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
$module_handler->invokeAll('image_style_flush', array($this));
|
||||
|
||||
// Clear caches so that formatters may be added for this style.
|
||||
drupal_theme_rebuild();
|
||||
|
||||
Cache::invalidateTags($this->getCacheTagsToInvalidate());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createDerivative($original_uri, $derivative_uri) {
|
||||
|
||||
// If the source file doesn't exist, return FALSE without creating folders.
|
||||
$image = \Drupal::service('image.factory')->get($original_uri);
|
||||
if (!$image->isValid()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
\Drupal::logger('image')->error('Failed to create style directory: %directory', array('%directory' => $directory));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
foreach ($this->getEffects() as $effect) {
|
||||
$effect->applyEffect($image);
|
||||
}
|
||||
|
||||
if (!$image->save($derivative_uri)) {
|
||||
if (file_exists($derivative_uri)) {
|
||||
\Drupal::logger('image')->error('Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $derivative_uri));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri) {
|
||||
foreach ($this->getEffects() as $effect) {
|
||||
$effect->transformDimensions($dimensions, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeExtension($extension) {
|
||||
foreach ($this->getEffects() as $effect) {
|
||||
$extension = $effect->getDerivativeExtension($extension);
|
||||
}
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPathToken($uri) {
|
||||
// Return the first 8 characters.
|
||||
return substr(Crypt::hmacBase64($this->id() . ':' . $this->addExtension($uri), $this->getPrivateKey() . $this->getHashSalt()), 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->effectsCollection) {
|
||||
$this->effectsCollection = new ImageEffectPluginCollection($this->getImageEffectPluginManager(), $this->effects);
|
||||
$this->effectsCollection->sort();
|
||||
}
|
||||
return $this->effectsCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPluginCollections() {
|
||||
return array('effects' => $this->getEffects());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addImageEffect(array $configuration) {
|
||||
$configuration['uuid'] = $this->uuidGenerator()->generate();
|
||||
$this->getEffects()->addInstanceId($configuration['uuid'], $configuration);
|
||||
return $configuration['uuid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReplacementID() {
|
||||
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId());
|
||||
return $storage->getReplacementId($this->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->get('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->set('name', $name);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image effect plugin manager.
|
||||
*
|
||||
* @return \Drupal\Component\Plugin\PluginManagerInterface
|
||||
* The image effect plugin manager.
|
||||
*/
|
||||
protected function getImageEffectPluginManager() {
|
||||
return \Drupal::service('plugin.manager.image.effect');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Drupal private key.
|
||||
*
|
||||
* @return string
|
||||
* The Drupal private key.
|
||||
*/
|
||||
protected function getPrivateKey() {
|
||||
return \Drupal::service('private_key')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a salt useful for hardening against SQL injection.
|
||||
*
|
||||
* @return string
|
||||
* A salt based on information in settings.php, not in the database.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getHashSalt() {
|
||||
return Settings::getHashSalt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extension to a path.
|
||||
*
|
||||
* If this image style changes the extension of the derivative, this method
|
||||
* adds the new extension to the given path. This way we avoid filename
|
||||
* clashes while still allowing us to find the source image.
|
||||
*
|
||||
* @param string $path
|
||||
* The path to add the extension to.
|
||||
*
|
||||
* @return string
|
||||
* The given path if this image style doesn't change its extension, or the
|
||||
* path with the added extension if it does.
|
||||
*/
|
||||
protected function addExtension($path) {
|
||||
$original_extension = pathinfo($path, PATHINFO_EXTENSION);
|
||||
$extension = $this->getDerivativeExtension($original_extension);
|
||||
if ($original_extension !== $extension) {
|
||||
$path .= '.' . $extension;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a wrapper for file_uri_scheme() to allow unit testing.
|
||||
*
|
||||
* Returns the scheme of a URI (e.g. a stream).
|
||||
*
|
||||
* @param string $uri
|
||||
* A stream, referenced as "scheme://target" or "data:target".
|
||||
*
|
||||
* @see file_uri_target()
|
||||
*
|
||||
* @todo: Remove when https://www.drupal.org/node/2050759 is in.
|
||||
*
|
||||
* @return string
|
||||
* A string containing the name of the scheme, or FALSE if none. For
|
||||
* example, the URI "public://example.txt" would return "public".
|
||||
*/
|
||||
protected function fileUriScheme($uri) {
|
||||
return file_uri_scheme($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a wrapper for file_uri_target() to allow unit testing.
|
||||
*
|
||||
* Returns the part of a URI after the schema.
|
||||
*
|
||||
* @param string $uri
|
||||
* A stream, referenced as "scheme://target" or "data:target".
|
||||
*
|
||||
* @see file_uri_scheme()
|
||||
*
|
||||
* @todo: Convert file_uri_target() into a proper injectable service.
|
||||
*
|
||||
* @return string|bool
|
||||
* A string containing the target (path), or FALSE if none.
|
||||
* For example, the URI "public://sample/test.txt" would return
|
||||
* "sample/test.txt".
|
||||
*/
|
||||
protected function fileUriTarget($uri) {
|
||||
return file_uri_target($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a wrapper for file_default_scheme() to allow unit testing.
|
||||
*
|
||||
* Gets the default file stream implementation.
|
||||
*
|
||||
* @todo: Convert file_default_scheme() into a proper injectable service.
|
||||
*
|
||||
* @return string
|
||||
* 'public', 'private' or any other file scheme defined as the default.
|
||||
*/
|
||||
protected function fileDefaultScheme() {
|
||||
return file_default_scheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stream wrapper manager service.
|
||||
*
|
||||
* @return \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
|
||||
* The stream wrapper manager service
|
||||
*
|
||||
* @todo Properly inject this service in Drupal 9.0.x.
|
||||
*/
|
||||
protected function getStreamWrapperManager() {
|
||||
return \Drupal::service('stream_wrapper_manager');
|
||||
}
|
||||
|
||||
}
|
63
web/core/modules/image/src/Form/ImageEffectAddForm.php
Normal file
63
web/core/modules/image/src/Form/ImageEffectAddForm.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\image\ImageEffectManager;
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an add form for image effects.
|
||||
*/
|
||||
class ImageEffectAddForm extends ImageEffectFormBase {
|
||||
|
||||
/**
|
||||
* The image effect manager.
|
||||
*
|
||||
* @var \Drupal\image\ImageEffectManager
|
||||
*/
|
||||
protected $effectManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ImageEffectAddForm.
|
||||
*
|
||||
* @param \Drupal\image\ImageEffectManager $effect_manager
|
||||
* The image effect manager.
|
||||
*/
|
||||
public function __construct(ImageEffectManager $effect_manager) {
|
||||
$this->effectManager = $effect_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.image.effect')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
|
||||
$form = parent::buildForm($form, $form_state, $image_style, $image_effect);
|
||||
|
||||
$form['#title'] = $this->t('Add %label effect', array('%label' => $this->imageEffect->label()));
|
||||
$form['actions']['submit']['#value'] = $this->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;
|
||||
}
|
||||
|
||||
}
|
75
web/core/modules/image/src/Form/ImageEffectDeleteForm.php
Normal file
75
web/core/modules/image/src/Form/ImageEffectDeleteForm.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
|
||||
/**
|
||||
* Form for deleting an image effect.
|
||||
*/
|
||||
class ImageEffectDeleteForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The image style containing the image effect to be deleted.
|
||||
*
|
||||
* @var \Drupal\image\ImageStyleInterface
|
||||
*/
|
||||
protected $imageStyle;
|
||||
|
||||
/**
|
||||
* The image effect to be deleted.
|
||||
*
|
||||
* @var \Drupal\image\ImageEffectInterface
|
||||
*/
|
||||
protected $imageEffect;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $this->imageStyle->label(), '@effect' => $this->imageEffect->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->imageStyle->urlInfo('edit-form');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'image_effect_delete_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
|
||||
$this->imageStyle = $image_style;
|
||||
$this->imageEffect = $this->imageStyle->getEffect($image_effect);
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->imageStyle->deleteImageEffect($this->imageEffect);
|
||||
drupal_set_message($this->t('The image effect %name has been deleted.', array('%name' => $this->imageEffect->label())));
|
||||
$form_state->setRedirectUrl($this->imageStyle->urlInfo('edit-form'));
|
||||
}
|
||||
|
||||
}
|
32
web/core/modules/image/src/Form/ImageEffectEditForm.php
Normal file
32
web/core/modules/image/src/Form/ImageEffectEditForm.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
|
||||
/**
|
||||
* Provides an edit form for image effects.
|
||||
*/
|
||||
class ImageEffectEditForm extends ImageEffectFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
|
||||
$form = parent::buildForm($form, $form_state, $image_style, $image_effect);
|
||||
|
||||
$form['#title'] = $this->t('Edit %label effect', array('%label' => $this->imageEffect->label()));
|
||||
$form['actions']['submit']['#value'] = $this->t('Update effect');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareImageEffect($image_effect) {
|
||||
return $this->imageStyle->getEffect($image_effect);
|
||||
}
|
||||
|
||||
}
|
141
web/core/modules/image/src/Form/ImageEffectFormBase.php
Normal file
141
web/core/modules/image/src/Form/ImageEffectFormBase.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\SubformState;
|
||||
use Drupal\image\ConfigurableImageEffectInterface;
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Provides a base form for image effects.
|
||||
*/
|
||||
abstract class ImageEffectFormBase extends FormBase {
|
||||
|
||||
/**
|
||||
* The image style.
|
||||
*
|
||||
* @var \Drupal\image\ImageStyleInterface
|
||||
*/
|
||||
protected $imageStyle;
|
||||
|
||||
/**
|
||||
* The image effect.
|
||||
*
|
||||
* @var \Drupal\image\ImageEffectInterface|\Drupal\image\ConfigurableImageEffectInterface
|
||||
*/
|
||||
protected $imageEffect;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'image_effect_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param \Drupal\image\ImageStyleInterface $image_style
|
||||
* The image style.
|
||||
* @param string $image_effect
|
||||
* The image effect ID.
|
||||
*
|
||||
* @return array
|
||||
* The form structure.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
|
||||
$this->imageStyle = $image_style;
|
||||
try {
|
||||
$this->imageEffect = $this->prepareImageEffect($image_effect);
|
||||
}
|
||||
catch (PluginNotFoundException $e) {
|
||||
throw new NotFoundHttpException("Invalid effect id: '$image_effect'.");
|
||||
}
|
||||
$request = $this->getRequest();
|
||||
|
||||
if (!($this->imageEffect instanceof ConfigurableImageEffectInterface)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$form['#attached']['library'][] = 'image/admin';
|
||||
$form['uuid'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $this->imageEffect->getUuid(),
|
||||
);
|
||||
$form['id'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $this->imageEffect->getPluginId(),
|
||||
);
|
||||
|
||||
$form['data'] = [];
|
||||
$subform_state = SubformState::createForSubform($form['data'], $form, $form_state);
|
||||
$form['data'] = $this->imageEffect->buildConfigurationForm($form['data'], $subform_state);
|
||||
$form['data']['#tree'] = TRUE;
|
||||
|
||||
// Check the URL for a weight, then the image effect, otherwise use default.
|
||||
$form['weight'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#value' => $request->query->has('weight') ? (int) $request->query->get('weight') : $this->imageEffect->getWeight(),
|
||||
);
|
||||
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#button_type' => 'primary',
|
||||
);
|
||||
$form['actions']['cancel'] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Cancel'),
|
||||
'#url' => $this->imageStyle->urlInfo('edit-form'),
|
||||
'#attributes' => ['class' => ['button']],
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// The image effect configuration is stored in the 'data' key in the form,
|
||||
// pass that through for validation.
|
||||
$this->imageEffect->validateConfigurationForm($form['data'], SubformState::createForSubform($form['data'], $form, $form_state));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->cleanValues();
|
||||
|
||||
// The image effect configuration is stored in the 'data' key in the form,
|
||||
// pass that through for submission.
|
||||
$this->imageEffect->submitConfigurationForm($form['data'], SubformState::createForSubform($form['data'], $form, $form_state));
|
||||
|
||||
$this->imageEffect->setWeight($form_state->getValue('weight'));
|
||||
if (!$this->imageEffect->getUuid()) {
|
||||
$this->imageStyle->addImageEffect($this->imageEffect->getConfiguration());
|
||||
}
|
||||
$this->imageStyle->save();
|
||||
|
||||
drupal_set_message($this->t('The image effect was successfully applied.'));
|
||||
$form_state->setRedirectUrl($this->imageStyle->urlInfo('edit-form'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
30
web/core/modules/image/src/Form/ImageStyleAddForm.php
Normal file
30
web/core/modules/image/src/Form/ImageStyleAddForm.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Controller for image style addition forms.
|
||||
*/
|
||||
class ImageStyleAddForm extends ImageStyleFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
drupal_set_message($this->t('Style %name was created.', array('%name' => $this->entity->label())));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = $this->t('Create new style');
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
83
web/core/modules/image/src/Form/ImageStyleDeleteForm.php
Normal file
83
web/core/modules/image/src/Form/ImageStyleDeleteForm.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityDeleteForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Creates a form to delete an image style.
|
||||
*/
|
||||
class ImageStyleDeleteForm extends EntityDeleteForm {
|
||||
|
||||
/**
|
||||
* Replacement options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $replacementOptions;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Optionally select a style before deleting %style', array('%style' => $this->entity->label()));
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
if (count($this->getReplacementOptions()) > 1) {
|
||||
return $this->t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.');
|
||||
}
|
||||
return $this->t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$replacement_styles = $this->getReplacementOptions();
|
||||
// If there are non-empty options in the list, allow the user to optionally
|
||||
// pick up a replacement.
|
||||
if (count($replacement_styles) > 1) {
|
||||
$form['replacement'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Replacement style'),
|
||||
'#options' => $replacement_styles,
|
||||
'#empty_option' => $this->t('- No replacement -'),
|
||||
'#weight' => -5,
|
||||
];
|
||||
}
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Save a selected replacement in the image style storage. It will be used
|
||||
// later, in the same request, when resolving dependencies.
|
||||
if ($replacement = $form_state->getValue('replacement')) {
|
||||
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage($this->entity->getEntityTypeId());
|
||||
$storage->setReplacementId($this->entity->id(), $replacement);
|
||||
}
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of image style replacement options.
|
||||
*
|
||||
* @return array
|
||||
* An option list suitable for the form select '#options'.
|
||||
*/
|
||||
protected function getReplacementOptions() {
|
||||
if (!isset($this->replacementOptions)) {
|
||||
$this->replacementOptions = array_diff_key(image_style_options(), [$this->getEntity()->id() => '']);
|
||||
}
|
||||
return $this->replacementOptions;
|
||||
}
|
||||
|
||||
}
|
282
web/core/modules/image/src/Form/ImageStyleEditForm.php
Normal file
282
web/core/modules/image/src/Form/ImageStyleEditForm.php
Normal file
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\image\ConfigurableImageEffectInterface;
|
||||
use Drupal\image\ImageEffectManager;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Controller for image style edit form.
|
||||
*/
|
||||
class ImageStyleEditForm extends ImageStyleFormBase {
|
||||
|
||||
/**
|
||||
* The image effect manager service.
|
||||
*
|
||||
* @var \Drupal\image\ImageEffectManager
|
||||
*/
|
||||
protected $imageEffectManager;
|
||||
|
||||
/**
|
||||
* Constructs an ImageStyleEditForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage
|
||||
* The storage.
|
||||
* @param \Drupal\image\ImageEffectManager $image_effect_manager
|
||||
* The image effect manager service.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $image_style_storage, ImageEffectManager $image_effect_manager) {
|
||||
parent::__construct($image_style_storage);
|
||||
$this->imageEffectManager = $image_effect_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager')->getStorage('image_style'),
|
||||
$container->get('plugin.manager.image.effect')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$user_input = $form_state->getUserInput();
|
||||
$form['#title'] = $this->t('Edit style %name', array('%name' => $this->entity->label()));
|
||||
$form['#tree'] = TRUE;
|
||||
$form['#attached']['library'][] = 'image/admin';
|
||||
|
||||
// Show the thumbnail preview.
|
||||
$preview_arguments = array('#theme' => 'image_style_preview', '#style' => $this->entity);
|
||||
$form['preview'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Preview'),
|
||||
'#markup' => drupal_render($preview_arguments),
|
||||
// Render preview above parent elements.
|
||||
'#weight' => -5,
|
||||
);
|
||||
|
||||
// Build the list of existing image effects for this image style.
|
||||
$form['effects'] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => array(
|
||||
$this->t('Effect'),
|
||||
$this->t('Weight'),
|
||||
$this->t('Operations'),
|
||||
),
|
||||
'#tabledrag' => array(
|
||||
array(
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'image-effect-order-weight',
|
||||
),
|
||||
),
|
||||
'#attributes' => array(
|
||||
'id' => 'image-style-effects',
|
||||
),
|
||||
'#empty' => t('There are currently no effects in this style. Add one by selecting an option below.'),
|
||||
// Render effects below parent elements.
|
||||
'#weight' => 5,
|
||||
);
|
||||
foreach ($this->entity->getEffects() as $effect) {
|
||||
$key = $effect->getUuid();
|
||||
$form['effects'][$key]['#attributes']['class'][] = 'draggable';
|
||||
$form['effects'][$key]['#weight'] = isset($user_input['effects']) ? $user_input['effects'][$key]['weight'] : NULL;
|
||||
$form['effects'][$key]['effect'] = array(
|
||||
'#tree' => FALSE,
|
||||
'data' => array(
|
||||
'label' => array(
|
||||
'#plain_text' => $effect->label(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$summary = $effect->getSummary();
|
||||
|
||||
if (!empty($summary)) {
|
||||
$summary['#prefix'] = ' ';
|
||||
$form['effects'][$key]['effect']['data']['summary'] = $summary;
|
||||
}
|
||||
|
||||
$form['effects'][$key]['weight'] = array(
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight for @title', array('@title' => $effect->label())),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $effect->getWeight(),
|
||||
'#attributes' => array(
|
||||
'class' => array('image-effect-order-weight'),
|
||||
),
|
||||
);
|
||||
|
||||
$links = array();
|
||||
$is_configurable = $effect instanceof ConfigurableImageEffectInterface;
|
||||
if ($is_configurable) {
|
||||
$links['edit'] = array(
|
||||
'title' => $this->t('Edit'),
|
||||
'url' => Url::fromRoute('image.effect_edit_form', [
|
||||
'image_style' => $this->entity->id(),
|
||||
'image_effect' => $key,
|
||||
]),
|
||||
);
|
||||
}
|
||||
$links['delete'] = array(
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('image.effect_delete', [
|
||||
'image_style' => $this->entity->id(),
|
||||
'image_effect' => $key,
|
||||
]),
|
||||
);
|
||||
$form['effects'][$key]['operations'] = array(
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
);
|
||||
}
|
||||
|
||||
// Build the new image effect addition form and add it to the effect list.
|
||||
$new_effect_options = array();
|
||||
$effects = $this->imageEffectManager->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(
|
||||
'#tree' => FALSE,
|
||||
'#weight' => isset($user_input['weight']) ? $user_input['weight'] : NULL,
|
||||
'#attributes' => array('class' => array('draggable')),
|
||||
);
|
||||
$form['effects']['new']['effect'] = array(
|
||||
'data' => array(
|
||||
'new' => array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Effect'),
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $new_effect_options,
|
||||
'#empty_option' => $this->t('Select a new effect'),
|
||||
),
|
||||
array(
|
||||
'add' => array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add'),
|
||||
'#validate' => array('::effectValidate'),
|
||||
'#submit' => array('::submitForm', '::effectSave'),
|
||||
),
|
||||
),
|
||||
),
|
||||
'#prefix' => '<div class="image-style-new">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
|
||||
$form['effects']['new']['weight'] = array(
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight for new effect'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => count($this->entity->getEffects()) + 1,
|
||||
'#attributes' => array('class' => array('image-effect-order-weight')),
|
||||
);
|
||||
$form['effects']['new']['operations'] = array(
|
||||
'data' => array(),
|
||||
);
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate handler for image effect.
|
||||
*/
|
||||
public function effectValidate($form, FormStateInterface $form_state) {
|
||||
if (!$form_state->getValue('new')) {
|
||||
$form_state->setErrorByName('new', $this->t('Select an effect to add.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for image effect.
|
||||
*/
|
||||
public function effectSave($form, FormStateInterface $form_state) {
|
||||
$this->save($form, $form_state);
|
||||
|
||||
// Check if this field has any configuration options.
|
||||
$effect = $this->imageEffectManager->getDefinition($form_state->getValue('new'));
|
||||
|
||||
// Load the configuration form for this option.
|
||||
if (is_subclass_of($effect['class'], '\Drupal\image\ConfigurableImageEffectInterface')) {
|
||||
$form_state->setRedirect(
|
||||
'image.effect_add_form',
|
||||
array(
|
||||
'image_style' => $this->entity->id(),
|
||||
'image_effect' => $form_state->getValue('new'),
|
||||
),
|
||||
array('query' => array('weight' => $form_state->getValue('weight')))
|
||||
);
|
||||
}
|
||||
// If there's no form, immediately add the image effect.
|
||||
else {
|
||||
$effect = array(
|
||||
'id' => $effect['id'],
|
||||
'data' => array(),
|
||||
'weight' => $form_state->getValue('weight'),
|
||||
);
|
||||
$effect_id = $this->entity->addImageEffect($effect);
|
||||
$this->entity->save();
|
||||
if (!empty($effect_id)) {
|
||||
drupal_set_message($this->t('The image effect was successfully applied.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
// Update image effect weights.
|
||||
if (!$form_state->isValueEmpty('effects')) {
|
||||
$this->updateEffectWeights($form_state->getValue('effects'));
|
||||
}
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
parent::save($form, $form_state);
|
||||
drupal_set_message($this->t('Changes to the style have been saved.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = $this->t('Update style');
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates image effect weights.
|
||||
*
|
||||
* @param array $effects
|
||||
* Associative array with effects having effect uuid as keys and array
|
||||
* with effect data as values.
|
||||
*/
|
||||
protected function updateEffectWeights(array $effects) {
|
||||
foreach ($effects as $uuid => $effect_data) {
|
||||
if ($this->entity->getEffects()->has($uuid)) {
|
||||
$this->entity->getEffect($uuid)->setWeight($effect_data['weight']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
50
web/core/modules/image/src/Form/ImageStyleFlushForm.php
Normal file
50
web/core/modules/image/src/Form/ImageStyleFlushForm.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Form controller for image style flush.
|
||||
*/
|
||||
class ImageStyleFlushForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to apply the updated %name image effect to all images?', array('%name' => $this->entity->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('This operation does not change the original images but the copies created for this style will be recreated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Flush');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->entity->urlInfo('collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->entity->flush();
|
||||
drupal_set_message($this->t('The image style %name has been flushed.', array('%name' => $this->entity->label())));
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
79
web/core/modules/image/src/Form/ImageStyleFormBase.php
Normal file
79
web/core/modules/image/src/Form/ImageStyleFormBase.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base form for image style add and edit forms.
|
||||
*/
|
||||
abstract class ImageStyleFormBase extends EntityForm {
|
||||
|
||||
/**
|
||||
* The entity being used by this form.
|
||||
*
|
||||
* @var \Drupal\image\ImageStyleInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* The image style entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $imageStyleStorage;
|
||||
|
||||
/**
|
||||
* Constructs a base class for image style add and edit forms.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage
|
||||
* The image style entity storage.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $image_style_storage) {
|
||||
$this->imageStyleStorage = $image_style_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager')->getStorage('image_style')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
|
||||
$form['label'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Image style name'),
|
||||
'#default_value' => $this->entity->label(),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['name'] = array(
|
||||
'#type' => 'machine_name',
|
||||
'#machine_name' => array(
|
||||
'exists' => array($this->imageStyleStorage, 'load'),
|
||||
),
|
||||
'#default_value' => $this->entity->id(),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
parent::save($form, $form_state);
|
||||
$form_state->setRedirectUrl($this->entity->urlInfo('edit-form'));
|
||||
}
|
||||
|
||||
}
|
168
web/core/modules/image/src/ImageEffectBase.php
Normal file
168
web/core/modules/image/src/ImageEffectBase.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a base class for image effects.
|
||||
*
|
||||
* @see \Drupal\image\Annotation\ImageEffect
|
||||
* @see \Drupal\image\ImageEffectInterface
|
||||
* @see \Drupal\image\ConfigurableImageEffectInterface
|
||||
* @see \Drupal\image\ConfigurableImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class ImageEffectBase extends PluginBase implements ImageEffectInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The image effect ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $uuid;
|
||||
|
||||
/**
|
||||
* The weight of the image effect.
|
||||
*
|
||||
* @var int|string
|
||||
*/
|
||||
protected $weight = '';
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->setConfiguration($configuration);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('logger.factory')->get('image')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri) {
|
||||
// Most image effects will not change the dimensions. This base
|
||||
// implementation represents this behavior. Override this method if your
|
||||
// image effect does change the dimensions.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeExtension($extension) {
|
||||
// Most image effects will not change the extension. This base
|
||||
// implementation represents this behavior. Override this method if your
|
||||
// image effect does change the extension.
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSummary() {
|
||||
return array(
|
||||
'#markup' => '',
|
||||
'#effect' => array(
|
||||
'id' => $this->pluginDefinition['id'],
|
||||
'label' => $this->label(),
|
||||
'description' => $this->pluginDefinition['description'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 getConfiguration() {
|
||||
return array(
|
||||
'uuid' => $this->getUuid(),
|
||||
'id' => $this->getPluginId(),
|
||||
'weight' => $this->getWeight(),
|
||||
'data' => $this->configuration,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {
|
||||
$configuration += array(
|
||||
'data' => array(),
|
||||
'uuid' => '',
|
||||
'weight' => '',
|
||||
);
|
||||
$this->configuration = $configuration['data'] + $this->defaultConfiguration();
|
||||
$this->uuid = $configuration['uuid'];
|
||||
$this->weight = $configuration['weight'];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
106
web/core/modules/image/src/ImageEffectInterface.php
Normal file
106
web/core/modules/image/src/ImageEffectInterface.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* Defines the interface for image effects.
|
||||
*
|
||||
* @see \Drupal\image\Annotation\ImageEffect
|
||||
* @see \Drupal\image\ImageEffectBase
|
||||
* @see \Drupal\image\ConfigurableImageEffectInterface
|
||||
* @see \Drupal\image\ConfigurableImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ImageEffectInterface extends PluginInspectionInterface, ConfigurablePluginInterface {
|
||||
|
||||
/**
|
||||
* Applies an image effect to the image object.
|
||||
*
|
||||
* @param \Drupal\Core\Image\ImageInterface $image
|
||||
* An image file object.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success. FALSE if unable to perform the image effect on the image.
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image);
|
||||
|
||||
/**
|
||||
* Determines the dimensions of the styled image.
|
||||
*
|
||||
* @param array &$dimensions
|
||||
* Dimensions to be modified - an array with the following keys:
|
||||
* - width: the width in pixels, or NULL if unknown
|
||||
* - height: the height in pixels, or NULL if unknown
|
||||
* When either of the dimensions are NULL, the corresponding HTML attribute
|
||||
* will be omitted when an image style using this image effect is used.
|
||||
* @param string $uri
|
||||
* Original image file URI. It is passed in to allow an effect to
|
||||
* optionally use this information to retrieve additional image metadata
|
||||
* to determine dimensions of the styled image.
|
||||
* ImageEffectInterface::transformDimensions key objective is to calculate
|
||||
* styled image dimensions without performing actual image operations, so
|
||||
* be aware that performing IO on the URI may lead to decrease in
|
||||
* performance.
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri);
|
||||
|
||||
/**
|
||||
* Returns the extension the derivative would have have after applying this
|
||||
* image effect.
|
||||
*
|
||||
* @param string $extension
|
||||
* The file extension the derivative has before applying.
|
||||
*
|
||||
* @return string
|
||||
* The file extension after applying.
|
||||
*/
|
||||
public function getDerivativeExtension($extension);
|
||||
|
||||
/**
|
||||
* Returns a render array summarizing the configuration of the image effect.
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*/
|
||||
public function getSummary();
|
||||
|
||||
/**
|
||||
* Returns the image effect label.
|
||||
*
|
||||
* @return string
|
||||
* The image effect label.
|
||||
*/
|
||||
public function label();
|
||||
|
||||
/**
|
||||
* Returns the unique ID representing the image effect.
|
||||
*
|
||||
* @return string
|
||||
* The image effect ID.
|
||||
*/
|
||||
public function getUuid();
|
||||
|
||||
/**
|
||||
* Returns the weight of the image effect.
|
||||
*
|
||||
* @return int|string
|
||||
* Either the integer weight of the image effect, or an empty string.
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
/**
|
||||
* Sets the weight for this image effect.
|
||||
*
|
||||
* @param int $weight
|
||||
* The weight for this image effect.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWeight($weight);
|
||||
|
||||
}
|
40
web/core/modules/image/src/ImageEffectManager.php
Normal file
40
web/core/modules/image/src/ImageEffectManager.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages image effect plugins.
|
||||
*
|
||||
* @see hook_image_effect_info_alter()
|
||||
* @see \Drupal\image\Annotation\ImageEffect
|
||||
* @see \Drupal\image\ConfigurableImageEffectInterface
|
||||
* @see \Drupal\image\ConfigurableImageEffectBase
|
||||
* @see \Drupal\image\ImageEffectInterface
|
||||
* @see \Drupal\image\ImageEffectBase
|
||||
* @see plugin_api
|
||||
*/
|
||||
class ImageEffectManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a new ImageEffectManager.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/ImageEffect', $namespaces, $module_handler, 'Drupal\image\ImageEffectInterface', 'Drupal\image\Annotation\ImageEffect');
|
||||
|
||||
$this->alterInfo('image_effect_info');
|
||||
$this->setCacheBackend($cache_backend, 'image_effect_plugins');
|
||||
}
|
||||
|
||||
}
|
34
web/core/modules/image/src/ImageEffectPluginCollection.php
Normal file
34
web/core/modules/image/src/ImageEffectPluginCollection.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
|
||||
|
||||
/**
|
||||
* A collection of image effects.
|
||||
*/
|
||||
class ImageEffectPluginCollection extends DefaultLazyPluginCollection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return \Drupal\image\ImageEffectInterface
|
||||
*/
|
||||
public function &get($instance_id) {
|
||||
return parent::get($instance_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sortHelper($aID, $bID) {
|
||||
$a_weight = $this->get($aID)->getWeight();
|
||||
$b_weight = $this->get($bID)->getWeight();
|
||||
if ($a_weight == $b_weight) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($a_weight < $b_weight) ? -1 : 1;
|
||||
}
|
||||
|
||||
}
|
197
web/core/modules/image/src/ImageStyleInterface.php
Normal file
197
web/core/modules/image/src/ImageStyleInterface.php
Normal file
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an image style entity.
|
||||
*/
|
||||
interface ImageStyleInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Returns the replacement ID.
|
||||
*
|
||||
* @return string|null
|
||||
* The replacement image style ID or NULL if no replacement has been
|
||||
* selected.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\image\ImageStyleStorageInterface::getReplacementId() instead.
|
||||
*
|
||||
* @see \Drupal\image\ImageStyleStorageInterface::getReplacementId()
|
||||
*/
|
||||
public function getReplacementID();
|
||||
|
||||
/**
|
||||
* Returns the image style.
|
||||
*
|
||||
* @return string
|
||||
* The name of the image style.
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Sets the name of the image style.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the image style.
|
||||
*
|
||||
* @return \Drupal\image\ImageStyleInterface
|
||||
* The class instance this method is called on.
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
|
||||
/**
|
||||
* 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 <img> tag. Requesting the URL will cause the image to be created.
|
||||
*
|
||||
* @see \Drupal\image\Controller\ImageStyleDownloadController::deliver()
|
||||
* @see file_url_transform_relative()
|
||||
*/
|
||||
public function buildUrl($path, $clean_urls = NULL);
|
||||
|
||||
/**
|
||||
* 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 original image of this style.
|
||||
*
|
||||
* @return string
|
||||
* An eight-character token which can be used to protect image style
|
||||
* derivatives against denial-of-service attacks.
|
||||
*/
|
||||
public function getPathToken($uri);
|
||||
|
||||
/**
|
||||
* 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 $this
|
||||
*/
|
||||
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.
|
||||
* @param string $uri
|
||||
* Original image file URI. It is passed in to allow effects to
|
||||
* optionally use this information to retrieve additional image metadata
|
||||
* to determine dimensions of the styled image.
|
||||
* ImageStyleInterface::transformDimensions key objective is to calculate
|
||||
* styled image dimensions without performing actual image operations, so
|
||||
* be aware that performing IO on the URI may lead to decrease in
|
||||
* performance.
|
||||
*
|
||||
* @see ImageEffectInterface::transformDimensions
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri);
|
||||
|
||||
/**
|
||||
* Determines the extension of the derivative without generating it.
|
||||
*
|
||||
* @param string $extension
|
||||
* The file extension of the original image.
|
||||
*
|
||||
* @return string
|
||||
* The extension the derivative image will have, given the extension of the
|
||||
* original.
|
||||
*/
|
||||
public function getDerivativeExtension($extension);
|
||||
|
||||
/**
|
||||
* 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\ImageEffectPluginCollection|\Drupal\image\ImageEffectInterface[]
|
||||
* The image effect plugin collection.
|
||||
*/
|
||||
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 addImageEffect(array $configuration);
|
||||
|
||||
/**
|
||||
* Deletes an image effect from this style.
|
||||
*
|
||||
* @param \Drupal\image\ImageEffectInterface $effect
|
||||
* The image effect object.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function deleteImageEffect(ImageEffectInterface $effect);
|
||||
|
||||
}
|
58
web/core/modules/image/src/ImageStyleListBuilder.php
Normal file
58
web/core/modules/image/src/ImageStyleListBuilder.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of image style entities.
|
||||
*
|
||||
* @see \Drupal\image\Entity\ImageStyle
|
||||
*/
|
||||
class ImageStyleListBuilder extends ConfigEntityListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Style name');
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['label'] = $entity->label();
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$flush = array(
|
||||
'title' => t('Flush'),
|
||||
'weight' => 200,
|
||||
'url' => $entity->urlInfo('flush-form'),
|
||||
);
|
||||
|
||||
return parent::getDefaultOperations($entity) + array(
|
||||
'flush' => $flush,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
$build['table']['#empty'] = $this->t('There are currently no styles. <a href=":url">Add a new one</a>.', [
|
||||
':url' => Url::fromRoute('image.style_add')->toString(),
|
||||
]);
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
46
web/core/modules/image/src/ImageStyleStorage.php
Normal file
46
web/core/modules/image/src/ImageStyleStorage.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
|
||||
|
||||
/**
|
||||
* Storage controller class for "image style" configuration entities.
|
||||
*/
|
||||
class ImageStyleStorage extends ConfigEntityStorage implements ImageStyleStorageInterface {
|
||||
|
||||
/**
|
||||
* Image style replacement memory storage.
|
||||
*
|
||||
* This value is not stored in the backend. It's used during the deletion of
|
||||
* an image style to save the replacement image style in the same request. The
|
||||
* value is used later, when resolving dependencies.
|
||||
*
|
||||
* @var string[]
|
||||
*
|
||||
* @see \Drupal\image\Form\ImageStyleDeleteForm::submitForm()
|
||||
*/
|
||||
protected $replacement = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setReplacementId($name, $replacement) {
|
||||
$this->replacement[$name] = $replacement;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReplacementId($name) {
|
||||
return isset($this->replacement[$name]) ? $this->replacement[$name] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearReplacementId($name) {
|
||||
unset($this->replacement[$name]);
|
||||
}
|
||||
|
||||
}
|
52
web/core/modules/image/src/ImageStyleStorageInterface.php
Normal file
52
web/core/modules/image/src/ImageStyleStorageInterface.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image;
|
||||
|
||||
/**
|
||||
* Interface for storage controller for "image style" configuration entities.
|
||||
*/
|
||||
interface ImageStyleStorageInterface {
|
||||
|
||||
/**
|
||||
* Stores a replacement ID for an image style being deleted.
|
||||
*
|
||||
* The method stores a replacement style to be used by the configuration
|
||||
* dependency system when a image style is deleted. The replacement style is
|
||||
* replacing the deleted style in other configuration entities that are
|
||||
* depending on the image style being deleted.
|
||||
*
|
||||
* @param string $name
|
||||
* The ID of the image style to be deleted.
|
||||
* @param string $replacement
|
||||
* The ID of the image style used as replacement.
|
||||
*/
|
||||
public function setReplacementId($name, $replacement);
|
||||
|
||||
/**
|
||||
* Retrieves the replacement ID of a deleted image style.
|
||||
*
|
||||
* The method is retrieving the value stored by ::setReplacementId().
|
||||
*
|
||||
* @param string $name
|
||||
* The ID of the image style to be replaced.
|
||||
*
|
||||
* @return string|null
|
||||
* The ID of the image style used as replacement, if there's any, or NULL.
|
||||
*
|
||||
* @see \Drupal\image\ImageStyleStorageInterface::setReplacementId()
|
||||
*/
|
||||
public function getReplacementId($name);
|
||||
|
||||
/**
|
||||
* Clears a replacement ID from the storage.
|
||||
*
|
||||
* The method clears the value previously stored with ::setReplacementId().
|
||||
*
|
||||
* @param string $name
|
||||
* The ID of the image style to be replaced.
|
||||
*
|
||||
* @see \Drupal\image\ImageStyleStorageInterface::setReplacementId()
|
||||
*/
|
||||
public function clearReplacementId($name);
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\PageCache;
|
||||
|
||||
use Drupal\Core\PageCache\ResponsePolicyInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Cache policy for image preview page.
|
||||
*
|
||||
* This policy rule denies caching of responses generated by the
|
||||
* entity.image.preview route.
|
||||
*/
|
||||
class DenyPrivateImageStyleDownload implements ResponsePolicyInterface {
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a deny image preview page cache policy.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*/
|
||||
public function __construct(RouteMatchInterface $route_match) {
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Response $response, Request $request) {
|
||||
if ($this->routeMatch->getRouteName() === 'image.style_private') {
|
||||
return static::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\PathProcessor;
|
||||
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Defines a path processor to rewrite image styles URLs.
|
||||
*
|
||||
* As the route system does not allow arbitrary amount of parameters convert
|
||||
* the file path to a query parameter on the request.
|
||||
*
|
||||
* This processor handles two different cases:
|
||||
* - public image styles: In order to allow the webserver to serve these files
|
||||
* directly, the route is registered under the same path as the image style so
|
||||
* it took over the first generation. Therefore the path processor converts
|
||||
* the file path to a query parameter.
|
||||
* - private image styles: In contrast to public image styles, private
|
||||
* derivatives are already using system/files/styles. Similar to public image
|
||||
* styles, it also converts the file path to a query parameter.
|
||||
*/
|
||||
class PathProcessorImageStyles implements InboundPathProcessorInterface {
|
||||
|
||||
/**
|
||||
* The stream wrapper manager service.
|
||||
*
|
||||
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
|
||||
*/
|
||||
protected $streamWrapperManager;
|
||||
|
||||
/**
|
||||
* Constructs a new PathProcessorImageStyles object.
|
||||
*
|
||||
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
|
||||
* The stream wrapper manager service.
|
||||
*/
|
||||
public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager) {
|
||||
$this->streamWrapperManager = $stream_wrapper_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processInbound($path, Request $request) {
|
||||
$directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
|
||||
if (strpos($path, '/' . $directory_path . '/styles/') === 0) {
|
||||
$path_prefix = '/' . $directory_path . '/styles/';
|
||||
}
|
||||
elseif (strpos($path, '/system/files/styles/') === 0) {
|
||||
$path_prefix = '/system/files/styles/';
|
||||
}
|
||||
else {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Strip out path prefix.
|
||||
$rest = preg_replace('|^' . preg_quote($path_prefix, '|') . '|', '', $path);
|
||||
|
||||
// Get the image style, scheme and path.
|
||||
if (substr_count($rest, '/') >= 2) {
|
||||
list($image_style, $scheme, $file) = explode('/', $rest, 3);
|
||||
|
||||
// Set the file as query parameter.
|
||||
$request->query->set('file', $file);
|
||||
|
||||
return $path_prefix . $image_style . '/' . $scheme;
|
||||
}
|
||||
else {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'image' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "image",
|
||||
* label = @Translation("Image"),
|
||||
* field_types = {
|
||||
* "image"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The image style entity storage.
|
||||
*
|
||||
* @var \Drupal\image\ImageStyleStorageInterface
|
||||
*/
|
||||
protected $imageStyleStorage;
|
||||
|
||||
/**
|
||||
* Constructs an ImageFormatter object.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the formatter.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
||||
* The definition of the field to which the formatter is associated.
|
||||
* @param array $settings
|
||||
* The formatter settings.
|
||||
* @param string $label
|
||||
* The formatter label display setting.
|
||||
* @param string $view_mode
|
||||
* The view mode.
|
||||
* @param array $third_party_settings
|
||||
* Any third party settings settings.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityStorageInterface $image_style_storage) {
|
||||
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
|
||||
$this->currentUser = $current_user;
|
||||
$this->imageStyleStorage = $image_style_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$configuration['field_definition'],
|
||||
$configuration['settings'],
|
||||
$configuration['label'],
|
||||
$configuration['view_mode'],
|
||||
$configuration['third_party_settings'],
|
||||
$container->get('current_user'),
|
||||
$container->get('entity.manager')->getStorage('image_style')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return array(
|
||||
'image_style' => '',
|
||||
'image_link' => '',
|
||||
) + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$image_styles = image_style_options(FALSE);
|
||||
$description_link = Link::fromTextAndUrl(
|
||||
$this->t('Configure Image Styles'),
|
||||
Url::fromRoute('entity.image_style.collection')
|
||||
);
|
||||
$element['image_style'] = [
|
||||
'#title' => t('Image style'),
|
||||
'#type' => 'select',
|
||||
'#default_value' => $this->getSetting('image_style'),
|
||||
'#empty_option' => t('None (original image)'),
|
||||
'#options' => $image_styles,
|
||||
'#description' => $description_link->toRenderable() + [
|
||||
'#access' => $this->currentUser->hasPermission('administer image styles')
|
||||
],
|
||||
];
|
||||
$link_types = array(
|
||||
'content' => t('Content'),
|
||||
'file' => t('File'),
|
||||
);
|
||||
$element['image_link'] = array(
|
||||
'#title' => t('Link image to'),
|
||||
'#type' => 'select',
|
||||
'#default_value' => $this->getSetting('image_link'),
|
||||
'#empty_option' => t('Nothing'),
|
||||
'#options' => $link_types,
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = array();
|
||||
|
||||
$image_styles = image_style_options(FALSE);
|
||||
// Unset possible 'No defined styles' option.
|
||||
unset($image_styles['']);
|
||||
// Styles could be lost because of enabled/disabled modules that defines
|
||||
// their styles in code.
|
||||
$image_style_setting = $this->getSetting('image_style');
|
||||
if (isset($image_styles[$image_style_setting])) {
|
||||
$summary[] = t('Image style: @style', array('@style' => $image_styles[$image_style_setting]));
|
||||
}
|
||||
else {
|
||||
$summary[] = t('Original image');
|
||||
}
|
||||
|
||||
$link_types = array(
|
||||
'content' => t('Linked to content'),
|
||||
'file' => t('Linked to file'),
|
||||
);
|
||||
// Display this setting only if image is linked.
|
||||
$image_link_setting = $this->getSetting('image_link');
|
||||
if (isset($link_types[$image_link_setting])) {
|
||||
$summary[] = $link_types[$image_link_setting];
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = array();
|
||||
$files = $this->getEntitiesToView($items, $langcode);
|
||||
|
||||
// Early opt-out if the field is empty.
|
||||
if (empty($files)) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
$url = NULL;
|
||||
$image_link_setting = $this->getSetting('image_link');
|
||||
// Check if the formatter involves a link.
|
||||
if ($image_link_setting == 'content') {
|
||||
$entity = $items->getEntity();
|
||||
if (!$entity->isNew()) {
|
||||
$url = $entity->urlInfo();
|
||||
}
|
||||
}
|
||||
elseif ($image_link_setting == 'file') {
|
||||
$link_file = TRUE;
|
||||
}
|
||||
|
||||
$image_style_setting = $this->getSetting('image_style');
|
||||
|
||||
// Collect cache tags to be added for each item in the field.
|
||||
$base_cache_tags = [];
|
||||
if (!empty($image_style_setting)) {
|
||||
$image_style = $this->imageStyleStorage->load($image_style_setting);
|
||||
$base_cache_tags = $image_style->getCacheTags();
|
||||
}
|
||||
|
||||
foreach ($files as $delta => $file) {
|
||||
$cache_contexts = [];
|
||||
if (isset($link_file)) {
|
||||
$image_uri = $file->getFileUri();
|
||||
// @todo Wrap in file_url_transform_relative(). This is currently
|
||||
// impossible. As a work-around, we currently add the 'url.site' cache
|
||||
// context to ensure different file URLs are generated for different
|
||||
// sites in a multisite setup, including HTTP and HTTPS versions of the
|
||||
// same site. Fix in https://www.drupal.org/node/2646744.
|
||||
$url = Url::fromUri(file_create_url($image_uri));
|
||||
$cache_contexts[] = 'url.site';
|
||||
}
|
||||
$cache_tags = Cache::mergeTags($base_cache_tags, $file->getCacheTags());
|
||||
|
||||
// Extract field item attributes for the theme function, and unset them
|
||||
// from the $item so that the field template does not re-render them.
|
||||
$item = $file->_referringItem;
|
||||
$item_attributes = $item->_attributes;
|
||||
unset($item->_attributes);
|
||||
|
||||
$elements[$delta] = array(
|
||||
'#theme' => 'image_formatter',
|
||||
'#item' => $item,
|
||||
'#item_attributes' => $item_attributes,
|
||||
'#image_style' => $image_style_setting,
|
||||
'#url' => $url,
|
||||
'#cache' => array(
|
||||
'tags' => $cache_tags,
|
||||
'contexts' => $cache_contexts,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
$style_id = $this->getSetting('image_style');
|
||||
/** @var \Drupal\image\ImageStyleInterface $style */
|
||||
if ($style_id && $style = ImageStyle::load($style_id)) {
|
||||
// If this formatter uses a valid image style to display the image, add
|
||||
// the image style configuration entity as dependency of this formatter.
|
||||
$dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
$changed = parent::onDependencyRemoval($dependencies);
|
||||
$style_id = $this->getSetting('image_style');
|
||||
/** @var \Drupal\image\ImageStyleInterface $style */
|
||||
if ($style_id && $style = ImageStyle::load($style_id)) {
|
||||
if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
|
||||
$replacement_id = $this->imageStyleStorage->getReplacementId($style_id);
|
||||
// If a valid replacement has been provided in the storage, replace the
|
||||
// image style with the replacement and signal that the formatter plugin
|
||||
// settings were updated.
|
||||
if ($replacement_id && ImageStyle::load($replacement_id)) {
|
||||
$this->setSetting('image_style', $replacement_id);
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
|
||||
use Drupal\field\FieldConfigInterface;
|
||||
use Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase;
|
||||
|
||||
/**
|
||||
* Base class for image file formatters.
|
||||
*/
|
||||
abstract class ImageFormatterBase extends FileFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) {
|
||||
// Add the default image if needed.
|
||||
if ($items->isEmpty()) {
|
||||
$default_image = $this->getFieldSetting('default_image');
|
||||
// If we are dealing with a configurable field, look in both
|
||||
// instance-level and field-level settings.
|
||||
if (empty($default_image['uuid']) && $this->fieldDefinition instanceof FieldConfigInterface) {
|
||||
$default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image');
|
||||
}
|
||||
if (!empty($default_image['uuid']) && $file = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid'])) {
|
||||
// Clone the FieldItemList into a runtime-only object for the formatter,
|
||||
// so that the fallback image can be rendered without affecting the
|
||||
// field values in the entity being rendered.
|
||||
$items = clone $items;
|
||||
$items->setValue(array(
|
||||
'target_id' => $file->id(),
|
||||
'alt' => $default_image['alt'],
|
||||
'title' => $default_image['title'],
|
||||
'width' => $default_image['width'],
|
||||
'height' => $default_image['height'],
|
||||
'entity' => $file,
|
||||
'_loaded' => TRUE,
|
||||
'_is_default' => TRUE,
|
||||
));
|
||||
$file->_referringItem = $items[0];
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getEntitiesToView($items, $langcode);
|
||||
}
|
||||
|
||||
}
|
499
web/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
Normal file
499
web/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
Normal file
|
@ -0,0 +1,499 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\Plugin\Field\FieldType\FileItem;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'image' field type.
|
||||
*
|
||||
* @FieldType(
|
||||
* id = "image",
|
||||
* label = @Translation("Image"),
|
||||
* description = @Translation("This field stores the ID of an image file as an integer value."),
|
||||
* category = @Translation("Reference"),
|
||||
* default_widget = "image_image",
|
||||
* default_formatter = "image",
|
||||
* column_groups = {
|
||||
* "file" = {
|
||||
* "label" = @Translation("File"),
|
||||
* "columns" = {
|
||||
* "target_id", "width", "height"
|
||||
* },
|
||||
* "require_all_groups_for_translation" = TRUE
|
||||
* },
|
||||
* "alt" = {
|
||||
* "label" = @Translation("Alt"),
|
||||
* "translatable" = TRUE
|
||||
* },
|
||||
* "title" = {
|
||||
* "label" = @Translation("Title"),
|
||||
* "translatable" = TRUE
|
||||
* },
|
||||
* },
|
||||
* list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
|
||||
* constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
|
||||
* )
|
||||
*/
|
||||
class ImageItem extends FileItem {
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultStorageSettings() {
|
||||
return array(
|
||||
'default_image' => array(
|
||||
'uuid' => NULL,
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
'width' => NULL,
|
||||
'height' => NULL,
|
||||
),
|
||||
) + parent::defaultStorageSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultFieldSettings() {
|
||||
$settings = array(
|
||||
'file_extensions' => 'png gif jpg jpeg',
|
||||
'alt_field' => 1,
|
||||
'alt_field_required' => 1,
|
||||
'title_field' => 0,
|
||||
'title_field_required' => 0,
|
||||
'max_resolution' => '',
|
||||
'min_resolution' => '',
|
||||
'default_image' => array(
|
||||
'uuid' => NULL,
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
'width' => NULL,
|
||||
'height' => NULL,
|
||||
),
|
||||
) + parent::defaultFieldSettings();
|
||||
|
||||
unset($settings['description_field']);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return array(
|
||||
'columns' => array(
|
||||
'target_id' => array(
|
||||
'description' => 'The ID of the file entity.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
),
|
||||
'alt' => array(
|
||||
'description' => "Alternative image text, for the image's 'alt' attribute.",
|
||||
'type' => 'varchar',
|
||||
'length' => 512,
|
||||
),
|
||||
'title' => array(
|
||||
'description' => "Image title text, for the image's 'title' attribute.",
|
||||
'type' => 'varchar',
|
||||
'length' => 1024,
|
||||
),
|
||||
'width' => array(
|
||||
'description' => 'The width of the image in pixels.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
),
|
||||
'height' => array(
|
||||
'description' => 'The height of the image in pixels.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'target_id' => array('target_id'),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
'target_id' => array(
|
||||
'table' => 'file_managed',
|
||||
'columns' => array('target_id' => 'fid'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties = parent::propertyDefinitions($field_definition);
|
||||
|
||||
unset($properties['display']);
|
||||
unset($properties['description']);
|
||||
|
||||
$properties['alt'] = DataDefinition::create('string')
|
||||
->setLabel(t('Alternative text'))
|
||||
->setDescription(t("Alternative image text, for the image's 'alt' attribute."));
|
||||
|
||||
$properties['title'] = DataDefinition::create('string')
|
||||
->setLabel(t('Title'))
|
||||
->setDescription(t("Image title text, for the image's 'title' attribute."));
|
||||
|
||||
$properties['width'] = DataDefinition::create('integer')
|
||||
->setLabel(t('Width'))
|
||||
->setDescription(t('The width of the image in pixels.'));
|
||||
|
||||
$properties['height'] = DataDefinition::create('integer')
|
||||
->setLabel(t('Height'))
|
||||
->setDescription(t('The height of the image in pixels.'));
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
|
||||
$element = array();
|
||||
|
||||
// We need the field-level 'default_image' setting, and $this->getSettings()
|
||||
// will only provide the instance-level one, so we need to explicitly fetch
|
||||
// the field.
|
||||
$settings = $this->getFieldDefinition()->getFieldStorageDefinition()->getSettings();
|
||||
|
||||
$scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
|
||||
$element['uri_scheme'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Upload destination'),
|
||||
'#options' => $scheme_options,
|
||||
'#default_value' => $settings['uri_scheme'],
|
||||
'#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
|
||||
);
|
||||
|
||||
// Add default_image element.
|
||||
static::defaultImageForm($element, $settings);
|
||||
$element['default_image']['#description'] = t('If no image is uploaded, this image will be shown on display.');
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
|
||||
// Get base form from FileItem.
|
||||
$element = parent::fieldSettingsForm($form, $form_state);
|
||||
|
||||
$settings = $this->getSettings();
|
||||
|
||||
// Add maximum and minimum resolution settings.
|
||||
$max_resolution = explode('x', $settings['max_resolution']) + array('', '');
|
||||
$element['max_resolution'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => t('Maximum image resolution'),
|
||||
'#element_validate' => array(array(get_class($this), 'validateResolution')),
|
||||
'#weight' => 4.1,
|
||||
'#field_prefix' => '<div class="container-inline">',
|
||||
'#field_suffix' => '</div>',
|
||||
'#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="http://wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image.'),
|
||||
);
|
||||
$element['max_resolution']['x'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => t('Maximum width'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $max_resolution[0],
|
||||
'#min' => 1,
|
||||
'#field_suffix' => ' × ',
|
||||
);
|
||||
$element['max_resolution']['y'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => t('Maximum height'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $max_resolution[1],
|
||||
'#min' => 1,
|
||||
'#field_suffix' => ' ' . t('pixels'),
|
||||
);
|
||||
|
||||
$min_resolution = explode('x', $settings['min_resolution']) + array('', '');
|
||||
$element['min_resolution'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => t('Minimum image resolution'),
|
||||
'#element_validate' => array(array(get_class($this), 'validateResolution')),
|
||||
'#weight' => 4.2,
|
||||
'#field_prefix' => '<div class="container-inline">',
|
||||
'#field_suffix' => '</div>',
|
||||
'#description' => t('The minimum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'),
|
||||
);
|
||||
$element['min_resolution']['x'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => t('Minimum width'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $min_resolution[0],
|
||||
'#min' => 1,
|
||||
'#field_suffix' => ' × ',
|
||||
);
|
||||
$element['min_resolution']['y'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => t('Minimum height'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $min_resolution[1],
|
||||
'#min' => 1,
|
||||
'#field_suffix' => ' ' . t('pixels'),
|
||||
);
|
||||
|
||||
// Remove the description option.
|
||||
unset($element['description_field']);
|
||||
|
||||
// Add title and alt configuration options.
|
||||
$element['alt_field'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable <em>Alt</em> field'),
|
||||
'#default_value' => $settings['alt_field'],
|
||||
'#description' => t('The alt attribute may be used by search engines, screen readers, and when the image cannot be loaded. Enabling this field is recommended.'),
|
||||
'#weight' => 9,
|
||||
);
|
||||
$element['alt_field_required'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('<em>Alt</em> field required'),
|
||||
'#default_value' => $settings['alt_field_required'],
|
||||
'#description' => t('Making this field required is recommended.'),
|
||||
'#weight' => 10,
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
':input[name="settings[alt_field]"]' => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
);
|
||||
$element['title_field'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable <em>Title</em> field'),
|
||||
'#default_value' => $settings['title_field'],
|
||||
'#description' => t('The title attribute is used as a tooltip when the mouse hovers over the image. Enabling this field is not recommended as it can cause problems with screen readers.'),
|
||||
'#weight' => 11,
|
||||
);
|
||||
$element['title_field_required'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('<em>Title</em> field required'),
|
||||
'#default_value' => $settings['title_field_required'],
|
||||
'#weight' => 12,
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
':input[name="settings[title_field]"]' => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Add default_image element.
|
||||
static::defaultImageForm($element, $settings);
|
||||
$element['default_image']['#description'] = t("If no image is uploaded, this image will be shown on display and will override the field's default image.");
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave() {
|
||||
parent::preSave();
|
||||
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
|
||||
// Determine the dimensions if necessary.
|
||||
if (empty($width) || empty($height)) {
|
||||
$image = \Drupal::service('image.factory')->get($this->entity->getFileUri());
|
||||
if ($image->isValid()) {
|
||||
$this->width = $image->getWidth();
|
||||
$this->height = $image->getHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
$random = new Random();
|
||||
$settings = $field_definition->getSettings();
|
||||
static $images = array();
|
||||
|
||||
$min_resolution = empty($settings['min_resolution']) ? '100x100' : $settings['min_resolution'];
|
||||
$max_resolution = empty($settings['max_resolution']) ? '600x600' : $settings['max_resolution'];
|
||||
$extensions = array_intersect(explode(' ', $settings['file_extensions']), array('png', 'gif', 'jpg', 'jpeg'));
|
||||
$extension = array_rand(array_combine($extensions, $extensions));
|
||||
// Generate a max of 5 different images.
|
||||
if (!isset($images[$extension][$min_resolution][$max_resolution]) || count($images[$extension][$min_resolution][$max_resolution]) <= 5) {
|
||||
$tmp_file = drupal_tempnam('temporary://', 'generateImage_');
|
||||
$destination = $tmp_file . '.' . $extension;
|
||||
file_unmanaged_move($tmp_file, $destination, FILE_CREATE_DIRECTORY);
|
||||
if ($path = $random->image(drupal_realpath($destination), $min_resolution, $max_resolution)) {
|
||||
$image = File::create();
|
||||
$image->setFileUri($path);
|
||||
$image->setOwnerId(\Drupal::currentUser()->id());
|
||||
$image->setMimeType(\Drupal::service('file.mime_type.guesser')->guess($path));
|
||||
$image->setFileName(drupal_basename($path));
|
||||
$destination_dir = static::doGetUploadLocation($settings);
|
||||
file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY);
|
||||
$destination = $destination_dir . '/' . basename($path);
|
||||
$file = file_move($image, $destination, FILE_CREATE_DIRECTORY);
|
||||
$images[$extension][$min_resolution][$max_resolution][$file->id()] = $file;
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Select one of the images we've already generated for this field.
|
||||
$image_index = array_rand($images[$extension][$min_resolution][$max_resolution]);
|
||||
$file = $images[$extension][$min_resolution][$max_resolution][$image_index];
|
||||
}
|
||||
|
||||
list($width, $height) = getimagesize($file->getFileUri());
|
||||
$values = array(
|
||||
'target_id' => $file->id(),
|
||||
'alt' => $random->sentences(4),
|
||||
'title' => $random->sentences(4),
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
);
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element validate function for resolution fields.
|
||||
*/
|
||||
public static function validateResolution($element, FormStateInterface $form_state) {
|
||||
if (!empty($element['x']['#value']) || !empty($element['y']['#value'])) {
|
||||
foreach (array('x', 'y') as $dimension) {
|
||||
if (!$element[$dimension]['#value']) {
|
||||
// We expect the field name placeholder value to be wrapped in t()
|
||||
// here, so it won't be escaped again as it's already marked safe.
|
||||
$form_state->setError($element[$dimension], t('Both a height and width value must be specified in the @name field.', array('@name' => $element['#title'])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
$form_state->setValueForElement($element, $element['x']['#value'] . 'x' . $element['y']['#value']);
|
||||
}
|
||||
else {
|
||||
$form_state->setValueForElement($element, '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the default_image details element.
|
||||
*
|
||||
* @param array $element
|
||||
* The form associative array passed by reference.
|
||||
* @param array $settings
|
||||
* The field settings array.
|
||||
*/
|
||||
protected function defaultImageForm(array &$element, array $settings) {
|
||||
$element['default_image'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => t('Default image'),
|
||||
'#open' => TRUE,
|
||||
);
|
||||
// Convert the stored UUID to a FID.
|
||||
$fids = [];
|
||||
$uuid = $settings['default_image']['uuid'];
|
||||
if ($uuid && ($file = $this->getEntityManager()->loadEntityByUuid('file', $uuid))) {
|
||||
$fids[0] = $file->id();
|
||||
}
|
||||
$element['default_image']['uuid'] = array(
|
||||
'#type' => 'managed_file',
|
||||
'#title' => t('Image'),
|
||||
'#description' => t('Image to be shown if no image is uploaded.'),
|
||||
'#default_value' => $fids,
|
||||
'#upload_location' => $settings['uri_scheme'] . '://default_images/',
|
||||
'#element_validate' => array(
|
||||
'\Drupal\file\Element\ManagedFile::validateManagedFile',
|
||||
array(get_class($this), 'validateDefaultImageForm'),
|
||||
),
|
||||
'#upload_validators' => $this->getUploadValidators(),
|
||||
);
|
||||
$element['default_image']['alt'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Alternative text'),
|
||||
'#description' => t('This text will be used by screen readers, search engines, and when the image cannot be loaded.'),
|
||||
'#default_value' => $settings['default_image']['alt'],
|
||||
'#maxlength' => 512,
|
||||
);
|
||||
$element['default_image']['title'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Title'),
|
||||
'#description' => t('The title attribute is used as a tooltip when the mouse hovers over the image.'),
|
||||
'#default_value' => $settings['default_image']['title'],
|
||||
'#maxlength' => 1024,
|
||||
);
|
||||
$element['default_image']['width'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $settings['default_image']['width'],
|
||||
);
|
||||
$element['default_image']['height'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $settings['default_image']['height'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the managed_file element for the default Image form.
|
||||
*
|
||||
* This function ensures the fid is a scalar value and not an array. It is
|
||||
* assigned as a #element_validate callback in
|
||||
* \Drupal\image\Plugin\Field\FieldType\ImageItem::defaultImageForm().
|
||||
*
|
||||
* @param array $element
|
||||
* The form element to process.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public static function validateDefaultImageForm(array &$element, FormStateInterface $form_state) {
|
||||
// Consolidate the array value of this field to a single FID as #extended
|
||||
// for default image is not TRUE and this is a single value.
|
||||
if (isset($element['fids']['#value'][0])) {
|
||||
$value = $element['fids']['#value'][0];
|
||||
// Convert the file ID to a uuid.
|
||||
if ($file = \Drupal::entityManager()->getStorage('file')->load($value)) {
|
||||
$value = $file->uuid();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$value = '';
|
||||
}
|
||||
$form_state->setValueForElement($element, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDisplayed() {
|
||||
// Image items do not have per-item visibility settings.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityManagerInterface.
|
||||
*/
|
||||
protected function getEntityManager() {
|
||||
if (!isset($this->entityManager)) {
|
||||
$this->entityManager = \Drupal::entityManager();
|
||||
}
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'image_image' widget.
|
||||
*
|
||||
* @FieldWidget(
|
||||
* id = "image_image",
|
||||
* label = @Translation("Image"),
|
||||
* field_types = {
|
||||
* "image"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ImageWidget extends FileWidget {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return array(
|
||||
'progress_indicator' => 'throbber',
|
||||
'preview_image_style' => 'thumbnail',
|
||||
) + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element = parent::settingsForm($form, $form_state);
|
||||
|
||||
$element['preview_image_style'] = array(
|
||||
'#title' => t('Preview image style'),
|
||||
'#type' => 'select',
|
||||
'#options' => image_style_options(FALSE),
|
||||
'#empty_option' => '<' . t('no preview') . '>',
|
||||
'#default_value' => $this->getSetting('preview_image_style'),
|
||||
'#description' => t('The preview image will be shown while editing the content.'),
|
||||
'#weight' => 15,
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = parent::settingsSummary();
|
||||
|
||||
$image_styles = image_style_options(FALSE);
|
||||
// Unset possible 'No defined styles' option.
|
||||
unset($image_styles['']);
|
||||
// Styles could be lost because of enabled/disabled modules that defines
|
||||
// their styles in code.
|
||||
$image_style_setting = $this->getSetting('preview_image_style');
|
||||
if (isset($image_styles[$image_style_setting])) {
|
||||
$preview_image_style = t('Preview image style: @style', array('@style' => $image_styles[$image_style_setting]));
|
||||
}
|
||||
else {
|
||||
$preview_image_style = t('No preview');
|
||||
}
|
||||
|
||||
array_unshift($summary, $preview_image_style);
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formMultipleElements().
|
||||
*
|
||||
* Special handling for draggable multiple widgets and 'add more' button.
|
||||
*/
|
||||
protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
|
||||
$elements = parent::formMultipleElements($items, $form, $form_state);
|
||||
|
||||
$cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
|
||||
$file_upload_help = array(
|
||||
'#theme' => 'file_upload_help',
|
||||
'#description' => '',
|
||||
'#upload_validators' => $elements[0]['#upload_validators'],
|
||||
'#cardinality' => $cardinality,
|
||||
);
|
||||
if ($cardinality == 1) {
|
||||
// If there's only one field, return it as delta 0.
|
||||
if (empty($elements[0]['#default_value']['fids'])) {
|
||||
$file_upload_help['#description'] = $this->getFilteredDescription();
|
||||
$elements[0]['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$elements['#file_upload_description'] = $file_upload_help;
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
||||
$element = parent::formElement($items, $delta, $element, $form, $form_state);
|
||||
|
||||
$field_settings = $this->getFieldSettings();
|
||||
|
||||
// Add upload resolution validation.
|
||||
if ($field_settings['max_resolution'] || $field_settings['min_resolution']) {
|
||||
$element['#upload_validators']['file_validate_image_resolution'] = array($field_settings['max_resolution'], $field_settings['min_resolution']);
|
||||
}
|
||||
|
||||
// If not using custom extension validation, ensure this is an image.
|
||||
$supported_extensions = array('png', 'gif', 'jpg', 'jpeg');
|
||||
$extensions = isset($element['#upload_validators']['file_validate_extensions'][0]) ? $element['#upload_validators']['file_validate_extensions'][0] : implode(' ', $supported_extensions);
|
||||
$extensions = array_intersect(explode(' ', $extensions), $supported_extensions);
|
||||
$element['#upload_validators']['file_validate_extensions'][0] = implode(' ', $extensions);
|
||||
|
||||
// Add properties needed by process() method.
|
||||
$element['#preview_image_style'] = $this->getSetting('preview_image_style');
|
||||
$element['#title_field'] = $field_settings['title_field'];
|
||||
$element['#title_field_required'] = $field_settings['title_field_required'];
|
||||
$element['#alt_field'] = $field_settings['alt_field'];
|
||||
$element['#alt_field_required'] = $field_settings['alt_field_required'];
|
||||
|
||||
// Default image.
|
||||
$default_image = $field_settings['default_image'];
|
||||
if (empty($default_image['uuid'])) {
|
||||
$default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image');
|
||||
}
|
||||
// Convert the stored UUID into a file ID.
|
||||
if (!empty($default_image['uuid']) && $entity = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid'])) {
|
||||
$default_image['fid'] = $entity->id();
|
||||
}
|
||||
$element['#default_image'] = !empty($default_image['fid']) ? $default_image : array();
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form API callback: Processes a image_image field element.
|
||||
*
|
||||
* Expands the image_image type to include the alt and title fields.
|
||||
*
|
||||
* This method is assigned as a #process callback in formElement() method.
|
||||
*/
|
||||
public static function process($element, FormStateInterface $form_state, $form) {
|
||||
$item = $element['#value'];
|
||||
$item['fids'] = $element['fids']['#value'];
|
||||
|
||||
$element['#theme'] = 'image_widget';
|
||||
|
||||
// Add the image preview.
|
||||
if (!empty($element['#files']) && $element['#preview_image_style']) {
|
||||
$file = reset($element['#files']);
|
||||
$variables = array(
|
||||
'style_name' => $element['#preview_image_style'],
|
||||
'uri' => $file->getFileUri(),
|
||||
);
|
||||
|
||||
// Determine image dimensions.
|
||||
if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
|
||||
$variables['width'] = $element['#value']['width'];
|
||||
$variables['height'] = $element['#value']['height'];
|
||||
}
|
||||
else {
|
||||
$image = \Drupal::service('image.factory')->get($file->getFileUri());
|
||||
if ($image->isValid()) {
|
||||
$variables['width'] = $image->getWidth();
|
||||
$variables['height'] = $image->getHeight();
|
||||
}
|
||||
else {
|
||||
$variables['width'] = $variables['height'] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
$element['preview'] = array(
|
||||
'#weight' => -10,
|
||||
'#theme' => 'image_style',
|
||||
'#width' => $variables['width'],
|
||||
'#height' => $variables['height'],
|
||||
'#style_name' => $variables['style_name'],
|
||||
'#uri' => $variables['uri'],
|
||||
);
|
||||
|
||||
// Store the dimensions in the form so the file doesn't have to be
|
||||
// accessed again. This is important for remote files.
|
||||
$element['width'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#value' => $variables['width'],
|
||||
);
|
||||
$element['height'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#value' => $variables['height'],
|
||||
);
|
||||
}
|
||||
elseif (!empty($element['#default_image'])) {
|
||||
$default_image = $element['#default_image'];
|
||||
$file = File::load($default_image['fid']);
|
||||
if (!empty($file)) {
|
||||
$element['preview'] = array(
|
||||
'#weight' => -10,
|
||||
'#theme' => 'image_style',
|
||||
'#width' => $default_image['width'],
|
||||
'#height' => $default_image['height'],
|
||||
'#style_name' => $element['#preview_image_style'],
|
||||
'#uri' => $file->getFileUri(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the additional alt and title fields.
|
||||
$element['alt'] = array(
|
||||
'#title' => t('Alternative text'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($item['alt']) ? $item['alt'] : '',
|
||||
'#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
|
||||
// @see https://www.drupal.org/node/465106#alt-text
|
||||
'#maxlength' => 512,
|
||||
'#weight' => -12,
|
||||
'#access' => (bool) $item['fids'] && $element['#alt_field'],
|
||||
'#required' => $element['#alt_field_required'],
|
||||
'#element_validate' => $element['#alt_field_required'] == 1 ? array(array(get_called_class(), 'validateRequiredFields')) : array(),
|
||||
);
|
||||
$element['title'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Title'),
|
||||
'#default_value' => isset($item['title']) ? $item['title'] : '',
|
||||
'#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
|
||||
'#maxlength' => 1024,
|
||||
'#weight' => -11,
|
||||
'#access' => (bool) $item['fids'] && $element['#title_field'],
|
||||
'#required' => $element['#title_field_required'],
|
||||
'#element_validate' => $element['#title_field_required'] == 1 ? array(array(get_called_class(), 'validateRequiredFields')) : array(),
|
||||
);
|
||||
|
||||
return parent::process($element, $form_state, $form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate callback for alt and title field, if the user wants them required.
|
||||
*
|
||||
* This is separated in a validate function instead of a #required flag to
|
||||
* avoid being validated on the process callback.
|
||||
*/
|
||||
public static function validateRequiredFields($element, FormStateInterface $form_state) {
|
||||
// Only do validation if the function is triggered from other places than
|
||||
// the image process form.
|
||||
if (!in_array('file_managed_file_submit', $form_state->getTriggeringElement()['#submit'])) {
|
||||
// If the image is not there, we do not check for empty values.
|
||||
$parents = $element['#parents'];
|
||||
$field = array_pop($parents);
|
||||
$image_field = NestedArray::getValue($form_state->getUserInput(), $parents);
|
||||
// We check for the array key, so that it can be NULL (like if the user
|
||||
// submits the form without using the "upload" button).
|
||||
if (!array_key_exists($field, $image_field)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form_state->setLimitValidationErrors([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
$style_id = $this->getSetting('preview_image_style');
|
||||
/** @var \Drupal\image\ImageStyleInterface $style */
|
||||
if ($style_id && $style = ImageStyle::load($style_id)) {
|
||||
// If this widget uses a valid image style to display the preview of the
|
||||
// uploaded image, add that image style configuration entity as dependency
|
||||
// of this widget.
|
||||
$dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
$changed = parent::onDependencyRemoval($dependencies);
|
||||
$style_id = $this->getSetting('preview_image_style');
|
||||
/** @var \Drupal\image\ImageStyleInterface $style */
|
||||
if ($style_id && $style = ImageStyle::load($style_id)) {
|
||||
if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
|
||||
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
|
||||
$storage = \Drupal::entityManager()->getStorage($style->getEntityTypeId());
|
||||
$replacement_id = $storage->getReplacementId($style_id);
|
||||
// If a valid replacement has been provided in the storage, replace the
|
||||
// preview image style with the replacement.
|
||||
if ($replacement_id && ImageStyle::load($replacement_id)) {
|
||||
$this->setSetting('preview_image_style', $replacement_id);
|
||||
}
|
||||
// If there's no replacement or the replacement is invalid, disable the
|
||||
// image preview.
|
||||
else {
|
||||
$this->setSetting('preview_image_style', '');
|
||||
}
|
||||
// Signal that the formatter plugin settings were updated.
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
use Drupal\image\ConfigurableImageEffectBase;
|
||||
|
||||
/**
|
||||
* Converts an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_convert",
|
||||
* label = @Translation("Convert"),
|
||||
* description = @Translation("Converts an image between extensions (e.g. from PNG to JPEG).")
|
||||
* )
|
||||
*/
|
||||
class ConvertImageEffect extends ConfigurableImageEffectBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
if (!$image->convert($this->configuration['extension'])) {
|
||||
$this->logger->error('Image convert failed using the %toolkit toolkit on %path (%mimetype)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType()));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeExtension($extension) {
|
||||
return $this->configuration['extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSummary() {
|
||||
$summary = array(
|
||||
'#markup' => Unicode::strtoupper($this->configuration['extension']),
|
||||
);
|
||||
$summary += parent::getSummary();
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
'extension' => NULL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$extensions = \Drupal::service('image.toolkit.manager')->getDefaultToolkit()->getSupportedExtensions();
|
||||
$options = array_combine(
|
||||
$extensions,
|
||||
array_map(array('\Drupal\Component\Utility\Unicode', 'strtoupper'), $extensions)
|
||||
);
|
||||
$form['extension'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Extension'),
|
||||
'#default_value' => $this->configuration['extension'],
|
||||
'#required' => TRUE,
|
||||
'#options' => $options,
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
$this->configuration['extension'] = $form_state->getValue('extension');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* Crops an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_crop",
|
||||
* label = @Translation("Crop"),
|
||||
* description = @Translation("Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.")
|
||||
* )
|
||||
*/
|
||||
class CropImageEffect extends ResizeImageEffect {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
list($x, $y) = explode('-', $this->configuration['anchor']);
|
||||
$x = image_filter_keyword($x, $image->getWidth(), $this->configuration['width']);
|
||||
$y = image_filter_keyword($y, $image->getHeight(), $this->configuration['height']);
|
||||
if (!$image->crop($x, $y, $this->configuration['width'], $this->configuration['height'])) {
|
||||
$this->logger->error('Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSummary() {
|
||||
$summary = array(
|
||||
'#theme' => 'image_crop_summary',
|
||||
'#data' => $this->configuration,
|
||||
);
|
||||
$summary += parent::getSummary();
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return parent::defaultConfiguration() + array(
|
||||
'anchor' => 'center-center',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
$form['anchor'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Anchor'),
|
||||
'#options' => array(
|
||||
'left-top' => t('Top left'),
|
||||
'center-top' => t('Top center'),
|
||||
'right-top' => t('Top right'),
|
||||
'left-center' => t('Center left'),
|
||||
'center-center' => t('Center'),
|
||||
'right-center' => t('Center right'),
|
||||
'left-bottom' => t('Bottom left'),
|
||||
'center-bottom' => t('Bottom center'),
|
||||
'right-bottom' => t('Bottom 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
|
||||
$this->configuration['anchor'] = $form_state->getValue('anchor');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
use Drupal\image\ImageEffectBase;
|
||||
|
||||
/**
|
||||
* Desaturates (grayscale) an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_desaturate",
|
||||
* label = @Translation("Desaturate"),
|
||||
* description = @Translation("Desaturate converts an image to grayscale.")
|
||||
* )
|
||||
*/
|
||||
class DesaturateImageEffect extends ImageEffectBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
if (!$image->desaturate()) {
|
||||
$this->logger->error('Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
use Drupal\image\ConfigurableImageEffectBase;
|
||||
|
||||
/**
|
||||
* Resizes an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_resize",
|
||||
* label = @Translation("Resize"),
|
||||
* description = @Translation("Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.")
|
||||
* )
|
||||
*/
|
||||
class ResizeImageEffect extends ConfigurableImageEffectBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
if (!$image->resize($this->configuration['width'], $this->configuration['height'])) {
|
||||
$this->logger->error('Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri) {
|
||||
// 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() {
|
||||
$summary = array(
|
||||
'#theme' => 'image_resize_summary',
|
||||
'#data' => $this->configuration,
|
||||
);
|
||||
$summary += parent::getSummary();
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
'width' => NULL,
|
||||
'height' => NULL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['width'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => t('Width'),
|
||||
'#default_value' => $this->configuration['width'],
|
||||
'#field_suffix' => ' ' . t('pixels'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 1,
|
||||
);
|
||||
$form['height'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => t('Height'),
|
||||
'#default_value' => $this->configuration['height'],
|
||||
'#field_suffix' => ' ' . t('pixels'),
|
||||
'#required' => TRUE,
|
||||
'#min' => 1,
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
|
||||
$this->configuration['height'] = $form_state->getValue('height');
|
||||
$this->configuration['width'] = $form_state->getValue('width');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Component\Utility\Color;
|
||||
use Drupal\Component\Utility\Rectangle;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
use Drupal\image\ConfigurableImageEffectBase;
|
||||
|
||||
/**
|
||||
* Rotates an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_rotate",
|
||||
* label = @Translation("Rotate"),
|
||||
* description = @Translation("Rotating an image may cause the dimensions of an image to increase to fit the diagonal.")
|
||||
* )
|
||||
*/
|
||||
class RotateImageEffect extends ConfigurableImageEffectBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
if (!empty($this->configuration['random'])) {
|
||||
$degrees = abs((float) $this->configuration['degrees']);
|
||||
$this->configuration['degrees'] = rand(-$degrees, $degrees);
|
||||
}
|
||||
|
||||
if (!$image->rotate($this->configuration['degrees'], $this->configuration['bgcolor'])) {
|
||||
$this->logger->error('Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri) {
|
||||
// If the rotate is not random and current dimensions are set,
|
||||
// then the new dimensions can be determined.
|
||||
if (!$this->configuration['random'] && $dimensions['width'] && $dimensions['height']) {
|
||||
$rect = new Rectangle($dimensions['width'], $dimensions['height']);
|
||||
$rect = $rect->rotate($this->configuration['degrees']);
|
||||
$dimensions['width'] = $rect->getBoundingWidth();
|
||||
$dimensions['height'] = $rect->getBoundingHeight();
|
||||
}
|
||||
else {
|
||||
$dimensions['width'] = $dimensions['height'] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSummary() {
|
||||
$summary = array(
|
||||
'#theme' => 'image_rotate_summary',
|
||||
'#data' => $this->configuration,
|
||||
);
|
||||
$summary += parent::getSummary();
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
'degrees' => 0,
|
||||
'bgcolor' => NULL,
|
||||
'random' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['degrees'] = array(
|
||||
'#type' => 'number',
|
||||
'#default_value' => $this->configuration['degrees'],
|
||||
'#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' => $this->configuration['bgcolor'],
|
||||
'#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,
|
||||
);
|
||||
$form['random'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->configuration['random'],
|
||||
'#title' => t('Randomize'),
|
||||
'#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
if (!$form_state->isValueEmpty('bgcolor') && !Color::validateHex($form_state->getValue('bgcolor'))) {
|
||||
$form_state->setErrorByName('bgcolor', $this->t('Background color must be a hexadecimal color value.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
|
||||
$this->configuration['degrees'] = $form_state->getValue('degrees');
|
||||
$this->configuration['bgcolor'] = $form_state->getValue('bgcolor');
|
||||
$this->configuration['random'] = $form_state->getValue('random');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* Scales and crops an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_scale_and_crop",
|
||||
* label = @Translation("Scale and crop"),
|
||||
* description = @Translation("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.")
|
||||
* )
|
||||
*/
|
||||
class ScaleAndCropImageEffect extends ResizeImageEffect {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
if (!$image->scaleAndCrop($this->configuration['width'], $this->configuration['height'])) {
|
||||
$this->logger->error('Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Component\Utility\Image;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* Scales an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_scale",
|
||||
* label = @Translation("Scale"),
|
||||
* description = @Translation("Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.")
|
||||
* )
|
||||
*/
|
||||
class ScaleImageEffect extends ResizeImageEffect {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
if (!$image->scale($this->configuration['width'], $this->configuration['height'], $this->configuration['upscale'])) {
|
||||
$this->logger->error('Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()));
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri) {
|
||||
if ($dimensions['width'] && $dimensions['height']) {
|
||||
Image::scaleDimensions($dimensions, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSummary() {
|
||||
$summary = array(
|
||||
'#theme' => 'image_scale_summary',
|
||||
'#data' => $this->configuration,
|
||||
);
|
||||
$summary += parent::getSummary();
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return parent::defaultConfiguration() + array(
|
||||
'upscale' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
$form['width']['#required'] = FALSE;
|
||||
$form['height']['#required'] = FALSE;
|
||||
$form['upscale'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->configuration['upscale'],
|
||||
'#title' => t('Allow Upscaling'),
|
||||
'#description' => t('Let scale make images larger than their original size.'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateConfigurationForm($form, $form_state);
|
||||
if ($form_state->isValueEmpty('width') && $form_state->isValueEmpty('height')) {
|
||||
$form_state->setErrorByName('data', $this->t('Width and height can not both be blank.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
|
||||
$this->configuration['upscale'] = $form_state->getValue('upscale');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityConfigBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Every migration that uses this destination must have an optional
|
||||
* dependency on the d6_file migration to ensure it runs first.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "entity:image_style"
|
||||
* )
|
||||
*/
|
||||
class EntityImageStyle extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
$effects = [];
|
||||
|
||||
// Need to set the effects property to null on the row before the ImageStyle
|
||||
// is created, this prevents improper effect plugin initialization.
|
||||
if ($row->getDestinationProperty('effects')) {
|
||||
$effects = $row->getDestinationProperty('effects');
|
||||
$row->setDestinationProperty('effects', []);
|
||||
}
|
||||
|
||||
/** @var \Drupal\Image\Entity\ImageStyle $style */
|
||||
$style = $this->getEntity($row, $old_destination_id_values);
|
||||
|
||||
// Iterate the effects array so each effect plugin can be initialized.
|
||||
// Catch any missing plugin exceptions.
|
||||
foreach ($effects as $effect) {
|
||||
try {
|
||||
$style->addImageEffect($effect);
|
||||
}
|
||||
catch (PluginNotFoundException $e) {
|
||||
throw new MigrateException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$style->save();
|
||||
|
||||
return array($style->id());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "d6_imagecache_actions"
|
||||
* )
|
||||
*/
|
||||
class ImageCacheActions extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$effects = [];
|
||||
|
||||
foreach ($row->getSourceProperty('actions') as $action) {
|
||||
$id = preg_replace('/^imagecache/', 'image', $action['action']);
|
||||
|
||||
if ($id === 'image_crop') {
|
||||
$action['data']['anchor'] = $action['data']['xoffset'] . '-' . $action['data']['yoffset'];
|
||||
|
||||
if (!preg_match('/^[a-z]*\-[a-z]*/', $action['data']['anchor'])) {
|
||||
$migrate_executable->message->display(
|
||||
'The Drupal 8 image crop effect does not support numeric values for x and y offsets. Use keywords to set crop effect offsets instead.',
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
unset($action['data']['xoffset']);
|
||||
unset($action['data']['yoffset']);
|
||||
}
|
||||
|
||||
$effects[] = [
|
||||
'id' => $id,
|
||||
'weight' => $action['weight'],
|
||||
'data' => $action['data'],
|
||||
];
|
||||
}
|
||||
|
||||
return $effects;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Drupal 6 imagecache presets source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_imagecache_presets",
|
||||
* source_provider = "imagecache"
|
||||
* )
|
||||
*/
|
||||
class ImageCachePreset extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('imagecache_preset', 'icp')
|
||||
->fields('icp');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'presetid' => $this->t('Preset ID'),
|
||||
'presetname' => $this->t('Preset Name'),
|
||||
];
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['presetid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$actions = array();
|
||||
|
||||
$results = $this->select('imagecache_action', 'ica')
|
||||
->fields('ica')
|
||||
->condition('presetid', $row->getSourceProperty('presetid'))
|
||||
->execute();
|
||||
|
||||
foreach ($results as $key => $result) {
|
||||
$actions[$key] = $result;
|
||||
$actions[$key]['data'] = unserialize($result['data']);
|
||||
}
|
||||
|
||||
$row->setSourceProperty('actions', $actions);
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Drupal image styles source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_image_styles",
|
||||
* source_provider = "image"
|
||||
* )
|
||||
*/
|
||||
class ImageStyles extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('image_styles', 'ims')
|
||||
->fields('ims');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'isid' => $this->t('The primary identifier for an image style.'),
|
||||
'name' => $this->t('The style machine name.'),
|
||||
'label' => $this->t('The style administrative name.'),
|
||||
];
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['isid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$effects = array();
|
||||
|
||||
$results = $this->select('image_effects', 'ie')
|
||||
->fields('ie')
|
||||
->condition('isid', $row->getSourceProperty('isid'))
|
||||
->execute();
|
||||
|
||||
foreach ($results as $key => $result) {
|
||||
$result['data'] = unserialize($result['data']);
|
||||
$effects[$key] = $result;
|
||||
}
|
||||
|
||||
$row->setSourceProperty('effects', $effects);
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
}
|
67
web/core/modules/image/src/Routing/ImageStyleRoutes.php
Normal file
67
web/core/modules/image/src/Routing/ImageStyleRoutes.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Routing;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Defines a route subscriber to register a url for serving image styles.
|
||||
*/
|
||||
class ImageStyleRoutes implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The stream wrapper manager service.
|
||||
*
|
||||
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
|
||||
*/
|
||||
protected $streamWrapperManager;
|
||||
|
||||
/**
|
||||
* Constructs a new PathProcessorImageStyles object.
|
||||
*
|
||||
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
|
||||
* The stream wrapper manager service.
|
||||
*/
|
||||
public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager) {
|
||||
$this->streamWrapperManager = $stream_wrapper_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('stream_wrapper_manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of route objects.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route[]
|
||||
* An array of route objects.
|
||||
*/
|
||||
public function routes() {
|
||||
$routes = array();
|
||||
// Generate image derivatives of publicly available files. If clean URLs are
|
||||
// disabled image derivatives will always be served through the menu system.
|
||||
// If clean URLs are enabled and the image derivative already exists, PHP
|
||||
// will be bypassed.
|
||||
$directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
|
||||
|
||||
$routes['image.style_public'] = new Route(
|
||||
'/' . $directory_path . '/styles/{image_style}/{scheme}',
|
||||
array(
|
||||
'_controller' => 'Drupal\image\Controller\ImageStyleDownloadController::deliver',
|
||||
),
|
||||
array(
|
||||
'_access' => 'TRUE',
|
||||
)
|
||||
);
|
||||
return $routes;
|
||||
}
|
||||
|
||||
}
|
52
web/core/modules/image/src/Tests/FileMoveTest.php
Normal file
52
web/core/modules/image/src/Tests/FileMoveTest.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
|
||||
/**
|
||||
* Tests the file move function for images and image styles.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class FileMoveTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('image');
|
||||
|
||||
/**
|
||||
* Tests moving a randomly generated image.
|
||||
*/
|
||||
function testNormal() {
|
||||
// Pick a file for testing.
|
||||
$file = File::create((array) current($this->drupalGetTestFiles('image')));
|
||||
|
||||
// Create derivative image.
|
||||
$styles = ImageStyle::loadMultiple();
|
||||
$style = reset($styles);
|
||||
$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.');
|
||||
|
||||
// Clone the object so we don't have to worry about the function changing
|
||||
// our reference copy.
|
||||
$desired_filepath = 'public://' . $this->randomMachineName();
|
||||
$result = file_move(clone $file, $desired_filepath, FILE_EXISTS_ERROR);
|
||||
|
||||
// Check if image has been moved.
|
||||
$this->assertTrue(file_exists($result->getFileUri()), 'Make sure image is moved successfully.');
|
||||
|
||||
// Check if derivative image has been flushed.
|
||||
$this->assertFalse(file_exists($derivative_uri), 'Make sure derivative image has been flushed.');
|
||||
}
|
||||
|
||||
}
|
526
web/core/modules/image/src/Tests/ImageAdminStylesTest.php
Normal file
526
web/core/modules/image/src/Tests/ImageAdminStylesTest.php
Normal file
|
@ -0,0 +1,526 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* Tests creation, deletion, and editing of image styles and effects.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageAdminStylesTest extends ImageFieldTestBase {
|
||||
|
||||
/**
|
||||
* Given an image style, generate an image.
|
||||
*/
|
||||
function createSampleImage(ImageStyleInterface $style) {
|
||||
static $file_path;
|
||||
|
||||
// First, we need to make sure we have an image in our testing
|
||||
// file directory. Copy over an image on the first run.
|
||||
if (!isset($file_path)) {
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$file = reset($files);
|
||||
$file_path = file_unmanaged_copy($file->uri);
|
||||
}
|
||||
|
||||
return $style->buildUrl($file_path) ? $file_path : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of images currently create for a style.
|
||||
*/
|
||||
function getImageCount(ImageStyleInterface $style) {
|
||||
return count(file_scan_directory('public://styles/' . $style->id(), '/.*/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating an image style with a numeric name and ensuring it can be
|
||||
* applied to an image.
|
||||
*/
|
||||
function testNumericStyleName() {
|
||||
$style_name = rand();
|
||||
$style_label = $this->randomString();
|
||||
$edit = array(
|
||||
'name' => $style_name,
|
||||
'label' => $style_label,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/media/image-styles/add', $edit, t('Create new style'));
|
||||
$this->assertRaw(t('Style %name was created.', array('%name' => $style_label)));
|
||||
$options = image_style_options();
|
||||
$this->assertTrue(array_key_exists($style_name, $options), format_string('Array key %key exists.', array('%key' => $style_name)));
|
||||
}
|
||||
|
||||
/**
|
||||
* General test to add a style, add/remove/edit effects to it, then delete it.
|
||||
*/
|
||||
function testStyle() {
|
||||
$admin_path = 'admin/config/media/image-styles';
|
||||
|
||||
// Setup a style to be created and effects to add to it.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style_label = $this->randomString();
|
||||
$style_path = $admin_path . '/manage/' . $style_name;
|
||||
$effect_edits = array(
|
||||
'image_resize' => array(
|
||||
'width' => 100,
|
||||
'height' => 101,
|
||||
),
|
||||
'image_scale' => array(
|
||||
'width' => 110,
|
||||
'height' => 111,
|
||||
'upscale' => 1,
|
||||
),
|
||||
'image_scale_and_crop' => array(
|
||||
'width' => 120,
|
||||
'height' => 121,
|
||||
),
|
||||
'image_crop' => array(
|
||||
'width' => 130,
|
||||
'height' => 131,
|
||||
'anchor' => 'left-top',
|
||||
),
|
||||
'image_desaturate' => array(
|
||||
// No options for desaturate.
|
||||
),
|
||||
'image_rotate' => array(
|
||||
'degrees' => 5,
|
||||
'random' => 1,
|
||||
'bgcolor' => '#FFFF00',
|
||||
),
|
||||
);
|
||||
|
||||
// Add style form.
|
||||
|
||||
$edit = array(
|
||||
'name' => $style_name,
|
||||
'label' => $style_label,
|
||||
);
|
||||
$this->drupalPostForm($admin_path . '/add', $edit, t('Create new style'));
|
||||
$this->assertRaw(t('Style %name was created.', array('%name' => $style_label)));
|
||||
|
||||
// Ensure that the expected entity operations are there.
|
||||
$this->drupalGet($admin_path);
|
||||
$this->assertLinkByHref($style_path);
|
||||
$this->assertLinkByHref($style_path . '/flush');
|
||||
$this->assertLinkByHref($style_path . '/delete');
|
||||
|
||||
// Add effect form.
|
||||
|
||||
// Add each sample effect to the style.
|
||||
foreach ($effect_edits as $effect => $edit) {
|
||||
$edit_data = array();
|
||||
foreach ($edit as $field => $value) {
|
||||
$edit_data['data[' . $field . ']'] = $value;
|
||||
}
|
||||
// Add the effect.
|
||||
$this->drupalPostForm($style_path, array('new' => $effect), t('Add'));
|
||||
if (!empty($edit)) {
|
||||
$this->drupalPostForm(NULL, $edit_data, t('Add effect'));
|
||||
}
|
||||
}
|
||||
|
||||
// Load the saved image style.
|
||||
$style = ImageStyle::load($style_name);
|
||||
|
||||
// Ensure that third party settings were added to the config entity.
|
||||
// These are added by a hook_image_style_presave() implemented in
|
||||
// image_module_test module.
|
||||
$this->assertEqual('bar', $style->getThirdPartySetting('image_module_test', 'foo'), 'Third party settings were added to the image style.');
|
||||
|
||||
// Ensure that the image style URI matches our expected path.
|
||||
$style_uri_path = $style->url();
|
||||
$this->assertTrue(strpos($style_uri_path, $style_path) !== FALSE, 'The image style URI is correct.');
|
||||
|
||||
// Confirm that all effects on the image style have settings that match
|
||||
// what was saved.
|
||||
$uuids = array();
|
||||
foreach ($style->getEffects() as $uuid => $effect) {
|
||||
// Store the uuid for later use.
|
||||
$uuids[$effect->getPluginId()] = $uuid;
|
||||
$effect_configuration = $effect->getConfiguration();
|
||||
foreach ($effect_edits[$effect->getPluginId()] as $field => $value) {
|
||||
$this->assertEqual($value, $effect_configuration['data'][$field], SafeMarkup::format('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($uuids[$effect_name]), format_string(
|
||||
'A %effect_name effect was saved with ID %uuid',
|
||||
array(
|
||||
'%effect_name' => $effect_name,
|
||||
'%uuid' => $uuids[$effect_name],
|
||||
)));
|
||||
}
|
||||
|
||||
// Image style overview form (ordering and renaming).
|
||||
|
||||
// Confirm the order of effects is maintained according to the order we
|
||||
// added the fields.
|
||||
$effect_edits_order = array_keys($effect_edits);
|
||||
$order_correct = TRUE;
|
||||
$index = 0;
|
||||
foreach ($style->getEffects() 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.');
|
||||
|
||||
// Test the style overview form.
|
||||
// Change the name of the style and adjust the weights of effects.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style_label = $this->randomMachineName();
|
||||
$weight = count($effect_edits);
|
||||
$edit = array(
|
||||
'name' => $style_name,
|
||||
'label' => $style_label,
|
||||
);
|
||||
foreach ($style->getEffects() as $uuid => $effect) {
|
||||
$edit['effects[' . $uuid . '][weight]'] = $weight;
|
||||
$weight--;
|
||||
}
|
||||
|
||||
// Create an image to make sure it gets flushed after saving.
|
||||
$image_path = $this->createSampleImage($style);
|
||||
$this->assertEqual($this->getImageCount($style), 1, format_string('Image style %style image %file successfully generated.', array('%style' => $style->label(), '%file' => $image_path)));
|
||||
|
||||
$this->drupalPostForm($style_path, $edit, t('Update style'));
|
||||
|
||||
// Note that after changing the style name, the style path is changed.
|
||||
$style_path = 'admin/config/media/image-styles/manage/' . $style_name;
|
||||
|
||||
// Check that the URL was updated.
|
||||
$this->drupalGet($style_path);
|
||||
$this->assertTitle(t('Edit style @name | Drupal', array('@name' => $style_label)));
|
||||
$this->assertResponse(200, format_string('Image style %original renamed to %new', array('%original' => $style->id(), '%new' => $style_name)));
|
||||
|
||||
// Check that the image was flushed after updating the style.
|
||||
// This is especially important when renaming the style. Make sure that
|
||||
// the old image directory has been deleted.
|
||||
$this->assertEqual($this->getImageCount($style), 0, format_string('Image style %style was flushed after renaming the style and updating the order of effects.', array('%style' => $style->label())));
|
||||
|
||||
// Load the style by the new name with the new weights.
|
||||
$style = ImageStyle::load($style_name);
|
||||
|
||||
// Confirm the new style order was saved.
|
||||
$effect_edits_order = array_reverse($effect_edits_order);
|
||||
$order_correct = TRUE;
|
||||
$index = 0;
|
||||
foreach ($style->getEffects() 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.');
|
||||
|
||||
// Image effect deletion form.
|
||||
|
||||
// Create an image to make sure it gets flushed after deleting an effect.
|
||||
$image_path = $this->createSampleImage($style);
|
||||
$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->drupalPostForm($style_path . '/effects/' . $uuids['image_crop'] . '/delete', array(), t('Delete'));
|
||||
// Confirm that the form submission was successful.
|
||||
$this->assertResponse(200);
|
||||
$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/' . $uuids['image_crop'] . '/delete');
|
||||
// Refresh the image style information and verify that the effect was
|
||||
// actually deleted.
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$style = $entity_type_manager->getStorage('image_style')->loadUnchanged($style->id());
|
||||
$this->assertFalse($style->getEffects()->has($uuids['image_crop']), format_string(
|
||||
'Effect with ID %uuid no longer found on image style %style',
|
||||
array(
|
||||
'%uuid' => $uuids['image_crop'],
|
||||
'%style' => $style->label(),
|
||||
)));
|
||||
|
||||
// Additional test on Rotate effect, for transparent background.
|
||||
$edit = array(
|
||||
'data[degrees]' => 5,
|
||||
'data[random]' => 0,
|
||||
'data[bgcolor]' => '',
|
||||
);
|
||||
$this->drupalPostForm($style_path, array('new' => 'image_rotate'), t('Add'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Add effect'));
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
$style = $entity_type_manager->getStorage('image_style')->loadUnchanged($style_name);
|
||||
$this->assertEqual(count($style->getEffects()), 6, 'Rotate effect with transparent background was added.');
|
||||
|
||||
// Style deletion form.
|
||||
|
||||
// Delete the style.
|
||||
$this->drupalPostForm($style_path . '/delete', array(), t('Delete'));
|
||||
|
||||
// Confirm the style directory has been removed.
|
||||
$directory = file_default_scheme() . '://styles/' . $style_name;
|
||||
$this->assertFalse(is_dir($directory), format_string('Image style %style directory removed on style deletion.', array('%style' => $style->label())));
|
||||
|
||||
$this->assertFalse(ImageStyle::load($style_name), format_string('Image style %style successfully deleted.', array('%style' => $style->label())));
|
||||
|
||||
// Test empty text when there are no image styles.
|
||||
|
||||
// Delete all image styles.
|
||||
foreach (ImageStyle::loadMultiple() as $image_style) {
|
||||
$image_style->delete();
|
||||
}
|
||||
|
||||
// Confirm that the empty text is correct on the image styles page.
|
||||
$this->drupalGet($admin_path);
|
||||
$this->assertRaw(t('There are currently no styles. <a href=":url">Add a new one</a>.', [
|
||||
':url' => \Drupal::url('image.style_add'),
|
||||
]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests editing Ajax-enabled image effect forms.
|
||||
*/
|
||||
public function testAjaxEnabledEffectForm() {
|
||||
$admin_path = 'admin/config/media/image-styles';
|
||||
|
||||
// Setup a style to be created and effects to add to it.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style_label = $this->randomString();
|
||||
$style_path = $admin_path . '/manage/' . $style_name;
|
||||
$effect_edit = [
|
||||
'data[test_parameter]' => 100,
|
||||
];
|
||||
|
||||
// Add style form.
|
||||
$edit = [
|
||||
'name' => $style_name,
|
||||
'label' => $style_label,
|
||||
];
|
||||
$this->drupalPostForm($admin_path . '/add', $edit, t('Create new style'));
|
||||
$this->assertRaw(t('Style %name was created.', ['%name' => $style_label]));
|
||||
|
||||
// Add two Ajax-enabled test effects.
|
||||
$this->drupalPostForm($style_path, ['new' => 'image_module_test_ajax'], t('Add'));
|
||||
$this->drupalPostForm(NULL, $effect_edit, t('Add effect'));
|
||||
$this->drupalPostForm($style_path, ['new' => 'image_module_test_ajax'], t('Add'));
|
||||
$this->drupalPostForm(NULL, $effect_edit, t('Add effect'));
|
||||
|
||||
// Load the saved image style.
|
||||
$style = ImageStyle::load($style_name);
|
||||
|
||||
// Edit back the effects.
|
||||
foreach ($style->getEffects() as $uuid => $effect) {
|
||||
$effect_path = $admin_path . '/manage/' . $style_name . '/effects/' . $uuid;
|
||||
$this->drupalGet($effect_path);
|
||||
$this->drupalPostAjaxForm(NULL, $effect_edit, ['op' => t('Ajax refresh')]);
|
||||
$this->drupalPostForm(NULL, $effect_edit, t('Update effect'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deleting a style and choosing a replacement style.
|
||||
*/
|
||||
function testStyleReplacement() {
|
||||
// Create a new style.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style_label = $this->randomString();
|
||||
$style = ImageStyle::create(array('name' => $style_name, 'label' => $style_label));
|
||||
$style->save();
|
||||
$style_path = 'admin/config/media/image-styles/manage/';
|
||||
|
||||
// Create an image field that uses the new style.
|
||||
$field_name = strtolower($this->randomMachineName(10));
|
||||
$this->createImageField($field_name, 'article');
|
||||
entity_get_display('node', 'article', 'default')
|
||||
->setComponent($field_name, array(
|
||||
'type' => 'image',
|
||||
'settings' => array('image_style' => $style_name),
|
||||
))
|
||||
->save();
|
||||
|
||||
// Create a new node with an image attached.
|
||||
$test_image = current($this->drupalGetTestFiles('image'));
|
||||
$nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName());
|
||||
$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(file_url_transform_relative($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->randomMachineName(10));
|
||||
$new_style_label = $this->randomString();
|
||||
$edit = array(
|
||||
'name' => $new_style_name,
|
||||
'label' => $new_style_label,
|
||||
);
|
||||
$this->drupalPostForm($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);
|
||||
|
||||
// Reload the image style using the new name.
|
||||
$style = ImageStyle::load($new_style_name);
|
||||
$this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), 'Image displayed using style replacement style.');
|
||||
|
||||
// Delete the style and choose a replacement style.
|
||||
$edit = array(
|
||||
'replacement' => 'thumbnail',
|
||||
);
|
||||
$this->drupalPostForm($style_path . $new_style_name . '/delete', $edit, t('Delete'));
|
||||
$message = t('The image style %name has been deleted.', array('%name' => $new_style_label));
|
||||
$this->assertRaw($message);
|
||||
|
||||
$replacement_style = ImageStyle::load('thumbnail');
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertRaw(file_url_transform_relative($replacement_style->buildUrl($original_uri)), 'Image displayed using style replacement style.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that editing an image effect does not cause it to be duplicated.
|
||||
*/
|
||||
function testEditEffect() {
|
||||
// Add a scale effect.
|
||||
$style_name = 'test_style_effect_edit';
|
||||
$this->drupalGet('admin/config/media/image-styles/add');
|
||||
$this->drupalPostForm(NULL, array('label' => 'Test style effect edit', 'name' => $style_name), t('Create new style'));
|
||||
$this->drupalPostForm(NULL, array('new' => 'image_scale_and_crop'), t('Add'));
|
||||
$this->drupalPostForm(NULL, array('data[width]' => '300', 'data[height]' => '200'), t('Add effect'));
|
||||
$this->assertText(t('Scale and crop 300×200'));
|
||||
|
||||
// There should normally be only one edit link on this page initially.
|
||||
$this->clickLink(t('Edit'));
|
||||
$this->drupalPostForm(NULL, array('data[width]' => '360', 'data[height]' => '240'), t('Update effect'));
|
||||
$this->assertText(t('Scale and crop 360×240'));
|
||||
|
||||
// Check that the previous effect is replaced.
|
||||
$this->assertNoText(t('Scale and crop 300×200'));
|
||||
|
||||
// Add another scale effect.
|
||||
$this->drupalGet('admin/config/media/image-styles/add');
|
||||
$this->drupalPostForm(NULL, array('label' => 'Test style scale edit scale', 'name' => 'test_style_scale_edit_scale'), t('Create new style'));
|
||||
$this->drupalPostForm(NULL, array('new' => 'image_scale'), t('Add'));
|
||||
$this->drupalPostForm(NULL, array('data[width]' => '12', 'data[height]' => '19'), t('Add effect'));
|
||||
|
||||
// Edit the scale effect that was just added.
|
||||
$this->clickLink(t('Edit'));
|
||||
$this->drupalPostForm(NULL, array('data[width]' => '24', 'data[height]' => '19'), t('Update effect'));
|
||||
$this->drupalPostForm(NULL, array('new' => 'image_scale'), t('Add'));
|
||||
|
||||
// Add another scale effect and make sure both exist.
|
||||
$this->drupalPostForm(NULL, array('data[width]' => '12', 'data[height]' => '19'), t('Add effect'));
|
||||
$this->assertText(t('Scale 24×19'));
|
||||
$this->assertText(t('Scale 12×19'));
|
||||
|
||||
// Try to edit a nonexistent effect.
|
||||
$uuid = $this->container->get('uuid');
|
||||
$this->drupalGet('admin/config/media/image-styles/manage/' . $style_name . '/effects/' . $uuid->generate());
|
||||
$this->assertResponse(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test flush user interface.
|
||||
*/
|
||||
public function testFlushUserInterface() {
|
||||
$admin_path = 'admin/config/media/image-styles';
|
||||
|
||||
// Create a new style.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style = ImageStyle::create(array('name' => $style_name, 'label' => $this->randomString()));
|
||||
$style->save();
|
||||
|
||||
// Create an image to make sure it gets flushed.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$image_uri = $files[0]->uri;
|
||||
$derivative_uri = $style->buildUri($image_uri);
|
||||
$this->assertTrue($style->createDerivative($image_uri, $derivative_uri));
|
||||
$this->assertEqual($this->getImageCount($style), 1);
|
||||
|
||||
// Go to image styles list page and check if the flush operation link
|
||||
// exists.
|
||||
$this->drupalGet($admin_path);
|
||||
$flush_path = $admin_path . '/manage/' . $style_name . '/flush';
|
||||
$this->assertLinkByHref($flush_path);
|
||||
|
||||
// Flush the image style derivatives using the user interface.
|
||||
$this->drupalPostForm($flush_path, array(), t('Flush'));
|
||||
|
||||
// The derivative image file should have been deleted.
|
||||
$this->assertEqual($this->getImageCount($style), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests image style configuration import that does a delete.
|
||||
*/
|
||||
function testConfigImport() {
|
||||
// Create a new style.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style_label = $this->randomString();
|
||||
$style = ImageStyle::create(array('name' => $style_name, 'label' => $style_label));
|
||||
$style->save();
|
||||
|
||||
// Create an image field that uses the new style.
|
||||
$field_name = strtolower($this->randomMachineName(10));
|
||||
$this->createImageField($field_name, 'article');
|
||||
entity_get_display('node', 'article', 'default')
|
||||
->setComponent($field_name, array(
|
||||
'type' => 'image',
|
||||
'settings' => array('image_style' => $style_name),
|
||||
))
|
||||
->save();
|
||||
|
||||
// Create a new node with an image attached.
|
||||
$test_image = current($this->drupalGetTestFiles('image'));
|
||||
$nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName());
|
||||
$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(file_url_transform_relative($style->buildUrl($original_uri)), format_string('Image displayed using style @style.', array('@style' => $style_name)));
|
||||
|
||||
// Copy config to sync, and delete the image style.
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$active = $this->container->get('config.storage');
|
||||
// Remove the image field from the display, to avoid a dependency error
|
||||
// during import.
|
||||
EntityViewDisplay::load('node.article.default')
|
||||
->removeComponent($field_name)
|
||||
->save();
|
||||
$this->copyConfig($active, $sync);
|
||||
$sync->delete('image.style.' . $style_name);
|
||||
$this->configImporter()->import();
|
||||
|
||||
$this->assertFalse(ImageStyle::load($style_name), 'Style deleted after config import.');
|
||||
$this->assertEqual($this->getImageCount($style), 0, 'Image style was flushed after being deleted by config import.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests access for the image style listing.
|
||||
*/
|
||||
public function testImageStyleAccess() {
|
||||
$style = ImageStyle::create(array('name' => 'style_foo', 'label' => $this->randomString()));
|
||||
$style->save();
|
||||
|
||||
$this->drupalGet('admin/config/media/image-styles');
|
||||
$this->clickLink(t('Edit'));
|
||||
$this->assertRaw(t('Select a new effect'));
|
||||
}
|
||||
|
||||
}
|
290
web/core/modules/image/src/Tests/ImageDimensionsTest.php
Normal file
290
web/core/modules/image/src/Tests/ImageDimensionsTest.php
Normal file
|
@ -0,0 +1,290 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests that images have correct dimensions when styled.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageDimensionsTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('image', 'image_module_test');
|
||||
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* Test styled image dimensions cumulatively.
|
||||
*/
|
||||
function testImageDimensions() {
|
||||
$image_factory = $this->container->get('image.factory');
|
||||
// Create a working copy of the file.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$file = reset($files);
|
||||
$original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
|
||||
|
||||
// Create a style.
|
||||
/** @var $style \Drupal\image\ImageStyleInterface */
|
||||
$style = ImageStyle::create(array('name' => 'test', 'label' => 'Test'));
|
||||
$style->save();
|
||||
$generated_uri = 'public://styles/test/public/' . \Drupal::service('file_system')->basename($original_uri);
|
||||
$url = file_url_transform_relative($style->buildUrl($original_uri));
|
||||
|
||||
$variables = array(
|
||||
'#theme' => 'image_style',
|
||||
'#style_name' => 'test',
|
||||
'#uri' => $original_uri,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
);
|
||||
// Verify that the original image matches the hard-coded values.
|
||||
$image_file = $image_factory->get($original_uri);
|
||||
$this->assertEqual($image_file->getWidth(), $variables['#width']);
|
||||
$this->assertEqual($image_file->getHeight(), $variables['#height']);
|
||||
|
||||
// Scale an image that is wider than it is high.
|
||||
$effect = array(
|
||||
'id' => 'image_scale',
|
||||
'data' => array(
|
||||
'width' => 120,
|
||||
'height' => 90,
|
||||
'upscale' => TRUE,
|
||||
),
|
||||
'weight' => 0,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="120" height="60" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 120);
|
||||
$this->assertEqual($image_file->getHeight(), 60);
|
||||
|
||||
// Rotate 90 degrees anticlockwise.
|
||||
$effect = array(
|
||||
'id' => 'image_rotate',
|
||||
'data' => array(
|
||||
'degrees' => -90,
|
||||
'random' => FALSE,
|
||||
),
|
||||
'weight' => 1,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="60" height="120" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 60);
|
||||
$this->assertEqual($image_file->getHeight(), 120);
|
||||
|
||||
// Scale an image that is higher than it is wide (rotated by previous effect).
|
||||
$effect = array(
|
||||
'id' => 'image_scale',
|
||||
'data' => array(
|
||||
'width' => 120,
|
||||
'height' => 90,
|
||||
'upscale' => TRUE,
|
||||
),
|
||||
'weight' => 2,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 45);
|
||||
$this->assertEqual($image_file->getHeight(), 90);
|
||||
|
||||
// Test upscale disabled.
|
||||
$effect = array(
|
||||
'id' => 'image_scale',
|
||||
'data' => array(
|
||||
'width' => 400,
|
||||
'height' => 200,
|
||||
'upscale' => FALSE,
|
||||
),
|
||||
'weight' => 3,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 45);
|
||||
$this->assertEqual($image_file->getHeight(), 90);
|
||||
|
||||
// Add a desaturate effect.
|
||||
$effect = array(
|
||||
'id' => 'image_desaturate',
|
||||
'data' => array(),
|
||||
'weight' => 4,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 45);
|
||||
$this->assertEqual($image_file->getHeight(), 90);
|
||||
|
||||
// Add a random rotate effect.
|
||||
$effect = array(
|
||||
'id' => 'image_rotate',
|
||||
'data' => array(
|
||||
'degrees' => 180,
|
||||
'random' => TRUE,
|
||||
),
|
||||
'weight' => 5,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
|
||||
|
||||
// Add a crop effect.
|
||||
$effect = array(
|
||||
'id' => 'image_crop',
|
||||
'data' => array(
|
||||
'width' => 30,
|
||||
'height' => 30,
|
||||
'anchor' => 'center-center',
|
||||
),
|
||||
'weight' => 6,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="30" height="30" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 30);
|
||||
$this->assertEqual($image_file->getHeight(), 30);
|
||||
|
||||
// Rotate to a non-multiple of 90 degrees.
|
||||
$effect = array(
|
||||
'id' => 'image_rotate',
|
||||
'data' => array(
|
||||
'degrees' => 57,
|
||||
'random' => FALSE,
|
||||
),
|
||||
'weight' => 7,
|
||||
);
|
||||
|
||||
$effect_id = $style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="41" height="41" alt="" class="image-style-test" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 41);
|
||||
$this->assertEqual($image_file->getHeight(), 41);
|
||||
|
||||
$effect_plugin = $style->getEffect($effect_id);
|
||||
$style->deleteImageEffect($effect_plugin);
|
||||
|
||||
// Ensure that an effect can unset dimensions.
|
||||
$effect = array(
|
||||
'id' => 'image_module_test_null',
|
||||
'data' => array(),
|
||||
'weight' => 8,
|
||||
);
|
||||
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" alt="" class="image-style-test" />');
|
||||
|
||||
// Test URI dependent image effect.
|
||||
$style = ImageStyle::create(['name' => 'test_uri', 'label' => 'Test URI']);
|
||||
$effect = [
|
||||
'id' => 'image_module_test_uri_dependent',
|
||||
'data' => [],
|
||||
'weight' => 0,
|
||||
];
|
||||
$style->addImageEffect($effect);
|
||||
$style->save();
|
||||
$variables = [
|
||||
'#theme' => 'image_style',
|
||||
'#style_name' => 'test_uri',
|
||||
'#uri' => $original_uri,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
];
|
||||
// PNG original image. Should be resized to 100x100.
|
||||
$generated_uri = 'public://styles/test_uri/public/' . \Drupal::service('file_system')->basename($original_uri);
|
||||
$url = file_url_transform_relative($style->buildUrl($original_uri));
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="100" height="100" alt="" class="image-style-test-uri" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 100);
|
||||
$this->assertEqual($image_file->getHeight(), 100);
|
||||
// GIF original image. Should be resized to 50x50.
|
||||
$file = $files[1];
|
||||
$original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
|
||||
$generated_uri = 'public://styles/test_uri/public/' . \Drupal::service('file_system')->basename($original_uri);
|
||||
$url = file_url_transform_relative($style->buildUrl($original_uri));
|
||||
$variables['#uri'] = $original_uri;
|
||||
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="50" height="50" alt="" class="image-style-test-uri" />');
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$this->drupalGet($this->getAbsoluteUrl($url));
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$image_file = $image_factory->get($generated_uri);
|
||||
$this->assertEqual($image_file->getWidth(), 50);
|
||||
$this->assertEqual($image_file->getHeight(), 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an image style element.
|
||||
*
|
||||
* drupal_render() alters the passed $variables array by adding a new key
|
||||
* '#printed' => TRUE. This prevents next call to re-render the element. We
|
||||
* wrap drupal_render() in a helper protected method and pass each time a
|
||||
* fresh array so that $variables won't get altered and the element is
|
||||
* re-rendered each time.
|
||||
*/
|
||||
protected function getImageTag($variables) {
|
||||
return str_replace("\n", NULL, \Drupal::service('renderer')->renderRoot($variables));
|
||||
}
|
||||
|
||||
}
|
202
web/core/modules/image/src/Tests/ImageEffectsTest.php
Normal file
202
web/core/modules/image/src/Tests/ImageEffectsTest.php
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\system\Tests\Image\ToolkitTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the image effects pass parameters to the toolkit correctly.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageEffectsTest extends ToolkitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('image', 'image_test', 'image_module_test');
|
||||
|
||||
/**
|
||||
* The image effect manager.
|
||||
*
|
||||
* @var \Drupal\image\ImageEffectManager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->manager = $this->container->get('plugin.manager.image.effect');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the image_resize_effect() function.
|
||||
*/
|
||||
function testResizeEffect() {
|
||||
$this->assertImageEffect('image_resize', array(
|
||||
'width' => 1,
|
||||
'height' => 2,
|
||||
));
|
||||
$this->assertToolkitOperationsCalled(array('resize'));
|
||||
|
||||
// Check the parameters.
|
||||
$calls = $this->imageTestGetAllCalls();
|
||||
$this->assertEqual($calls['resize'][0][0], 1, 'Width was passed correctly');
|
||||
$this->assertEqual($calls['resize'][0][1], 2, 'Height was passed correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the image_scale_effect() function.
|
||||
*/
|
||||
function testScaleEffect() {
|
||||
// @todo: need to test upscaling.
|
||||
$this->assertImageEffect('image_scale', array(
|
||||
'width' => 10,
|
||||
'height' => 10,
|
||||
));
|
||||
$this->assertToolkitOperationsCalled(array('scale'));
|
||||
|
||||
// Check the parameters.
|
||||
$calls = $this->imageTestGetAllCalls();
|
||||
$this->assertEqual($calls['scale'][0][0], 10, 'Width was passed correctly');
|
||||
$this->assertEqual($calls['scale'][0][1], 10, 'Height was based off aspect ratio and passed correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the image_crop_effect() function.
|
||||
*/
|
||||
function testCropEffect() {
|
||||
// @todo should test the keyword offsets.
|
||||
$this->assertImageEffect('image_crop', array(
|
||||
'anchor' => 'top-1',
|
||||
'width' => 3,
|
||||
'height' => 4,
|
||||
));
|
||||
$this->assertToolkitOperationsCalled(array('crop'));
|
||||
|
||||
// Check the parameters.
|
||||
$calls = $this->imageTestGetAllCalls();
|
||||
$this->assertEqual($calls['crop'][0][0], 0, 'X was passed correctly');
|
||||
$this->assertEqual($calls['crop'][0][1], 1, 'Y was passed correctly');
|
||||
$this->assertEqual($calls['crop'][0][2], 3, 'Width was passed correctly');
|
||||
$this->assertEqual($calls['crop'][0][3], 4, 'Height was passed correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ConvertImageEffect plugin.
|
||||
*/
|
||||
function testConvertEffect() {
|
||||
// Test jpeg.
|
||||
$this->assertImageEffect('image_convert', array(
|
||||
'extension' => 'jpeg',
|
||||
));
|
||||
$this->assertToolkitOperationsCalled(array('convert'));
|
||||
|
||||
// Check the parameters.
|
||||
$calls = $this->imageTestGetAllCalls();
|
||||
$this->assertEqual($calls['convert'][0][0], 'jpeg', 'Extension was passed correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the image_scale_and_crop_effect() function.
|
||||
*/
|
||||
function testScaleAndCropEffect() {
|
||||
$this->assertImageEffect('image_scale_and_crop', array(
|
||||
'width' => 5,
|
||||
'height' => 10,
|
||||
));
|
||||
$this->assertToolkitOperationsCalled(array('scale_and_crop'));
|
||||
|
||||
// Check the parameters.
|
||||
$calls = $this->imageTestGetAllCalls();
|
||||
$this->assertEqual($calls['scale_and_crop'][0][0], 5, 'Width was computed and passed correctly');
|
||||
$this->assertEqual($calls['scale_and_crop'][0][1], 10, 'Height was computed and passed correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the image_desaturate_effect() function.
|
||||
*/
|
||||
function testDesaturateEffect() {
|
||||
$this->assertImageEffect('image_desaturate', array());
|
||||
$this->assertToolkitOperationsCalled(array('desaturate'));
|
||||
|
||||
// Check the parameters.
|
||||
$calls = $this->imageTestGetAllCalls();
|
||||
$this->assertEqual(count($calls['desaturate'][0]), 0, 'No parameters were passed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the image_rotate_effect() function.
|
||||
*/
|
||||
function testRotateEffect() {
|
||||
// @todo: need to test with 'random' => TRUE
|
||||
$this->assertImageEffect('image_rotate', array(
|
||||
'degrees' => 90,
|
||||
'bgcolor' => '#fff',
|
||||
));
|
||||
$this->assertToolkitOperationsCalled(array('rotate'));
|
||||
|
||||
// Check the parameters.
|
||||
$calls = $this->imageTestGetAllCalls();
|
||||
$this->assertEqual($calls['rotate'][0][0], 90, 'Degrees were passed correctly');
|
||||
$this->assertEqual($calls['rotate'][0][1], '#fff', 'Background color was passed correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test image effect caching.
|
||||
*/
|
||||
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.
|
||||
$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_module_test_image_effect_info_alter');
|
||||
$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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if validation errors are passed plugin form to the parent form.
|
||||
*/
|
||||
public function testEffectFormValidationErrors() {
|
||||
$account = $this->drupalCreateUser(['administer image styles']);
|
||||
$this->drupalLogin($account);
|
||||
/** @var \Drupal\image\ImageStyleInterface $style */
|
||||
$style = ImageStyle::load('thumbnail');
|
||||
// Image Scale is the only effect shipped with 'thumbnail', by default.
|
||||
$uuids = $style->getEffects()->getInstanceIds();
|
||||
$uuid = key($uuids);
|
||||
|
||||
// We are posting the form with both, width and height, empty.
|
||||
$edit = ['data[width]' => '', 'data[height]' => ''];
|
||||
$path = 'admin/config/media/image-styles/manage/thumbnail/effects/' . $uuid;
|
||||
$this->drupalPostForm($path, $edit, t('Update effect'));
|
||||
// Check that the error message has been displayed.
|
||||
$this->assertText(t('Width and height can not both be blank.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the effect processing of an image effect plugin.
|
||||
*
|
||||
* @param string $effect_name
|
||||
* The name of the image effect to test.
|
||||
* @param array $data
|
||||
* The data to pass to the image effect.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertImageEffect($effect_name, array $data) {
|
||||
$effect = $this->manager->createInstance($effect_name, array('data' => $data));
|
||||
return $this->assertTrue($effect->applyEffect($this->image), 'Function returned the expected value.');
|
||||
}
|
||||
|
||||
}
|
350
web/core/modules/image/src/Tests/ImageFieldDefaultImagesTest.php
Normal file
350
web/core/modules/image/src/Tests/ImageFieldDefaultImagesTest.php
Normal file
|
@ -0,0 +1,350 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests setting up default images both to the field and field field.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('field_ui');
|
||||
|
||||
/**
|
||||
* Tests CRUD for fields and fields fields with default images.
|
||||
*/
|
||||
public function testDefaultImages() {
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
// Create files to use as the default images.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
// Create 10 files so the default image fids are not a single value.
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$filename = $this->randomMachineName() . "$i";
|
||||
$desired_filepath = 'public://' . $filename;
|
||||
file_unmanaged_copy($files[0]->uri, $desired_filepath, FILE_EXISTS_ERROR);
|
||||
$file = File::create(['uri' => $desired_filepath, 'filename' => $filename, 'name' => $filename]);
|
||||
$file->save();
|
||||
}
|
||||
$default_images = array();
|
||||
foreach (array('field', 'field', 'field2', 'field_new', 'field_new') as $image_target) {
|
||||
$file = File::create((array) array_pop($files));
|
||||
$file->save();
|
||||
$default_images[$image_target] = $file;
|
||||
}
|
||||
|
||||
// Create an image field and add an field to the article content type.
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$storage_settings['default_image'] = array(
|
||||
'uuid' => $default_images['field']->uuid(),
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
);
|
||||
$field_settings['default_image'] = array(
|
||||
'uuid' => $default_images['field']->uuid(),
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
);
|
||||
$widget_settings = array(
|
||||
'preview_image_style' => 'medium',
|
||||
);
|
||||
$field = $this->createImageField($field_name, 'article', $storage_settings, $field_settings, $widget_settings);
|
||||
|
||||
// The field default image id should be 2.
|
||||
$this->assertEqual($field->getSetting('default_image')['uuid'], $default_images['field']->uuid());
|
||||
|
||||
// Also test \Drupal\field\Entity\FieldConfig::getSettings().
|
||||
$this->assertEqual($field->getSettings()['default_image']['uuid'], $default_images['field']->uuid());
|
||||
|
||||
$field_storage = $field->getFieldStorageDefinition();
|
||||
|
||||
// The field default image id should be 1.
|
||||
$this->assertEqual($field_storage->getSetting('default_image')['uuid'], $default_images['field']->uuid());
|
||||
|
||||
// Also test \Drupal\field\Entity\FieldStorageConfig::getSettings().
|
||||
$this->assertEqual($field_storage->getSettings()['default_image']['uuid'], $default_images['field']->uuid());
|
||||
|
||||
// Add another field with another default image to the page content type.
|
||||
$field2 = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'page',
|
||||
'label' => $field->label(),
|
||||
'required' => $field->isRequired(),
|
||||
'settings' => array(
|
||||
'default_image' => array(
|
||||
'uuid' => $default_images['field2']->uuid(),
|
||||
'alt' => '',
|
||||
'title' => '',
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
),
|
||||
),
|
||||
]);
|
||||
$field2->save();
|
||||
|
||||
$widget_settings = entity_get_form_display('node', $field->getTargetBundle(), 'default')->getComponent($field_name);
|
||||
entity_get_form_display('node', 'page', 'default')
|
||||
->setComponent($field_name, $widget_settings)
|
||||
->save();
|
||||
entity_get_display('node', 'page', 'default')
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Confirm the defaults are present on the article field settings form.
|
||||
$field_id = $field->id();
|
||||
$this->drupalGet("admin/structure/types/manage/article/fields/$field_id/storage");
|
||||
$this->assertFieldByXpath(
|
||||
'//input[@name="settings[default_image][uuid][fids]"]',
|
||||
$default_images['field']->id(),
|
||||
format_string(
|
||||
'Article image field default equals expected file ID of @fid.',
|
||||
array('@fid' => $default_images['field']->id())
|
||||
)
|
||||
);
|
||||
// Confirm the defaults are present on the article field edit form.
|
||||
$this->drupalGet("admin/structure/types/manage/article/fields/$field_id");
|
||||
$this->assertFieldByXpath(
|
||||
'//input[@name="settings[default_image][uuid][fids]"]',
|
||||
$default_images['field']->id(),
|
||||
format_string(
|
||||
'Article image field field default equals expected file ID of @fid.',
|
||||
array('@fid' => $default_images['field']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Confirm the defaults are present on the page field settings form.
|
||||
$this->drupalGet("admin/structure/types/manage/page/fields/$field_id/storage");
|
||||
$this->assertFieldByXpath(
|
||||
'//input[@name="settings[default_image][uuid][fids]"]',
|
||||
$default_images['field']->id(),
|
||||
format_string(
|
||||
'Page image field default equals expected file ID of @fid.',
|
||||
array('@fid' => $default_images['field']->id())
|
||||
)
|
||||
);
|
||||
// Confirm the defaults are present on the page field edit form.
|
||||
$field2_id = $field2->id();
|
||||
$this->drupalGet("admin/structure/types/manage/page/fields/$field2_id");
|
||||
$this->assertFieldByXpath(
|
||||
'//input[@name="settings[default_image][uuid][fids]"]',
|
||||
$default_images['field2']->id(),
|
||||
format_string(
|
||||
'Page image field field default equals expected file ID of @fid.',
|
||||
array('@fid' => $default_images['field2']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Confirm that the image default is shown for a new article node.
|
||||
$article = $this->drupalCreateNode(array('type' => 'article'));
|
||||
$article_built = $this->drupalBuildEntityView($article);
|
||||
$this->assertEqual(
|
||||
$article_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field']->id(),
|
||||
format_string(
|
||||
'A new article node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Also check that the field renders without warnings when the label is
|
||||
// hidden.
|
||||
EntityViewDisplay::load('node.article.default')
|
||||
->setComponent($field_name, array('label' => 'hidden', 'type' => 'image'))
|
||||
->save();
|
||||
$this->drupalGet('node/' . $article->id());
|
||||
|
||||
// Confirm that the image default is shown for a new page node.
|
||||
$page = $this->drupalCreateNode(array('type' => 'page'));
|
||||
$page_built = $this->drupalBuildEntityView($page);
|
||||
$this->assertEqual(
|
||||
$page_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field2']->id(),
|
||||
format_string(
|
||||
'A new page node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field2']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Upload a new default for the field storage.
|
||||
$default_image_settings = $field_storage->getSetting('default_image');
|
||||
$default_image_settings['uuid'] = $default_images['field_new']->uuid();
|
||||
$field_storage->setSetting('default_image', $default_image_settings);
|
||||
$field_storage->save();
|
||||
|
||||
// Confirm that the new default is used on the article field settings form.
|
||||
$this->drupalGet("admin/structure/types/manage/article/fields/$field_id/storage");
|
||||
$this->assertFieldByXpath(
|
||||
'//input[@name="settings[default_image][uuid][fids]"]',
|
||||
$default_images['field_new']->id(),
|
||||
format_string(
|
||||
'Updated image field default equals expected file ID of @fid.',
|
||||
array('@fid' => $default_images['field_new']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Reload the nodes and confirm the field field defaults are used.
|
||||
$node_storage->resetCache(array($article->id(), $page->id()));
|
||||
$article_built = $this->drupalBuildEntityView($article = $node_storage->load($article->id()));
|
||||
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
|
||||
$this->assertEqual(
|
||||
$article_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field']->id(),
|
||||
format_string(
|
||||
'An existing article node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field']->id())
|
||||
)
|
||||
);
|
||||
$this->assertEqual(
|
||||
$page_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field2']->id(),
|
||||
format_string(
|
||||
'An existing page node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field2']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Upload a new default for the article's field field.
|
||||
$default_image_settings = $field->getSetting('default_image');
|
||||
$default_image_settings['uuid'] = $default_images['field_new']->uuid();
|
||||
$field->setSetting('default_image', $default_image_settings);
|
||||
$field->save();
|
||||
|
||||
// Confirm the new field field default is used on the article field
|
||||
// admin form.
|
||||
$this->drupalGet("admin/structure/types/manage/article/fields/$field_id");
|
||||
$this->assertFieldByXpath(
|
||||
'//input[@name="settings[default_image][uuid][fids]"]',
|
||||
$default_images['field_new']->id(),
|
||||
format_string(
|
||||
'Updated article image field field default equals expected file ID of @fid.',
|
||||
array('@fid' => $default_images['field_new']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Reload the nodes.
|
||||
$node_storage->resetCache(array($article->id(), $page->id()));
|
||||
$article_built = $this->drupalBuildEntityView($article = $node_storage->load($article->id()));
|
||||
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
|
||||
|
||||
// Confirm the article uses the new default.
|
||||
$this->assertEqual(
|
||||
$article_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field_new']->id(),
|
||||
format_string(
|
||||
'An existing article node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field_new']->id())
|
||||
)
|
||||
);
|
||||
// Confirm the page remains unchanged.
|
||||
$this->assertEqual(
|
||||
$page_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field2']->id(),
|
||||
format_string(
|
||||
'An existing page node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field2']->id())
|
||||
)
|
||||
);
|
||||
|
||||
// Confirm the default image is shown on the node form.
|
||||
$file = File::load($default_images['field_new']->id());
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertRaw($file->getFilename());
|
||||
|
||||
// Remove the instance default from articles.
|
||||
$default_image_settings = $field->getSetting('default_image');
|
||||
$default_image_settings['uuid'] = 0;
|
||||
$field->setSetting('default_image', $default_image_settings);
|
||||
$field->save();
|
||||
|
||||
// Confirm the article field field default has been removed.
|
||||
$this->drupalGet("admin/structure/types/manage/article/fields/$field_id");
|
||||
$this->assertFieldByXpath(
|
||||
'//input[@name="settings[default_image][uuid][fids]"]',
|
||||
'',
|
||||
'Updated article image field field default has been successfully removed.'
|
||||
);
|
||||
|
||||
// Reload the nodes.
|
||||
$node_storage->resetCache(array($article->id(), $page->id()));
|
||||
$article_built = $this->drupalBuildEntityView($article = $node_storage->load($article->id()));
|
||||
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
|
||||
// Confirm the article uses the new field (not field) default.
|
||||
$this->assertEqual(
|
||||
$article_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field_new']->id(),
|
||||
format_string(
|
||||
'An existing article node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field_new']->id())
|
||||
)
|
||||
);
|
||||
// Confirm the page remains unchanged.
|
||||
$this->assertEqual(
|
||||
$page_built[$field_name][0]['#item']->target_id,
|
||||
$default_images['field2']->id(),
|
||||
format_string(
|
||||
'An existing page node without an image has the expected default image file ID of @fid.',
|
||||
array('@fid' => $default_images['field2']->id())
|
||||
)
|
||||
);
|
||||
|
||||
$non_image = $this->drupalGetTestFiles('text');
|
||||
$this->drupalPostForm(NULL, array('files[settings_default_image_uuid]' => drupal_realpath($non_image[0]->uri)), t("Upload"));
|
||||
$this->assertText('The specified file text-0.txt could not be uploaded.');
|
||||
$this->assertText('Only files with the following extensions are allowed: png gif jpg jpeg.');
|
||||
|
||||
// Confirm the default image is shown on the node form.
|
||||
$file = File::load($default_images['field_new']->id());
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertRaw($file->getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests image field and field having an invalid default image.
|
||||
*/
|
||||
public function testInvalidDefaultImage() {
|
||||
$field_storage = FieldStorageConfig::create(array(
|
||||
'field_name' => Unicode::strtolower($this->randomMachineName()),
|
||||
'entity_type' => 'node',
|
||||
'type' => 'image',
|
||||
'settings' => array(
|
||||
'default_image' => array(
|
||||
'uuid' => 100000,
|
||||
)
|
||||
),
|
||||
));
|
||||
$field_storage->save();
|
||||
$settings = $field_storage->getSettings();
|
||||
// The non-existent default image should not be saved.
|
||||
$this->assertNull($settings['default_image']['uuid']);
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'page',
|
||||
'label' => $this->randomMachineName(),
|
||||
'settings' => array(
|
||||
'default_image' => array(
|
||||
'uuid' => 100000,
|
||||
)
|
||||
),
|
||||
]);
|
||||
$field->save();
|
||||
$settings = $field->getSettings();
|
||||
// The non-existent default image should not be saved.
|
||||
$this->assertNull($settings['default_image']['uuid']);
|
||||
}
|
||||
|
||||
}
|
448
web/core/modules/image/src/Tests/ImageFieldDisplayTest.php
Normal file
448
web/core/modules/image/src/Tests/ImageFieldDisplayTest.php
Normal file
|
@ -0,0 +1,448 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
|
||||
/**
|
||||
* Tests the display of image fields.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageFieldDisplayTest extends ImageFieldTestBase {
|
||||
|
||||
protected $dumpHeaders = TRUE;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('field_ui');
|
||||
|
||||
/**
|
||||
* Test image formatters on node display for public files.
|
||||
*/
|
||||
function testImageFieldFormattersPublic() {
|
||||
$this->_testImageFieldFormatters('public');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test image formatters on node display for private files.
|
||||
*/
|
||||
function testImageFieldFormattersPrivate() {
|
||||
// Remove access content permission from anonymous users.
|
||||
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, array('access content' => FALSE));
|
||||
$this->_testImageFieldFormatters('private');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test image formatters on node display.
|
||||
*/
|
||||
function _testImageFieldFormatters($scheme) {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$field_settings = array('alt_field_required' => 0);
|
||||
$instance = $this->createImageField($field_name, 'article', array('uri_scheme' => $scheme), $field_settings);
|
||||
|
||||
// Go to manage display page.
|
||||
$this->drupalGet("admin/structure/types/manage/article/display");
|
||||
|
||||
// Test for existence of link to image styles configuration.
|
||||
$this->drupalPostAjaxForm(NULL, array(), "{$field_name}_settings_edit");
|
||||
$this->assertLinkByHref(\Drupal::url('entity.image_style.collection'), 0, 'Link to image styles configuration is found');
|
||||
|
||||
// Remove 'administer image styles' permission from testing admin user.
|
||||
$admin_user_roles = $this->adminUser->getRoles(TRUE);
|
||||
user_role_change_permissions(reset($admin_user_roles), array('administer image styles' => FALSE));
|
||||
|
||||
// Go to manage display page again.
|
||||
$this->drupalGet("admin/structure/types/manage/article/display");
|
||||
|
||||
// Test for absence of link to image styles configuration.
|
||||
$this->drupalPostAjaxForm(NULL, array(), "{$field_name}_settings_edit");
|
||||
$this->assertNoLinkByHref(\Drupal::url('entity.image_style.collection'), 'Link to image styles configuration is absent when permissions are insufficient');
|
||||
|
||||
// Restore 'administer image styles' permission to testing admin user
|
||||
user_role_change_permissions(reset($admin_user_roles), array('administer image styles' => TRUE));
|
||||
|
||||
// Create a new node with an image attached.
|
||||
$test_image = current($this->drupalGetTestFiles('image'));
|
||||
|
||||
// Ensure that preview works.
|
||||
$this->previewNodeImage($test_image, $field_name, 'article');
|
||||
|
||||
// After previewing, make the alt field required. It cannot be required
|
||||
// during preview because the form validation will fail.
|
||||
$instance->setSetting('alt_field_required', 1);
|
||||
$instance->save();
|
||||
|
||||
// Create alt text for the image.
|
||||
$alt = $this->randomMachineName();
|
||||
|
||||
// Save node.
|
||||
$nid = $this->uploadNodeImage($test_image, $field_name, 'article', $alt);
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
|
||||
// Test that the default formatter is being used.
|
||||
$file = $node->{$field_name}->entity;
|
||||
$image_uri = $file->getFileUri();
|
||||
$image = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $image_uri,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
'#alt' => $alt,
|
||||
);
|
||||
$default_output = str_replace("\n", NULL, $renderer->renderRoot($image));
|
||||
$this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.');
|
||||
|
||||
// Test the image linked to file formatter.
|
||||
$display_options = array(
|
||||
'type' => 'image',
|
||||
'settings' => array('image_link' => 'file'),
|
||||
);
|
||||
$display = entity_get_display('node', $node->getType(), 'default');
|
||||
$display->setComponent($field_name, $display_options)
|
||||
->save();
|
||||
|
||||
$image = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $image_uri,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
'#alt' => $alt,
|
||||
);
|
||||
$default_output = '<a href="' . file_create_url($image_uri) . '">' . $renderer->renderRoot($image) . '</a>';
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertCacheTag($file->getCacheTags()[0]);
|
||||
// @todo Remove in https://www.drupal.org/node/2646744.
|
||||
$this->assertCacheContext('url.site');
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
|
||||
$this->assertRaw($default_output, 'Image linked to file formatter displaying correctly on full node view.');
|
||||
// Verify that the image can be downloaded.
|
||||
$this->assertEqual(file_get_contents($test_image->uri), $this->drupalGet(file_create_url($image_uri)), 'File was downloaded successfully.');
|
||||
if ($scheme == 'private') {
|
||||
// Only verify HTTP headers when using private scheme and the headers are
|
||||
// sent by Drupal.
|
||||
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png', 'Content-Type header was sent.');
|
||||
$this->assertTrue(strstr($this->drupalGetHeader('Cache-Control'), 'private') !== FALSE, 'Cache-Control header was sent.');
|
||||
|
||||
// Log out and try to access the file.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet(file_create_url($image_uri));
|
||||
$this->assertResponse('403', 'Access denied to original image as anonymous user.');
|
||||
|
||||
// Log in again.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
// Test the image linked to content formatter.
|
||||
$display_options['settings']['image_link'] = 'content';
|
||||
$display->setComponent($field_name, $display_options)
|
||||
->save();
|
||||
$image = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $image_uri,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
);
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertCacheTag($file->getCacheTags()[0]);
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
|
||||
$elements = $this->xpath(
|
||||
'//a[@href=:path]/img[@src=:url and @alt=:alt and @width=:width and @height=:height]',
|
||||
array(
|
||||
':path' => $node->url(),
|
||||
':url' => file_url_transform_relative(file_create_url($image['#uri'])),
|
||||
':width' => $image['#width'],
|
||||
':height' => $image['#height'],
|
||||
':alt' => $alt,
|
||||
)
|
||||
);
|
||||
$this->assertEqual(count($elements), 1, 'Image linked to content formatter displaying correctly on full node view.');
|
||||
|
||||
// Test the image style 'thumbnail' formatter.
|
||||
$display_options['settings']['image_link'] = '';
|
||||
$display_options['settings']['image_style'] = 'thumbnail';
|
||||
$display->setComponent($field_name, $display_options)
|
||||
->save();
|
||||
|
||||
// Ensure the derivative image is generated so we do not have to deal with
|
||||
// image style callback paths.
|
||||
$this->drupalGet(ImageStyle::load('thumbnail')->buildUrl($image_uri));
|
||||
$image_style = array(
|
||||
'#theme' => 'image_style',
|
||||
'#uri' => $image_uri,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
'#style_name' => 'thumbnail',
|
||||
'#alt' => $alt,
|
||||
);
|
||||
$default_output = $renderer->renderRoot($image_style);
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$image_style = ImageStyle::load('thumbnail');
|
||||
$this->assertCacheTag($image_style->getCacheTags()[0]);
|
||||
$this->assertRaw($default_output, 'Image style thumbnail formatter displaying correctly on full node view.');
|
||||
|
||||
if ($scheme == 'private') {
|
||||
// Log out and try to access the file.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet(ImageStyle::load('thumbnail')->buildUrl($image_uri));
|
||||
$this->assertResponse('403', 'Access denied to image style thumbnail as anonymous user.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for image field settings.
|
||||
*/
|
||||
function testImageFieldSettings() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
$test_image = current($this->drupalGetTestFiles('image'));
|
||||
list(, $test_image_extension) = explode('.', $test_image->filename);
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$field_settings = array(
|
||||
'alt_field' => 1,
|
||||
'file_extensions' => $test_image_extension,
|
||||
'max_filesize' => '50 KB',
|
||||
'max_resolution' => '100x100',
|
||||
'min_resolution' => '10x10',
|
||||
'title_field' => 1,
|
||||
);
|
||||
$widget_settings = array(
|
||||
'preview_image_style' => 'medium',
|
||||
);
|
||||
$field = $this->createImageField($field_name, 'article', array(), $field_settings, $widget_settings);
|
||||
|
||||
// Verify that the min/max resolution set on the field are properly
|
||||
// extracted, and displayed, on the image field's configuration form.
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/' . $field->id());
|
||||
$this->assertFieldByName('settings[max_resolution][x]', '100', 'Expected max resolution X value of 100.');
|
||||
$this->assertFieldByName('settings[max_resolution][y]', '100', 'Expected max resolution Y value of 100.');
|
||||
$this->assertFieldByName('settings[min_resolution][x]', '10', 'Expected min resolution X value of 10.');
|
||||
$this->assertFieldByName('settings[min_resolution][y]', '10', 'Expected min resolution Y value of 10.');
|
||||
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertText(t('50 KB limit.'), 'Image widget max file size is displayed on article form.');
|
||||
$this->assertText(t('Allowed types: @extensions.', array('@extensions' => $test_image_extension)), 'Image widget allowed file types displayed on article form.');
|
||||
$this->assertText(t('Images must be larger than 10x10 pixels. Images larger than 100x100 pixels will be resized.'), 'Image widget allowed resolution displayed on article form.');
|
||||
|
||||
// We have to create the article first and then edit it because the alt
|
||||
// and title fields do not display until the image has been attached.
|
||||
|
||||
// Create alt text for the image.
|
||||
$alt = $this->randomMachineName();
|
||||
|
||||
$nid = $this->uploadNodeImage($test_image, $field_name, 'article', $alt);
|
||||
$this->drupalGet('node/' . $nid . '/edit');
|
||||
$this->assertFieldByName($field_name . '[0][alt]', '', 'Alt field displayed on article form.');
|
||||
$this->assertFieldByName($field_name . '[0][title]', '', 'Title field displayed on article form.');
|
||||
// Verify that the attached image is being previewed using the 'medium'
|
||||
// style.
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
$file = $node->{$field_name}->entity;
|
||||
|
||||
$url = file_url_transform_relative(file_create_url(ImageStyle::load('medium')->buildUrl($file->getFileUri())));
|
||||
$this->assertTrue($this->cssSelect('img[width=40][height=20][class=image-style-medium][src="' . $url . '"]'));
|
||||
|
||||
// Add alt/title fields to the image and verify that they are displayed.
|
||||
$image = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $file->getFileUri(),
|
||||
'#alt' => $alt,
|
||||
'#title' => $this->randomMachineName(),
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
);
|
||||
$edit = array(
|
||||
$field_name . '[0][alt]' => $image['#alt'],
|
||||
$field_name . '[0][title]' => $image['#title'],
|
||||
);
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
|
||||
$default_output = str_replace("\n", NULL, $renderer->renderRoot($image));
|
||||
$this->assertRaw($default_output, 'Image displayed using user supplied alt and title attributes.');
|
||||
|
||||
// Verify that alt/title longer than allowed results in a validation error.
|
||||
$test_size = 2000;
|
||||
$edit = array(
|
||||
$field_name . '[0][alt]' => $this->randomMachineName($test_size),
|
||||
$field_name . '[0][title]' => $this->randomMachineName($test_size),
|
||||
);
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
|
||||
$schema = $field->getFieldStorageDefinition()->getSchema();
|
||||
$this->assertRaw(t('Alternative text cannot be longer than %max characters but is currently %length characters long.', array(
|
||||
'%max' => $schema['columns']['alt']['length'],
|
||||
'%length' => $test_size,
|
||||
)));
|
||||
$this->assertRaw(t('Title cannot be longer than %max characters but is currently %length characters long.', array(
|
||||
'%max' => $schema['columns']['title']['length'],
|
||||
'%length' => $test_size,
|
||||
)));
|
||||
|
||||
// Set cardinality to unlimited and add upload a second image.
|
||||
// The image widget is extending on the file widget, but the image field
|
||||
// type does not have the 'display_field' setting which is expected by
|
||||
// the file widget. This resulted in notices before when cardinality is not
|
||||
// 1, so we need to make sure the file widget prevents these notices by
|
||||
// providing all settings, even if they are not used.
|
||||
// @see FileWidget::formMultipleElements().
|
||||
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.' . $field_name . '/storage', array('cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED), t('Save field settings'));
|
||||
$edit = array(
|
||||
'files[' . $field_name . '_1][]' => drupal_realpath($test_image->uri),
|
||||
);
|
||||
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
|
||||
// Add the required alt text.
|
||||
$this->drupalPostForm(NULL, [$field_name . '[1][alt]' => $alt], t('Save and keep published'));
|
||||
$this->assertText(format_string('Article @title has been updated.', array('@title' => $node->getTitle())));
|
||||
|
||||
// Assert ImageWidget::process() calls FieldWidget::process().
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$edit = array(
|
||||
'files[' . $field_name . '_2][]' => drupal_realpath($test_image->uri),
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, $field_name . '_2_upload_button');
|
||||
$this->assertNoRaw('<input multiple type="file" id="edit-' . strtr($field_name, '_', '-') . '-2-upload" name="files[' . $field_name . '_2][]" size="22" class="js-form-file form-file">');
|
||||
$this->assertRaw('<input multiple type="file" id="edit-' . strtr($field_name, '_', '-') . '-3-upload" name="files[' . $field_name . '_3][]" size="22" class="js-form-file form-file">');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test use of a default image with an image field.
|
||||
*/
|
||||
function testImageFieldDefaultImage() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
// Create a new image field.
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$this->createImageField($field_name, 'article');
|
||||
|
||||
// Create a new node, with no images and verify that no images are
|
||||
// displayed.
|
||||
$node = $this->drupalCreateNode(array('type' => 'article'));
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
// Verify that no image is displayed on the page by checking for the class
|
||||
// that would be used on the image field.
|
||||
$this->assertNoPattern('<div class="(.*?)field--name-' . strtr($field_name, '_', '-') . '(.*?)">', 'No image displayed when no image is attached and no default image specified.');
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
|
||||
|
||||
// Add a default image to the public image field.
|
||||
$images = $this->drupalGetTestFiles('image');
|
||||
$alt = $this->randomString(512);
|
||||
$title = $this->randomString(1024);
|
||||
$edit = array(
|
||||
// Get the path of the 'image-test.png' file.
|
||||
'files[settings_default_image_uuid]' => drupal_realpath($images[0]->uri),
|
||||
'settings[default_image][alt]' => $alt,
|
||||
'settings[default_image][title]' => $title,
|
||||
);
|
||||
$this->drupalPostForm("admin/structure/types/manage/article/fields/node.article.$field_name/storage", $edit, t('Save field settings'));
|
||||
// Clear field definition cache so the new default image is detected.
|
||||
\Drupal::entityManager()->clearCachedFieldDefinitions();
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
|
||||
$default_image = $field_storage->getSetting('default_image');
|
||||
$file = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid']);
|
||||
$this->assertTrue($file->isPermanent(), 'The default image status is permanent.');
|
||||
$image = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $file->getFileUri(),
|
||||
'#alt' => $alt,
|
||||
'#title' => $title,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
);
|
||||
$default_output = str_replace("\n", NULL, $renderer->renderRoot($image));
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->assertCacheTag($file->getCacheTags()[0]);
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
|
||||
$this->assertRaw($default_output, 'Default image displayed when no user supplied image is present.');
|
||||
|
||||
// Create a node with an image attached and ensure that the default image
|
||||
// is not displayed.
|
||||
|
||||
// Create alt text for the image.
|
||||
$alt = $this->randomMachineName();
|
||||
|
||||
// Upload the 'image-test.gif' file.
|
||||
$nid = $this->uploadNodeImage($images[2], $field_name, 'article', $alt);
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
$file = $node->{$field_name}->entity;
|
||||
$image = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $file->getFileUri(),
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
'#alt' => $alt,
|
||||
);
|
||||
$image_output = str_replace("\n", NULL, $renderer->renderRoot($image));
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertCacheTag($file->getCacheTags()[0]);
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
|
||||
$this->assertNoRaw($default_output, 'Default image is not displayed when user supplied image is present.');
|
||||
$this->assertRaw($image_output, 'User supplied image is displayed.');
|
||||
|
||||
// Remove default image from the field and make sure it is no longer used.
|
||||
$edit = array(
|
||||
'settings[default_image][uuid][fids]' => 0,
|
||||
);
|
||||
$this->drupalPostForm("admin/structure/types/manage/article/fields/node.article.$field_name/storage", $edit, t('Save field settings'));
|
||||
// Clear field definition cache so the new default image is detected.
|
||||
\Drupal::entityManager()->clearCachedFieldDefinitions();
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
|
||||
$default_image = $field_storage->getSetting('default_image');
|
||||
$this->assertFalse($default_image['uuid'], 'Default image removed from field.');
|
||||
// Create an image field that uses the private:// scheme and test that the
|
||||
// default image works as expected.
|
||||
$private_field_name = strtolower($this->randomMachineName());
|
||||
$this->createImageField($private_field_name, 'article', array('uri_scheme' => 'private'));
|
||||
// Add a default image to the new field.
|
||||
$edit = array(
|
||||
// Get the path of the 'image-test.gif' file.
|
||||
'files[settings_default_image_uuid]' => drupal_realpath($images[2]->uri),
|
||||
'settings[default_image][alt]' => $alt,
|
||||
'settings[default_image][title]' => $title,
|
||||
);
|
||||
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.' . $private_field_name . '/storage', $edit, t('Save field settings'));
|
||||
// Clear field definition cache so the new default image is detected.
|
||||
\Drupal::entityManager()->clearCachedFieldDefinitions();
|
||||
|
||||
$private_field_storage = FieldStorageConfig::loadByName('node', $private_field_name);
|
||||
$default_image = $private_field_storage->getSetting('default_image');
|
||||
$file = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid']);
|
||||
$this->assertEqual('private', file_uri_scheme($file->getFileUri()), 'Default image uses private:// scheme.');
|
||||
$this->assertTrue($file->isPermanent(), 'The default image status is permanent.');
|
||||
// Create a new node with no image attached and ensure that default private
|
||||
// image is displayed.
|
||||
$node = $this->drupalCreateNode(array('type' => 'article'));
|
||||
$image = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $file->getFileUri(),
|
||||
'#alt' => $alt,
|
||||
'#title' => $title,
|
||||
'#width' => 40,
|
||||
'#height' => 20,
|
||||
);
|
||||
$default_output = str_replace("\n", NULL, $renderer->renderRoot($image));
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$this->assertCacheTag($file->getCacheTags()[0]);
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
|
||||
$this->assertRaw($default_output, 'Default private image displayed when no user supplied image is present.');
|
||||
}
|
||||
|
||||
}
|
109
web/core/modules/image/src/Tests/ImageFieldTestBase.php
Normal file
109
web/core/modules/image/src/Tests/ImageFieldTestBase.php
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* TODO: Test the following functions.
|
||||
*
|
||||
* image.effects.inc:
|
||||
* image_style_generate()
|
||||
* \Drupal\image\ImageStyleInterface::createDerivative()
|
||||
*
|
||||
* image.module:
|
||||
* image_style_options()
|
||||
* \Drupal\image\ImageStyleInterface::flush()
|
||||
* image_filter_keyword()
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class provides methods specifically for testing Image's field handling.
|
||||
*/
|
||||
abstract class ImageFieldTestBase extends WebTestBase {
|
||||
|
||||
use ImageFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'image', 'field_ui', 'image_module_test');
|
||||
|
||||
/**
|
||||
* An user with permissions to administer content types and image styles.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create Basic page and Article node types.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
|
||||
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
|
||||
}
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer node fields', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer image styles', 'administer node display'));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview an image in a node.
|
||||
*
|
||||
* @param \Drupal\Core\Image\ImageInterface $image
|
||||
* A file object representing the image to upload.
|
||||
* @param string $field_name
|
||||
* Name of the image field the image should be attached to.
|
||||
* @param string $type
|
||||
* The type of node to create.
|
||||
*/
|
||||
function previewNodeImage($image, $field_name, $type) {
|
||||
$edit = array(
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
);
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
|
||||
$this->drupalPostForm('node/add/' . $type, $edit, t('Preview'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload an image to a node.
|
||||
*
|
||||
* @param $image
|
||||
* A file object representing the image to upload.
|
||||
* @param $field_name
|
||||
* Name of the image field the image should be attached to.
|
||||
* @param $type
|
||||
* The type of node to create.
|
||||
* @param $alt
|
||||
* The alt text for the image. Use if the field settings require alt text.
|
||||
*/
|
||||
function uploadNodeImage($image, $field_name, $type, $alt = '') {
|
||||
$edit = array(
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
);
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
|
||||
$this->drupalPostForm('node/add/' . $type, $edit, t('Save and publish'));
|
||||
if ($alt) {
|
||||
// Add alt text.
|
||||
$this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save and publish'));
|
||||
}
|
||||
|
||||
// Retrieve ID of the newly created node from the current URL.
|
||||
$matches = array();
|
||||
preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
|
||||
return isset($matches[1]) ? $matches[1] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fid of the last inserted file.
|
||||
*/
|
||||
protected function getLastFileId() {
|
||||
return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
|
||||
}
|
||||
|
||||
}
|
159
web/core/modules/image/src/Tests/ImageFieldValidateTest.php
Normal file
159
web/core/modules/image/src/Tests/ImageFieldValidateTest.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
/**
|
||||
* Tests validation functions such as min/max resolution.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageFieldValidateTest extends ImageFieldTestBase {
|
||||
/**
|
||||
* Test min/max resolution settings.
|
||||
*/
|
||||
function testResolution() {
|
||||
$field_names = [
|
||||
0 => strtolower($this->randomMachineName()),
|
||||
1 => strtolower($this->randomMachineName()),
|
||||
2 => strtolower($this->randomMachineName()),
|
||||
];
|
||||
$min_resolution = [
|
||||
'width' => 50,
|
||||
'height' => 50
|
||||
];
|
||||
$max_resolution = [
|
||||
'width' => 100,
|
||||
'height' => 100
|
||||
];
|
||||
$no_height_min_resolution = [
|
||||
'width' => 50,
|
||||
'height' => NULL
|
||||
];
|
||||
$no_height_max_resolution = [
|
||||
'width' => 100,
|
||||
'height' => NULL
|
||||
];
|
||||
$no_width_min_resolution = [
|
||||
'width' => NULL,
|
||||
'height' => 50
|
||||
];
|
||||
$no_width_max_resolution = [
|
||||
'width' => NULL,
|
||||
'height' => 100
|
||||
];
|
||||
$field_settings = [
|
||||
0 => $this->getFieldSettings($min_resolution, $max_resolution),
|
||||
1 => $this->getFieldSettings($no_height_min_resolution, $no_height_max_resolution),
|
||||
2 => $this->getFieldSettings($no_width_min_resolution, $no_width_max_resolution),
|
||||
];
|
||||
$this->createImageField($field_names[0], 'article', [], $field_settings[0]);
|
||||
$this->createImageField($field_names[1], 'article', [], $field_settings[1]);
|
||||
$this->createImageField($field_names[2], 'article', [], $field_settings[2]);
|
||||
|
||||
// We want a test image that is too small, and a test image that is too
|
||||
// big, so cycle through test image files until we have what we need.
|
||||
$image_that_is_too_big = FALSE;
|
||||
$image_that_is_too_small = FALSE;
|
||||
$image_factory = $this->container->get('image.factory');
|
||||
foreach ($this->drupalGetTestFiles('image') as $image) {
|
||||
$image_file = $image_factory->get($image->uri);
|
||||
if ($image_file->getWidth() > $max_resolution['width']) {
|
||||
$image_that_is_too_big = $image;
|
||||
}
|
||||
if ($image_file->getWidth() < $min_resolution['width']) {
|
||||
$image_that_is_too_small = $image;
|
||||
}
|
||||
if ($image_that_is_too_small && $image_that_is_too_big) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->uploadNodeImage($image_that_is_too_small, $field_names[0], 'article');
|
||||
$this->assertRaw(t('The specified file %name could not be uploaded.', ['%name' => $image_that_is_too_small->filename]));
|
||||
$this->assertRaw(t('The image is too small; the minimum dimensions are %dimensions pixels.', ['%dimensions' => '50x50']));
|
||||
$this->uploadNodeImage($image_that_is_too_big, $field_names[0], 'article');
|
||||
$this->assertText(t('The image was resized to fit within the maximum allowed dimensions of 100x100 pixels.'));
|
||||
$this->uploadNodeImage($image_that_is_too_small, $field_names[1], 'article');
|
||||
$this->assertRaw(t('The specified file %name could not be uploaded.', ['%name' => $image_that_is_too_small->filename]));
|
||||
$this->uploadNodeImage($image_that_is_too_big, $field_names[1], 'article');
|
||||
$this->assertText(t('The image was resized to fit within the maximum allowed width of 100 pixels.'));
|
||||
$this->uploadNodeImage($image_that_is_too_small, $field_names[2], 'article');
|
||||
$this->assertRaw(t('The specified file %name could not be uploaded.', ['%name' => $image_that_is_too_small->filename]));
|
||||
$this->uploadNodeImage($image_that_is_too_big, $field_names[2], 'article');
|
||||
$this->assertText(t('The image was resized to fit within the maximum allowed height of 100 pixels.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that required alt/title fields gets validated right.
|
||||
*/
|
||||
function testRequiredAttributes() {
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$field_settings = array(
|
||||
'alt_field' => 1,
|
||||
'alt_field_required' => 1,
|
||||
'title_field' => 1,
|
||||
'title_field_required' => 1,
|
||||
'required' => 1,
|
||||
);
|
||||
$instance = $this->createImageField($field_name, 'article', array(), $field_settings);
|
||||
$images = $this->drupalGetTestFiles('image');
|
||||
// Let's just use the first image.
|
||||
$image = $images[0];
|
||||
$this->uploadNodeImage($image, $field_name, 'article');
|
||||
|
||||
// Look for form-required for the alt text.
|
||||
$elements = $this->xpath('//label[@for="edit-' . $field_name . '-0-alt" and @class="js-form-required form-required"]/following-sibling::input[@id="edit-' . $field_name . '-0-alt"]');
|
||||
|
||||
$this->assertTrue(isset($elements[0]), 'Required marker is shown for the required alt text.');
|
||||
|
||||
$elements = $this->xpath('//label[@for="edit-' . $field_name . '-0-title" and @class="js-form-required form-required"]/following-sibling::input[@id="edit-' . $field_name . '-0-title"]');
|
||||
|
||||
$this->assertTrue(isset($elements[0]), 'Required marker is shown for the required title text.');
|
||||
|
||||
$this->assertText(t('Alternative text field is required.'));
|
||||
$this->assertText(t('Title field is required.'));
|
||||
|
||||
$instance->setSetting('alt_field_required', 0);
|
||||
$instance->setSetting('title_field_required', 0);
|
||||
$instance->save();
|
||||
|
||||
$edit = array(
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
);
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
|
||||
|
||||
$this->assertNoText(t('Alternative text field is required.'));
|
||||
$this->assertNoText(t('Title field is required.'));
|
||||
|
||||
$instance->setSetting('required', 0);
|
||||
$instance->setSetting('alt_field_required', 1);
|
||||
$instance->setSetting('title_field_required', 1);
|
||||
$instance->save();
|
||||
|
||||
$edit = array(
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
);
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
|
||||
|
||||
$this->assertNoText(t('Alternative text field is required.'));
|
||||
$this->assertNoText(t('Title field is required.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field settings.
|
||||
*
|
||||
* @param int[] $min_resolution
|
||||
* The minimum width and height resolution setting.
|
||||
* @param int[] $max_resolution
|
||||
* The maximum width and height resolution setting.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFieldSettings($min_resolution, $max_resolution) {
|
||||
return [
|
||||
'max_resolution' => $max_resolution['width'] . 'x' . $max_resolution['height'],
|
||||
'min_resolution' => $min_resolution['width'] . 'x' . $min_resolution['height'],
|
||||
'alt_field' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
31
web/core/modules/image/src/Tests/ImageFieldWidgetTest.php
Normal file
31
web/core/modules/image/src/Tests/ImageFieldWidgetTest.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
/**
|
||||
* Tests the image field widget.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageFieldWidgetTest extends ImageFieldTestBase {
|
||||
|
||||
/**
|
||||
* Tests file widget element.
|
||||
*/
|
||||
public function testWidgetElement() {
|
||||
// Check for image widget in add/node/article page
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$min_resolution = 50;
|
||||
$max_resolution = 100;
|
||||
$field_settings = array(
|
||||
'max_resolution' => $max_resolution . 'x' . $max_resolution,
|
||||
'min_resolution' => $min_resolution . 'x' . $min_resolution,
|
||||
'alt_field' => 0,
|
||||
);
|
||||
$this->createImageField($field_name, 'article', array(), $field_settings, array(), array(), 'Image test on [site:name]');
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertNotEqual(0, count($this->xpath('//div[contains(@class, "field--widget-image-image")]')), 'Image field widget found on add/node page', 'Browser');
|
||||
$this->assertNoText('Image test on [site:name]');
|
||||
}
|
||||
|
||||
}
|
225
web/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
Normal file
225
web/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
Normal file
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* Uploads images to translated nodes.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('language', 'content_translation', 'field_ui');
|
||||
|
||||
/**
|
||||
* The name of the image field used in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create the "Basic page" node type.
|
||||
// @todo Remove the disabling of new revision creation in
|
||||
// https://www.drupal.org/node/1239558.
|
||||
$this->drupalCreateContentType(['type' => 'basicpage', 'name' => 'Basic page', 'new_revision' => FALSE]);
|
||||
|
||||
// Create a image field on the "Basic page" node type.
|
||||
$this->fieldName = strtolower($this->randomMachineName());
|
||||
$this->createImageField($this->fieldName, 'basicpage', [], ['title_field' => 1]);
|
||||
|
||||
// Create and log in user.
|
||||
$permissions = array(
|
||||
'access administration pages',
|
||||
'administer content translation',
|
||||
'administer content types',
|
||||
'administer languages',
|
||||
'administer node fields',
|
||||
'create content translations',
|
||||
'create basicpage content',
|
||||
'edit any basicpage content',
|
||||
'translate any entity',
|
||||
'delete any basicpage content',
|
||||
);
|
||||
$admin_user = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Add a second and third language.
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'fr';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
$edit = array();
|
||||
$edit['predefined_langcode'] = 'nl';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests synced file fields on translated nodes.
|
||||
*/
|
||||
public function testSyncedImages() {
|
||||
// Enable translation for "Basic page" nodes.
|
||||
$edit = array(
|
||||
'entity_types[node]' => 1,
|
||||
'settings[node][basicpage][translatable]' => 1,
|
||||
"settings[node][basicpage][fields][$this->fieldName]" => 1,
|
||||
"settings[node][basicpage][columns][$this->fieldName][file]" => 1,
|
||||
// Explicitly disable alt and title since the javascript disables the
|
||||
// checkboxes on the form.
|
||||
"settings[node][basicpage][columns][$this->fieldName][alt]" => FALSE,
|
||||
"settings[node][basicpage][columns][$this->fieldName][title]" => FALSE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
|
||||
|
||||
// Verify that the image field on the "Basic basic" node type is
|
||||
// translatable.
|
||||
$definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'basicpage');
|
||||
$this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node image field is translatable.');
|
||||
|
||||
// Create a default language node.
|
||||
$default_language_node = $this->drupalCreateNode(array('type' => 'basicpage', 'title' => 'Lost in translation'));
|
||||
|
||||
// Edit the node to upload a file.
|
||||
$edit = array();
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[0]->uri);
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
|
||||
$edit = [$this->fieldName . '[0][alt]' => 'Lost in translation image', $this->fieldName . '[0][title]' => 'Lost in translation image title'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$first_fid = $this->getLastFileId();
|
||||
|
||||
// Translate the node into French: remove the existing file.
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/fr', array(), t('Remove'));
|
||||
|
||||
// Upload a different file.
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = 'Scarlett Johansson';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[1]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$edit = [$this->fieldName . '[0][alt]' => 'Scarlett Johansson image', $this->fieldName . '[0][title]' => 'Scarlett Johansson image title'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
// This inspects the HTML after the post of the translation, the image
|
||||
// should be displayed on the original node.
|
||||
$this->assertRaw('alt="Lost in translation image"');
|
||||
$this->assertRaw('title="Lost in translation image title"');
|
||||
$second_fid = $this->getLastFileId();
|
||||
// View the translated node.
|
||||
$this->drupalGet('fr/node/' . $default_language_node->id());
|
||||
$this->assertRaw('alt="Scarlett Johansson image"');
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
/* @var $file \Drupal\file\FileInterface */
|
||||
|
||||
// Ensure the file status of the first file permanent.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the second file is permanent.
|
||||
$file = File::load($second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Translate the node into dutch: remove the existing file.
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/nl', array(), t('Remove'));
|
||||
|
||||
// Upload a different file.
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = 'Akiko Takeshita';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[2]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$edit = [$this->fieldName . '[0][alt]' => 'Akiko Takeshita image', $this->fieldName . '[0][title]' => 'Akiko Takeshita image title'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$third_fid = $this->getLastFileId();
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the first file is untouched.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
|
||||
// This inspects the HTML after the post of the translation, the image
|
||||
// should be displayed on the original node.
|
||||
$this->assertRaw('alt="Lost in translation image"');
|
||||
$this->assertRaw('title="Lost in translation image title"');
|
||||
// View the translated node.
|
||||
$this->drupalGet('nl/node/' . $default_language_node->id());
|
||||
$this->assertRaw('alt="Akiko Takeshita image"');
|
||||
$this->assertRaw('title="Akiko Takeshita image title"');
|
||||
|
||||
// Ensure the file status of the second file is permanent.
|
||||
$file = File::load($second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the third file is permanent.
|
||||
$file = File::load($third_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Edit the second translation: remove the existing file.
|
||||
$this->drupalPostForm('fr/node/' . $default_language_node->id() . '/edit', array(), t('Remove'));
|
||||
|
||||
// Upload a different file.
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = 'Giovanni Ribisi';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[3]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$name = $this->fieldName . '[0][alt]';
|
||||
|
||||
$edit = [$name => 'Giovanni Ribisi image'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$replaced_second_fid = $this->getLastFileId();
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the first and third files are untouched.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
|
||||
|
||||
$file = File::load($third_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the replaced second file is permanent.
|
||||
$file = File::load($replaced_second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Delete the third translation.
|
||||
$this->drupalPostForm('nl/node/' . $default_language_node->id() . '/delete', array(), t('Delete Dutch translation'));
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the first and replaced second files are untouched.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
|
||||
|
||||
$file = File::load($replaced_second_fid);
|
||||
$this->assertTrue($file->isPermanent());
|
||||
|
||||
// Ensure the file status of the third file is now temporary.
|
||||
$file = File::load($third_fid);
|
||||
$this->assertTrue($file->isTemporary());
|
||||
|
||||
// Delete the all translations.
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/delete', array(), t('Delete all translations'));
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
|
||||
|
||||
// Ensure the file status of the all files are now temporary.
|
||||
$file = File::load($first_fid);
|
||||
$this->assertTrue($file->isTemporary(), 'First file still exists and is temporary.');
|
||||
|
||||
$file = File::load($replaced_second_fid);
|
||||
$this->assertTrue($file->isTemporary());
|
||||
}
|
||||
|
||||
}
|
81
web/core/modules/image/src/Tests/ImageStyleDeleteTest.php
Normal file
81
web/core/modules/image/src/Tests/ImageStyleDeleteTest.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
|
||||
/**
|
||||
* Tests image style deletion using the UI.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageStyleDeleteTest extends ImageFieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Create an image field 'foo' having the image style 'medium' as widget
|
||||
// preview and as formatter.
|
||||
$this->createImageField('foo', 'page', [], [], ['preview_image_style' => 'medium'], ['image_style' => 'medium']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests image style deletion.
|
||||
*/
|
||||
public function testDelete() {
|
||||
$this->drupalGet('admin/config/media/image-styles/manage/medium/delete');
|
||||
// Checks that the 'replacement' select element is displayed.
|
||||
$this->assertFieldByName('replacement');
|
||||
// Checks that UI messages are correct.
|
||||
$this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
|
||||
$this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
|
||||
|
||||
// Delete 'medium' image style but replace it with 'thumbnail'. This style
|
||||
// is involved in 'node.page.default' display view and form.
|
||||
$this->drupalPostForm(NULL, ['replacement' => 'thumbnail'], t('Delete'));
|
||||
|
||||
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
|
||||
$view_display = EntityViewDisplay::load('node.page.default');
|
||||
// Checks that the formatter setting is replaced.
|
||||
if ($this->assertNotNull($component = $view_display->getComponent('foo'))) {
|
||||
$this->assertIdentical($component['settings']['image_style'], 'thumbnail');
|
||||
}
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
|
||||
$form_display = EntityFormDisplay::load('node.page.default');
|
||||
// Check that the widget setting is replaced.
|
||||
if ($this->assertNotNull($component = $form_display->getComponent('foo'))) {
|
||||
$this->assertIdentical($component['settings']['preview_image_style'], 'thumbnail');
|
||||
}
|
||||
|
||||
$this->drupalGet('admin/config/media/image-styles/manage/thumbnail/delete');
|
||||
// Checks that the 'replacement' select element is displayed.
|
||||
$this->assertFieldByName('replacement');
|
||||
// Checks that UI messages are correct.
|
||||
$this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
|
||||
$this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
|
||||
|
||||
// Delete 'thumbnail' image style. Provide no replacement.
|
||||
$this->drupalPostForm(NULL, [], t('Delete'));
|
||||
|
||||
$view_display = EntityViewDisplay::load('node.page.default');
|
||||
// Checks that the formatter setting is disabled.
|
||||
$this->assertNull($view_display->getComponent('foo'));
|
||||
$this->assertNotNull($view_display->get('hidden')['foo']);
|
||||
// Checks that widget setting is preserved with the image preview disabled.
|
||||
$form_display = EntityFormDisplay::load('node.page.default');
|
||||
$this->assertNotNull($widget = $form_display->getComponent('foo'));
|
||||
$this->assertIdentical($widget['settings']['preview_image_style'], '');
|
||||
|
||||
// Now, there's only one image style configured on the system: 'large'.
|
||||
$this->drupalGet('admin/config/media/image-styles/manage/large/delete');
|
||||
// Checks that the 'replacement' select element is not displayed.
|
||||
$this->assertNoFieldByName('replacement');
|
||||
// Checks that UI messages are correct.
|
||||
$this->assertNoRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
|
||||
$this->assertRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
|
||||
}
|
||||
|
||||
}
|
110
web/core/modules/image/src/Tests/ImageStyleFlushTest.php
Normal file
110
web/core/modules/image/src/Tests/ImageStyleFlushTest.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
|
||||
/**
|
||||
* Tests flushing of image styles.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageStyleFlushTest extends ImageFieldTestBase {
|
||||
|
||||
/**
|
||||
* Given an image style and a wrapper, generate an image.
|
||||
*/
|
||||
function createSampleImage($style, $wrapper) {
|
||||
static $file;
|
||||
|
||||
if (!isset($file)) {
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$file = reset($files);
|
||||
}
|
||||
|
||||
// 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 = $style->buildUri($source_uri);
|
||||
$derivative = $style->createDerivative($source_uri, $derivative_uri);
|
||||
|
||||
return $derivative ? $derivative_uri : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of images currently created for a style in a wrapper.
|
||||
*/
|
||||
function getImageCount($style, $wrapper) {
|
||||
return count(file_scan_directory($wrapper . '://styles/' . $style->id(), '/.*/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* General test to flush a style.
|
||||
*/
|
||||
function testFlush() {
|
||||
|
||||
// Setup a style to be created and effects to add to it.
|
||||
$style_name = strtolower($this->randomMachineName(10));
|
||||
$style_label = $this->randomString();
|
||||
$style_path = 'admin/config/media/image-styles/manage/' . $style_name;
|
||||
$effect_edits = array(
|
||||
'image_resize' => array(
|
||||
'data[width]' => 100,
|
||||
'data[height]' => 101,
|
||||
),
|
||||
'image_scale' => array(
|
||||
'data[width]' => 110,
|
||||
'data[height]' => 111,
|
||||
'data[upscale]' => 1,
|
||||
),
|
||||
);
|
||||
|
||||
// Add style form.
|
||||
$edit = array(
|
||||
'name' => $style_name,
|
||||
'label' => $style_label,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/media/image-styles/add', $edit, t('Create new style'));
|
||||
|
||||
// Add each sample effect to the style.
|
||||
foreach ($effect_edits as $effect => $edit) {
|
||||
// Add the effect.
|
||||
$this->drupalPostForm($style_path, array('new' => $effect), t('Add'));
|
||||
if (!empty($edit)) {
|
||||
$this->drupalPostForm(NULL, $edit, t('Add effect'));
|
||||
}
|
||||
}
|
||||
|
||||
// Load the saved image style.
|
||||
$style = ImageStyle::load($style_name);
|
||||
|
||||
// Create an image for the 'public' wrapper.
|
||||
$image_path = $this->createSampleImage($style, 'public');
|
||||
// Expecting to find 2 images, one is the sample.png image shown in
|
||||
// image style preview.
|
||||
$this->assertEqual($this->getImageCount($style, 'public'), 2, format_string('Image style %style image %file successfully generated.', array('%style' => $style->label(), '%file' => $image_path)));
|
||||
|
||||
// Create an image for the 'private' wrapper.
|
||||
$image_path = $this->createSampleImage($style, 'private');
|
||||
$this->assertEqual($this->getImageCount($style, 'private'), 1, format_string('Image style %style image %file successfully generated.', array('%style' => $style->label(), '%file' => $image_path)));
|
||||
|
||||
// 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();
|
||||
$uuids = array();
|
||||
foreach ($style->getEffects() as $uuid => $effect) {
|
||||
$uuids[$effect->getPluginId()] = $uuid;
|
||||
}
|
||||
$this->drupalPostForm($style_path . '/effects/' . $uuids['image_scale'] . '/delete', array(), t('Delete'));
|
||||
$this->assertResponse(200);
|
||||
$this->drupalPostForm($style_path, array(), t('Update style'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Post flush, expected 1 image in the 'public' wrapper (sample.png).
|
||||
$this->assertEqual($this->getImageCount($style, 'public'), 1, format_string('Image style %style flushed correctly for %wrapper wrapper.', array('%style' => $style->label(), '%wrapper' => 'public')));
|
||||
|
||||
// Post flush, expected no image in the 'private' wrapper.
|
||||
$this->assertEqual($this->getImageCount($style, 'private'), 0, format_string('Image style %style flushed correctly for %wrapper wrapper.', array('%style' => $style->label(), '%wrapper' => 'private')));
|
||||
}
|
||||
|
||||
}
|
268
web/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php
Normal file
268
web/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php
Normal file
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the functions for generating paths and URLs for image styles.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageStylesPathAndUrlTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('image', 'image_module_test');
|
||||
|
||||
/**
|
||||
* @var \Drupal\image\ImageStyleInterface
|
||||
*/
|
||||
protected $style;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->style = ImageStyle::create(array('name' => 'style_foo', 'label' => $this->randomString()));
|
||||
$this->style->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\image\ImageStyleInterface::buildUri().
|
||||
*/
|
||||
function testImageStylePath() {
|
||||
$scheme = 'public';
|
||||
$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 = $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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an image style URL using the "public://" scheme.
|
||||
*/
|
||||
function testImageStyleUrlAndPathPublic() {
|
||||
$this->doImageStyleUrlAndPathTests('public');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an image style URL using the "private://" scheme.
|
||||
*/
|
||||
function testImageStyleUrlAndPathPrivate() {
|
||||
$this->doImageStyleUrlAndPathTests('private');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an image style URL with the "public://" scheme and unclean URLs.
|
||||
*/
|
||||
function testImageStyleUrlAndPathPublicUnclean() {
|
||||
$this->doImageStyleUrlAndPathTests('public', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an image style URL with the "private://" schema and unclean URLs.
|
||||
*/
|
||||
function testImageStyleUrlAndPathPrivateUnclean() {
|
||||
$this->doImageStyleUrlAndPathTests('private', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an image style URL with a file URL that has an extra slash in it.
|
||||
*/
|
||||
function testImageStyleUrlExtraSlash() {
|
||||
$this->doImageStyleUrlAndPathTests('public', TRUE, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an invalid source image returns a 404.
|
||||
*/
|
||||
function testImageStyleUrlForMissingSourceImage() {
|
||||
$non_existent_uri = 'public://foo.png';
|
||||
$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 building an image style URL.
|
||||
*/
|
||||
function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_slash = FALSE) {
|
||||
$this->prepareRequestForGenerator($clean_url);
|
||||
|
||||
// Make the default scheme neither "public" nor "private" to verify the
|
||||
// functions work for other than the default scheme.
|
||||
$this->config('system.file')->set('default_scheme', 'temporary')->save();
|
||||
|
||||
// Create the directories for the styles.
|
||||
$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.');
|
||||
|
||||
// Create a working copy of the file.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$file = array_shift($files);
|
||||
$original_uri = file_unmanaged_copy($file->uri, $scheme . '://', FILE_EXISTS_RENAME);
|
||||
// Let the image_module_test module know about this file, so it can claim
|
||||
// ownership in hook_file_download().
|
||||
\Drupal::state()->set('image.test_file_download', $original_uri);
|
||||
$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 = $this->style->buildUri($original_uri);
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$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
|
||||
// in it.
|
||||
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 = $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.');
|
||||
}
|
||||
// Add some extra chars to the token.
|
||||
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
|
||||
$this->assertResponse(403, 'Image was inaccessible at the URL with an invalid token.');
|
||||
// Change the parameter name so the token is missing.
|
||||
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', 'wrongparam=', $generate_url));
|
||||
$this->assertResponse(403, 'Image was inaccessible at the URL with a missing token.');
|
||||
|
||||
// Check that the generated URL is the same when we pass in a relative path
|
||||
// rather than a URI. We need to temporarily switch the default scheme to
|
||||
// match the desired scheme before testing this, then switch it back to the
|
||||
// "temporary" scheme used throughout this test afterwards.
|
||||
$this->config('system.file')->set('default_scheme', $scheme)->save();
|
||||
$relative_path = file_uri_target($original_uri);
|
||||
$generate_url_from_relative_path = $this->style->buildUrl($relative_path, $clean_url);
|
||||
$this->assertEqual($generate_url, $generate_url_from_relative_path);
|
||||
$this->config('system.file')->set('default_scheme', 'temporary')->save();
|
||||
|
||||
// Fetch the URL that generates the file.
|
||||
$this->drupalGet($generate_url);
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
|
||||
$this->assertRaw(file_get_contents($generated_uri), 'URL returns expected file.');
|
||||
$image = $this->container->get('image.factory')->get($generated_uri);
|
||||
$this->assertEqual($this->drupalGetHeader('Content-Type'), $image->getMimeType(), 'Expected Content-Type was reported.');
|
||||
$this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize(), 'Expected Content-Length was reported.');
|
||||
|
||||
// Check that we did not download the original file.
|
||||
$original_image = $this->container->get('image.factory')->get($original_uri);
|
||||
$this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize());
|
||||
|
||||
if ($scheme == 'private') {
|
||||
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
|
||||
$this->assertNotEqual(strpos($this->drupalGetHeader('Cache-Control'), 'no-cache'), FALSE, 'Cache-Control header contains \'no-cache\' to prevent caching.');
|
||||
$this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', 'Expected custom header has been added.');
|
||||
|
||||
// Make sure that a second request to the already existing derivative
|
||||
// works too.
|
||||
$this->drupalGet($generate_url);
|
||||
$this->assertResponse(200, 'Image was generated at the URL.');
|
||||
|
||||
// Check that the second request also returned the generated image.
|
||||
$this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize());
|
||||
|
||||
// Check that we did not download the original file.
|
||||
$this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize());
|
||||
|
||||
// Make sure that access is denied for existing style files if we do not
|
||||
// have access.
|
||||
\Drupal::state()->delete('image.test_file_download');
|
||||
$this->drupalGet($generate_url);
|
||||
$this->assertResponse(403, 'Confirmed that access is denied for the private image style.');
|
||||
|
||||
// Repeat this with a different file that we do not have access to and
|
||||
// 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->id() . '/' . $scheme . '/' . drupal_basename($original_uri_noaccess);
|
||||
$this->assertFalse(file_exists($generated_uri_noaccess), 'Generated file does not exist.');
|
||||
$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.');
|
||||
// Verify that images are not appended to the response. Currently this test only uses PNG images.
|
||||
if (strpos($generate_url, '.png') === FALSE ) {
|
||||
$this->fail('Confirming that private image styles are not appended require PNG file.');
|
||||
}
|
||||
else {
|
||||
// Check for PNG-Signature (cf. http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.2) in the
|
||||
// response body.
|
||||
$this->assertNoRaw( chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), 'No PNG signature found in the response body.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
|
||||
$this->assertEqual(strpos($this->drupalGetHeader('Cache-Control'), 'no-cache'), FALSE, 'Cache-Control header contains \'no-cache\' to prevent caching.');
|
||||
|
||||
if ($clean_url) {
|
||||
// Add some extra chars to the token.
|
||||
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
|
||||
$this->assertResponse(200, 'Existing image was accessible at the URL with an invalid token.');
|
||||
}
|
||||
}
|
||||
|
||||
// Allow insecure image derivatives to be created for the remainder of this
|
||||
// test.
|
||||
$this->config('image.settings')->set('allow_insecure_derivatives', TRUE)->save();
|
||||
|
||||
// Create another working copy of the file.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$file = array_shift($files);
|
||||
$original_uri = file_unmanaged_copy($file->uri, $scheme . '://', FILE_EXISTS_RENAME);
|
||||
// Let the image_module_test module know about this file, so it can claim
|
||||
// ownership in hook_file_download().
|
||||
\Drupal::state()->set('image.test_file_download', $original_uri);
|
||||
|
||||
// Suppress the security token in the URL, then get the URL of a file that
|
||||
// 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.
|
||||
$this->config('image.settings')->set('suppress_itok_output', TRUE)->save();
|
||||
$generated_uri = $this->style->buildUri($original_uri);
|
||||
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
|
||||
$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.');
|
||||
|
||||
// Stop supressing the security token in the URL.
|
||||
$this->config('image.settings')->set('suppress_itok_output', FALSE)->save();
|
||||
// Ensure allow_insecure_derivatives is enabled.
|
||||
$this->assertEqual($this->config('image.settings')->get('allow_insecure_derivatives'), TRUE);
|
||||
// Check that a security token is still required when generating a second
|
||||
// image derivative using the first one as a source.
|
||||
$nested_url = $this->style->buildUrl($generated_uri, $clean_url);
|
||||
$matches_expected_url_format = (boolean) preg_match('/styles\/' . $this->style->id() . '\/' . $scheme . '\/styles\/' . $this->style->id() . '\/' . $scheme . '/', $nested_url);
|
||||
$this->assertTrue($matches_expected_url_format, "URL for a derivative of an image style matches expected format.");
|
||||
$nested_url_with_wrong_token = str_replace(IMAGE_DERIVATIVE_TOKEN . '=', 'wrongparam=', $nested_url);
|
||||
$this->drupalGet($nested_url_with_wrong_token);
|
||||
$this->assertResponse(403, 'Image generated from an earlier derivative was inaccessible at the URL with a missing token.');
|
||||
// Check that this restriction cannot be bypassed by adding extra slashes
|
||||
// to the URL.
|
||||
$this->drupalGet(substr_replace($nested_url_with_wrong_token, '//styles/', strrpos($nested_url_with_wrong_token, '/styles/'), strlen('/styles/')));
|
||||
$this->assertResponse(403, 'Image generated from an earlier derivative was inaccessible at the URL with a missing token, even with an extra forward slash in the URL.');
|
||||
$this->drupalGet(substr_replace($nested_url_with_wrong_token, '////styles/', strrpos($nested_url_with_wrong_token, '/styles/'), strlen('/styles/')));
|
||||
$this->assertResponse(403, 'Image generated from an earlier derivative was inaccessible at the URL with a missing token, even with multiple forward slashes in the URL.');
|
||||
// Make sure the image can still be generated if a correct token is used.
|
||||
$this->drupalGet($nested_url);
|
||||
$this->assertResponse(200, 'Image was accessible when a correct token was provided in the URL.');
|
||||
|
||||
// Check that requesting a nonexistent image does not create any new
|
||||
// directories in the file system.
|
||||
$directory = $scheme . '://styles/' . $this->style->id() . '/' . $scheme . '/' . $this->randomMachineName();
|
||||
$this->drupalGet(file_create_url($directory . '/' . $this->randomString()));
|
||||
$this->assertFalse(file_exists($directory), 'New directory was not created in the filesystem when requesting an unauthorized image.');
|
||||
}
|
||||
|
||||
}
|
221
web/core/modules/image/src/Tests/ImageThemeFunctionTest.php
Normal file
221
web/core/modules/image/src/Tests/ImageThemeFunctionTest.php
Normal file
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\image\Entity\ImageStyle;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests image theme functions.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageThemeFunctionTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('image', 'entity_test');
|
||||
|
||||
/**
|
||||
* Created file entity.
|
||||
*
|
||||
* @var \Drupal\file\Entity\File
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Image\ImageFactory
|
||||
*/
|
||||
protected $imageFactory;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
FieldStorageConfig::create(array(
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'image_test',
|
||||
'type' => 'image',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
))->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'image_test',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example.jpg');
|
||||
$this->image = File::create([
|
||||
'uri' => 'public://example.jpg',
|
||||
]);
|
||||
$this->image->save();
|
||||
$this->imageFactory = $this->container->get('image.factory');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests usage of the image field formatters.
|
||||
*/
|
||||
function testImageFormatterTheme() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
// Create an image.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$file = reset($files);
|
||||
$original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
|
||||
|
||||
// Create a style.
|
||||
$style = ImageStyle::create(array('name' => 'test', 'label' => 'Test'));
|
||||
$style->save();
|
||||
$url = file_url_transform_relative($style->buildUrl($original_uri));
|
||||
|
||||
// Create a test entity with the image field set.
|
||||
$entity = EntityTest::create();
|
||||
$entity->image_test->target_id = $this->image->id();
|
||||
$entity->image_test->alt = NULL;
|
||||
$entity->image_test->uri = $original_uri;
|
||||
$image = $this->imageFactory->get('public://example.jpg');
|
||||
$entity->save();
|
||||
|
||||
// Create the base element that we'll use in the tests below.
|
||||
$path = $this->randomMachineName();
|
||||
$base_element = array(
|
||||
'#theme' => 'image_formatter',
|
||||
'#image_style' => 'test',
|
||||
'#item' => $entity->image_test,
|
||||
'#url' => Url::fromUri('base:' . $path),
|
||||
);
|
||||
|
||||
// Test using theme_image_formatter() with a NULL value for the alt option.
|
||||
$element = $base_element;
|
||||
$this->setRawContent($renderer->renderRoot($element));
|
||||
$elements = $this->xpath('//a[@href=:path]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height]', array(':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()));
|
||||
$this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders with a NULL value for the alt option.');
|
||||
|
||||
// Test using theme_image_formatter() without an image title, alt text, or
|
||||
// link options.
|
||||
$element = $base_element;
|
||||
$element['#item']->alt = '';
|
||||
$this->setRawContent($renderer->renderRoot($element));
|
||||
$elements = $this->xpath('//a[@href=:path]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height and @alt=""]', array(':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()));
|
||||
$this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders without title, alt, or path options.');
|
||||
|
||||
// Link the image to a fragment on the page, and not a full URL.
|
||||
$fragment = $this->randomMachineName();
|
||||
$element = $base_element;
|
||||
$element['#url'] = Url::fromRoute('<none>', [], ['fragment' => $fragment]);
|
||||
$this->setRawContent($renderer->renderRoot($element));
|
||||
$elements = $this->xpath('//a[@href=:fragment]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height and @alt=""]', array(
|
||||
':fragment' => '#' . $fragment,
|
||||
':url' => $url,
|
||||
':width' => $image->getWidth(),
|
||||
':height' => $image->getHeight()
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders a link fragment.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests usage of the image style theme function.
|
||||
*/
|
||||
function testImageStyleTheme() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
// Create an image.
|
||||
$files = $this->drupalGetTestFiles('image');
|
||||
$file = reset($files);
|
||||
$original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
|
||||
|
||||
// Create a style.
|
||||
$style = ImageStyle::create(array('name' => 'image_test', 'label' => 'Test'));
|
||||
$style->save();
|
||||
$url = file_url_transform_relative($style->buildUrl($original_uri));
|
||||
|
||||
// Create the base element that we'll use in the tests below.
|
||||
$base_element = array(
|
||||
'#theme' => 'image_style',
|
||||
'#style_name' => 'image_test',
|
||||
'#uri' => $original_uri,
|
||||
);
|
||||
|
||||
$element = $base_element;
|
||||
$this->setRawContent($renderer->renderRoot($element));
|
||||
$elements = $this->xpath('//img[@class="image-style-image-test" and @src=:url and @alt=""]', array(':url' => $url));
|
||||
$this->assertEqual(count($elements), 1, 'theme_image_style() renders an image correctly.');
|
||||
|
||||
// Test using theme_image_style() with a NULL value for the alt option.
|
||||
$element = $base_element;
|
||||
$element['#alt'] = NULL;
|
||||
$this->setRawContent($renderer->renderRoot($element));
|
||||
$elements = $this->xpath('//img[@class="image-style-image-test" and @src=:url]', array(':url' => $url));
|
||||
$this->assertEqual(count($elements), 1, 'theme_image_style() renders an image correctly with a NULL value for the alt option.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests image alt attribute functionality.
|
||||
*/
|
||||
function testImageAltFunctionality() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
|
||||
// Test using alt directly with alt attribute.
|
||||
$image_with_alt_property = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => '/core/themes/bartik/logo.svg',
|
||||
'#alt' => 'Regular alt',
|
||||
'#title' => 'Test title',
|
||||
'#width' => '50%',
|
||||
'#height' => '50%',
|
||||
'#attributes' => array('class' => 'image-with-regular-alt', 'id' => 'my-img'),
|
||||
);
|
||||
|
||||
$this->setRawContent($renderer->renderRoot($image_with_alt_property));
|
||||
$elements = $this->xpath('//img[contains(@class, class) and contains(@alt, :alt)]', array(":class" => "image-with-regular-alt", ":alt" => "Regular alt"));
|
||||
$this->assertEqual(count($elements), 1, 'Regular alt displays correctly');
|
||||
|
||||
// Test using alt attribute inside attributes.
|
||||
$image_with_alt_attribute_alt_attribute = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => '/core/themes/bartik/logo.svg',
|
||||
'#width' => '50%',
|
||||
'#height' => '50%',
|
||||
'#attributes' => array(
|
||||
'class' => 'image-with-attribute-alt',
|
||||
'id' => 'my-img',
|
||||
'title' => 'New test title',
|
||||
'alt' => 'Attribute alt',
|
||||
),
|
||||
);
|
||||
|
||||
$this->setRawContent($renderer->renderRoot($image_with_alt_attribute_alt_attribute));
|
||||
$elements = $this->xpath('//img[contains(@class, class) and contains(@alt, :alt)]', array(":class" => "image-with-attribute-alt", ":alt" => "Attribute alt"));
|
||||
$this->assertEqual(count($elements), 1, 'Attribute alt displays correctly');
|
||||
|
||||
// Test using alt attribute as property and inside attributes.
|
||||
$image_with_alt_attribute_both = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => '/core/themes/bartik/logo.svg',
|
||||
'#width' => '50%',
|
||||
'#height' => '50%',
|
||||
'#alt' => 'Kitten sustainable',
|
||||
'#attributes' => array(
|
||||
'class' => 'image-with-attribute-alt',
|
||||
'id' => 'my-img',
|
||||
'title' => 'New test title',
|
||||
'alt' => 'Attribute alt',
|
||||
),
|
||||
);
|
||||
|
||||
$this->setRawContent($renderer->renderRoot($image_with_alt_attribute_both));
|
||||
$elements = $this->xpath('//img[contains(@class, class) and contains(@alt, :alt)]', array(":class" => "image-with-attribute-alt", ":alt" => "Attribute alt"));
|
||||
$this->assertEqual(count($elements), 1, 'Attribute alt overrides alt property if both set.');
|
||||
}
|
||||
|
||||
}
|
54
web/core/modules/image/src/Tests/Update/ImageUpdateTest.php
Normal file
54
web/core/modules/image/src/Tests/Update/ImageUpdateTest.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests\Update;
|
||||
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests Image update path.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class ImageUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests image_post_update_image_style_dependencies().
|
||||
*
|
||||
* @see image_post_update_image_style_dependencies()
|
||||
*/
|
||||
public function testPostUpdateImageStylesDependencies() {
|
||||
$view = 'core.entity_view_display.node.article.default';
|
||||
$form = 'core.entity_form_display.node.article.default';
|
||||
|
||||
// Check that view display 'node.article.default' doesn't depend on image
|
||||
// style 'image.style.large'.
|
||||
$dependencies = $this->config($view)->get('dependencies.config');
|
||||
$this->assertFalse(in_array('image.style.large', $dependencies));
|
||||
// Check that form display 'node.article.default' doesn't depend on image
|
||||
// style 'image.style.thumbnail'.
|
||||
$dependencies = $this->config($form)->get('dependencies.config');
|
||||
$this->assertFalse(in_array('image.style.thumbnail', $dependencies));
|
||||
|
||||
// Run updates.
|
||||
$this->runUpdates();
|
||||
|
||||
// Check that view display 'node.article.default' depend on image style
|
||||
// 'image.style.large'.
|
||||
$dependencies = $this->config($view)->get('dependencies.config');
|
||||
$this->assertTrue(in_array('image.style.large', $dependencies));
|
||||
// Check that form display 'node.article.default' depend on image style
|
||||
// 'image.style.thumbnail'.
|
||||
$dependencies = $this->config($view)->get('dependencies.config');
|
||||
$this->assertTrue(in_array('image.style.large', $dependencies));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image\Tests\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\views\Tests\ViewTestBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests image on user relationship handler.
|
||||
*
|
||||
* @group image
|
||||
*/
|
||||
class RelationshipUserImageDataTest extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('image', 'image_test_views', 'user');
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('test_image_user_image_data');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create the user profile field and instance.
|
||||
FieldStorageConfig::create(array(
|
||||
'entity_type' => 'user',
|
||||
'field_name' => 'user_picture',
|
||||
'type' => 'image',
|
||||
'translatable' => '0',
|
||||
))->save();
|
||||
FieldConfig::create([
|
||||
'label' => 'User Picture',
|
||||
'description' => '',
|
||||
'field_name' => 'user_picture',
|
||||
'entity_type' => 'user',
|
||||
'bundle' => 'user',
|
||||
'required' => 0,
|
||||
])->save();
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), array('image_test_views'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests using the views image relationship.
|
||||
*/
|
||||
public function testViewsHandlerRelationshipUserImageData() {
|
||||
$file = File::create([
|
||||
'fid' => 2,
|
||||
'uid' => 2,
|
||||
'filename' => 'image-test.jpg',
|
||||
'uri' => "public://image-test.jpg",
|
||||
'filemime' => 'image/jpeg',
|
||||
'created' => 1,
|
||||
'changed' => 1,
|
||||
'status' => FILE_STATUS_PERMANENT,
|
||||
]);
|
||||
$file->enforceIsNew();
|
||||
file_put_contents($file->getFileUri(), file_get_contents('core/modules/simpletest/files/image-1.png'));
|
||||
$file->save();
|
||||
|
||||
$account = $this->drupalCreateUser();
|
||||
$account->user_picture->target_id = 2;
|
||||
$account->save();
|
||||
|
||||
$view = Views::getView('test_image_user_image_data');
|
||||
// Tests \Drupal\taxonomy\Plugin\views\relationship\NodeTermData::calculateDependencies().
|
||||
$expected = [
|
||||
'module' => [
|
||||
'file',
|
||||
'user',
|
||||
],
|
||||
];
|
||||
$this->assertIdentical($expected, $view->getDependencies());
|
||||
$this->executeView($view);
|
||||
$expected_result = array(
|
||||
array(
|
||||
'file_managed_user__user_picture_fid' => '2',
|
||||
),
|
||||
);
|
||||
$column_map = array('file_managed_user__user_picture_fid' => 'file_managed_user__user_picture_fid');
|
||||
$this->assertIdenticalResultset($view, $expected_result, $column_map);
|
||||
}
|
||||
|
||||
}
|
14
web/core/modules/image/templates/image-anchor.html.twig
Normal file
14
web/core/modules/image/templates/image-anchor.html.twig
Normal file
|
@ -0,0 +1,14 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for a 3x3 grid of checkboxes for image anchors.
|
||||
*
|
||||
* Available variables:
|
||||
* - table: HTML for the table of image anchors.
|
||||
*
|
||||
* @see template_preprocess_image_anchor()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{{ table }}
|
|
@ -0,0 +1,32 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for a summary of an image crop effect.
|
||||
*
|
||||
* Available variables:
|
||||
* - data: The current configuration for this resize effect, including:
|
||||
* - width: The width of the resized image.
|
||||
* - height: The height of the resized image.
|
||||
* - anchor: The part of the image that will be retained after cropping.
|
||||
* - anchor_label: The translated label of the crop anchor.
|
||||
* - effect: The effect information, including:
|
||||
* - id: The effect identifier.
|
||||
* - label: The effect name.
|
||||
* - description: The effect description.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% if data.width and data.height -%}
|
||||
{{ data.width }}×{{ data.height }}
|
||||
{%- else -%}
|
||||
{% if data.width %}
|
||||
{% trans %}
|
||||
width {{ data.width }}
|
||||
{% endtrans %}
|
||||
{% elseif data.height %}
|
||||
{% trans %}
|
||||
height {{ data.height }}
|
||||
{% endtrans %}
|
||||
{% endif %}
|
||||
{%- endif %}
|
20
web/core/modules/image/templates/image-formatter.html.twig
Normal file
20
web/core/modules/image/templates/image-formatter.html.twig
Normal file
|
@ -0,0 +1,20 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to display a formatted image field.
|
||||
*
|
||||
* Available variables:
|
||||
* - image: A collection of image data.
|
||||
* - image_style: An optional image style.
|
||||
* - url: An optional URL the image can be linked to.
|
||||
*
|
||||
* @see template_preprocess_image_formatter()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% if url %}
|
||||
<a href="{{ url }}">{{ image }}</a>
|
||||
{% else %}
|
||||
{{ image }}
|
||||
{% endif %}
|
|
@ -0,0 +1,30 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for a summary of an image resize effect.
|
||||
*
|
||||
* Available variables:
|
||||
* - data: The current configuration for this resize effect, including:
|
||||
* - width: The width of the resized image.
|
||||
* - height: The height of the resized image.
|
||||
* - effect: The effect information, including:
|
||||
* - id: The effect identifier.
|
||||
* - label: The effect name.
|
||||
* - description: The effect description.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% if data.width and data.height -%}
|
||||
{{ data.width }}×{{ data.height }}
|
||||
{%- else -%}
|
||||
{% if data.width %}
|
||||
{% trans %}
|
||||
width {{ data.width }}
|
||||
{% endtrans %}
|
||||
{% elseif data.height %}
|
||||
{% trans %}
|
||||
height {{ data.height }}
|
||||
{% endtrans %}
|
||||
{% endif %}
|
||||
{%- endif %}
|
|
@ -0,0 +1,28 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for a summary of an image rotate effect.
|
||||
*
|
||||
* Available variables:
|
||||
* - data: The current configuration for this resize effect, including:
|
||||
* - degrees: Degrees to rotate the image, positive values will rotate the
|
||||
* image clockwise, negative values counter-clockwise.
|
||||
* - bgcolor: The hex background color of the new areas created as consequence
|
||||
* of rotation.
|
||||
* - random: If the rotation angle is randomized.
|
||||
* - effect: The effect information, including:
|
||||
* - id: The effect identifier.
|
||||
* - label: The effect name.
|
||||
* - description: The effect description.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% if data.random %}
|
||||
{% set degrees = data.degrees|abs %}
|
||||
{% trans %}
|
||||
random between -{{ degrees }}° and {{ degrees }}°
|
||||
{% endtrans %}
|
||||
{% else %}
|
||||
{{ data.degrees }}°
|
||||
{% endif %}
|
|
@ -0,0 +1,37 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for a summary of an image scale effect.
|
||||
*
|
||||
* Available variables:
|
||||
* - data: The current configuration for this resize effect, including:
|
||||
* - width: The width of the resized image.
|
||||
* - height: The height of the resized image.
|
||||
* - upscale: If images larger than their original size can scale.
|
||||
* - effect: The effect information, including:
|
||||
* - id: The effect identifier.
|
||||
* - label: The effect name.
|
||||
* - description: The effect description.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% if data.width and data.height -%}
|
||||
{{ data.width }}×{{ data.height }}
|
||||
{%- else -%}
|
||||
{% if data.width %}
|
||||
{% trans %}
|
||||
width {{ data.width }}
|
||||
{% endtrans %}
|
||||
{% elseif data.height %}
|
||||
{% trans %}
|
||||
height {{ data.height }}
|
||||
{% endtrans %}
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
|
||||
{% if data.upscale %}
|
||||
{% trans %}
|
||||
(upscaling allowed)
|
||||
{% endtrans %}
|
||||
{% endif %}
|
|
@ -0,0 +1,57 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to display a preview of an image style.
|
||||
*
|
||||
* Available variables:
|
||||
* - style_id: The ID of the image style.
|
||||
* - style_name: The name of the image style.
|
||||
* - cache_bypass: A timestamp token used to avoid browser caching of images.
|
||||
* - original: An associative array containing:
|
||||
* - url: The URL of the original image.
|
||||
* - width: The width in pixels of the original image.
|
||||
* - height: The height in pixels of the original image.
|
||||
* - rendered: The render array for the original image.
|
||||
* - derivative: An associative array containing:
|
||||
* - url: The URL of the derivative image.
|
||||
* - width: The width in pixels of the derivative image.
|
||||
* - height: The height in pixels of the derivative image.
|
||||
* - rendered: The rendered derivative image.
|
||||
* - preview: An associative array containing:
|
||||
* - original: An associative array containing:
|
||||
* - width: The width in pixels of the original image in the preview.
|
||||
* - height: The height in pixels of the original image in the preview.
|
||||
* - derivative: An associative array containing:
|
||||
* - width: The width in pixels of the derivative image in the preview.
|
||||
* - height: The height in pixels of the derivative image in the preview.
|
||||
*
|
||||
* @see template_preprocess_image_style_preview()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<div class="image-style-preview preview clearfix">
|
||||
{# Preview of the original image. #}
|
||||
<div class="preview-image-wrapper">
|
||||
{{ 'original'|t }} (<a href="{{ original.url }}">{{ 'view actual size'|t }}</a>)
|
||||
<div class="preview-image original-image" style="width: {{ preview.original.width }}px; height: {{ preview.original.height }}px;">
|
||||
<a href="{{ original.url }}">
|
||||
{{ original.rendered }}
|
||||
</a>
|
||||
<div class="height" style="height: {{ preview.original.height }}px"><span>{{ original.height }}px</span></div>
|
||||
<div class="width" style="width: {{ preview.original.width }}px"><span>{{ original.width }}px</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Derivative of the image style. #}
|
||||
<div class="preview-image-wrapper">
|
||||
{{ style_name }} (<a href="{{ derivative.url }}?{{ cache_bypass }}">{{ 'view actual size'|t }}</a>)
|
||||
<div class="preview-image modified-image" style="width: {{ preview.derivative.width }}px; height: {{ preview.derivative.height }}px;">
|
||||
<a href="{{ derivative.url }}?{{ cache_bypass }}">
|
||||
{{ derivative.rendered }}
|
||||
</a>
|
||||
<div class="height" style="height: {{ preview.derivative.height }}px"><span>{{ derivative.height }}px</span></div>
|
||||
<div class="width" style="width: {{ preview.derivative.width }}px"><span>{{ derivative.width }}px</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
20
web/core/modules/image/templates/image-style.html.twig
Normal file
20
web/core/modules/image/templates/image-style.html.twig
Normal file
|
@ -0,0 +1,20 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for an image using a specific image style.
|
||||
*
|
||||
* Available variables:
|
||||
* - attributes: HTML attributes for the image, including the following:
|
||||
* - src: Full URL or relative path to the image file.
|
||||
* - class: One or more classes to be applied to the image.
|
||||
* - width: The width of the image (if known).
|
||||
* - height: The height of the image (if known).
|
||||
* - title: The title of the image.
|
||||
* - alt: The alternative text for the image.
|
||||
*
|
||||
* @see template_preprocess_image_style()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{{ image }}
|
19
web/core/modules/image/templates/image-widget.html.twig
Normal file
19
web/core/modules/image/templates/image-widget.html.twig
Normal file
|
@ -0,0 +1,19 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for an image field widget.
|
||||
*
|
||||
* Available variables:
|
||||
* - attributes: HTML attributes for the containing element.
|
||||
* - data: Render elements of the image widget.
|
||||
*
|
||||
* @see template_preprocess_image_widget()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<div{{ attributes }}>
|
||||
{{ data.preview }}
|
||||
{# Render widget data without the image preview that was output already. #}
|
||||
{{ data|without('preview') }}
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
image.effect.image_module_test_ajax:
|
||||
type: mapping
|
||||
label: 'Ajax test'
|
||||
mapping:
|
||||
test_parameter:
|
||||
type: integer
|
||||
label: 'Test Parameter'
|
||||
|
||||
image.style.*.third_party.image_module_test:
|
||||
type: mapping
|
||||
label: 'Schema for image_module_test module additions to image_style entity'
|
||||
mapping:
|
||||
foo:
|
||||
type: string
|
||||
label: 'Label for foo'
|
|
@ -0,0 +1,6 @@
|
|||
name: 'Image test'
|
||||
type: module
|
||||
description: 'Provides hook implementations for testing Image module functionality.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides Image module hook implementations for testing purposes.
|
||||
*/
|
||||
|
||||
use Drupal\image\ImageStyleInterface;
|
||||
|
||||
function image_module_test_file_download($uri) {
|
||||
$default_uri = \Drupal::state()->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++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_image_style_presave().
|
||||
*
|
||||
* Used to save test third party settings in the image style entity.
|
||||
*/
|
||||
function image_module_test_image_style_presave(ImageStyleInterface $style) {
|
||||
$style->setThirdPartySetting('image_module_test', 'foo', 'bar');
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image_module_test\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\HtmlCommand;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
use Drupal\image\ConfigurableImageEffectBase;
|
||||
|
||||
/**
|
||||
* Provides a test effect using Ajax in the configuration form.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_module_test_ajax",
|
||||
* label = @Translation("Ajax test")
|
||||
* )
|
||||
*/
|
||||
class AjaxTestImageEffect extends ConfigurableImageEffectBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'test_parameter' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['test_parameter'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => t('Test parameter'),
|
||||
'#default_value' => $this->configuration['test_parameter'],
|
||||
'#min' => 0,
|
||||
];
|
||||
$form['ajax_refresh'] = [
|
||||
'#type' => 'button',
|
||||
'#value' => $this->t('Ajax refresh'),
|
||||
'#ajax' => ['callback' => [$this, 'ajaxCallback']],
|
||||
];
|
||||
$form['ajax_value'] = [
|
||||
'#id' => 'ajax-value',
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Ajax value'),
|
||||
'#markup' => 'bar',
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback.
|
||||
*/
|
||||
public function ajaxCallback($form, FormStateInterface $form_state) {
|
||||
$item = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Ajax value'),
|
||||
'#markup' => microtime(),
|
||||
];
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new HtmlCommand('#ajax-value', $item));
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
|
||||
$this->configuration['test_parameter'] = $form_state->getValue('test_parameter');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image_module_test\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
use Drupal\image\ImageEffectBase;
|
||||
|
||||
/**
|
||||
* Performs no operation on an image resource.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_module_test_null",
|
||||
* label = @Translation("Image module test")
|
||||
* )
|
||||
*/
|
||||
class NullTestImageEffect extends ImageEffectBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri) {
|
||||
// Unset image dimensions.
|
||||
$dimensions['width'] = $dimensions['height'] = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\image_module_test\Plugin\ImageEffect;
|
||||
|
||||
use Drupal\Core\Image\ImageInterface;
|
||||
use Drupal\image\ImageEffectBase;
|
||||
|
||||
/**
|
||||
* Performs an image operation that depends on the URI of the original image.
|
||||
*
|
||||
* @ImageEffect(
|
||||
* id = "image_module_test_uri_dependent",
|
||||
* label = @Translation("URI dependent test image effect")
|
||||
* )
|
||||
*/
|
||||
class UriDependentTestImageEffect extends ImageEffectBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformDimensions(array &$dimensions, $uri) {
|
||||
$dimensions = $this->getUriDependentDimensions($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEffect(ImageInterface $image) {
|
||||
$dimensions = $this->getUriDependentDimensions($image->getSource());
|
||||
return $image->resize($dimensions['width'], $dimensions['height']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the image dimensions dependent on the image file extension.
|
||||
*
|
||||
* @param string $uri
|
||||
* Original image file URI.
|
||||
*
|
||||
* @return array
|
||||
* Associative array.
|
||||
* - width: Integer with the derivative image width.
|
||||
* - height: Integer with the derivative image height.
|
||||
*/
|
||||
protected function getUriDependentDimensions($uri) {
|
||||
$dimensions = [];
|
||||
$extension = pathinfo($uri, PATHINFO_EXTENSION);
|
||||
switch (strtolower($extension)) {
|
||||
case 'png':
|
||||
$dimensions['width'] = $dimensions['height'] = 100;
|
||||
break;
|
||||
|
||||
case 'gif':
|
||||
$dimensions['width'] = $dimensions['height'] = 50;
|
||||
break;
|
||||
|
||||
default:
|
||||
$dimensions['width'] = $dimensions['height'] = 20;
|
||||
break;
|
||||
|
||||
}
|
||||
return $dimensions;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
name: 'Image test views'
|
||||
type: module
|
||||
description: 'Provides default views for views image tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- image
|
||||
- views
|
|
@ -0,0 +1,77 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- user
|
||||
id: test_image_user_image_data
|
||||
label: test_image_user_image_data
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: users_field_data
|
||||
base_field: uid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access user profiles'
|
||||
cache:
|
||||
type: tag
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
name: name
|
||||
fid: fid
|
||||
info:
|
||||
name:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
fid:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: '-1'
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
relationships:
|
||||
user_picture_target_id:
|
||||
id: user_picture_target_id
|
||||
table: user__user_picture
|
||||
field: user_picture_target_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: 'image from user_picture'
|
||||
required: true
|
||||
plugin_id: standard
|
||||
arguments: { }
|
||||
display_extenders: { }
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\image\Kernel;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Provides a helper method for creating Image fields.
|
||||
*/
|
||||
trait ImageFieldCreationTrait {
|
||||
|
||||
/**
|
||||
* Create a new image field.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the new field (all lowercase), exclude the "field_" prefix.
|
||||
* @param string $type_name
|
||||
* The node type that this field will be added to.
|
||||
* @param array $storage_settings
|
||||
* (optional) A list of field storage settings that will be added to the
|
||||
* defaults.
|
||||
* @param array $field_settings
|
||||
* (optional) A list of instance settings that will be added to the instance
|
||||
* defaults.
|
||||
* @param array $widget_settings
|
||||
* (optional) Widget settings to be added to the widget defaults.
|
||||
* @param array $formatter_settings
|
||||
* (optional) Formatter settings to be added to the formatter defaults.
|
||||
* @param string $description
|
||||
* (optional) A description for the field. Defaults to ''.
|
||||
*/
|
||||
protected function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array(), $description = '') {
|
||||
FieldStorageConfig::create(array(
|
||||
'field_name' => $name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'image',
|
||||
'settings' => $storage_settings,
|
||||
'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1,
|
||||
))->save();
|
||||
|
||||
$field_config = FieldConfig::create([
|
||||
'field_name' => $name,
|
||||
'label' => $name,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $type_name,
|
||||
'required' => !empty($field_settings['required']),
|
||||
'settings' => $field_settings,
|
||||
'description' => $description,
|
||||
]);
|
||||
$field_config->save();
|
||||
|
||||
entity_get_form_display('node', $type_name, 'default')
|
||||
->setComponent($name, array(
|
||||
'type' => 'image_image',
|
||||
'settings' => $widget_settings,
|
||||
))
|
||||
->save();
|
||||
|
||||
entity_get_display('node', $type_name, 'default')
|
||||
->setComponent($name, array(
|
||||
'type' => 'image',
|
||||
'settings' => $formatter_settings,
|
||||
))
|
||||
->save();
|
||||
|
||||
return $field_config;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue