upgrades core to 8.4.2

This commit is contained in:
Bachir Soussi Chiadmi
2017-11-14 16:09:54 +01:00
parent bd60eff9b3
commit c2b4e25be4
3504 changed files with 140306 additions and 38684 deletions
@@ -14,8 +14,7 @@ field_default_image:
type: mapping
mapping:
uuid:
type: string
label: 'Image'
type: uuid
alt:
type: label
label: 'Alternative text'
@@ -21,7 +21,7 @@ image.style.*:
weight:
type: integer
uuid:
type: string
type: uuid
image.effect.*:
type: mapping
@@ -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 {
+3 -3
View File
@@ -8,8 +8,8 @@ dependencies:
- file
configure: entity.image_style.collection
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1502903957
datestamp: 1509719929
+31 -3
View File
@@ -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);
@@ -292,10 +302,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 +432,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 +470,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());
}
}
+338
View File
@@ -0,0 +1,338 @@
/**
* @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':
var 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.
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) {
$(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));
+40 -162
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,8 +44,6 @@
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');
});
@@ -76,11 +52,8 @@
case 'active':
var 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.
var $dropzone = this.renderDropzone('upload', Drupal.t('Drop file here or click to upload'));
$dropzone.on('dragenter', function (e) {
@@ -91,7 +64,6 @@
});
$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]);
@@ -99,20 +71,13 @@
});
$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 () {
if (this.files.length) {
self.uploadImage(this.files[0]);
}
});
$('<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();
@@ -140,78 +105,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 +146,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 +153,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 +197,24 @@
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);
+82
View File
@@ -0,0 +1,82 @@
/**
* @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));
+9 -66
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);
@@ -8,6 +8,7 @@ source:
- 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
@@ -8,7 +8,7 @@ process:
name: name
label: label
effects:
plugin: iterator
plugin: sub_process
source: effects
process:
id: name
+25 -2
View File
@@ -14,9 +14,11 @@ use Drupal\image\ImageEffectInterface;
use Drupal\image\ImageStyleInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
/**
* Defines an image style configuration entity.
*
@@ -274,9 +276,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 +341,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(
Unicode::strtolower(pathinfo($uri, PATHINFO_EXTENSION)),
$this->getImageFactory()->getSupportedExtensions()
);
}
/**
* {@inheritdoc}
*/
@@ -408,6 +421,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.
*
@@ -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;
@@ -58,7 +59,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 +144,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'];
@@ -194,4 +194,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);
}
@@ -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;
@@ -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);
}
}
/**
@@ -10,7 +10,7 @@ use Drupal\migrate\Row;
*
* @MigrateSource(
* id = "d6_imagecache_presets",
* source_provider = "imagecache"
* source_module = "imagecache"
* )
*/
class ImageCachePreset extends DrupalSqlBase {
@@ -10,7 +10,7 @@ use Drupal\migrate\Row;
*
* @MigrateSource(
* id = "d7_image_styles",
* source_provider = "image"
* source_module = "image"
* )
*/
class ImageStyles extends DrupalSqlBase {
@@ -203,6 +203,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->assertTrue($option[1] == 'Ajax test', '"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.
@@ -1,6 +1,7 @@
<?php
namespace Drupal\image\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
@@ -8,7 +9,7 @@ use Drupal\file\Entity\File;
use Drupal\field\Entity\FieldStorageConfig;
/**
* 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
*/
@@ -22,7 +23,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 +38,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 +74,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 +106,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 +124,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 +146,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 +184,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 +221,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 +267,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.
@@ -307,13 +310,52 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
$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([
@@ -282,7 +282,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 +292,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'],
@@ -314,9 +314,9 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$edit = [
'files[' . $field_name . '_1][]' => drupal_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().
@@ -90,10 +90,10 @@ abstract class ImageFieldTestBase extends WebTestBase {
'title[0][value]' => $this->randomMachineName(),
];
$edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
$this->drupalPostForm('node/add/' . $type, $edit, t('Save and publish'));
$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.
@@ -119,7 +119,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 +132,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.'));
@@ -29,6 +29,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.
@@ -193,13 +193,13 @@ 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 ) {
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.');
$this->assertNoRaw(chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), 'No PNG signature found in the response body.');
}
}
else {
@@ -139,7 +139,7 @@ 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;
}
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1502903957
datestamp: 1509719929
@@ -8,8 +8,8 @@ dependencies:
- image
- views
# Information added by Drupal.org packaging script on 2017-08-16
version: '8.3.7'
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1502903957
datestamp: 1509719929
@@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\image\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests Image update path.
*
* @group image
*/
class ImageUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
];
}
/**
* Tests image_post_update_image_style_dependencies().
*
* @see image_post_update_image_style_dependencies()
*/
public function testPostUpdateImageStylesDependencies() {
$view = 'core.entity_view_display.node.article.default';
$form = 'core.entity_form_display.node.article.default';
// Check that view display 'node.article.default' doesn't depend on image
// style 'image.style.large'.
$dependencies = $this->config($view)->get('dependencies.config');
$this->assertFalse(in_array('image.style.large', $dependencies));
// Check that form display 'node.article.default' doesn't depend on image
// style 'image.style.thumbnail'.
$dependencies = $this->config($form)->get('dependencies.config');
$this->assertFalse(in_array('image.style.thumbnail', $dependencies));
// Run updates.
$this->runUpdates();
// Check that view display 'node.article.default' depend on image style
// 'image.style.large'.
$dependencies = $this->config($view)->get('dependencies.config');
$this->assertTrue(in_array('image.style.large', $dependencies));
// Check that form display 'node.article.default' depend on image style
// 'image.style.thumbnail'.
$dependencies = $this->config($view)->get('dependencies.config');
$this->assertTrue(in_array('image.style.large', $dependencies));
}
}
@@ -7,6 +7,8 @@ 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;
/**
@@ -99,4 +101,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);
}
}
@@ -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;
@@ -129,4 +130,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);
}
}
}
@@ -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);
}
/**