Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -14,8 +14,7 @@ field_default_image:
type: mapping
mapping:
uuid:
type: string
label: 'Image'
type: uuid
alt:
type: label
label: 'Alternative text'

View file

@ -21,7 +21,7 @@ image.style.*:
weight:
type: integer
uuid:
type: string
type: uuid
image.effect.*:
type: mapping
@ -76,6 +76,10 @@ image.effect.image_desaturate:
image.effect.image_scale_and_crop:
type: image_size
label: 'Image scale and crop'
mapping:
anchor:
label: 'Anchor'
type: string
image.settings:
type: config_object

View file

@ -5,12 +5,12 @@
.quickedit-image-dropzone {
background: rgba(116, 183, 255, 0.8);
transition: background .2s;
transition: background 0.2s;
}
.quickedit-image-icon {
margin: 0 0 10px 0;
transition: margin .5s;
transition: margin 0.5s;
}
.quickedit-image-dropzone.hover {
@ -51,9 +51,9 @@
}
@keyframes quickedit-image-spin {
0% {transform: rotate(0deg);}
50% {transform: rotate(180deg);}
100% {transform: rotate(360deg);}
0% { transform: rotate(0deg); }
50% { transform: rotate(180deg); }
100% { transform: rotate(360deg); }
}
.quickedit-image-text {

View file

@ -5,7 +5,6 @@
* Implement an image field, based on the file module's file field.
*/
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Render\Element;
/**
@ -64,7 +63,7 @@ function template_preprocess_image_formatter(&$variables) {
$item = $variables['item'];
// Do not output an empty 'title' attribute.
if (Unicode::strlen($item->title) != 0) {
if (mb_strlen($item->title) != 0) {
$variables['image']['#title'] = $item->title;
}

View file

@ -5,5 +5,5 @@ package: Field types
version: VERSION
core: 8.x
dependencies:
- file
- drupal:file
configure: entity.image_style.collection

View file

@ -67,4 +67,8 @@ function image_requirements($phase) {
*/
function image_update_8201() {
// Empty update to trigger a cache flush.
// Use hook_post_update_NAME() instead to clear the cache. The use of
// hook_update_N() to clear the cache has been deprecated see
// https://www.drupal.org/node/2960601 for more details.
}

View file

@ -7,7 +7,7 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\image\Entity\ImageStyle;
@ -16,6 +16,8 @@ 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.
*
* @see https://www.drupal.org/node/1820974
*/
const IMAGE_STORAGE_NORMAL = 1;
@ -23,6 +25,8 @@ 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.
*
* @see https://www.drupal.org/node/1820974
*/
const IMAGE_STORAGE_OVERRIDE = 2;
@ -30,6 +34,8 @@ 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.
*
* @see https://www.drupal.org/node/1820974
*/
const IMAGE_STORAGE_DEFAULT = 4;
@ -37,6 +43,8 @@ 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.
*
* @see https://www.drupal.org/node/1820974
*/
define('IMAGE_STORAGE_EDITABLE', IMAGE_STORAGE_NORMAL | IMAGE_STORAGE_OVERRIDE);
@ -44,6 +52,8 @@ 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.
*
* @see https://www.drupal.org/node/1820974
*/
define('IMAGE_STORAGE_MODULE', IMAGE_STORAGE_OVERRIDE | IMAGE_STORAGE_DEFAULT);
@ -142,6 +152,9 @@ function image_theme() {
'image_crop_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
'image_scale_and_crop_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
'image_rotate_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
@ -200,7 +213,7 @@ function image_file_download($uri) {
/**
* Implements hook_file_move().
*/
function image_file_move(File $file, File $source) {
function image_file_move(FileInterface $file, FileInterface $source) {
// Delete any image derivatives at the original image path.
image_path_flush($source->getFileUri());
}
@ -208,7 +221,7 @@ function image_file_move(File $file, File $source) {
/**
* Implements hook_ENTITY_TYPE_predelete() for file entities.
*/
function image_file_predelete(File $file) {
function image_file_predelete(FileInterface $file) {
// Delete any image derivatives of this image.
image_path_flush($file->getFileUri());
}
@ -292,10 +305,28 @@ function template_preprocess_image_style(&$variables) {
'#width' => $dimensions['width'],
'#height' => $dimensions['height'],
'#attributes' => $variables['attributes'],
'#uri' => $style->buildUrl($variables['uri']),
'#style_name' => $variables['style_name'],
];
// If the current image toolkit supports this file type, prepare the URI for
// the derivative image. If not, just use the original image resized to the
// dimensions specified by the style.
if ($style->supportsUri($variables['uri'])) {
$variables['image']['#uri'] = $style->buildUrl($variables['uri']);
}
else {
$variables['image']['#uri'] = $variables['uri'];
// Don't render the image by default, but allow other preprocess functions
// to override that if they need to.
$variables['image']['#access'] = FALSE;
// Inform the site builders why their image didn't work.
\Drupal::logger('image')->warning('Could not apply @style image style to @uri because the style does not support it.', [
'@style' => $style->label(),
'@uri' => $variables['uri'],
]);
}
if (isset($variables['alt']) || array_key_exists('alt', $variables)) {
$variables['image']['#alt'] = $variables['alt'];
}
@ -404,7 +435,7 @@ function image_field_storage_config_update(FieldStorageConfigInterface $field_st
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);
file_move($file_new, $directory . $file_new->getFilename());
}
}
@ -442,7 +473,7 @@ function image_field_config_update(FieldConfigInterface $field) {
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);
file_move($file_new, $directory . $file_new->getFilename());
}
}

View file

@ -5,6 +5,7 @@
* Post-update functions for Image.
*/
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
@ -20,3 +21,19 @@ function image_post_update_image_style_dependencies() {
$display->save();
}
}
/**
* Add 'anchor' setting to 'Scale and crop' effects.
*/
function image_post_update_scale_and_crop_effect_add_anchor(&$sandbox = NULL) {
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'image_style', function ($image_style) {
/** @var \Drupal\image\ImageStyleInterface $image_style */
$effects = $image_style->getEffects();
foreach ($effects as $effect) {
if ($effect->getPluginId() === 'image_scale_and_crop') {
return TRUE;
}
}
return FALSE;
});
}

View file

@ -0,0 +1,376 @@
/**
* @file
* Drag+drop based in-place editor for images.
*/
(function($, _, Drupal) {
Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend(
/** @lends Drupal.quickedit.editors.image# */ {
/**
* @constructs
*
* @augments Drupal.quickedit.EditorView
*
* @param {object} options
* Options for the image editor.
*/
initialize(options) {
Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
// Set our original value to our current HTML (for reverting).
this.model.set('originalValue', this.$el.html().trim());
// $.val() callback function for copying input from our custom form to
// the Quick Edit Field Form.
this.model.set('currentValue', function(index, value) {
const matches = $(this)
.attr('name')
.match(/(alt|title)]$/);
if (matches) {
const name = matches[1];
const $toolgroup = $(
`#${options.fieldModel.toolbarView.getMainWysiwygToolgroupId()}`,
);
const $input = $toolgroup.find(
`.quickedit-image-field-info input[name="${name}"]`,
);
if ($input.length) {
return $input.val();
}
}
});
},
/**
* @inheritdoc
*
* @param {Drupal.quickedit.FieldModel} fieldModel
* The field model that holds the state.
* @param {string} state
* The state to change to.
* @param {object} options
* State options, if needed by the state change.
*/
stateChange(fieldModel, state, options) {
const from = fieldModel.previous('state');
switch (state) {
case 'inactive':
break;
case 'candidate':
if (from !== 'inactive') {
this.$el.find('.quickedit-image-dropzone').remove();
this.$el.removeClass('quickedit-image-element');
}
if (from === 'invalid') {
this.removeValidationErrors();
}
break;
case 'highlighted':
break;
case 'activating':
// Defer updating the field model until the current state change has
// propagated, to not trigger a nested state change event.
_.defer(() => {
fieldModel.set('state', 'active');
});
break;
case 'active': {
const self = this;
// Indicate that this element is being edited by Quick Edit Image.
this.$el.addClass('quickedit-image-element');
// Render our initial dropzone element. Once the user reverts changes
// or saves a new image, this element is removed.
const $dropzone = this.renderDropzone(
'upload',
Drupal.t('Drop file here or click to upload'),
);
$dropzone.on('dragenter', function(e) {
$(this).addClass('hover');
});
$dropzone.on('dragleave', function(e) {
$(this).removeClass('hover');
});
$dropzone.on('drop', function(e) {
// Only respond when a file is dropped (could be another element).
if (
e.originalEvent.dataTransfer &&
e.originalEvent.dataTransfer.files.length
) {
$(this).removeClass('hover');
self.uploadImage(e.originalEvent.dataTransfer.files[0]);
}
});
$dropzone.on('click', e => {
// Create an <input> element without appending it to the DOM, and
// trigger a click event. This is the easiest way to arbitrarily
// open the browser's upload dialog.
$('<input type="file">')
.trigger('click')
.on('change', function() {
if (this.files.length) {
self.uploadImage(this.files[0]);
}
});
});
// Prevent the browser's default behavior when dragging files onto
// the document (usually opens them in the same tab).
$dropzone.on('dragover dragenter dragleave drop click', e => {
e.preventDefault();
e.stopPropagation();
});
this.renderToolbar(fieldModel);
break;
}
case 'changed':
break;
case 'saving':
if (from === 'invalid') {
this.removeValidationErrors();
}
this.save(options);
break;
case 'saved':
break;
case 'invalid':
this.showValidationErrors();
break;
}
},
/**
* Validates/uploads a given file.
*
* @param {File} file
* The file to upload.
*/
uploadImage(file) {
// Indicate loading by adding a special class to our icon.
this.renderDropzone(
'upload loading',
Drupal.t('Uploading <i>@file</i>…', { '@file': file.name }),
);
// Build a valid URL for our endpoint.
const fieldID = this.fieldModel.get('fieldID');
const url = Drupal.quickedit.util.buildUrl(
fieldID,
Drupal.url(
'quickedit/image/upload/!entity_type/!id/!field_name/!langcode/!view_mode',
),
);
// Construct form data that our endpoint can consume.
const data = new FormData();
data.append('files[image]', file);
// Construct a POST request to our endpoint.
const self = this;
this.ajax({
type: 'POST',
url,
data,
success(response) {
const $el = $(self.fieldModel.get('el'));
// Indicate that the field has changed - this enables the
// "Save" button.
self.fieldModel.set('state', 'changed');
self.fieldModel.get('entity').set('inTempStore', true);
self.removeValidationErrors();
// Replace our html with the new image. If we replaced our entire
// element with data.html, we would have to implement complicated logic
// like what's in Drupal.quickedit.AppView.renderUpdatedField.
const $content = $(response.html)
.closest('[data-quickedit-field-id]')
.children();
$el.empty().append($content);
},
});
},
/**
* Utility function to make an AJAX request to the server.
*
* In addition to formatting the correct request, this also handles error
* codes and messages by displaying them visually inline with the image.
*
* Drupal.ajax is not called here as the Form API is unused by this
* in-place editor, and our JSON requests/responses try to be
* editor-agnostic. Ideally similar logic and routes could be used by
* modules like CKEditor for drag+drop file uploads as well.
*
* @param {object} options
* Ajax options.
* @param {string} options.type
* The type of request (i.e. GET, POST, PUT, DELETE, etc.)
* @param {string} options.url
* The URL for the request.
* @param {*} options.data
* The data to send to the server.
* @param {function} options.success
* A callback function used when a request is successful, without errors.
*/
ajax(options) {
const defaultOptions = {
context: this,
dataType: 'json',
cache: false,
contentType: false,
processData: false,
error() {
this.renderDropzone(
'error',
Drupal.t('A server error has occurred.'),
);
},
};
const ajaxOptions = $.extend(defaultOptions, options);
const successCallback = ajaxOptions.success;
// Handle the success callback.
ajaxOptions.success = function(response) {
if (response.main_error) {
this.renderDropzone('error', response.main_error);
if (response.errors.length) {
this.model.set('validationErrors', response.errors);
}
this.showValidationErrors();
} else {
successCallback(response);
}
};
$.ajax(ajaxOptions);
},
/**
* Renders our toolbar form for editing metadata.
*
* @param {Drupal.quickedit.FieldModel} fieldModel
* The current Field Model.
*/
renderToolbar(fieldModel) {
const $toolgroup = $(
`#${fieldModel.toolbarView.getMainWysiwygToolgroupId()}`,
);
let $toolbar = $toolgroup.find('.quickedit-image-field-info');
if ($toolbar.length === 0) {
// Perform an AJAX request for extra image info (alt/title).
const fieldID = fieldModel.get('fieldID');
const url = Drupal.quickedit.util.buildUrl(
fieldID,
Drupal.url(
'quickedit/image/info/!entity_type/!id/!field_name/!langcode/!view_mode',
),
);
const self = this;
self.ajax({
type: 'GET',
url,
success(response) {
$toolbar = $(Drupal.theme.quickeditImageToolbar(response));
$toolgroup.append($toolbar);
$toolbar.on('keyup paste', () => {
fieldModel.set('state', 'changed');
});
// Re-position the toolbar, which could have changed size.
fieldModel.get('entity').toolbarView.position();
},
});
}
},
/**
* Renders our dropzone element.
*
* @param {string} state
* The current state of our editor. Only used for visual styling.
* @param {string} text
* The text to display in the dropzone area.
*
* @return {jQuery}
* The rendered dropzone.
*/
renderDropzone(state, text) {
let $dropzone = this.$el.find('.quickedit-image-dropzone');
// If the element already exists, modify its contents.
if ($dropzone.length) {
$dropzone
.removeClass('upload error hover loading')
.addClass(`.quickedit-image-dropzone ${state}`)
.children('.quickedit-image-text')
.html(text);
} else {
$dropzone = $(
Drupal.theme('quickeditImageDropzone', {
state,
text,
}),
);
this.$el.append($dropzone);
}
return $dropzone;
},
/**
* @inheritdoc
*/
revert() {
this.$el.html(this.model.get('originalValue'));
},
/**
* @inheritdoc
*/
getQuickEditUISettings() {
return {
padding: false,
unifiedToolbar: true,
fullWidthToolbar: true,
popup: false,
};
},
/**
* @inheritdoc
*/
showValidationErrors() {
const errors = Drupal.theme('quickeditImageErrors', {
errors: this.model.get('validationErrors'),
});
$(`#${this.fieldModel.toolbarView.getMainWysiwygToolgroupId()}`).append(
errors,
);
this.getEditedElement().addClass('quickedit-validation-error');
// Re-position the toolbar, which could have changed size.
this.fieldModel.get('entity').toolbarView.position();
},
/**
* @inheritdoc
*/
removeValidationErrors() {
$(`#${this.fieldModel.toolbarView.getMainWysiwygToolgroupId()}`)
.find('.quickedit-image-errors')
.remove();
this.getEditedElement().removeClass('quickedit-validation-error');
},
},
);
})(jQuery, _, Drupal);

View file

@ -1,28 +1,17 @@
/**
* @file
* Drag+drop based in-place editor for images.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, _, Drupal) {
'use strict';
Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.image# */{
/**
* @constructs
*
* @augments Drupal.quickedit.EditorView
*
* @param {object} options
* Options for the image editor.
*/
initialize: function (options) {
Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend({
initialize: function initialize(options) {
Drupal.quickedit.EditorView.prototype.initialize.call(this, options);
// Set our original value to our current HTML (for reverting).
this.model.set('originalValue', this.$el.html().trim());
// $.val() callback function for copying input from our custom form to
// the Quick Edit Field Form.
this.model.set('currentValue', function (index, value) {
var matches = $(this).attr('name').match(/(alt|title)]$/);
if (matches) {
@ -35,18 +24,7 @@
}
});
},
/**
* @inheritdoc
*
* @param {Drupal.quickedit.FieldModel} fieldModel
* The field model that holds the state.
* @param {string} state
* The state to change to.
* @param {object} options
* State options, if needed by the state change.
*/
stateChange: function (fieldModel, state, options) {
stateChange: function stateChange(fieldModel, state, options) {
var from = fieldModel.previous('state');
switch (state) {
case 'inactive':
@ -66,60 +44,49 @@
break;
case 'activating':
// Defer updating the field model until the current state change has
// propagated, to not trigger a nested state change event.
_.defer(function () {
fieldModel.set('state', 'active');
});
break;
case 'active':
var self = this;
{
var self = this;
// Indicate that this element is being edited by Quick Edit Image.
this.$el.addClass('quickedit-image-element');
this.$el.addClass('quickedit-image-element');
// Render our initial dropzone element. Once the user reverts changes
// or saves a new image, this element is removed.
var $dropzone = this.renderDropzone('upload', Drupal.t('Drop file here or click to upload'));
var $dropzone = this.renderDropzone('upload', Drupal.t('Drop file here or click to upload'));
$dropzone.on('dragenter', function (e) {
$(this).addClass('hover');
});
$dropzone.on('dragleave', function (e) {
$(this).removeClass('hover');
});
$dropzone.on('drop', function (e) {
// Only respond when a file is dropped (could be another element).
if (e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length) {
$dropzone.on('dragenter', function (e) {
$(this).addClass('hover');
});
$dropzone.on('dragleave', function (e) {
$(this).removeClass('hover');
self.uploadImage(e.originalEvent.dataTransfer.files[0]);
}
});
});
$dropzone.on('click', function (e) {
// Create an <input> element without appending it to the DOM, and
// trigger a click event. This is the easiest way to arbitrarily
// open the browser's upload dialog.
$('<input type="file">')
.trigger('click')
.on('change', function () {
$dropzone.on('drop', function (e) {
if (e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length) {
$(this).removeClass('hover');
self.uploadImage(e.originalEvent.dataTransfer.files[0]);
}
});
$dropzone.on('click', function (e) {
$('<input type="file">').trigger('click').on('change', function () {
if (this.files.length) {
self.uploadImage(this.files[0]);
}
});
});
});
// Prevent the browser's default behavior when dragging files onto
// the document (usually opens them in the same tab).
$dropzone.on('dragover dragenter dragleave drop click', function (e) {
e.preventDefault();
e.stopPropagation();
});
$dropzone.on('dragover dragenter dragleave drop click', function (e) {
e.preventDefault();
e.stopPropagation();
});
this.renderToolbar(fieldModel);
break;
this.renderToolbar(fieldModel);
break;
}
case 'changed':
break;
@ -140,78 +107,40 @@
break;
}
},
uploadImage: function uploadImage(file) {
this.renderDropzone('upload loading', Drupal.t('Uploading <i>@file</i>…', { '@file': file.name }));
/**
* Validates/uploads a given file.
*
* @param {File} file
* The file to upload.
*/
uploadImage: function (file) {
// Indicate loading by adding a special class to our icon.
this.renderDropzone('upload loading', Drupal.t('Uploading <i>@file</i>…', {'@file': file.name}));
// Build a valid URL for our endpoint.
var fieldID = this.fieldModel.get('fieldID');
var url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/upload/!entity_type/!id/!field_name/!langcode/!view_mode'));
// Construct form data that our endpoint can consume.
var data = new FormData();
data.append('files[image]', file);
// Construct a POST request to our endpoint.
var self = this;
this.ajax({
type: 'POST',
url: url,
data: data,
success: function (response) {
success: function success(response) {
var $el = $(self.fieldModel.get('el'));
// Indicate that the field has changed - this enables the
// "Save" button.
self.fieldModel.set('state', 'changed');
self.fieldModel.get('entity').set('inTempStore', true);
self.removeValidationErrors();
// Replace our html with the new image. If we replaced our entire
// element with data.html, we would have to implement complicated logic
// like what's in Drupal.quickedit.AppView.renderUpdatedField.
var $content = $(response.html).closest('[data-quickedit-field-id]').children();
$el.empty().append($content);
}
});
},
/**
* Utility function to make an AJAX request to the server.
*
* In addition to formatting the correct request, this also handles error
* codes and messages by displaying them visually inline with the image.
*
* Drupal.ajax is not called here as the Form API is unused by this
* in-place editor, and our JSON requests/responses try to be
* editor-agnostic. Ideally similar logic and routes could be used by
* modules like CKEditor for drag+drop file uploads as well.
*
* @param {object} options
* Ajax options.
* @param {string} options.type
* The type of request (i.e. GET, POST, PUT, DELETE, etc.)
* @param {string} options.url
* The URL for the request.
* @param {*} options.data
* The data to send to the server.
* @param {function} options.success
* A callback function used when a request is successful, without errors.
*/
ajax: function (options) {
ajax: function ajax(options) {
var defaultOptions = {
context: this,
dataType: 'json',
cache: false,
contentType: false,
processData: false,
error: function () {
error: function error() {
this.renderDropzone('error', Drupal.t('A server error has occurred.'));
}
};
@ -219,7 +148,6 @@
var ajaxOptions = $.extend(defaultOptions, options);
var successCallback = ajaxOptions.success;
// Handle the success callback.
ajaxOptions.success = function (response) {
if (response.main_error) {
this.renderDropzone('error', response.main_error);
@ -227,67 +155,41 @@
this.model.set('validationErrors', response.errors);
}
this.showValidationErrors();
}
else {
} else {
successCallback(response);
}
};
$.ajax(ajaxOptions);
},
/**
* Renders our toolbar form for editing metadata.
*
* @param {Drupal.quickedit.FieldModel} fieldModel
* The current Field Model.
*/
renderToolbar: function (fieldModel) {
renderToolbar: function renderToolbar(fieldModel) {
var $toolgroup = $('#' + fieldModel.toolbarView.getMainWysiwygToolgroupId());
var $toolbar = $toolgroup.find('.quickedit-image-field-info');
if ($toolbar.length === 0) {
// Perform an AJAX request for extra image info (alt/title).
var fieldID = fieldModel.get('fieldID');
var url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/info/!entity_type/!id/!field_name/!langcode/!view_mode'));
var self = this;
self.ajax({
type: 'GET',
url: url,
success: function (response) {
success: function success(response) {
$toolbar = $(Drupal.theme.quickeditImageToolbar(response));
$toolgroup.append($toolbar);
$toolbar.on('keyup paste', function () {
fieldModel.set('state', 'changed');
});
// Re-position the toolbar, which could have changed size.
fieldModel.get('entity').toolbarView.position();
}
});
}
},
/**
* Renders our dropzone element.
*
* @param {string} state
* The current state of our editor. Only used for visual styling.
* @param {string} text
* The text to display in the dropzone area.
*
* @return {jQuery}
* The rendered dropzone.
*/
renderDropzone: function (state, text) {
renderDropzone: function renderDropzone(state, text) {
var $dropzone = this.$el.find('.quickedit-image-dropzone');
// If the element already exists, modify its contents.
if ($dropzone.length) {
$dropzone
.removeClass('upload error hover loading')
.addClass('.quickedit-image-dropzone ' + state)
.children('.quickedit-image-text')
.html(text);
}
else {
$dropzone.removeClass('upload error hover loading').addClass('.quickedit-image-dropzone ' + state).children('.quickedit-image-text').html(text);
} else {
$dropzone = $(Drupal.theme('quickeditImageDropzone', {
state: state,
text: text
@ -297,46 +199,29 @@
return $dropzone;
},
/**
* @inheritdoc
*/
revert: function () {
revert: function revert() {
this.$el.html(this.model.get('originalValue'));
},
/**
* @inheritdoc
*/
getQuickEditUISettings: function () {
return {padding: false, unifiedToolbar: true, fullWidthToolbar: true, popup: false};
getQuickEditUISettings: function getQuickEditUISettings() {
return {
padding: false,
unifiedToolbar: true,
fullWidthToolbar: true,
popup: false
};
},
/**
* @inheritdoc
*/
showValidationErrors: function () {
showValidationErrors: function showValidationErrors() {
var errors = Drupal.theme('quickeditImageErrors', {
errors: this.model.get('validationErrors')
});
$('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId())
.append(errors);
this.getEditedElement()
.addClass('quickedit-validation-error');
// Re-position the toolbar, which could have changed size.
$('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId()).append(errors);
this.getEditedElement().addClass('quickedit-validation-error');
this.fieldModel.get('entity').toolbarView.position();
},
/**
* @inheritdoc
*/
removeValidationErrors: function () {
$('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId())
.find('.quickedit-image-errors').remove();
this.getEditedElement()
.removeClass('quickedit-validation-error');
removeValidationErrors: function removeValidationErrors() {
$('#' + this.fieldModel.toolbarView.getMainWysiwygToolgroupId()).find('.quickedit-image-errors').remove();
this.getEditedElement().removeClass('quickedit-validation-error');
}
});
})(jQuery, _, Drupal);
})(jQuery, _, Drupal);

View file

@ -0,0 +1,92 @@
/**
* @file
* Provides theme functions for image Quick Edit's client-side HTML.
*/
(function(Drupal) {
/**
* Theme function for validation errors of the Image in-place editor.
*
* @param {object} settings
* Settings object used to construct the markup.
* @param {string} settings.errors
* Already escaped HTML representing error messages.
*
* @return {string}
* The corresponding HTML.
*/
Drupal.theme.quickeditImageErrors = function(settings) {
return `<div class="quickedit-image-errors">${settings.errors}</div>`;
};
/**
* Theme function for the dropzone element of the Image module's in-place
* editor.
*
* @param {object} settings
* Settings object used to construct the markup.
* @param {string} settings.state
* State of the upload.
* @param {string} settings.text
* Text to display inline with the dropzone element.
*
* @return {string}
* The corresponding HTML.
*/
Drupal.theme.quickeditImageDropzone = function(settings) {
return (
`<div class="quickedit-image-dropzone ${settings.state}">` +
' <i class="quickedit-image-icon"></i>' +
` <span class="quickedit-image-text">${settings.text}</span>` +
'</div>'
);
};
/**
* Theme function for the toolbar of the Image module's in-place editor.
*
* @param {object} settings
* Settings object used to construct the markup.
* @param {bool} settings.alt_field
* Whether or not the "Alt" field is enabled for this field.
* @param {bool} settings.alt_field_required
* Whether or not the "Alt" field is required for this field.
* @param {string} settings.alt
* The current value for the "Alt" field.
* @param {bool} settings.title_field
* Whether or not the "Title" field is enabled for this field.
* @param {bool} settings.title_field_required
* Whether or not the "Title" field is required for this field.
* @param {string} settings.title
* The current value for the "Title" field.
*
* @return {string}
* The corresponding HTML.
*/
Drupal.theme.quickeditImageToolbar = function(settings) {
let html = '<form class="quickedit-image-field-info">';
if (settings.alt_field) {
html +=
`<div><label for="alt" class="${
settings.alt_field_required ? 'required' : ''
}">${Drupal.t('Alternative text')}</label>` +
`<input type="text" placeholder="${settings.alt}" value="${
settings.alt
}" name="alt" ${settings.alt_field_required ? 'required' : ''}/>` +
' </div>';
}
if (settings.title_field) {
html +=
`<div><label for="title" class="${
settings.title_field_required ? 'form-required' : ''
}">${Drupal.t('Title')}</label>` +
`<input type="text" placeholder="${settings.title}" value="${
settings.title
}" name="title" ${settings.title_field_required ? 'required' : ''}/>` +
'</div>';
}
html += '</form>';
return html;
};
})(Drupal);

View file

@ -1,86 +1,29 @@
/**
* @file
* Provides theme functions for image Quick Edit's client-side HTML.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function (Drupal) {
'use strict';
/**
* Theme function for validation errors of the Image in-place editor.
*
* @param {object} settings
* Settings object used to construct the markup.
* @param {string} settings.errors
* Already escaped HTML representing error messages.
*
* @return {string}
* The corresponding HTML.
*/
Drupal.theme.quickeditImageErrors = function (settings) {
return '<div class="quickedit-image-errors">' + settings.errors + '</div>';
};
/**
* Theme function for the dropzone element of the Image module's in-place
* editor.
*
* @param {object} settings
* Settings object used to construct the markup.
* @param {string} settings.state
* State of the upload.
* @param {string} settings.text
* Text to display inline with the dropzone element.
*
* @return {string}
* The corresponding HTML.
*/
Drupal.theme.quickeditImageDropzone = function (settings) {
return '<div class="quickedit-image-dropzone ' + settings.state + '">' +
' <i class="quickedit-image-icon"></i>' +
' <span class="quickedit-image-text">' + settings.text + '</span>' +
'</div>';
return '<div class="quickedit-image-dropzone ' + settings.state + '">' + ' <i class="quickedit-image-icon"></i>' + (' <span class="quickedit-image-text">' + settings.text + '</span>') + '</div>';
};
/**
* Theme function for the toolbar of the Image module's in-place editor.
*
* @param {object} settings
* Settings object used to construct the markup.
* @param {bool} settings.alt_field
* Whether or not the "Alt" field is enabled for this field.
* @param {bool} settings.alt_field_required
* Whether or not the "Alt" field is required for this field.
* @param {string} settings.alt
* The current value for the "Alt" field.
* @param {bool} settings.title_field
* Whether or not the "Title" field is enabled for this field.
* @param {bool} settings.title_field_required
* Whether or not the "Title" field is required for this field.
* @param {string} settings.title
* The current value for the "Title" field.
*
* @return {string}
* The corresponding HTML.
*/
Drupal.theme.quickeditImageToolbar = function (settings) {
var html = '<form class="quickedit-image-field-info">';
if (settings.alt_field) {
html += ' <div>' +
' <label for="alt" class="' + (settings.alt_field_required ? 'required' : '') + '">' + Drupal.t('Alternative text') + '</label>' +
' <input type="text" placeholder="' + settings.alt + '" value="' + settings.alt + '" name="alt" ' + (settings.alt_field_required ? 'required' : '') + '/>' +
' </div>';
html += '<div><label for="alt" class="' + (settings.alt_field_required ? 'required' : '') + '">' + Drupal.t('Alternative text') + '</label>' + ('<input type="text" placeholder="' + settings.alt + '" value="' + settings.alt + '" name="alt" ' + (settings.alt_field_required ? 'required' : '') + '/>') + ' </div>';
}
if (settings.title_field) {
html += ' <div>' +
' <label for="title" class="' + (settings.title_field_required ? 'form-required' : '') + '">' + Drupal.t('Title') + '</label>' +
' <input type="text" placeholder="' + settings.title + '" value="' + settings.title + '" name="title" ' + (settings.title_field_required ? 'required' : '') + '/>' +
' </div>';
html += '<div><label for="title" class="' + (settings.title_field_required ? 'form-required' : '') + '">' + Drupal.t('Title') + '</label>' + ('<input type="text" placeholder="' + settings.title + '" value="' + settings.title + '" name="title" ' + (settings.title_field_required ? 'required' : '') + '/>') + '</div>';
}
html += '</form>';
return html;
};
})(Drupal);
})(Drupal);

View file

@ -2,6 +2,7 @@ id: d6_imagecache_presets
label: ImageCache Presets
migration_tags:
- Drupal 6
- Configuration
source:
plugin: d6_imagecache_presets
process:

View file

@ -2,12 +2,14 @@ id: d7_image_settings
label: Image configuration
migration_tags:
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- allow_insecure_derivatives
- suppress_itok_output
- image_style_preview_image
source_module: image
process:
suppress_itok_output: suppress_itok_output
allow_insecure_derivatives: allow_insecure_derivatives

View file

@ -2,13 +2,14 @@ id: d7_image_styles
label: Image styles
migration_tags:
- Drupal 7
- Configuration
source:
plugin: d7_image_styles
process:
name: name
label: label
effects:
plugin: iterator
plugin: sub_process
source: effects
process:
id: name

View file

@ -11,6 +11,7 @@ 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\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
@ -79,6 +80,8 @@ class ImageStyleDownloadController extends FileDownloadController {
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response
* The transferred file as response or some error response.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* Thrown when the file request is invalid.
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* Thrown when the user does not have access to the file.
* @throws \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException
@ -104,7 +107,11 @@ class ImageStyleDownloadController extends FileDownloadController {
$valid &= $request->query->get(IMAGE_DERIVATIVE_TOKEN) === $image_style->getPathToken($image_uri);
}
if (!$valid) {
throw new AccessDeniedHttpException();
// Return a 404 (Page Not Found) rather than a 403 (Access Denied) as the
// image token is for DDoS protection rather than access checking. 404s
// are more likely to be cached (e.g. at a proxy) which enhances
// protection from DDoS.
throw new NotFoundHttpException();
}
$derivative_uri = $image_style->buildUri($image_uri);

View file

@ -10,7 +10,7 @@ use Drupal\Core\Image\ImageFactory;
use Drupal\Core\Render\Element\StatusMessages;
use Drupal\Core\Render\RendererInterface;
use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\user\PrivateTempStoreFactory;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@ -23,7 +23,7 @@ class QuickEditImageController extends ControllerBase {
/**
* Stores The Quick Edit tempstore.
*
* @var \Drupal\user\PrivateTempStore
* @var \Drupal\Core\TempStore\PrivateTempStore
*/
protected $tempStore;
@ -48,7 +48,7 @@ class QuickEditImageController extends ControllerBase {
* The renderer.
* @param \Drupal\Core\Image\ImageFactory $image_factory
* The image factory.
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
*/
public function __construct(RendererInterface $renderer, ImageFactory $image_factory, PrivateTempStoreFactory $temp_store_factory) {
@ -64,7 +64,7 @@ class QuickEditImageController extends ControllerBase {
return new static(
$container->get('renderer'),
$container->get('image.factory'),
$container->get('user.private_tempstore')
$container->get('tempstore.private')
);
}

View file

@ -17,12 +17,20 @@ 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"),
* label_collection = @Translation("Image styles"),
* label_singular = @Translation("image style"),
* label_plural = @Translation("image styles"),
* label_count = @PluralTranslation(
* singular = "@count image style",
* plural = "@count image styles",
* ),
* handlers = {
* "form" = {
* "add" = "Drupal\image\Form\ImageStyleAddForm",
@ -274,9 +282,8 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
* {@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);
$image = $this->getImageFactory()->get($original_uri);
if (!$image->isValid()) {
return FALSE;
}
@ -340,6 +347,18 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
return $this;
}
/**
* {@inheritdoc}
*/
public function supportsUri($uri) {
// Only support the URI if its extension is supported by the current image
// toolkit.
return in_array(
mb_strtolower(pathinfo($uri, PATHINFO_EXTENSION)),
$this->getImageFactory()->getSupportedExtensions()
);
}
/**
* {@inheritdoc}
*/
@ -408,6 +427,16 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
return \Drupal::service('plugin.manager.image.effect');
}
/**
* Returns the image factory.
*
* @return \Drupal\Core\Image\ImageFactory
* The image factory.
*/
protected function getImageFactory() {
return \Drupal::service('image.factory');
}
/**
* Gets the Drupal private key.
*

View file

@ -9,6 +9,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an add form for image effects.
*
* @internal
*/
class ImageEffectAddForm extends ImageEffectFormBase {

View file

@ -8,6 +8,8 @@ use Drupal\image\ImageStyleInterface;
/**
* Form for deleting an image effect.
*
* @internal
*/
class ImageEffectDeleteForm extends ConfirmFormBase {
@ -68,7 +70,7 @@ class ImageEffectDeleteForm extends ConfirmFormBase {
*/
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.', ['%name' => $this->imageEffect->label()]));
$this->messenger()->addStatus($this->t('The image effect %name has been deleted.', ['%name' => $this->imageEffect->label()]));
$form_state->setRedirectUrl($this->imageStyle->urlInfo('edit-form'));
}

View file

@ -7,6 +7,8 @@ use Drupal\image\ImageStyleInterface;
/**
* Provides an edit form for image effects.
*
* @internal
*/
class ImageEffectEditForm extends ImageEffectFormBase {

View file

@ -123,7 +123,7 @@ abstract class ImageEffectFormBase extends FormBase {
}
$this->imageStyle->save();
drupal_set_message($this->t('The image effect was successfully applied.'));
$this->messenger()->addStatus($this->t('The image effect was successfully applied.'));
$form_state->setRedirectUrl($this->imageStyle->urlInfo('edit-form'));
}

View file

@ -6,6 +6,8 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Controller for image style addition forms.
*
* @internal
*/
class ImageStyleAddForm extends ImageStyleFormBase {
@ -14,7 +16,7 @@ class ImageStyleAddForm extends ImageStyleFormBase {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
drupal_set_message($this->t('Style %name was created.', ['%name' => $this->entity->label()]));
$this->messenger()->addStatus($this->t('Style %name was created.', ['%name' => $this->entity->label()]));
}
/**

View file

@ -7,6 +7,8 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Creates a form to delete an image style.
*
* @internal
*/
class ImageStyleDeleteForm extends EntityDeleteForm {
@ -23,6 +25,7 @@ class ImageStyleDeleteForm extends EntityDeleteForm {
public function getQuestion() {
return $this->t('Optionally select a style before deleting %style', ['%style' => $this->entity->label()]);
}
/**
* {@inheritdoc}
*/

View file

@ -2,6 +2,7 @@
namespace Drupal\image\Form;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
@ -11,6 +12,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Controller for image style edit form.
*
* @internal
*/
class ImageStyleEditForm extends ImageStyleFormBase {
@ -58,7 +61,7 @@ class ImageStyleEditForm extends ImageStyleFormBase {
$form['preview'] = [
'#type' => 'item',
'#title' => $this->t('Preview'),
'#markup' => drupal_render($preview_arguments),
'#markup' => \Drupal::service('renderer')->render($preview_arguments),
// Render preview above parent elements.
'#weight' => -5,
];
@ -143,7 +146,7 @@ class ImageStyleEditForm extends ImageStyleFormBase {
$new_effect_options = [];
$effects = $this->imageEffectManager->getDefinitions();
uasort($effects, function ($a, $b) {
return strcasecmp($a['id'], $b['id']);
return Unicode::strcasecmp($a['label'], $b['label']);
});
foreach ($effects as $effect => $definition) {
$new_effect_options[$effect] = $definition['label'];
@ -228,7 +231,7 @@ class ImageStyleEditForm extends ImageStyleFormBase {
$effect_id = $this->entity->addImageEffect($effect);
$this->entity->save();
if (!empty($effect_id)) {
drupal_set_message($this->t('The image effect was successfully applied.'));
$this->messenger()->addStatus($this->t('The image effect was successfully applied.'));
}
}
}
@ -251,17 +254,7 @@ class ImageStyleEditForm extends ImageStyleFormBase {
*/
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;
$this->messenger()->addStatus($this->t('Changes to the style have been saved.'));
}
/**

View file

@ -7,6 +7,8 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for image style flush.
*
* @internal
*/
class ImageStyleFlushForm extends EntityConfirmFormBase {
@ -43,7 +45,7 @@ class ImageStyleFlushForm extends EntityConfirmFormBase {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->flush();
drupal_set_message($this->t('The image style %name has been flushed.', ['%name' => $this->entity->label()]));
$this->messenger()->addStatus($this->t('The image style %name has been flushed.', ['%name' => $this->entity->label()]));
$form_state->setRedirectUrl($this->getCancelUrl());
}

View file

@ -50,8 +50,7 @@ interface ImageEffectInterface extends PluginInspectionInterface, ConfigurablePl
public function transformDimensions(array &$dimensions, $uri);
/**
* Returns the extension the derivative would have have after applying this
* image effect.
* Returns the extension of the derivative after applying this image effect.
*
* @param string $extension
* The file extension the derivative has before applying.

View file

@ -42,7 +42,6 @@ interface ImageStyleInterface extends ConfigEntityInterface {
*/
public function setName($name);
/**
* Returns the URI of this image when using this style.
*
@ -194,4 +193,15 @@ interface ImageStyleInterface extends ConfigEntityInterface {
*/
public function deleteImageEffect(ImageEffectInterface $effect);
/**
* Determines if this style can be applied to a given image.
*
* @param string $uri
* The URI of the image.
*
* @return bool
* TRUE if the image is supported, FALSE otherwise.
*/
public function supportsUri($uri);
}

View file

@ -39,9 +39,17 @@ class ImageStyleListBuilder extends ConfigEntityListBuilder {
'url' => $entity->urlInfo('flush-form'),
];
return parent::getDefaultOperations($entity) + [
$operations = parent::getDefaultOperations($entity) + [
'flush' => $flush,
];
// Remove destination URL from the edit link to allow editing image
// effects.
if (isset($operations['edit'])) {
$operations['edit']['url'] = $entity->toUrl('edit-form');
}
return $operations;
}
/**

View file

@ -48,8 +48,11 @@ class PathProcessorImageStyles implements InboundPathProcessorInterface {
if (strpos($path, '/' . $directory_path . '/styles/') === 0) {
$path_prefix = '/' . $directory_path . '/styles/';
}
elseif (strpos($path, '/system/files/styles/') === 0) {
// Check if the string '/system/files/styles/' exists inside the path,
// that means we have a case of private file's image style.
elseif (strpos($path, '/system/files/styles/') !== FALSE) {
$path_prefix = '/system/files/styles/';
$path = substr($path, strpos($path, $path_prefix), strlen($path));
}
else {
return $path;

View file

@ -115,7 +115,7 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
'#empty_option' => t('None (original image)'),
'#options' => $image_styles,
'#description' => $description_link->toRenderable() + [
'#access' => $this->currentUser->hasPermission('administer image styles')
'#access' => $this->currentUser->hasPermission('administer image styles'),
],
];
$link_types = [

View file

@ -3,6 +3,7 @@
namespace Drupal\image\Plugin\Field\FieldType;
use Drupal\Component\Utility\Random;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
@ -262,7 +263,7 @@ class ImageItem extends FileItem {
'#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.'),
'#description' => t('Short description of the image used by screen readers and displayed when the image is not loaded. Enabling this field is recommended.'),
'#weight' => 9,
];
$element['alt_field_required'] = [
@ -313,13 +314,18 @@ class ImageItem extends FileItem {
$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();
if ($this->entity && $this->entity instanceof EntityInterface) {
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();
}
}
}
else {
trigger_error(sprintf("Missing file with ID %s.", $this->target_id), E_USER_WARNING);
}
}
/**
@ -338,8 +344,8 @@ class ImageItem extends FileItem {
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)) {
file_unmanaged_move($tmp_file, $destination);
if ($path = $random->image(\Drupal::service('file_system')->realpath($destination), $min_resolution, $max_resolution)) {
$image = File::create();
$image->setFileUri($path);
$image->setOwnerId(\Drupal::currentUser()->id());
@ -348,7 +354,7 @@ class ImageItem extends FileItem {
$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);
$file = file_move($image, $destination);
$images[$extension][$min_resolution][$max_resolution][$file->id()] = $file;
}
else {
@ -427,7 +433,7 @@ class ImageItem extends FileItem {
$element['default_image']['alt'] = [
'#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.'),
'#description' => t('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility.'),
'#default_value' => $settings['default_image']['alt'],
'#maxlength' => 512,
];

View file

@ -2,9 +2,12 @@
namespace Drupal\image\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Image\ImageFactory;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
use Drupal\image\Entity\ImageStyle;
@ -22,6 +25,36 @@ use Drupal\image\Entity\ImageStyle;
*/
class ImageWidget extends FileWidget {
/**
* The image factory service.
*
* @var \Drupal\Core\Image\ImageFactory
*/
protected $imageFactory;
/**
* Constructs an ImageWidget object.
*
* @param string $plugin_id
* The plugin_id for the widget.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the widget is associated.
* @param array $settings
* The widget settings.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
* The element info manager service.
* @param \Drupal\Core\Image\ImageFactory $image_factory
* The image factory service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $element_info, ImageFactory $image_factory = NULL) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $element_info);
$this->imageFactory = $image_factory ?: \Drupal::service('image.factory');
}
/**
* {@inheritdoc}
*/
@ -112,15 +145,21 @@ class ImageWidget extends FileWidget {
$field_settings = $this->getFieldSettings();
// Add image validation.
$element['#upload_validators']['file_validate_is_image'] = [];
// Add upload resolution validation.
if ($field_settings['max_resolution'] || $field_settings['min_resolution']) {
$element['#upload_validators']['file_validate_image_resolution'] = [$field_settings['max_resolution'], $field_settings['min_resolution']];
}
// If not using custom extension validation, ensure this is an image.
$supported_extensions = ['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);
$extensions = $field_settings['file_extensions'];
$supported_extensions = $this->imageFactory->getSupportedExtensions();
// If using custom extension validation, ensure that the extensions are
// supported by the current image toolkit. Otherwise, validate against all
// toolkit supported extensions.
$extensions = !empty($extensions) ? array_intersect(explode(' ', $extensions), $supported_extensions) : $supported_extensions;
$element['#upload_validators']['file_validate_extensions'][0] = implode(' ', $extensions);
// Add mobile device image capture acceptance.
@ -224,7 +263,7 @@ class ImageWidget extends FileWidget {
'#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.'),
'#description' => t('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility.'),
// @see https://www.drupal.org/node/465106#alt-text
'#maxlength' => 512,
'#weight' => -12,
@ -256,7 +295,8 @@ class ImageWidget extends FileWidget {
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'])) {
$triggering_element = $form_state->getTriggeringElement();
if (empty($triggering_element['#submit']) || !in_array('file_managed_file_submit', $triggering_element['#submit'])) {
// If the image is not there, we do not check for empty values.
$parents = $element['#parents'];
$field = array_pop($parents);

View file

@ -2,7 +2,6 @@
namespace Drupal\image\Plugin\ImageEffect;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Image\ImageInterface;
use Drupal\image\ConfigurableImageEffectBase;
@ -41,7 +40,7 @@ class ConvertImageEffect extends ConfigurableImageEffectBase {
*/
public function getSummary() {
$summary = [
'#markup' => Unicode::strtoupper($this->configuration['extension']),
'#markup' => mb_strtoupper($this->configuration['extension']),
];
$summary += parent::getSummary();
@ -64,7 +63,7 @@ class ConvertImageEffect extends ConfigurableImageEffectBase {
$extensions = \Drupal::service('image.toolkit.manager')->getDefaultToolkit()->getSupportedExtensions();
$options = array_combine(
$extensions,
array_map(['\Drupal\Component\Utility\Unicode', 'strtoupper'], $extensions)
array_map('mb_strtoupper', $extensions)
);
$form['extension'] = [
'#type' => 'select',

View file

@ -13,17 +13,38 @@ use Drupal\Core\Image\ImageInterface;
* 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 {
class ScaleAndCropImageEffect extends CropImageEffect {
/**
* {@inheritdoc}
*/
public function applyEffect(ImageInterface $image) {
if (!$image->scaleAndCrop($this->configuration['width'], $this->configuration['height'])) {
$width = $this->configuration['width'];
$height = $this->configuration['height'];
$scale = max($width / $image->getWidth(), $height / $image->getHeight());
list($x, $y) = explode('-', $this->configuration['anchor']);
$x = image_filter_keyword($x, $image->getWidth() * $scale, $width);
$y = image_filter_keyword($y, $image->getHeight() * $scale, $height);
if (!$image->apply('scale_and_crop', ['x' => $x, 'y' => $y, 'width' => $width, 'height' => $height])) {
$this->logger->error('Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', ['%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()]);
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getSummary() {
$summary = [
'#theme' => 'image_scale_and_crop_summary',
'#data' => $this->configuration,
];
$summary += parent::getSummary();
return $summary;
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\image\Plugin\migrate\cckfield\d7;
@trigger_error('ImageField is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.x. Use \Drupal\image\Plugin\migrate\field\d7\ImageField instead.', E_USER_DEPRECATED);
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "image",
* core = {7},
* source_module = "image",
* destination_module = "file"
* )
*
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.x. Use
* \Drupal\image\Plugin\migrate\field\d7\ImageField instead.
*
* @see https://www.drupal.org/node/2751897
*/
class ImageField extends CckFieldPluginBase {
/**
* {@inheritdoc}
*/
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => [
'target_id' => 'fid',
'alt' => 'alt',
'title' => 'title',
'width' => 'width',
'height' => 'height',
],
];
$migration->mergeProcessOfProperty($field_name, $process);
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace Drupal\image\Plugin\migrate\field\d6;
use Drupal\file\Plugin\migrate\field\d6\FileField;
/**
* @MigrateField(
* id = "imagefield",
* core = {6},
* source_module = "imagefield",
* destination_module = "image"
* )
*/
class ImageField extends FileField {}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\image\Plugin\migrate\field\d7;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
/**
* @MigrateField(
* id = "image",
* core = {7},
* source_module = "image",
* destination_module = "image"
* )
*/
class ImageField extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => [
'target_id' => 'fid',
'alt' => 'alt',
'title' => 'title',
'width' => 'width',
'height' => 'height',
],
];
$migration->mergeProcessOfProperty($field_name, $process);
}
}

View file

@ -10,7 +10,7 @@ use Drupal\migrate\Row;
*
* @MigrateSource(
* id = "d6_imagecache_presets",
* source_provider = "imagecache"
* source_module = "imagecache"
* )
*/
class ImageCachePreset extends DrupalSqlBase {

View file

@ -10,7 +10,7 @@ use Drupal\migrate\Row;
*
* @MigrateSource(
* id = "d7_image_styles",
* source_provider = "image"
* source_module = "image"
* )
*/
class ImageStyles extends DrupalSqlBase {

View file

@ -20,7 +20,7 @@ class ImageStyleRoutes implements ContainerInjectionInterface {
protected $streamWrapperManager;
/**
* Constructs a new PathProcessorImageStyles object.
* Constructs a new ImageStyleRoutes object.
*
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
* The stream wrapper manager service.

View file

@ -2,17 +2,20 @@
namespace Drupal\image\Tests;
@trigger_error('The ' . __NAMESPACE__ . '\ImageFieldTestBase class is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use \Drupal\Tests\image\Functional\ImageFieldTestBase instead. See https://www.drupal.org/node/2863626.', E_USER_DEPRECATED);
use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
use Drupal\simpletest\WebTestBase;
/**
* TODO: Test the following functions.
*
* image.effects.inc:
* In file:
* - image.effects.inc:
* image_style_generate()
* \Drupal\image\ImageStyleInterface::createDerivative()
*
* image.module:
* - image.module:
* image_style_options()
* \Drupal\image\ImageStyleInterface::flush()
* image_filter_keyword()
@ -69,7 +72,7 @@ abstract class ImageFieldTestBase extends WebTestBase {
$edit = [
'title[0][value]' => $this->randomMachineName(),
];
$edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
$edit['files[' . $field_name . '_0]'] = \Drupal::service('file_system')->realpath($image->uri);
$this->drupalPostForm('node/add/' . $type, $edit, t('Preview'));
}
@ -89,11 +92,11 @@ abstract class ImageFieldTestBase extends WebTestBase {
$edit = [
'title[0][value]' => $this->randomMachineName(),
];
$edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
$this->drupalPostForm('node/add/' . $type, $edit, t('Save and publish'));
$edit['files[' . $field_name . '_0]'] = \Drupal::service('file_system')->realpath($image->uri);
$this->drupalPostForm('node/add/' . $type, $edit, t('Save'));
if ($alt) {
// Add alt text.
$this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save and publish'));
$this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save'));
}
// Retrieve ID of the newly created node from the current URL.

View file

@ -0,0 +1,32 @@
{#
/**
* @file
* Default theme implementation for a summary of an image scale and 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 %}

View file

@ -0,0 +1,28 @@
langcode: en
status: true
name: test_scale_and_crop_add_anchor
label: test_scale_and_crop_add_anchor
effects:
8c7170c9-5bcc-40f9-8698-f88a8be6d434:
uuid: 8c7170c9-5bcc-40f9-8698-f88a8be6d434
id: image_scale_and_crop
weight: 1
data:
width: 100
height: 100
a8d83b12-abc6-40c8-9c2f-78a4e421cf97:
uuid: a8d83b12-abc6-40c8-9c2f-78a4e421cf97
id: image_scale_and_crop
weight: 2
data:
width: 100
height: 100
anchor: left-top
1bffd475-19d0-439a-b6a1-7e5850ce40f9:
uuid: 1bffd475-19d0-439a-b6a1-7e5850ce40f9
id: image_rotate
weight: 3
data:
degrees: 180
bgcolor: ''
random: false

View file

@ -0,0 +1,19 @@
<?php
/**
* @file
* Test fixture.
*/
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
$connection->insert('config')
->fields([
'collection' => '',
'name' => 'image.style.test_scale_and_crop_add_anchor',
'data' => serialize(Yaml::decode(file_get_contents('core/modules/image/tests/fixtures/update/image.image_style.test_scale_and_crop_add_anchor.yml'))),
])
->execute();

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\image_module_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Empty renderer for a dummy field with an AJAX handler.
*
* @FieldFormatter(
* id = "image_module_test_dummy_ajax_formatter",
* module = "image_module_test",
* label = @Translation("Dummy AJAX"),
* field_types= {
* "image_module_test_dummy_ajax"
* }
* )
*/
class DummyAjaxFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = t('Renders nothing');
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$element = [];
return $element;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\image_module_test\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines a dummy field containing an AJAX handler.
*
* @FieldType(
* id = "image_module_test_dummy_ajax",
* label = @Translation("Dummy AJAX"),
* description = @Translation("A field containing an AJAX handler."),
* category = @Translation("Field"),
* default_widget = "image_module_test_dummy_ajax_widget",
* default_formatter = "image_module_test_dummy_ajax_formatter"
* )
*/
class DummyAjaxItem extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'value' => [
'type' => 'varchar',
'length' => 255,
],
],
];
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
return empty($this->get('value')->getValue());
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Dummy string value'));
return $properties;
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\image_module_test\Plugin\Field\FieldWidget;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Default widget for Dummy AJAX test.
*
* @FieldWidget(
* id = "image_module_test_dummy_ajax_widget",
* label = @Translation("Dummy AJAX widget"),
* field_types = {
* "image_module_test_dummy_ajax"
* },
* multiple_values = TRUE,
* )
*/
class DummyAjaxWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['select_widget'] = [
'#type' => 'select',
'#title' => $this->t('Dummy select'),
'#options' => ['pow' => 'Pow!', 'bam' => 'Bam!'],
'#required' => TRUE,
'#ajax' => [
'callback' => get_called_class() . '::dummyAjaxCallback',
'effect' => 'fade',
],
];
return $element;
}
/**
* Ajax callback for Dummy AJAX test.
*
* @param array $form
* The build form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* Ajax response.
*/
public static function dummyAjaxCallback(array &$form, FormStateInterface $form_state) {
return new AjaxResponse();
}
}

View file

@ -5,5 +5,5 @@ package: Testing
version: VERSION
core: 8.x
dependencies:
- image
- views
- drupal:image
- drupal:views

View file

@ -1,17 +1,23 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\file\Entity\File;
use Drupal\simpletest\WebTestBase;
use Drupal\image\Entity\ImageStyle;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the file move function for images and image styles.
*
* @group image
*/
class FileMoveTest extends WebTestBase {
class FileMoveTest extends BrowserTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* Modules to enable.

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\Tests\image\Functional\Hal;
use Drupal\Tests\image\Functional\Rest\ImageStyleResourceTestBase;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group hal
*/
class ImageStyleHalJsonAnonTest extends ImageStyleResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\image\Functional\Hal;
use Drupal\Tests\image\Functional\Rest\ImageStyleResourceTestBase;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class ImageStyleHalJsonBasicAuthTest extends ImageStyleResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal', 'basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\image\Functional\Hal;
use Drupal\Tests\image\Functional\Rest\ImageStyleResourceTestBase;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class ImageStyleHalJsonCookieTest extends ImageStyleResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -1,13 +1,14 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\FormattableMarkup;
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;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests creation, deletion, and editing of image styles and effects.
@ -16,6 +17,11 @@ use Drupal\file\Entity\File;
*/
class ImageAdminStylesTest extends ImageFieldTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* Given an image style, generate an image.
*/
@ -146,7 +152,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$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.', ['%field' => $field, '%effect' => $effect->getPluginId(), '%value' => $value]));
$this->assertEqual($value, $effect_configuration['data'][$field], new FormattableMarkup('The %field field in the %effect effect has the correct value of %value.', ['%field' => $field, '%effect' => $effect->getPluginId(), '%value' => $value]));
}
}
@ -193,7 +199,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$image_path = $this->createSampleImage($style);
$this->assertEqual($this->getImageCount($style), 1, format_string('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path]));
$this->drupalPostForm($style_path, $edit, t('Update style'));
$this->drupalPostForm($style_path, $edit, t('Save'));
// Note that after changing the style name, the style path is changed.
$style_path = 'admin/config/media/image-styles/manage/' . $style_name;
@ -203,6 +209,10 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$this->assertTitle(t('Edit style @name | Drupal', ['@name' => $style_label]));
$this->assertResponse(200, format_string('Image style %original renamed to %new', ['%original' => $style->id(), '%new' => $style_name]));
// Check that the available image effects are properly sorted.
$option = $this->xpath('//select[@id=:id]//option', [':id' => 'edit-new--2']);
$this->assertEquals('Ajax test', $option[1]->getText(), '"Ajax test" is the first selectable effect.');
// 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.
@ -286,47 +296,6 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
}
/**
* 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.
*/
@ -368,7 +337,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
'name' => $new_style_name,
'label' => $new_style_label,
];
$this->drupalPostForm($style_path . $style_name, $edit, t('Update style'));
$this->drupalPostForm($style_path . $style_name, $edit, t('Save'));
$this->assertText(t('Changes to the style have been saved.'), format_string('Style %name was renamed to %new_name.', ['%name' => $style_name, '%new_name' => $new_style_name]));
$this->drupalGet('node/' . $nid);
@ -418,9 +387,20 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
// Edit the scale effect that was just added.
$this->clickLink(t('Edit'));
$this->drupalPostForm(NULL, ['data[width]' => '24', 'data[height]' => '19'], t('Update effect'));
$this->drupalPostForm(NULL, ['new' => 'image_scale'], t('Add'));
// Add another scale effect and make sure both exist.
// Add another scale effect and make sure both exist. Click through from
// the overview to make sure that it is possible to add new effect then.
$this->drupalGet('admin/config/media/image-styles');
$rows = $this->xpath('//table/tbody/tr');
$i = 0;
foreach ($rows as $row) {
if ($row->find('css', 'td')->getText() === 'Test style scale edit scale') {
$this->clickLink('Edit', $i);
break;
}
$i++;
}
$this->drupalPostForm(NULL, ['new' => 'image_scale'], t('Add'));
$this->drupalPostForm(NULL, ['data[width]' => '12', 'data[height]' => '19'], t('Add effect'));
$this->assertText(t('Scale 24×19'));
$this->assertText(t('Scale 12×19'));

View file

@ -1,16 +1,22 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\image\Entity\ImageStyle;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests that images have correct dimensions when styled.
*
* @group image
*/
class ImageDimensionsTest extends WebTestBase {
class ImageDimensionsTest extends BrowserTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* Modules to enable.
@ -173,7 +179,6 @@ class ImageDimensionsTest extends WebTestBase {
$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 = [
'id' => 'image_crop',
@ -208,14 +213,18 @@ class ImageDimensionsTest extends WebTestBase {
$effect_id = $style->addImageEffect($effect);
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="41" height="41" alt="" class="image-style-test" />');
// @todo Uncomment this once
// https://www.drupal.org/project/drupal/issues/2670966 is resolved.
// $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);
// @todo Uncomment this once
// https://www.drupal.org/project/drupal/issues/2670966 is resolved.
// $this->assertEqual($image_file->getWidth(), 41);
// $this->assertEqual($image_file->getHeight(), 41);
$effect_plugin = $style->getEffect($effect_id);
$style->deleteImageEffect($effect_plugin);
@ -277,9 +286,9 @@ class ImageDimensionsTest extends WebTestBase {
/**
* 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
* Function 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.
*/

View file

@ -3,7 +3,7 @@
namespace Drupal\Tests\image\Functional;
use Drupal\image\Entity\ImageStyle;
use Drupal\system\Tests\Image\ToolkitTestBase;
use Drupal\FunctionalTests\Image\ToolkitTestBase;
/**
* Tests that the image effects pass parameters to the toolkit correctly.
@ -111,8 +111,29 @@ class ImageEffectsTest extends ToolkitTestBase {
// 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');
$this->assertEqual($calls['scale_and_crop'][0][0], 7.5, 'X was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][1], 0, 'Y was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][2], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][3], 10, 'Height was computed and passed correctly');
}
/**
* Test the image_scale_and_crop_effect() function with an anchor.
*/
public function testScaleAndCropEffectWithAnchor() {
$this->assertImageEffect('image_scale_and_crop', [
'anchor' => 'top-1',
'width' => 5,
'height' => 10,
]);
$this->assertToolkitOperationsCalled(['scale_and_crop']);
// Check the parameters.
$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['scale_and_crop'][0][0], 0, 'X was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][1], 1, 'Y was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][2], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][3], 10, 'Height was computed and passed correctly');
}
/**

View file

@ -1,19 +1,29 @@
<?php
namespace Drupal\image\Tests;
use Drupal\Component\Utility\Unicode;
namespace Drupal\Tests\image\Functional;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\EntityViewTrait;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests setting up default images both to the field and field field.
* Tests setting up default images both to the field and field storage.
*
* @group image
*/
class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
use EntityViewTrait {
buildEntityView as drupalBuildEntityView;
}
/**
* Modules to enable.
*
@ -22,7 +32,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
public static $modules = ['field_ui'];
/**
* Tests CRUD for fields and fields fields with default images.
* Tests CRUD for fields and field storages with default images.
*/
public function testDefaultImages() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
@ -37,16 +47,17 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
$file->save();
}
$default_images = [];
foreach (['field', 'field', 'field2', 'field_new', 'field_new'] as $image_target) {
foreach (['field_storage', 'field', 'field2', 'field_storage_new', 'field_new', 'field_storage_private', 'field_private'] 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.
// Create an image field storage and add a field to the article content
// type.
$field_name = strtolower($this->randomMachineName());
$storage_settings['default_image'] = [
'uuid' => $default_images['field']->uuid(),
'uuid' => $default_images['field_storage']->uuid(),
'alt' => '',
'title' => '',
'width' => 0,
@ -72,11 +83,11 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
$field_storage = $field->getFieldStorageDefinition();
// The field default image id should be 1.
$this->assertEqual($field_storage->getSetting('default_image')['uuid'], $default_images['field']->uuid());
// The field storage default image id should be 1.
$this->assertEqual($field_storage->getSetting('default_image')['uuid'], $default_images['field_storage']->uuid());
// Also test \Drupal\field\Entity\FieldStorageConfig::getSettings().
$this->assertEqual($field_storage->getSettings()['default_image']['uuid'], $default_images['field']->uuid());
$this->assertEqual($field_storage->getSettings()['default_image']['uuid'], $default_images['field_storage']->uuid());
// Add another field with another default image to the page content type.
$field2 = FieldConfig::create([
@ -104,15 +115,16 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
->setComponent($field_name)
->save();
// Confirm the defaults are present on the article field settings form.
// Confirm the defaults are present on the article field storage 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(),
$default_images['field_storage']->id(),
format_string(
'Article image field default equals expected file ID of @fid.',
['@fid' => $default_images['field']->id()]
'Article image field storage default equals expected file ID of @fid.',
['@fid' => $default_images['field_storage']->id()]
)
);
// Confirm the defaults are present on the article field edit form.
@ -121,19 +133,19 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
'//input[@name="settings[default_image][uuid][fids]"]',
$default_images['field']->id(),
format_string(
'Article image field field default equals expected file ID of @fid.',
'Article image field default equals expected file ID of @fid.',
['@fid' => $default_images['field']->id()]
)
);
// Confirm the defaults are present on the page field settings form.
// Confirm the defaults are present on the page field storage 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(),
$default_images['field_storage']->id(),
format_string(
'Page image field default equals expected file ID of @fid.',
['@fid' => $default_images['field']->id()]
'Page image field storage default equals expected file ID of @fid.',
['@fid' => $default_images['field_storage']->id()]
)
);
// Confirm the defaults are present on the page field edit form.
@ -143,7 +155,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
'//input[@name="settings[default_image][uuid][fids]"]',
$default_images['field2']->id(),
format_string(
'Page image field field default equals expected file ID of @fid.',
'Page image field default equals expected file ID of @fid.',
['@fid' => $default_images['field2']->id()]
)
);
@ -181,22 +193,23 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
// 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();
$default_image_settings['uuid'] = $default_images['field_storage_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.
// Confirm that the new default is used on the article field storage
// 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(),
$default_images['field_storage_new']->id(),
format_string(
'Updated image field default equals expected file ID of @fid.',
['@fid' => $default_images['field_new']->id()]
'Updated image field storage default equals expected file ID of @fid.',
['@fid' => $default_images['field_storage_new']->id()]
)
);
// Reload the nodes and confirm the field field defaults are used.
// Reload the nodes and confirm the field defaults are used.
$node_storage->resetCache([$article->id(), $page->id()]);
$article_built = $this->drupalBuildEntityView($article = $node_storage->load($article->id()));
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
@ -217,20 +230,19 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
)
);
// Upload a new default for the article's field field.
// Upload a new default for the article's 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.
// Confirm the new 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.',
'Updated article image field default equals expected file ID of @fid.',
['@fid' => $default_images['field_new']->id()]
)
);
@ -264,31 +276,31 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
$this->drupalGet('node/add/article');
$this->assertRaw($file->getFilename());
// Remove the instance default from articles.
// Remove the field 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.
// Confirm the article 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.'
'Updated article image field default has been successfully removed.'
);
// Reload the nodes.
$node_storage->resetCache([$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.
// Confirm the article uses the new field storage (not field) default.
$this->assertEqual(
$article_built[$field_name][0]['#item']->target_id,
$default_images['field_new']->id(),
$default_images['field_storage_new']->id(),
format_string(
'An existing article node without an image has the expected default image file ID of @fid.',
['@fid' => $default_images['field_new']->id()]
['@fid' => $default_images['field_storage_new']->id()]
)
);
// Confirm the page remains unchanged.
@ -302,28 +314,67 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
);
$non_image = $this->drupalGetTestFiles('text');
$this->drupalPostForm(NULL, ['files[settings_default_image_uuid]' => drupal_realpath($non_image[0]->uri)], t("Upload"));
$this->drupalPostForm(NULL, ['files[settings_default_image_uuid]' => \Drupal::service('file_system')->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());
$file = File::load($default_images['field_storage_new']->id());
$this->drupalGet('node/add/article');
$this->assertRaw($file->getFilename());
// Change the default image for the field storage and also change the upload
// destination to the private filesystem at the same time.
$default_image_settings = $field_storage->getSetting('default_image');
$default_image_settings['uuid'] = $default_images['field_storage_private']->uuid();
$field_storage->setSetting('default_image', $default_image_settings);
$field_storage->setSetting('uri_scheme', 'private');
$field_storage->save();
// Confirm that the new default is used on the article field storage
// 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_storage_private']->id(),
format_string(
'Updated image field storage default equals expected file ID of @fid.',
['@fid' => $default_images['field_storage_private']->id()]
)
);
// Upload a new default for the article's field after setting the field
// storage upload destination to 'private'.
$default_image_settings = $field->getSetting('default_image');
$default_image_settings['uuid'] = $default_images['field_private']->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_private']->id(),
format_string(
'Updated article image field default equals expected file ID of @fid.',
['@fid' => $default_images['field_private']->id()]
)
);
}
/**
* Tests image field and field having an invalid default image.
* Tests image field and field storage having an invalid default image.
*/
public function testInvalidDefaultImage() {
$field_storage = FieldStorageConfig::create([
'field_name' => Unicode::strtolower($this->randomMachineName()),
'field_name' => mb_strtolower($this->randomMachineName()),
'entity_type' => 'node',
'type' => 'image',
'settings' => [
'default_image' => [
'uuid' => 100000,
]
],
],
]);
$field_storage->save();
@ -338,7 +389,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
'settings' => [
'default_image' => [
'uuid' => 100000,
]
],
],
]);
$field->save();

View file

@ -1,9 +1,11 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\user\RoleInterface;
use Drupal\image\Entity\ImageStyle;
@ -14,6 +16,12 @@ use Drupal\image\Entity\ImageStyle;
*/
class ImageFieldDisplayTest extends ImageFieldTestBase {
use AssertPageCacheContextsAndTagsTrait;
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
protected $dumpHeaders = TRUE;
/**
@ -54,7 +62,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$this->drupalGet("admin/structure/types/manage/article/display");
// Test for existence of link to image styles configuration.
$this->drupalPostAjaxForm(NULL, [], "{$field_name}_settings_edit");
$this->drupalPostForm(NULL, [], "{$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.
@ -65,7 +73,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$this->drupalGet("admin/structure/types/manage/article/display");
// Test for absence of link to image styles configuration.
$this->drupalPostAjaxForm(NULL, [], "{$field_name}_settings_edit");
$this->drupalPostForm(NULL, [], "{$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
@ -258,8 +266,11 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$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.');
// Verify that the optional fields alt & title are saved & filled.
$this->assertFieldByName($field_name . '[0][alt]', $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([$nid]);
@ -282,7 +293,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$field_name . '[0][alt]' => $image['#alt'],
$field_name . '[0][title]' => $image['#title'],
];
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
$default_output = str_replace("\n", NULL, $renderer->renderRoot($image));
$this->assertRaw($default_output, 'Image displayed using user supplied alt and title attributes.');
@ -292,7 +303,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$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'));
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
$schema = $field->getFieldStorageDefinition()->getSchema();
$this->assertRaw(t('Alternative text cannot be longer than %max characters but is currently %length characters long.', [
'%max' => $schema['columns']['alt']['length'],
@ -312,21 +323,21 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
// @see FileWidget::formMultipleElements().
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.' . $field_name . '/storage', ['cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED], t('Save field settings'));
$edit = [
'files[' . $field_name . '_1][]' => drupal_realpath($test_image->uri),
'files[' . $field_name . '_1][]' => \Drupal::service('file_system')->realpath($test_image->uri),
];
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
// Add the required alt text.
$this->drupalPostForm(NULL, [$field_name . '[1][alt]' => $alt], t('Save and keep published'));
$this->drupalPostForm(NULL, [$field_name . '[1][alt]' => $alt], t('Save'));
$this->assertText(format_string('Article @title has been updated.', ['@title' => $node->getTitle()]));
// Assert ImageWidget::process() calls FieldWidget::process().
$this->drupalGet('node/' . $node->id() . '/edit');
$edit = [
'files[' . $field_name . '_2][]' => drupal_realpath($test_image->uri),
'files[' . $field_name . '_2][]' => \Drupal::service('file_system')->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">');
$this->drupalPostForm(NULL, $edit, $field_name . '_2_upload_button');
$this->assertSession()->elementNotExists('css', 'input[name="files[' . $field_name . '_2][]"]');
$this->assertSession()->elementExists('css', 'input[name="files[' . $field_name . '_3][]"]');
}
/**
@ -357,7 +368,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$title = $this->randomString(1024);
$edit = [
// Get the path of the 'image-test.png' file.
'files[settings_default_image_uuid]' => drupal_realpath($images[0]->uri),
'files[settings_default_image_uuid]' => \Drupal::service('file_system')->realpath($images[0]->uri),
'settings[default_image][alt]' => $alt,
'settings[default_image][title]' => $title,
];
@ -410,10 +421,11 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$this->assertRaw($image_output, 'User supplied image is displayed.');
// Remove default image from the field and make sure it is no longer used.
$edit = [
'settings[default_image][uuid][fids]' => 0,
];
$this->drupalPostForm("admin/structure/types/manage/article/fields/node.article.$field_name/storage", $edit, t('Save field settings'));
// Can't use fillField cause Mink can't fill hidden fields.
$this->drupalGet("admin/structure/types/manage/article/fields/node.article.$field_name/storage");
$this->getSession()->getPage()->find('css', 'input[name="settings[default_image][uuid][fids]"]')->setValue(0);
$this->getSession()->getPage()->pressButton(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);
@ -426,7 +438,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
// Add a default image to the new field.
$edit = [
// Get the path of the 'image-test.gif' file.
'files[settings_default_image_uuid]' => drupal_realpath($images[2]->uri),
'files[settings_default_image_uuid]' => \Drupal::service('file_system')->realpath($images[2]->uri),
'settings[default_image][alt]' => $alt,
'settings[default_image][title]' => $title,
];

View file

@ -8,11 +8,12 @@ use Drupal\Tests\BrowserTestBase;
/**
* TODO: Test the following functions.
*
* image.effects.inc:
* In file:
* - image.effects.inc:
* image_style_generate()
* \Drupal\image\ImageStyleInterface::createDerivative()
*
* image.module:
* - image.module:
* image_style_options()
* \Drupal\image\ImageStyleInterface::flush()
* image_filter_keyword()
@ -66,7 +67,7 @@ abstract class ImageFieldTestBase extends BrowserTestBase {
$edit = [
'title[0][value]' => $this->randomMachineName(),
];
$edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
$edit['files[' . $field_name . '_0]'] = \Drupal::service('file_system')->realpath($image->uri);
$this->drupalPostForm('node/add/' . $type, $edit, t('Preview'));
}
@ -86,11 +87,11 @@ abstract class ImageFieldTestBase extends BrowserTestBase {
$edit = [
'title[0][value]' => $this->randomMachineName(),
];
$edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
$this->drupalPostForm('node/add/' . $type, $edit, t('Save and publish'));
$edit['files[' . $field_name . '_0]'] = \Drupal::service('file_system')->realpath($image->uri);
$this->drupalPostForm('node/add/' . $type, $edit, t('Save'));
if ($alt) {
// Add alt text.
$this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save and publish'));
$this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save'));
}
// Retrieve ID of the newly created node from the current URL.

View file

@ -1,6 +1,8 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests validation functions such as min/max resolution.
@ -8,6 +10,66 @@ namespace Drupal\image\Tests;
* @group image
*/
class ImageFieldValidateTest extends ImageFieldTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* Test image validity.
*/
public function testValid() {
$file_system = $this->container->get('file_system');
$image_files = $this->drupalGetTestFiles('image');
$field_name = strtolower($this->randomMachineName());
$this->createImageField($field_name, 'article', [], ['file_directory' => 'test-upload']);
$expected_path = 'public://test-upload';
// Create alt text for the image.
$alt = $this->randomMachineName();
// Create a node with a valid image.
$node = $this->uploadNodeImage($image_files[0], $field_name, 'article', $alt);
$this->assertTrue(file_exists($expected_path . '/' . $image_files[0]->filename));
// Remove the image.
$this->drupalPostForm('node/' . $node . '/edit', [], t('Remove'));
$this->drupalPostForm(NULL, [], t('Save'));
// Get invalid image test files from simpletest.
$files = file_scan_directory(drupal_get_path('module', 'simpletest') . '/files', '/invalid-img-.*/');
$invalid_image_files = [];
foreach ($files as $file) {
$invalid_image_files[$file->filename] = $file;
}
// Try uploading a zero-byte image.
$zero_size_image = $invalid_image_files['invalid-img-zero-size.png'];
$edit = [
'files[' . $field_name . '_0]' => $file_system->realpath($zero_size_image->uri),
];
$this->drupalPostForm('node/' . $node . '/edit', $edit, t('Upload'));
$this->assertFalse(file_exists($expected_path . '/' . $zero_size_image->filename));
// Try uploading an invalid image.
$invalid_image = $invalid_image_files['invalid-img-test.png'];
$edit = [
'files[' . $field_name . '_0]' => $file_system->realpath($invalid_image->uri),
];
$this->drupalPostForm('node/' . $node . '/edit', $edit, t('Upload'));
$this->assertFalse(file_exists($expected_path . '/' . $invalid_image->filename));
// Upload a valid image again.
$valid_image = $image_files[0];
$edit = [
'files[' . $field_name . '_0]' => $file_system->realpath($valid_image->uri),
];
$this->drupalPostForm('node/' . $node . '/edit', $edit, t('Upload'));
$this->assertTrue(file_exists($expected_path . '/' . $valid_image->filename));
}
/**
* Test min/max resolution settings.
*/
@ -19,27 +81,27 @@ class ImageFieldValidateTest extends ImageFieldTestBase {
];
$min_resolution = [
'width' => 50,
'height' => 50
'height' => 50,
];
$max_resolution = [
'width' => 100,
'height' => 100
'height' => 100,
];
$no_height_min_resolution = [
'width' => 50,
'height' => NULL
'height' => NULL,
];
$no_height_max_resolution = [
'width' => 100,
'height' => NULL
'height' => NULL,
];
$no_width_min_resolution = [
'width' => NULL,
'height' => 50
'height' => 50,
];
$no_width_max_resolution = [
'width' => NULL,
'height' => 100
'height' => 100,
];
$field_settings = [
0 => $this->getFieldSettings($min_resolution, $max_resolution),
@ -62,6 +124,7 @@ class ImageFieldValidateTest extends ImageFieldTestBase {
}
if ($image_file->getWidth() < $min_resolution['width']) {
$image_that_is_too_small = $image;
$image_that_is_too_small_file = $image_file;
}
if ($image_that_is_too_small && $image_that_is_too_big) {
break;
@ -69,7 +132,11 @@ class ImageFieldValidateTest extends ImageFieldTestBase {
}
$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->assertRaw(t('The image is too small. The minimum dimensions are %dimensions pixels and the image size is %widthx%height pixels.', [
'%dimensions' => '50x50',
'%width' => $image_that_is_too_small_file->getWidth(),
'%height' => $image_that_is_too_small_file->getHeight(),
]));
$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');
@ -119,7 +186,7 @@ class ImageFieldValidateTest extends ImageFieldTestBase {
$edit = [
'title[0][value]' => $this->randomMachineName(),
];
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$this->assertNoText(t('Alternative text field is required.'));
$this->assertNoText(t('Title field is required.'));
@ -132,7 +199,7 @@ class ImageFieldValidateTest extends ImageFieldTestBase {
$edit = [
'title[0][value]' => $this->randomMachineName(),
];
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
$this->drupalPostForm('node/add/article', $edit, t('Save'));
$this->assertNoText(t('Alternative text field is required.'));
$this->assertNoText(t('Title field is required.'));

View file

@ -2,6 +2,8 @@
namespace Drupal\Tests\image\Functional;
use Drupal\field\Entity\FieldConfig;
/**
* Tests the image field widget.
*
@ -27,6 +29,22 @@ class ImageFieldWidgetTest extends ImageFieldTestBase {
$this->assertNotEqual(0, count($this->xpath('//div[contains(@class, "field--widget-image-image")]')), 'Image field widget found on add/node page', 'Browser');
$this->assertNotEqual(0, count($this->xpath('//input[contains(@accept, "image/*")]')), 'Image field widget limits accepted files.', 'Browser');
$this->assertNoText('Image test on [site:name]');
// Check for allowed image file extensions - default.
$this->assertText('Allowed types: png gif jpg jpeg.');
// Try adding to the field config an unsupported extension, should not
// appear in the allowed types.
$field_config = FieldConfig::loadByName('node', 'article', $field_name);
$field_config->setSetting('file_extensions', 'png gif jpg jpeg tiff')->save();
$this->drupalGet('node/add/article');
$this->assertText('Allowed types: png gif jpg jpeg.');
// Add a supported extension and remove some supported ones, we should see
// the intersect of those entered in field config with those supported.
$field_config->setSetting('file_extensions', 'png jpe tiff')->save();
$this->drupalGet('node/add/article');
$this->assertText('Allowed types: png jpe.');
}
}

View file

@ -1,8 +1,9 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\file\Entity\File;
use Drupal\Tests\TestFileCreationTrait;
/**
* Uploads images to translated nodes.
@ -11,6 +12,11 @@ use Drupal\file\Entity\File;
*/
class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* {@inheritdoc}
*/
@ -29,6 +35,9 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
protected function setUp() {
parent::setUp();
// This test expects unused managed files to be marked as a temporary file.
$this->config('file.settings')->set('make_unused_managed_files_temporary', TRUE)->save();
// Create the "Basic page" node type.
// @todo Remove the disabling of new revision creation in
// https://www.drupal.org/node/1239558.
@ -92,7 +101,7 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
// Edit the node to upload a file.
$edit = [];
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[0]->uri);
$edit[$name] = \Drupal::service('file_system')->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'));
@ -105,7 +114,7 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
$edit = [];
$edit['title[0][value]'] = 'Scarlett Johansson';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[1]->uri);
$edit[$name] = \Drupal::service('file_system')->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)'));
@ -137,7 +146,7 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
$edit = [];
$edit['title[0][value]'] = 'Akiko Takeshita';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[2]->uri);
$edit[$name] = \Drupal::service('file_system')->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)'));
@ -172,7 +181,7 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
$edit = [];
$edit['title[0][value]'] = 'Giovanni Ribisi';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[3]->uri);
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('image')[3]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$name = $this->fieldName . '[0][alt]';

View file

@ -1,8 +1,9 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\image\Entity\ImageStyle;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests flushing of image styles.
@ -11,6 +12,11 @@ use Drupal\image\Entity\ImageStyle;
*/
class ImageStyleFlushTest extends ImageFieldTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* Given an image style and a wrapper, generate an image.
*/
@ -97,7 +103,7 @@ class ImageStyleFlushTest extends ImageFieldTestBase {
}
$this->drupalPostForm($style_path . '/effects/' . $uuids['image_scale'] . '/delete', [], t('Delete'));
$this->assertResponse(200);
$this->drupalPostForm($style_path, [], t('Update style'));
$this->drupalPostForm($style_path, [], t('Save'));
$this->assertResponse(200);
// Post flush, expected 1 image in the 'public' wrapper (sample.png).

View file

@ -1,34 +1,52 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\image\Entity\ImageStyle;
use Drupal\simpletest\WebTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the functions for generating paths and URLs for image styles.
*
* @group image
*/
class ImageStylesPathAndUrlTest extends WebTestBase {
class ImageStylesPathAndUrlTest extends BrowserTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['image', 'image_module_test'];
public static $modules = ['image', 'image_module_test', 'language'];
/**
* The image style.
*
* @var \Drupal\image\ImageStyleInterface
*/
protected $style;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->style = ImageStyle::create(['name' => 'style_foo', 'label' => $this->randomString()]);
$this->style = ImageStyle::create([
'name' => 'style_foo',
'label' => $this->randomString(),
]);
$this->style->save();
// Create a new language.
ConfigurableLanguage::createFromLangcode('fr')->save();
}
/**
@ -73,6 +91,20 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$this->doImageStyleUrlAndPathTests('private', FALSE);
}
/**
* Tests an image style URL with the "public://" schema and language prefix.
*/
public function testImageStyleUrlAndPathPublicLanguage() {
$this->doImageStyleUrlAndPathTests('public', TRUE, TRUE, 'fr');
}
/**
* Tests an image style URL with the "private://" schema and language prefix.
*/
public function testImageStyleUrlAndPathPrivateLanguage() {
$this->doImageStyleUrlAndPathTests('private', TRUE, TRUE, 'fr');
}
/**
* Tests an image style URL with a file URL that has an extra slash in it.
*/
@ -93,7 +125,7 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
/**
* Tests building an image style URL.
*/
public function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_slash = FALSE) {
public function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_slash = FALSE, $langcode = FALSE) {
$this->prepareRequestForGenerator($clean_url);
// Make the default scheme neither "public" nor "private" to verify the
@ -105,6 +137,13 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$status = file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
$this->assertNotIdentical(FALSE, $status, 'Created the directory for the generated images for the test style.');
// Override the language to build the URL for the correct language.
if ($langcode) {
$language_manager = \Drupal::service('language_manager');
$language = $language_manager->getLanguage($langcode);
$language_manager->setConfigOverrideLanguage($language);
}
// Create a working copy of the file.
$files = $this->drupalGetTestFiles('image');
$file = array_shift($files);
@ -119,6 +158,11 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$generate_url = $this->style->buildUrl($original_uri, $clean_url);
// Make sure that language prefix is never added to the image style URL.
if ($langcode) {
$this->assertTrue(strpos($generate_url, "/$langcode/") === FALSE, 'Langcode was not found in the image style 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.
@ -132,10 +176,10 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
}
// 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.');
$this->assertResponse(404, '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.');
$this->assertResponse(404, '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
@ -151,13 +195,15 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$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.');
// assertRaw can't be used with string containing non UTF-8 chars.
$this->assertNotEmpty(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);
$original_image = $this->container->get('image.factory')
->get($original_uri);
$this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize());
if ($scheme == 'private') {
@ -192,14 +238,17 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$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 ) {
// 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.');
// Check for PNG-Signature
// (cf. http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.2)
// in the response body.
$raw = $this->getSession()->getPage()->getContent();
$this->assertFalse(strpos($raw, chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10)));
}
}
else {
@ -215,7 +264,9 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
// Allow insecure image derivatives to be created for the remainder of this
// test.
$this->config('image.settings')->set('allow_insecure_derivatives', TRUE)->save();
$this->config('image.settings')
->set('allow_insecure_derivatives', TRUE)
->save();
// Create another working copy of the file.
$files = $this->drupalGetTestFiles('image');
@ -236,10 +287,11 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$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.
// Stop suppressing 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);
$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);
@ -247,13 +299,13 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$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.');
$this->assertResponse(404, '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->assertResponse(404, '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.');
$this->assertResponse(404, '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.');

View file

@ -1,18 +1,23 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the endpoints used by the "image" in-place editor.
*
* @group image
*/
class QuickEditImageControllerTest extends WebTestBase {
class QuickEditImageControllerTest extends BrowserTestBase {
use ImageFieldCreationTrait;
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
}
/**
* {@inheritdoc}
@ -77,8 +82,11 @@ class QuickEditImageControllerTest extends WebTestBase {
]);
$this->drupalGet('quickedit/image/info/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default');
$this->assertResponse('403');
$this->drupalPost('quickedit/image/upload/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default', 'application/json', []);
$this->assertResponse('403');
/** @var \Symfony\Component\BrowserKit\Client $client */
$client = $this->getSession()->getDriver()->getClient();
$client->request('POST', '/quickedit/image/upload/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default');
$this->assertEquals('403', $client->getResponse()->getStatus());
}
/**
@ -90,7 +98,8 @@ class QuickEditImageControllerTest extends WebTestBase {
'type' => 'article',
'title' => t('Test Node'),
]);
$info = $this->drupalGetJSON('quickedit/image/info/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default');
$json = $this->drupalGet('quickedit/image/info/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default', ['query' => ['_format' => 'json']]);
$info = Json::decode($json);
// Assert that the default settings for our field are respected by our JSON
// endpoint.
$this->assertTrue($info['alt_field']);
@ -118,8 +127,10 @@ class QuickEditImageControllerTest extends WebTestBase {
}
}
$this->assertTrue($valid_image);
$this->drupalLogin($this->contentAuthorUser);
$this->uploadImage($valid_image, $node->id(), $this->fieldName, $node->language()->getId());
$this->assertText('fid', t('Valid upload completed successfully.'));
$this->assertContains('"fid":"1"', $this->getSession()->getPage()->getContent(), 'Valid upload completed successfully.');
}
/**
@ -139,14 +150,16 @@ class QuickEditImageControllerTest extends WebTestBase {
foreach ($this->drupalGetTestFiles('image') as $image) {
/** @var \Drupal\Core\Image\ImageInterface $image_file */
$image_file = $image_factory->get($image->uri);
if ($image_file->getWidth() < 50 || $image_file->getWidth() > 100 ) {
if ($image_file->getWidth() < 50 || $image_file->getWidth() > 100) {
$invalid_image = $image;
break;
}
}
$this->assertTrue($invalid_image);
$this->drupalLogin($this->contentAuthorUser);
$this->uploadImage($invalid_image, $node->id(), $this->fieldName, $node->language()->getId());
$this->assertText('main_error', t('Invalid upload returned errors.'));
$this->assertContains('"main_error":"The image failed validation."', $this->getSession()->getPage()->getContent(), 'Invalid upload returned errors.');
}
/**
@ -160,27 +173,14 @@ class QuickEditImageControllerTest extends WebTestBase {
* The target field machine name.
* @param string $langcode
* The langcode to use when setting the field's value.
*
* @return mixed
* The content returned from the call to $this->curlExec().
*/
public function uploadImage($image, $nid, $field_name, $langcode) {
$filepath = $this->container->get('file_system')->realpath($image->uri);
$data = [
'files[image]' => curl_file_create($filepath),
];
$path = 'quickedit/image/upload/node/' . $nid . '/' . $field_name . '/' . $langcode . '/default';
// We assemble the curl request ourselves as drupalPost cannot process file
// uploads, and drupalPostForm only works with typical Drupal forms.
return $this->curlExec([
CURLOPT_URL => $this->buildUrl($path, []),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $data,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: multipart/form-data',
],
]);
$this->prepareRequest();
$client = $this->getSession()->getDriver()->getClient();
$client->request('POST', $this->buildUrl($path, []), [], ['files[image]' => $filepath]);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\image\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ImageStyleJsonAnonTest extends ImageStyleResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\image\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ImageStyleJsonBasicAuthTest extends ImageStyleResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\image\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ImageStyleJsonCookieTest extends ImageStyleResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View file

@ -0,0 +1,115 @@
<?php
namespace Drupal\Tests\image\Functional\Rest;
use Drupal\image\Entity\ImageStyle;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
/**
* ResourceTestBase for ImageStyle entity.
*/
abstract class ImageStyleResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['image'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'image_style';
/**
* The ImageStyle entity.
*
* @var \Drupal\image\ImageStyleInterface
*/
protected $entity;
/**
* The effect UUID.
*
* @var string
*/
protected $effectUuid;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer image styles']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" image style.
$camelids = ImageStyle::create([
'name' => 'camelids',
'label' => 'Camelids',
]);
// Add an image effect.
$effect = [
'id' => 'image_scale_and_crop',
'data' => [
'anchor' => 'center-center',
'width' => 120,
'height' => 121,
],
'weight' => 0,
];
$this->effectUuid = $camelids->addImageEffect($effect);
$camelids->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'effects' => [
$this->effectUuid => [
'uuid' => $this->effectUuid,
'id' => 'image_scale_and_crop',
'weight' => 0,
'data' => [
'anchor' => 'center-center',
'width' => 120,
'height' => 121,
],
],
],
'label' => 'Camelids',
'langcode' => 'en',
'name' => 'camelids',
'status' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
return "The 'administer image styles' permission is required.";
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\Tests\image\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ImageStyleXmlAnonTest extends ImageStyleResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
public function testGet() {
// @todo Remove this method override in https://www.drupal.org/node/2905655
$this->markTestSkipped();
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\Tests\image\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ImageStyleXmlBasicAuthTest extends ImageStyleResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
/**
* {@inheritdoc}
*/
public function testGet() {
// @todo Remove this method override in https://www.drupal.org/node/2905655
$this->markTestSkipped();
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\Tests\image\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ImageStyleXmlCookieTest extends ImageStyleResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
public function testGet() {
// @todo Remove this method override in https://www.drupal.org/node/2905655
$this->markTestSkipped();
}
}

View file

@ -1,13 +1,14 @@
<?php
namespace Drupal\image\Tests\Update;
namespace Drupal\Tests\image\Functional\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests Image update path.
*
* @group image
* @group legacy
*/
class ImageUpdateTest extends UpdatePathTestBase {
@ -16,7 +17,7 @@ class ImageUpdateTest extends UpdatePathTestBase {
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
];
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\Tests\image\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests adding an 'anchor' setting to existing scale and crop image effects.
*
* @see image_post_update_scale_and_crop_effect_add_anchor()
*
* @group Update
* @group legacy
*/
class ScaleAndCropAddAnchorUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
__DIR__ . '/../../../fixtures/update/test_scale_and_crop_add_anchor.php',
];
}
/**
* Tests that 'anchor' setting is properly added.
*/
public function testImagePostUpdateScaleAndCropEffectAddAnchor() {
// Test that the first effect does not have an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.8c7170c9-5bcc-40f9-8698-f88a8be6d434.data');
$this->assertFalse(array_key_exists('anchor', $effect_data));
// Test that the second effect has an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.a8d83b12-abc6-40c8-9c2f-78a4e421cf97.data');
$this->assertTrue(array_key_exists('anchor', $effect_data));
// Test that the third effect does not have an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.1bffd475-19d0-439a-b6a1-7e5850ce40f9.data');
$this->assertFalse(array_key_exists('anchor', $effect_data));
$this->runUpdates();
// Test that the first effect now has an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.8c7170c9-5bcc-40f9-8698-f88a8be6d434.data');
$this->assertTrue(array_key_exists('anchor', $effect_data));
$this->assertEquals('center-center', $effect_data['anchor']);
// Test that the second effect's 'anchor' setting is unchanged.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.a8d83b12-abc6-40c8-9c2f-78a4e421cf97.data');
$this->assertTrue(array_key_exists('anchor', $effect_data));
$this->assertEquals('left-top', $effect_data['anchor']);
// Test that the third effect still does not have an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.1bffd475-19d0-439a-b6a1-7e5850ce40f9.data');
$this->assertFalse(array_key_exists('anchor', $effect_data));
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Drupal\Tests\image\FunctionalJavascript;
use Drupal\image\Entity\ImageStyle;
/**
* Tests creation, deletion, and editing of image styles and effects.
*
* @group image
*/
class ImageAdminStylesTest extends ImageFieldTestBase {
/**
* 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.
$page = $this->getSession()->getPage();
$assert = $this->assertSession();
$this->drupalGet($admin_path . '/add');
$page->findField('label')->setValue($style_label);
$assert->waitForElementVisible('named', ['button', 'Edit'])->press();
$assert->waitForElementVisible('named', ['id_or_name', 'name'])->setValue($style_name);
$page->pressButton('Create new style');
$assert->pageTextContains("Style $style_label was created.");
// 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);
$page->findField('data[test_parameter]')->setValue(111);
$ajax_value = $page->find('css', '#ajax-value')->getText();
$this->assertSame('Ajax value bar', $ajax_value);
$this->getSession()->getPage()->pressButton('Ajax refresh');
$this->assertTrue($page->waitFor(10, function ($page) {
$ajax_value = $page->find('css', '#ajax-value')->getText();
return preg_match('/^Ajax value [0-9.]+ [0-9.]+$/', $ajax_value);
}));
$page->pressButton('Update effect');
$assert->pageTextContains('The image effect was successfully applied.');
}
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\Tests\image\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
/**
* This class provides methods specifically for testing Image's field handling.
*/
abstract class ImageFieldTestBase extends WebDriverTestBase {
use ImageFieldCreationTrait;
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'node',
'image',
'field_ui',
'image_module_test',
];
/**
* An user with permissions to administer content types and image styles.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create Basic page and Article node types.
if ($this->profile !== 'standard') {
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
}
$this->adminUser = $this->drupalCreateUser([
'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);
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\image\FunctionalJavascript;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
/**
* Tests validation functions such as min/max resolution.
*
* @group image
*/
class ImageFieldValidateTest extends ImageFieldTestBase {
/**
* Test the validation message is displayed only once for ajax uploads.
*/
public function testAJAXValidationMessage() {
$field_name = strtolower($this->randomMachineName());
$this->createImageField($field_name, 'article', ['cardinality' => -1]);
$this->drupalGet('node/add/article');
/** @var \Drupal\file\FileInterface[] $text_files */
$text_files = $this->drupalGetTestFiles('text');
$text_file = reset($text_files);
$field = $this->getSession()->getPage()->findField('files[' . $field_name . '_0][]');
$field->attachFile($this->container->get('file_system')->realpath($text_file->uri));
$this->assertSession()->waitForElement('css', '.messages--error');
$elements = $this->xpath('//div[contains(@class, :class)]', [
':class' => 'messages--error',
]);
$this->assertEqual(count($elements), 1, 'Ajax validation messages are displayed once.');
}
/**
* Tests that image field validation works with other form submit handlers.
*/
public function testFriendlyAjaxValidation() {
// Add a custom field to the Article content type that contains an AJAX
// handler on a select field.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_dummy_select',
'type' => 'image_module_test_dummy_ajax',
'entity_type' => 'node',
'cardinality' => 1,
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'entity_type' => 'node',
'bundle' => 'article',
'field_name' => 'field_dummy_select',
'label' => t('Dummy select'),
])->save();
\Drupal::entityTypeManager()
->getStorage('entity_form_display')
->load('node.article.default')
->setComponent(
'field_dummy_select',
[
'type' => 'image_module_test_dummy_ajax_widget',
'weight' => 1,
])
->save();
// Then, add an image field.
$this->createImageField('field_dummy_image', 'article');
// Open an article and trigger the AJAX handler.
$this->drupalGet('node/add/article');
$id = $this->getSession()->getPage()->find('css', '[name="form_build_id"]')->getValue();
$field = $this->getSession()->getPage()->findField('field_dummy_select[select_widget]');
$field->setValue('bam');
// Make sure that the operation did not end with an exception.
$this->assertSession()->waitForElement('css', "[name='form_build_id']:not([value='$id'])");
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Drupal\Tests\image\FunctionalJavascript;
/**
* @see \Drupal\image\Plugin\InPlaceEditor\Image
* @see \Drupal\Tests\quickedit\FunctionalJavascript\QuickEditJavascriptTestBase
*/
trait QuickEditImageEditorTestTrait {
/**
* Awaits the 'image' in-place editor.
*/
protected function awaitImageEditor() {
$this->assertJsCondition('document.querySelector(".quickedit-image-field-info") !== null', 10000);
$quickedit_entity_toolbar = $this->getSession()->getPage()->findById('quickedit-entity-toolbar');
$this->assertNotNull($quickedit_entity_toolbar->find('css', 'form.quickedit-image-field-info input[name="alt"]'));
}
/**
* Simulates typing in the 'image' in-place editor 'alt' attribute text input.
*
* @param string $text
* The text to type.
*/
protected function typeInImageEditorAltTextInput($text) {
$quickedit_entity_toolbar = $this->getSession()->getPage()->findById('quickedit-entity-toolbar');
$input = $quickedit_entity_toolbar->find('css', 'form.quickedit-image-field-info input[name="alt"]');
$input->setValue($text);
}
/**
* Simulates dragging and dropping an image on the 'image' in-place editor.
*
* @param string $file_uri
* The URI of the image file to drag and drop.
*/
protected function dropImageOnImageEditor($file_uri) {
// Our headless browser can't drag+drop files, but we can mock the event.
// Append a hidden upload element to the DOM.
$script = 'jQuery("<input id=\"quickedit-image-test-input\" type=\"file\" />").appendTo("body")';
$this->getSession()->executeScript($script);
// Find the element, and set its value to our new image.
$input = $this->assertSession()->elementExists('css', '#quickedit-image-test-input');
$filepath = $this->container->get('file_system')->realpath($file_uri);
$input->attachFile($filepath);
// Trigger the upload logic with a mock "drop" event.
$script = 'var e = jQuery.Event("drop");'
. 'e.originalEvent = {dataTransfer: {files: jQuery("#quickedit-image-test-input").get(0).files}};'
. 'e.preventDefault = e.stopPropagation = function () {};'
. 'jQuery(".quickedit-image-dropzone").trigger(e);';
$this->getSession()->executeScript($script);
// Wait for the dropzone element to be removed (i.e. loading is done).
$js_condition = <<<JS
function () {
var activeFieldID = Drupal.quickedit.collections.entities
.findWhere({state:'opened'})
.get('fields')
.filter(function (fieldModel) {
var state = fieldModel.get('state');
return state === 'active' || state === 'changed';
})[0]
.get('fieldID')
return document.querySelector('[data-quickedit-field-id="' + activeFieldID + '"] .quickedit-image-dropzone') === null;
}();
JS;
$this->assertJsCondition($js_condition, 20000);
}
}

View file

@ -3,24 +3,24 @@
namespace Drupal\Tests\image\FunctionalJavascript;
use Drupal\file\Entity\File;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\Tests\image\Kernel\ImageFieldCreationTrait;
use Drupal\Tests\quickedit\FunctionalJavascript\QuickEditJavascriptTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the JavaScript functionality of the "image" in-place editor.
*
* @coversDefaultClass \Drupal\image\Plugin\InPlaceEditor\Image
* @group image
*/
class QuickEditImageTest extends JavascriptTestBase {
class QuickEditImageTest extends QuickEditJavascriptTestBase {
use ImageFieldCreationTrait;
use TestFileCreationTrait;
use QuickEditImageEditorTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'image', 'field_ui', 'contextual', 'quickedit', 'toolbar'];
public static $modules = ['node', 'image', 'field_ui'];
/**
* A user with permissions to edit Articles and use Quick Edit.
@ -52,9 +52,12 @@ class QuickEditImageTest extends JavascriptTestBase {
}
/**
* Tests if an image can be uploaded inline with Quick Edit.
* Test that quick editor works correctly with images.
*
* @covers ::isCompatible
* @covers ::getAttachments
*/
public function testUpload() {
public function testImageInPlaceEditor() {
// Create a field with a basic filetype restriction.
$field_name = strtolower($this->randomMachineName());
$field_settings = [
@ -114,52 +117,82 @@ class QuickEditImageTest extends JavascriptTestBase {
// Assert that the initial image is present.
$this->assertSession()->elementExists('css', $entity_selector . ' ' . $field_selector . ' ' . $original_image_selector);
// Wait until Quick Edit loads.
$condition = "jQuery('" . $entity_selector . " .quickedit').length > 0";
$this->assertJsCondition($condition, 10000);
// Initial state.
$this->awaitQuickEditForEntity('node', 1);
$this->assertEntityInstanceStates([
'node/1[0]' => 'closed',
]);
$this->assertEntityInstanceFieldStates('node', 1, 0, [
'node/1/title/en/full' => 'inactive',
'node/1/uid/en/full' => 'inactive',
'node/1/created/en/full' => 'inactive',
'node/1/body/en/full' => 'inactive',
'node/1/' . $field_name . '/en/full' => 'inactive',
]);
// Initiate Quick Editing.
$this->click('.contextual-toolbar-tab button');
$this->click($entity_selector . ' [data-contextual-id] > button');
$this->click($entity_selector . ' [data-contextual-id] .quickedit > a');
// Start in-place editing of the article node.
$this->startQuickEditViaToolbar('node', 1, 0);
$this->assertEntityInstanceStates([
'node/1[0]' => 'opened',
]);
$this->assertQuickEditEntityToolbar((string) $node->label(), NULL);
$this->assertEntityInstanceFieldStates('node', 1, 0, [
'node/1/title/en/full' => 'candidate',
'node/1/uid/en/full' => 'candidate',
'node/1/created/en/full' => 'candidate',
'node/1/body/en/full' => 'candidate',
'node/1/' . $field_name . '/en/full' => 'candidate',
]);
// Click the image field.
$this->click($field_selector);
// Wait for the field info to load and set new alt text.
$condition = "jQuery('.quickedit-image-field-info').length > 0";
$this->assertJsCondition($condition, 10000);
$input = $this->assertSession()->elementExists('css', '.quickedit-image-field-info input[name="alt"]');
$input->setValue('New text');
// Check that our Dropzone element exists.
$this->awaitImageEditor();
$this->assertSession()->elementExists('css', $field_selector . ' .quickedit-image-dropzone');
$this->assertEntityInstanceFieldStates('node', 1, 0, [
'node/1/title/en/full' => 'candidate',
'node/1/uid/en/full' => 'candidate',
'node/1/created/en/full' => 'candidate',
'node/1/body/en/full' => 'candidate',
'node/1/' . $field_name . '/en/full' => 'active',
]);
// Our headless browser can't drag+drop files, but we can mock the event.
// Append a hidden upload element to the DOM.
$script = 'jQuery("<input id=\"quickedit-image-test-input\" type=\"file\" />").appendTo("body")';
$this->getSession()->executeScript($script);
// Type new 'alt' text.
$this->typeInImageEditorAltTextInput('New text');
$this->assertEntityInstanceFieldStates('node', 1, 0, [
'node/1/title/en/full' => 'candidate',
'node/1/uid/en/full' => 'candidate',
'node/1/created/en/full' => 'candidate',
'node/1/body/en/full' => 'candidate',
'node/1/' . $field_name . '/en/full' => 'changed',
]);
// Find the element, and set its value to our new image.
$input = $this->assertSession()->elementExists('css', '#quickedit-image-test-input');
$filepath = $this->container->get('file_system')->realpath($valid_images[1]->uri);
$input->attachFile($filepath);
// Trigger the upload logic with a mock "drop" event.
$script = 'var e = jQuery.Event("drop");'
. 'e.originalEvent = {dataTransfer: {files: jQuery("#quickedit-image-test-input").get(0).files}};'
. 'e.preventDefault = e.stopPropagation = function () {};'
. 'jQuery(".quickedit-image-dropzone").trigger(e);';
$this->getSession()->executeScript($script);
// Wait for the dropzone element to be removed (i.e. loading is done).
$condition = "jQuery('" . $field_selector . " .quickedit-image-dropzone').length == 0";
$this->assertJsCondition($condition, 20000);
// Drag and drop an image.
$this->dropImageOnImageEditor($valid_images[1]->uri);
// To prevent 403s on save, we re-set our request (cookie) state.
$this->prepareRequest();
// Save the change.
$this->click('.quickedit-button.action-save');
$this->assertSession()->assertWaitOnAjaxRequest();
// Click 'Save'.
$this->saveQuickEdit();
$this->assertEntityInstanceStates([
'node/1[0]' => 'committing',
]);
$this->assertEntityInstanceFieldStates('node', 1, 0, [
'node/1/title/en/full' => 'candidate',
'node/1/uid/en/full' => 'candidate',
'node/1/created/en/full' => 'candidate',
'node/1/body/en/full' => 'candidate',
'node/1/' . $field_name . '/en/full' => 'saving',
]);
$this->assertEntityInstanceFieldMarkup('node', 1, 0, [
'node/1/' . $field_name . '/en/full' => '.quickedit-changed',
]);
// Wait for the saving of the image field to complete.
$this->assertJsCondition("Drupal.quickedit.collections.entities.get('node/1[0]').get('state') === 'closed'");
$this->assertEntityInstanceStates([
'node/1[0]' => 'closed',
]);
// Re-visit the page to make sure the edit worked.
$this->drupalGet('node/' . $node->id());

View file

@ -2,11 +2,12 @@
namespace Drupal\Tests\image\Kernel;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\image\Entity\ImageStyle;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
/**
@ -56,7 +57,7 @@ class ImageFormatterTest extends FieldKernelTestBase {
$this->entityType = 'entity_test';
$this->bundle = $this->entityType;
$this->fieldName = Unicode::strtolower($this->randomMachineName());
$this->fieldName = mb_strtolower($this->randomMachineName());
FieldStorageConfig::create([
'entity_type' => $this->entityType,
@ -99,4 +100,89 @@ class ImageFormatterTest extends FieldKernelTestBase {
$this->assertEquals($entity->{$this->fieldName}[1]->entity->getCacheTags(), $build[$this->fieldName][1]['#cache']['tags'], 'Second image cache tags is as expected');
}
/**
* Tests ImageFormatter's handling of SVG images.
*
* @requires extension gd
*/
public function testImageFormatterSvg() {
// Install the default image styles.
$this->installConfig(['image']);
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$png = File::create([
'uri' => 'public://test-image.png',
]);
$png->save();
// We need to create an actual empty PNG, or the GD toolkit will not
// consider the image valid.
$png_resource = imagecreate(300, 300);
imagefill($png_resource, 0, 0, imagecolorallocate($png_resource, 0, 0, 0));
imagepng($png_resource, $png->getFileUri());
$svg = File::create([
'uri' => 'public://test-image.svg',
]);
$svg->save();
// We don't have to put any real SVG data in here, because the GD toolkit
// won't be able to load it anyway.
touch($svg->getFileUri());
$entity = EntityTest::create([
'name' => $this->randomMachineName(),
$this->fieldName => [$png, $svg],
]);
$entity->save();
// Ensure that the display is using the medium image style.
$component = $this->display->getComponent($this->fieldName);
$component['settings']['image_style'] = 'medium';
$this->display->setComponent($this->fieldName, $component)->save();
$build = $this->display->build($entity);
// The first image is a PNG, so it is supported by the GD image toolkit.
// The image style should be applied with its cache tags, image derivative
// computed with its URI and dimensions.
$this->assertCacheTags($build[$this->fieldName][0], ImageStyle::load('medium')->getCacheTags());
$renderer->renderRoot($build[$this->fieldName][0]);
$this->assertEquals('medium', $build[$this->fieldName][0]['#image_style']);
// We check that the image URL contains the expected style directory
// structure.
$this->assertTrue(strpos($build[$this->fieldName][0]['#markup'], 'styles/medium/public/test-image.png') !== FALSE);
$this->assertTrue(strpos($build[$this->fieldName][0]['#markup'], 'width="220"') !== FALSE);
$this->assertTrue(strpos($build[$this->fieldName][0]['#markup'], 'height="220"') !== FALSE);
// The second image is an SVG, which is not supported by the GD toolkit.
// The image style should still be applied with its cache tags, but image
// derivative will not be available so <img> tag will point to the original
// image.
$this->assertCacheTags($build[$this->fieldName][1], ImageStyle::load('medium')->getCacheTags());
$renderer->renderRoot($build[$this->fieldName][1]);
$this->assertEquals('medium', $build[$this->fieldName][1]['#image_style']);
// We check that the image URL does not contain the style directory
// structure.
$this->assertFalse(strpos($build[$this->fieldName][1]['#markup'], 'styles/medium/public/test-image.svg'));
// Since we did not store original image dimensions, width and height
// HTML attributes will not be present.
$this->assertFalse(strpos($build[$this->fieldName][1]['#markup'], 'width'));
$this->assertFalse(strpos($build[$this->fieldName][1]['#markup'], 'height'));
}
/**
* Asserts that a renderable array has a set of cache tags.
*
* @param array $renderable
* The renderable array. Must have a #cache[tags] element.
* @param array $cache_tags
* The expected cache tags.
*/
protected function assertCacheTags(array $renderable, array $cache_tags) {
$diff = array_diff($cache_tags, $renderable['#cache']['tags']);
$this->assertEmpty($diff);
}
}

View file

@ -22,7 +22,7 @@ class ImageImportTest extends KernelTestBase {
*/
public function testImport() {
$style = ImageStyle::create([
'name' => 'test'
'name' => 'test',
]);
$style->addImageEffect(['id' => 'image_module_test_null']);

View file

@ -2,6 +2,7 @@
namespace Drupal\Tests\image\Kernel;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
@ -10,6 +11,7 @@ use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\user\Entity\Role;
/**
* Tests using entity fields of the image field type.
@ -40,6 +42,14 @@ class ImageItemTest extends FieldKernelTestBase {
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installConfig(['user']);
// Give anonymous users permission to access content, so that we can view
// and download public file.
$anonymous_role = Role::load(Role::ANONYMOUS_ID);
$anonymous_role->grantPermission('access content');
$anonymous_role->save();
$this->installEntitySchema('file');
$this->installSchema('file', ['file_usage']);
@ -57,7 +67,7 @@ class ImageItemTest extends FieldKernelTestBase {
'file_extensions' => 'jpg',
],
])->save();
file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example.jpg');
file_unmanaged_copy($this->root . '/core/misc/druplicon.png', 'public://example.jpg');
$this->image = File::create([
'uri' => 'public://example.jpg',
]);
@ -90,7 +100,7 @@ class ImageItemTest extends FieldKernelTestBase {
$this->assertEqual($entity->image_test->entity->uuid(), $this->image->uuid());
// Make sure the computed entity reflects updates to the referenced file.
file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example-2.jpg');
file_unmanaged_copy($this->root . '/core/misc/druplicon.png', 'public://example-2.jpg');
$image2 = File::create([
'uri' => 'public://example-2.jpg',
]);
@ -129,4 +139,26 @@ class ImageItemTest extends FieldKernelTestBase {
$this->assertEqual($entity->image_test->entity->get('filemime')->value, 'image/jpeg');
}
/**
* Tests a malformed image.
*/
public function testImageItemMalformed() {
// Validate entity is an image and don't gather dimensions if it is not.
$entity = EntityTest::create();
$entity->image_test = NULL;
$entity->image_test->target_id = 9999;
// PHPUnit re-throws E_USER_WARNING as an exception.
try {
$entity->save();
$this->fail('Exception did not fail');
}
catch (EntityStorageException $exception) {
$this->assertInstanceOf(\PHPUnit_Framework_Error_Warning::class, $exception->getPrevious());
$this->assertEquals($exception->getMessage(), 'Missing file with ID 9999.');
$this->assertEmpty($entity->image_test->width);
$this->assertEmpty($entity->image_test->height);
}
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Drupal\image\Tests;
namespace Drupal\Tests\image\Kernel;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
@ -8,22 +8,28 @@ 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\KernelTests\KernelTestBase;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests image theme functions.
*
* @group image
*/
class ImageThemeFunctionTest extends WebTestBase {
class ImageThemeFunctionTest extends KernelTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['image', 'entity_test'];
public static $modules = ['entity_test', 'field', 'file', 'image', 'system', 'simpletest', 'user'];
/**
* Created file entity.
@ -40,6 +46,11 @@ class ImageThemeFunctionTest extends WebTestBase {
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('file');
$this->installSchema('file', ['file_usage']);
$this->installEntitySchema('user');
FieldStorageConfig::create([
'entity_type' => 'entity_test',
'field_name' => 'image_test',
@ -51,7 +62,7 @@ class ImageThemeFunctionTest extends WebTestBase {
'field_name' => 'image_test',
'bundle' => 'entity_test',
])->save();
file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example.jpg');
file_unmanaged_copy($this->root . '/core/misc/druplicon.png', 'public://example.jpg');
$this->image = File::create([
'uri' => 'public://example.jpg',
]);
@ -96,7 +107,7 @@ class ImageThemeFunctionTest extends WebTestBase {
// 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]', [':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()]);
$elements = $this->xpath('//a[@href=:path]/img[@src=:url and @width=:width and @height=:height]', [':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
@ -104,7 +115,7 @@ class ImageThemeFunctionTest extends WebTestBase {
$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=""]', [':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()]);
$elements = $this->xpath('//a[@href=:path]/img[@src=:url and @width=:width and @height=:height and @alt=""]', [':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.
@ -112,11 +123,11 @@ class ImageThemeFunctionTest extends WebTestBase {
$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=""]', [
$elements = $this->xpath('//a[@href=:fragment]/img[@src=:url and @width=:width and @height=:height and @alt=""]', [
':fragment' => '#' . $fragment,
':url' => $url,
':width' => $image->getWidth(),
':height' => $image->getHeight()
':height' => $image->getHeight(),
]);
$this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders a link fragment.');
}
@ -147,14 +158,14 @@ class ImageThemeFunctionTest extends WebTestBase {
$element = $base_element;
$this->setRawContent($renderer->renderRoot($element));
$elements = $this->xpath('//img[@class="image-style-image-test" and @src=:url and @alt=""]', [':url' => $url]);
$elements = $this->xpath('//img[@src=:url and @alt=""]', [':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]', [':url' => $url]);
$elements = $this->xpath('//img[@src=:url]', [':url' => $url]);
$this->assertEqual(count($elements), 1, 'theme_image_style() renders an image correctly with a NULL value for the alt option.');
}

View file

@ -141,9 +141,11 @@ class MigrateImageCacheTest extends MigrateDrupal6TestBase {
$this->startCollectingMessages();
$this->executeMigration('d6_imagecache_presets');
$this->assertEqual(['error' => [
'The Drupal 8 image crop effect does not support numeric values for x and y offsets. Use keywords to set crop effect offsets instead.'
]], $this->migrateMessages);
$this->assertEqual([
'error' => [
'The Drupal 8 image crop effect does not support numeric values for x and y offsets. Use keywords to set crop effect offsets instead.',
],
], $this->migrateMessages);
}
/**

View file

@ -0,0 +1,51 @@
<?php
namespace Drupal\Tests\image\Kernel\Migrate\d6;
use Drupal\node\Entity\Node;
use Drupal\Tests\node\Kernel\Migrate\d6\MigrateNodeTestBase;
use Drupal\Tests\file\Kernel\Migrate\d6\FileMigrationTestTrait;
/**
* Image migration test.
*
* This extends the node test, because the D6 fixture has images; they just
* need to be migrated into D8.
*
* @group migrate_drupal_6
*/
class MigrateImageTest extends MigrateNodeTestBase {
use FileMigrationTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->setUpMigratedFiles();
$this->installSchema('file', ['file_usage']);
$this->executeMigrations([
'd6_node',
]);
}
/**
* Test image migration from Drupal 6 to 8.
*/
public function testNode() {
$node = Node::load(9);
// Test the image field sub fields.
$this->assertSame('2', $node->field_test_imagefield->target_id);
$this->assertSame('Test alt', $node->field_test_imagefield->alt);
$this->assertSame('Test title', $node->field_test_imagefield->title);
$this->assertSame('80', $node->field_test_imagefield->width);
$this->assertSame('60', $node->field_test_imagefield->height);
}
}

View file

@ -32,7 +32,7 @@ class MigrateImageStylesTest extends MigrateDrupal7TestBase {
* Test the image styles migration.
*/
public function testImageStylesMigration() {
$this->assertEntity('custom_image_style_1', "Custom image style 1", ['image_scale_and_crop', 'image_desaturate'], [['width' => 55, 'height' => 55], []]);
$this->assertEntity('custom_image_style_1', "Custom image style 1", ['image_scale_and_crop', 'image_desaturate'], [['width' => 55, 'height' => 55, 'anchor' => 'center-center'], []]);
$this->assertEntity('custom_image_style_2', "Custom image style 2", ['image_resize', 'image_rotate'], [['width' => 55, 'height' => 100], ['degrees' => 45, 'bgcolor' => '#FFFFFF', 'random' => FALSE]]);
$this->assertEntity('custom_image_style_3', "Custom image style 3", ['image_scale', 'image_crop'], [['width' => 150, 'height' => NULL, 'upscale' => FALSE], ['width' => 50, 'height' => 50, 'anchor' => 'left-top']]);
}

View file

@ -1,10 +1,11 @@
<?php
namespace Drupal\image\Tests\Views;
namespace Drupal\Tests\image\Kernel\Views;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
use Drupal\views\Tests\ViewTestBase;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\user\Entity\User;
use Drupal\views\Views;
use Drupal\views\Tests\ViewTestData;
use Drupal\field\Entity\FieldStorageConfig;
@ -14,14 +15,14 @@ use Drupal\field\Entity\FieldStorageConfig;
*
* @group image
*/
class RelationshipUserImageDataTest extends ViewTestBase {
class RelationshipUserImageDataTest extends ViewsKernelTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['image', 'image_test_views', 'user'];
public static $modules = ['file', 'field', 'image', 'image_test_views', 'system', 'user'];
/**
* Views used by this test.
@ -30,8 +31,12 @@ class RelationshipUserImageDataTest extends ViewTestBase {
*/
public static $testViews = ['test_image_user_image_data'];
protected function setUp() {
parent::setUp();
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->installEntitySchema('file');
$this->installSchema('file', ['file_usage']);
$this->installEntitySchema('user');
// Create the user profile field and instance.
FieldStorageConfig::create([
@ -70,7 +75,9 @@ class RelationshipUserImageDataTest extends ViewTestBase {
file_put_contents($file->getFileUri(), file_get_contents('core/modules/simpletest/files/image-1.png'));
$file->save();
$account = $this->drupalCreateUser();
$account = User::create([
'name' => 'foo',
]);
$account->user_picture->target_id = 2;
$account->save();

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\image\Unit\Plugin\migrate\field\d7;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\image\Plugin\migrate\field\d7\ImageField;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\image\Plugin\migrate\field\d7\ImageField
* @group image
* @group legacy
*/
class ImageFieldTest extends UnitTestCase {
/**
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldInterface
*/
protected $plugin;
/**
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->plugin = new ImageField([], 'image', []);
$migration = $this->prophesize(MigrationInterface::class);
// The plugin's processFieldValues() method will call
// mergeProcessOfProperty() and return nothing. So, in order to examine the
// process pipeline created by the plugin, we need to ensure that
// getProcess() always returns the last input to mergeProcessOfProperty().
$migration->mergeProcessOfProperty(Argument::type('string'), Argument::type('array'))
->will(function ($arguments) use ($migration) {
$migration->getProcess()->willReturn($arguments[1]);
});
$this->migration = $migration->reveal();
}
/**
* @covers ::processFieldValues
* @expectedDeprecation Deprecated in Drupal 8.6.0, to be removed before Drupal 9.0.0. Use defineValueProcessPipeline() instead. See https://www.drupal.org/node/2944598.
*/
public function testProcessFieldValues() {
$this->plugin->processFieldValues($this->migration, 'somefieldname', []);
$expected = [
'plugin' => 'sub_process',
'source' => 'somefieldname',
'process' => [
'target_id' => 'fid',
'alt' => 'alt',
'title' => 'title',
'width' => 'width',
'height' => 'height',
],
];
$this->assertSame($expected, $this->migration->getProcess());
}
}