Update to Drupal 8.0.3. For more information, see https://www.drupal.org/drupal-8.0.3-release-notes

This commit is contained in:
Pantheon Automation 2016-02-03 14:56:31 -08:00 committed by Greg Anderson
parent 10f9f7fbde
commit 9db4fae9a7
202 changed files with 3806 additions and 760 deletions

View file

@ -1,97 +1,3 @@
{
"extends": "eslint:recommended",
"env": {
"browser": true
},
"globals": {
"Drupal": true,
"drupalSettings": true,
"drupalTranslations": true,
"domready": true,
"jQuery": true,
"_": true,
"matchMedia": true,
"Backbone": true,
"Modernizr": true,
"CKEDITOR": true
},
"rules": {
// Errors.
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
"brace-style": [2, "stroustrup", {"allowSingleLine": true}],
"comma-dangle": [2, "never"],
"comma-spacing": 2,
"comma-style": [2, "last"],
"computed-property-spacing": [2, "never"],
"curly": [2, "all"],
"eol-last": 2,
"eqeqeq": [2, "smart"],
"guard-for-in": 2,
"indent": [2, 2, {"SwitchCase": 1}],
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
"linebreak-style": [2, "unix"],
"lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}],
"new-parens": 2,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 2,
"no-empty-label": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-parens": [2, "functions"],
"no-implied-eval": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-nested-ternary": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-return-assign": 2,
"no-script-url": 2,
"no-sequences": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-trailing-spaces": 2,
"no-undef-init": 2,
"no-undefined": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
"no-with": 2,
"object-curly-spacing": [2, "never"],
"one-var": [2, "never"],
"quote-props": [2, "consistent-as-needed"],
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "always"],
"semi-spacing": [2, {"before": false, "after": true}],
"space-after-keywords": [2, "always"],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always"],
"strict": 2,
"yoda": [2, "never"],
// Warnings.
"max-nested-callbacks": [1, 3],
"valid-jsdoc": [1, {
"prefer": {
"returns": "return",
"property": "prop"
},
"requireReturn": false
}]
}
"extends": "./core/.eslintrc"
}

View file

@ -3,7 +3,7 @@
#
# Protect files and directories from prying eyes.
<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$">
<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock))$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>

97
core/.eslintrc Normal file
View file

@ -0,0 +1,97 @@
{
"extends": "eslint:recommended",
"env": {
"browser": true
},
"globals": {
"Drupal": true,
"drupalSettings": true,
"drupalTranslations": true,
"domready": true,
"jQuery": true,
"_": true,
"matchMedia": true,
"Backbone": true,
"Modernizr": true,
"CKEDITOR": true
},
"rules": {
// Errors.
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
"brace-style": [2, "stroustrup", {"allowSingleLine": true}],
"comma-dangle": [2, "never"],
"comma-spacing": 2,
"comma-style": [2, "last"],
"computed-property-spacing": [2, "never"],
"curly": [2, "all"],
"eol-last": 2,
"eqeqeq": [2, "smart"],
"guard-for-in": 2,
"indent": [2, 2, {"SwitchCase": 1}],
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
"linebreak-style": [2, "unix"],
"lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}],
"new-parens": 2,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 2,
"no-empty-label": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-parens": [2, "functions"],
"no-implied-eval": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-nested-ternary": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-return-assign": 2,
"no-script-url": 2,
"no-sequences": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-trailing-spaces": 2,
"no-undef-init": 2,
"no-undefined": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
"no-with": 2,
"object-curly-spacing": [2, "never"],
"one-var": [2, "never"],
"quote-props": [2, "consistent-as-needed"],
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "always"],
"semi-spacing": [2, {"before": false, "after": true}],
"space-after-keywords": [2, "always"],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always"],
"strict": 2,
"yoda": [2, "never"],
// Warnings.
"max-nested-callbacks": [1, 3],
"valid-jsdoc": [1, {
"prefer": {
"returns": "return",
"property": "prop"
},
"requireReturn": false
}]
}
}

View file

@ -328,10 +328,10 @@ function theme_get_setting($setting_name, $theme = NULL) {
// Generate the path to the logo image.
if ($cache[$theme]->get('logo.use_default')) {
$cache[$theme]->set('logo.url', file_create_url($theme_object->getPath() . '/logo.svg'));
$cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($theme_object->getPath() . '/logo.svg')));
}
elseif ($logo_path = $cache[$theme]->get('logo.path')) {
$cache[$theme]->set('logo.url', file_create_url($logo_path));
$cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo_path)));
}
// Generate the path to the favicon.
@ -339,14 +339,14 @@ function theme_get_setting($setting_name, $theme = NULL) {
$favicon_path = $cache[$theme]->get('favicon.path');
if ($cache[$theme]->get('favicon.use_default')) {
if (file_exists($favicon = $theme_object->getPath() . '/favicon.ico')) {
$cache[$theme]->set('favicon.url', file_create_url($favicon));
$cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon)));
}
else {
$cache[$theme]->set('favicon.url', file_create_url('core/misc/favicon.ico'));
$cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url('core/misc/favicon.ico')));
}
}
elseif ($favicon_path) {
$cache[$theme]->set('favicon.url', file_create_url($favicon_path));
$cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon_path)));
}
else {
$cache[$theme]->set('features.favicon', FALSE);
@ -741,7 +741,7 @@ function template_preprocess_links(&$variables) {
*/
function template_preprocess_image(&$variables) {
if (!empty($variables['uri'])) {
$variables['attributes']['src'] = file_create_url($variables['uri']);
$variables['attributes']['src'] = file_url_transform_relative(file_create_url($variables['uri']));
}
// Generate a srcset attribute conforming to the spec at
// http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset
@ -749,7 +749,7 @@ function template_preprocess_image(&$variables) {
$srcset = array();
foreach ($variables['srcset'] as $src) {
// URI is mandatory.
$source = file_create_url($src['uri']);
$source = file_url_transform_relative(file_create_url($src['uri']));
if (isset($src['width']) && !empty($src['width'])) {
$source .= ' ' . $src['width'];
}

View file

@ -7,6 +7,8 @@
// Change the directory to the Drupal root.
chdir('..');
// Store the Drupal root path.
$root_path = realpath('');
/**
* Global flag to indicate the site is in installation mode.
@ -32,6 +34,6 @@ if (function_exists('opcache_get_status') && opcache_get_status()['opcache_enabl
}
// Start the installer.
$class_loader = require_once 'autoload.php';
require_once __DIR__ . '/includes/install.core.inc';
$class_loader = require_once $root_path . '/autoload.php';
require_once $root_path . '/core/includes/install.core.inc';
install_drupal($class_loader);

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.0.2';
const VERSION = '8.0.3';
/**
* Core API compatibility.

View file

@ -135,7 +135,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
// assets: output a LINK tag for a file CSS asset.
if (count($css_assets) <= 31) {
$element = $link_element_defaults;
$element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string;
$element['#attributes']['href'] = file_url_transform_relative(file_create_url($css_asset['data'])) . $query_string_separator . $query_string;
$element['#attributes']['media'] = $css_asset['media'];
$element['#browsers'] = $css_asset['browsers'];
$elements[] = $element;
@ -148,7 +148,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
// LINK tag.
if (!$css_asset['preprocess']) {
$element = $link_element_defaults;
$element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string;
$element['#attributes']['href'] = file_url_transform_relative(file_create_url($css_asset['data'])) . $query_string_separator . $query_string;
$element['#attributes']['media'] = $css_asset['media'];
$element['#browsers'] = $css_asset['browsers'];
$elements[] = $element;
@ -168,7 +168,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
// control browser-caching. IE7 does not support a media type on
// the @import statement, so we instead specify the media for
// the group on the STYLE tag.
$import[] = '@import url("' . Html::escape(file_create_url($next_css_asset['data']) . '?' . $query_string) . '");';
$import[] = '@import url("' . Html::escape(file_url_transform_relative(file_create_url($next_css_asset['data'])) . '?' . $query_string) . '");';
// Move the outer for loop skip the next item, since we
// processed it here.
$i = $j;

View file

@ -265,7 +265,7 @@ class CssOptimizer implements AssetOptimizerInterface {
$last = $path;
$path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
}
return 'url(' . file_create_url($path) . ')';
return 'url(' . file_url_transform_relative(file_create_url($path)) . ')';
}
}

View file

@ -79,7 +79,7 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface {
case 'file':
$query_string = $js_asset['version'] == -1 ? $default_query_string : 'v=' . $js_asset['version'];
$query_string_separator = (strpos($js_asset['data'], '?') !== FALSE) ? '&' : '?';
$element['#attributes']['src'] = file_create_url($js_asset['data']);
$element['#attributes']['src'] = file_url_transform_relative(file_create_url($js_asset['data']));
// Only add the cache-busting query string if this isn't an aggregate
// file.
if (!isset($js_asset['preprocessed'])) {

View file

@ -18,7 +18,7 @@ abstract class ConfigFactoryOverrideBase implements EventSubscriberInterface {
* Reacts to the ConfigEvents::COLLECTION_INFO event.
*
* @param \Drupal\Core\Config\ConfigCollectionInfo $collection_info
* The configuration collection names event.
* The configuration collection info event.
*/
abstract public function addCollections(ConfigCollectionInfo $collection_info);

View file

@ -39,16 +39,6 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
*/
protected $originalId;
/**
* The name of the property that is used to store plugin configuration.
*
* This is needed when the entity uses a LazyPluginCollection, to dictate
* where the plugin configuration should be stored.
*
* @var string
*/
protected $pluginConfigKey;
/**
* The enabled/disabled status of the configuration entity.
*

View file

@ -169,9 +169,6 @@ interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInter
* Dependency types are, for example, entity, module and theme.
*
* @return bool
* TRUE if the entity has changed, FALSE if not.
*
* @return bool
* TRUE if the entity has been changed as a result, FALSE if not.
*
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager

View file

@ -95,12 +95,13 @@ class FileStorage implements StorageInterface {
if (!$this->exists($name)) {
return FALSE;
}
$data = file_get_contents($this->getFilePath($name));
$filepath = $this->getFilePath($name);
$data = file_get_contents($filepath);
try {
$data = $this->decode($data);
}
catch (InvalidDataTypeException $e) {
throw new UnsupportedDataTypeConfigException("Invalid data type in config $name: {$e->getMessage()}");
throw new UnsupportedDataTypeConfigException('Invalid data type in config ' . $name . ', found in file' . $filepath . ' : ' . $e->getMessage());
}
return $data;
}

View file

@ -86,6 +86,7 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
// Add default values for data type and replace variables.
$definition += array('type' => 'undefined');
$replace = [];
$type = $definition['type'];
if (strpos($type, ']')) {
// Replace variable names in definition.
@ -102,7 +103,7 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
unset($definition['type']);
}
// Add default values from type definition.
$definition += $this->getDefinition($type);
$definition += $this->_getDefinitionWithReplacements($type, $replace);
$data_definition = $this->createDataDefinition($definition['type']);
@ -116,10 +117,17 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
}
/**
* {@inheritdoc}
* Determines the typed config type for a plugin ID.
*
* @param string $base_plugin_id
* The plugin ID.
* @param array $definitions
* An array of typed config definitions.
*
* @return string
* The typed config type for the given plugin ID.
*/
public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
$definitions = $this->getDefinitions();
protected function _determineType($base_plugin_id, array $definitions) {
if (isset($definitions[$base_plugin_id])) {
$type = $base_plugin_id;
}
@ -131,6 +139,27 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
// If we don't have definition, return the 'undefined' element.
$type = 'undefined';
}
return $type;
}
/**
* Gets a schema definition with replacements for dynamic names.
*
* @param string $base_plugin_id
* A plugin ID.
* @param array $replacements
* An array of replacements for dynamic type names.
* @param bool $exception_on_invalid
* (optional) This parameter is passed along to self::getDefinition().
* However, self::getDefinition() does not respect this parameter, so it is
* effectively useless in this context.
*
* @return array
* A schema definition array.
*/
protected function _getDefinitionWithReplacements($base_plugin_id, array $replacements, $exception_on_invalid = TRUE) {
$definitions = $this->getDefinitions();
$type = $this->_determineType($base_plugin_id, $definitions);
$definition = $definitions[$type];
// Check whether this type is an extension of another one and compile it.
if (isset($definition['type'])) {
@ -138,6 +167,15 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
// Preserve integer keys on merge, so sequence item types can override
// parent settings as opposed to adding unused second, third, etc. items.
$definition = NestedArray::mergeDeepArray(array($merge, $definition), TRUE);
// Replace dynamic portions of the definition type.
if (!empty($replacements) && strpos($definition['type'], ']')) {
$sub_type = $this->_determineType($this->replaceName($definition['type'], $replacements), $definitions);
// Merge the newly determined subtype definition with the original
// definition.
$definition = NestedArray::mergeDeepArray([$definitions[$sub_type], $definition], TRUE);
}
// Unset type so we try the merge only once per type.
unset($definition['type']);
$this->definitions[$type] = $definition;
@ -150,6 +188,13 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
return $definition;
}
/**
* {@inheritdoc}
*/
public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
return $this->_getDefinitionWithReplacements($base_plugin_id, [], $exception_on_invalid);
}
/**
* {@inheritdoc}
*/

View file

@ -64,10 +64,9 @@ class PagerSelectExtender extends SelectExtender {
* to it.
*/
public function execute() {
// Add convenience tag to mark that this is an extended query. We have to
// do this in the constructor to ensure that it is set before preExecute()
// gets called.
// By calling preExecute() here, we force it to preprocess the extender
// object rather than just the base query object. That means
// hook_query_alter() gets access to the extended object.
if (!$this->preExecute($this)) {
return NULL;
}

View file

@ -968,11 +968,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
}
// If the class loader is still the same, possibly upgrade to the APCu class
// If the class loader is still the same, possibly upgrade to the APC class
// loader.
// ApcClassLoader does not support APCu without backwards compatibility
// enabled.
if ($class_loader_class == get_class($this->classLoader)
&& Settings::get('class_loader_auto_detect', TRUE)
&& function_exists('apcu_fetch')) {
&& extension_loaded('apc')) {
$prefix = Settings::getApcuPrefix('class_loader', $this->root);
$apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $this->classLoader);
$this->classLoader->unregister();

View file

@ -440,7 +440,8 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
$result = [];
$args = array_slice(func_get_args(), 2);
foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
$langcodes = array_keys($entity->getTranslationLanguages());
foreach ($langcodes as $langcode) {
$translation = $entity->getTranslation($langcode);
// For non translatable fields, there is only one field object instance
// across all translations and it has as parent entity the entity in the
@ -453,6 +454,20 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
$result[$langcode][$name] = $args ? call_user_func_array([$items, $method], $args) : $items->{$method}();
}
}
// We need to call the delete method for field items of removed
// translations.
if ($method == 'postSave' && !empty($entity->original)) {
$original_langcodes = array_keys($entity->original->getTranslationLanguages());
foreach (array_diff($original_langcodes, $langcodes) as $removed_langcode) {
$translation = $entity->original->getTranslation($removed_langcode);
$fields = $translation->getTranslatableFields();
foreach ($fields as $name => $items) {
$items->delete();
}
}
}
return $result;
}

View file

@ -330,11 +330,11 @@ class EntityAutocomplete extends Textfield {
// Take "label (entity id)', match the ID from parenthesis when it's a
// number.
if (preg_match("/.+\((\d+)\)/", $input, $matches)) {
if (preg_match("/.+\s\((\d+)\)/", $input, $matches)) {
$match = $matches[1];
}
// Match the ID when it's a string (e.g. for config entity types).
elseif (preg_match("/.+\(([\w.]+)\)/", $input, $matches)) {
elseif (preg_match("/.+\s\(([\w.]+)\)/", $input, $matches)) {
$match = $matches[1];
}

View file

@ -83,8 +83,11 @@ class AuthenticationSubscriber implements EventSubscriberInterface {
$account = $this->authenticationProvider->authenticate($request);
if ($account) {
$this->accountProxy->setAccount($account);
return;
}
}
// No account has been set explicitly, initialize the timezone here.
date_default_timezone_set(drupal_get_user_timezone());
}
}

View file

@ -237,7 +237,9 @@ interface ModuleHandlerInterface {
*
* @return array
* An array of return values of the hook implementations. If modules return
* arrays from their implementations, those are merged into one array.
* arrays from their implementations, those are merged into one array
* recursively. Note: integer keys in arrays will be lost, as the merge is
* done using array_merge_recursive().
*/
public function invokeAll($hook, array $args = array());

View file

@ -70,7 +70,7 @@ abstract class FieldItemBase extends Map implements FieldItemInterface {
* {@inheritdoc}
*/
public function getLangcode() {
return $this->parent->getLangcode();
return $this->getParent()->getLangcode();
}
/**

View file

@ -102,28 +102,28 @@ interface FieldItemListInterface extends ListInterface, AccessibleInterface {
/**
* Magic method: Gets a property value of to the first field item.
*
* @see \Drupal\Core\Field\FieldItemInterface::__get()
* @see \Drupal\Core\Field\FieldItemInterface::__set()
*/
public function __get($property_name);
/**
* Magic method: Sets a property value of the first field item.
*
* @see \Drupal\Core\Field\FieldItemInterface::__set()
* @see \Drupal\Core\Field\FieldItemInterface::__get()
*/
public function __set($property_name, $value);
/**
* Magic method: Determines whether a property of the first field item is set.
*
* @see \Drupal\Core\Field\FieldItemInterface::__isset()
* @see \Drupal\Core\Field\FieldItemInterface::__unset()
*/
public function __isset($property_name);
/**
* Magic method: Unsets a property of the first field item.
*
* @see \Drupal\Core\Field\FieldItemInterface::__unset()
* @see \Drupal\Core\Field\FieldItemInterface::__isset()
*/
public function __unset($property_name);

View file

@ -83,6 +83,7 @@ class BooleanFormatter extends FormatterBase {
}
}
$field_name = $this->fieldDefinition->getName();
$form['format'] = [
'#type' => 'select',
'#title' => $this->t('Output format'),
@ -95,7 +96,7 @@ class BooleanFormatter extends FormatterBase {
'#default_value' => $this->getSetting('format_custom_true'),
'#states' => [
'visible' => [
'select[name="fields[field_boolean][settings_edit_form][settings][format]"]' => ['value' => 'custom'],
'select[name="fields[' . $field_name . '][settings_edit_form][settings][format]"]' => ['value' => 'custom'],
],
],
];
@ -105,7 +106,7 @@ class BooleanFormatter extends FormatterBase {
'#default_value' => $this->getSetting('format_custom_false'),
'#states' => [
'visible' => [
'select[name="fields[field_boolean][settings_edit_form][settings][format]"]' => ['value' => 'custom'],
'select[name="fields[' . $field_name . '][settings_edit_form][settings][format]"]' => ['value' => 'custom'],
],
],
];

View file

@ -55,7 +55,7 @@ class DecimalFormatter extends NumericFormatterBase {
$range = range(0, 10);
$elements['scale'] = array(
'#type' => 'select',
'#title' => t('Scale', array(), array('decimal places')),
'#title' => t('Scale', array(), array('context' => 'decimal places')),
'#options' => array_combine($range, $range),
'#default_value' => $this->getSetting('scale'),
'#description' => t('The number of digits to the right of the decimal.'),

View file

@ -131,7 +131,7 @@ class TimestampFormatter extends FormatterBase implements ContainerFactoryPlugin
);
$elements['custom_date_format']['#states']['visible'][] = array(
':input[name="options[settings][date_format]"]' => array('value' => 'custom'),
':input[name="name="fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings][date_format]"]' => array('value' => 'custom'),
);
$elements['timezone'] = array(

View file

@ -81,7 +81,7 @@ class DecimalItem extends NumericItemBase {
$range = range(0, 10);
$element['scale'] = array(
'#type' => 'select',
'#title' => t('Scale', array(), array('decimal places')),
'#title' => t('Scale', array(), array('context' => 'decimal places')),
'#options' => array_combine($range, $range),
'#default_value' => $settings['scale'],
'#description' => t('The number of digits to the right of the decimal.'),

View file

@ -62,6 +62,9 @@ class PasswordItem extends StringItem {
$this->value = $entity->original->{$this->getFieldDefinition()->getName()}->value;
}
}
// Ensure that the existing password is unset to minimise risks of it
// getting serialized and stored somewhere.
$this->existing = NULL;
}
/**

View file

@ -75,7 +75,7 @@ interface WidgetBaseInterface extends PluginSettingsInterface {
/**
* Retrieves processing information about the widget from $form_state.
*
* This method is static so that is can be used in static Form API callbacks.
* This method is static so that it can be used in static Form API callbacks.
*
* @param array $parents
* The array of #parents where the field lives in the form.
@ -95,7 +95,7 @@ interface WidgetBaseInterface extends PluginSettingsInterface {
/**
* Stores processing information about the widget in $form_state.
*
* This method is static so that is can be used in static Form API #callbacks.
* This method is static so that it can be used in static Form API #callbacks.
*
* @param array $parents
* The array of #parents where the widget lives in the form.

View file

@ -146,8 +146,6 @@ function callback_batch_finished($success, $results, $operations) {
*
* @param \Drupal\Core\Ajax\CommandInterface[] $data
* An array of all the rendered commands that will be sent to the client.
*
* @see \Drupal\Core\Ajax\AjaxResponse::ajaxRender()
*/
function hook_ajax_render_alter(array &$data) {
// Inject any new status messages into the content area.

View file

@ -253,6 +253,7 @@ class LanguageManager implements LanguageManagerInterface {
'dz' => array('Dzongkha', 'རྫོང་ཁ'),
'el' => array('Greek', 'Ελληνικά'),
'en' => array('English', 'English'),
'en-x-simple' => array('Simple English', 'Simple English'),
'eo' => array('Esperanto', 'Esperanto'),
'es' => array('Spanish', 'Español'),
'et' => array('Estonian', 'Eesti'),

View file

@ -93,11 +93,19 @@ class LoggerChannel implements LoggerChannelInterface {
$context['request_uri'] = $request->getUri();
$context['referer'] = $request->headers->get('Referer', '');
$context['ip'] = $request->getClientIP();
try {
if ($this->currentUser) {
$context['user'] = $this->currentUser;
$context['uid'] = $this->currentUser->id();
}
}
catch (\Exception $e) {
// An exception might be thrown if the database connection is not
// available or due to another unexpected reason. It is more important
// to log the error that we already have so any additional exceptions
// are ignored.
}
}
if (is_string($level)) {
// Convert to integer equivalent for consistency with RFC 5424.

View file

@ -23,10 +23,7 @@ interface LoggerChannelFactoryInterface {
public function get($channel);
/**
* Adds a logger.
*
* Here is were all services tagged as 'logger' are being retrieved and then
* passed to the channels after instantiation.
* Adds a logger to all the channels.
*
* @param \Psr\Log\LoggerInterface $logger
* The PSR-3 logger to add.

View file

@ -8,6 +8,9 @@
namespace Drupal\Core\Menu;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
@ -182,8 +185,10 @@ class LocalActionManager extends DefaultPluginManager implements LocalActionMana
$links = array();
/** @var $plugin \Drupal\Core\Menu\LocalActionInterface */
foreach ($this->instances[$route_appears] as $plugin_id => $plugin) {
$cacheability = new CacheableMetadata();
$route_name = $plugin->getRouteName();
$route_parameters = $plugin->getRouteParameters($this->routeMatch);
$access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE);
$links[$plugin_id] = array(
'#theme' => 'menu_local_action',
'#link' => array(
@ -191,10 +196,22 @@ class LocalActionManager extends DefaultPluginManager implements LocalActionMana
'url' => Url::fromRoute($route_name, $route_parameters),
'localized_options' => $plugin->getOptions($this->routeMatch),
),
'#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE),
'#access' => $access,
'#weight' => $plugin->getWeight(),
);
$cacheability->addCacheableDependency($access);
// For backward compatibility in 8.0.x, plugins that do not implement
// the \Drupal\Core\Cache\CacheableDependencyInterface are assumed
// to be cacheable forever.
if ($plugin instanceof CacheableDependencyInterface) {
$cacheability->addCacheableDependency($plugin);
}
else {
$cacheability->setCacheMaxAge(Cache::PERMANENT);
}
$cacheability->applyTo($links[$plugin_id]);
}
$links['#cache']['contexts'][] = 'route';
return $links;
}

View file

@ -8,7 +8,6 @@
namespace Drupal\Core\Menu\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Menu\LocalActionManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -84,18 +83,8 @@ class LocalActionsBlock extends BlockBase implements ContainerFactoryPluginInter
public function build() {
$route_name = $this->routeMatch->getRouteName();
$local_actions = $this->localActionManager->getActionsForRoute($route_name);
if (empty($local_actions)) {
return [];
}
return $local_actions;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return Cache::mergeContexts(parent::getCacheContexts(), ['route']);
}
}

View file

@ -75,7 +75,7 @@ class ImageButton extends Submit {
$element['#attributes']['type'] = 'image';
Element::setAttributes($element, array('id', 'name', 'value'));
$element['#attributes']['src'] = file_create_url($element['#src']);
$element['#attributes']['src'] = file_url_transform_relative(file_create_url($element['#src']));
if (!empty($element['#title'])) {
$element['#attributes']['alt'] = $element['#title'];
$element['#attributes']['title'] = $element['#title'];

View file

@ -22,8 +22,8 @@ use Drupal\Component\Utility\Html as HtmlUtility;
* $form['settings']['active'] = array(
* '#type' => 'radios',
* '#title' => t('Poll status'),
* '#default_value' => 1
* '#options' => array(0 => t('Closed'), 1 => t('Active'),
* '#default_value' => 1,
* '#options' => array(0 => t('Closed'), 1 => t('Active')),
* );
* @endcode
*

View file

@ -34,7 +34,7 @@ use Drupal\Component\Utility\Html as HtmlUtility;
* @code
* $form['contacts'] = array(
* '#type' => 'table',
* '#title' => 'Sample Table',
* '#caption' => 'Sample Table',
* '#header' => array('Name', 'Phone'),
* );
*

View file

@ -16,7 +16,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
* Provides a form element for a table with radios or checkboxes in left column.
*
* Properties:
* - #headers: Table headers used in the table.
* - #header: Table headers used in the table.
* - #options: An associative array where each key is the value returned when
* a user selects the radio button or checkbox, and each value is the row of
* table data.

View file

@ -56,7 +56,7 @@ class AccountProxy implements AccountProxyInterface {
// After the container is rebuilt, DrupalKernel sets the initial
// account to the id of the logged in user. This is necessary in order
// to refresh the user account reference here.
$this->account = $this->loadUserEntity($this->initialAccountId);
$this->setAccount($this->loadUserEntity($this->initialAccountId));
}
else {
$this->account = new AnonymousUserSession();

View file

@ -135,7 +135,9 @@ class TwigExtension extends \Twig_Extension {
new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
new \Twig_SimpleFunction('link', array($this, 'getLink')),
new \Twig_SimpleFunction('file_url', 'file_create_url'),
new \Twig_SimpleFunction('file_url', function ($uri) {
return file_url_transform_relative(file_create_url($uri));
}),
new \Twig_SimpleFunction('attach_library', [$this, 'attachLibrary']),
new \Twig_SimpleFunction('active_theme_path', [$this, 'getActiveThemePath']),
new \Twig_SimpleFunction('active_theme', [$this, 'getActiveTheme']),

View file

@ -318,7 +318,6 @@ class ThemeInitialization implements ThemeInitializationInterface {
$stylesheets_remove = array();
// Grab stylesheets from base theme.
foreach ($base_themes as $base) {
$base_theme_path = $base->getPath();
if (!empty($base->info['stylesheets-remove'])) {
foreach ($base->info['stylesheets-remove'] as $css_file) {
$css_file = $this->resolveStyleSheetPlaceholders($css_file);

View file

@ -293,6 +293,11 @@ class Url {
* @see \Drupal\Core\Url::fromUserInput()
*/
public static function fromUri($uri, $options = []) {
// parse_url() incorrectly parses base:number/... as hostname:port/...
// and not the scheme. Prevent that by prefixing the path with a slash.
if (preg_match('/^base:\d/', $uri)) {
$uri = str_replace('base:', 'base:/', $uri);
}
$uri_parts = parse_url($uri);
if ($uri_parts === FALSE) {
throw new \InvalidArgumentException("The URI '$uri' is malformed.");

View file

@ -31,6 +31,7 @@
// `jQuery(event.target).remove()` as well, to remove the dialog on
// closing.
close: function (event) {
Drupal.dialog(event.target).close();
Drupal.detachBehaviors(event.target, null, 'unload');
}
};
@ -60,6 +61,19 @@
* @return {Drupal.dialog~dialogDefinition}
*/
Drupal.dialog = function (element, options) {
var undef;
var $element = $(element);
var dialog = {
open: false,
returnValue: undef,
show: function () {
openDialog({modal: false});
},
showModal: function () {
openDialog({modal: true});
},
close: closeDialog
};
function openDialog(settings) {
settings = $.extend({}, drupalSettings.dialog, options, settings);
@ -78,20 +92,6 @@
$(window).trigger('dialog:afterclose', [dialog, $element]);
}
var undef;
var $element = $(element);
var dialog = {
open: false,
returnValue: undef,
show: function () {
openDialog({modal: false});
},
showModal: function () {
openDialog({modal: true});
},
close: closeDialog
};
return dialog;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -28,6 +28,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class MessageAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
/**
* The token service.
*
* @var \Drupal\Core\Utility\Token
*/
protected $token;
@ -48,10 +50,8 @@ class MessageAction extends ConfigurableActionBase implements ContainerFactoryPl
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Utility\Token
* The token service.
* @param \Drupal\Core\Utility\Token $token
* The token replacement service.
* The token service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/

View file

@ -323,14 +323,22 @@ function book_node_prepare_form(NodeInterface $node, $operation, FormStateInterf
}
/**
* Implements hook_form_FORM_ID_alter() for node_delete_confirm().
* Implements hook_form_BASE_FORM_ID_alter().
*
* Alters the confirm form for a single node deletion.
*
* @see node_delete_confirm()
*/
function book_form_node_delete_confirm_alter(&$form, FormStateInterface $form_state) {
$node = Node::load($form['nid']['#value']);
function book_form_node_confirm_form_alter(&$form, FormStateInterface $form_state) {
// Only need to alter the delete operation form.
if ($form_state->getFormObject()->getOperation() !== 'delete') {
return;
}
/** @var \Drupal\node\NodeInterface $node */
$node = $form_state->getFormObject()->getEntity();
if (!book_type_is_allowed($node->getType())) {
// Not a book node.
return;
}
if (isset($node->book) && $node->book['has_children']) {
$form['book_warning'] = array(

View file

@ -439,9 +439,9 @@ class BookManager implements BookManagerInterface {
if ($nid == $original['bid']) {
// Handle deletion of a top-level post.
$result = $this->bookOutlineStorage->loadBookChildren($nid);
foreach ($result as $child) {
$child['bid'] = $child['nid'];
$children = $this->entityManager->getStorage('node')->loadMultiple(array_keys($result));
foreach ($children as $child) {
$child->book['bid'] = $child->id();
$this->updateOutline($child);
}
}

View file

@ -30,7 +30,7 @@ class BookTest extends WebTestBase {
/**
* A book node.
*
* @var object
* @var \Drupal\node\NodeInterface
*/
protected $book;
@ -77,11 +77,13 @@ class BookTest extends WebTestBase {
$this->bookAuthor = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books'));
$this->webUser = $this->drupalCreateUser(array('access printer-friendly version', 'node test view'));
$this->webUserWithoutNodeAccess = $this->drupalCreateUser(array('access printer-friendly version'));
$this->adminUser = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'node test view', 'administer content types', 'administer site configuration'));
$this->adminUser = $this->drupalCreateUser(array('create new books', 'create book content', 'edit any book content', 'delete any book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'node test view', 'administer content types', 'administer site configuration'));
}
/**
* Creates a new book with a page hierarchy.
*
* @return \Drupal\node\NodeInterface[]
*/
function createBook() {
// Create new book.
@ -498,6 +500,25 @@ class BookTest extends WebTestBase {
$node_storage->resetCache(array($this->book->id()));
$node = $node_storage->load($this->book->id());
$this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.');
// Tests directly deleting a book parent.
$nodes = $this->createBook();
$this->drupalLogin($this->adminUser);
$this->drupalGet($this->book->urlInfo('delete-form'));
$this->assertRaw(t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $this->book->label()]));
// Delete parent, and visit a child page.
$this->drupalPostForm($this->book->urlInfo('delete-form'), [], t('Delete'));
$this->drupalGet($nodes[0]->urlInfo());
$this->assertResponse(200);
$this->assertText($nodes[0]->label());
// The book parents should be updated.
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$node_storage->resetCache();
$child = $node_storage->load($nodes[0]->id());
$this->assertEqual($child->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.');
// 3rd-level children should now be 2nd-level.
$second = $node_storage->load($nodes[1]->id());
$this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.');
}
/**

View file

@ -35,10 +35,11 @@ function hook_ckeditor_plugin_info_alter(array &$plugins) {
* iframe versions of CKEditor.
*
* Front-end themes (and base themes) can easily specify CSS files to be used in
* iframe instances of CKEditor through an entry in their .info file:
* iframe instances of CKEditor through an entry in their .info.yml file:
*
* @code
* ckeditor_stylesheets[] = css/ckeditor-iframe.css
* ckeditor_stylesheets:
* - css/ckeditor-iframe.css
* @endcode
*
* @param array &$css

View file

@ -19,6 +19,7 @@ use Drupal\Component\Annotation\Plugin;
* @see \Drupal\ckeditor\CKEditorPluginInterface
* @see \Drupal\ckeditor\CKEditorPluginBase
* @see \Drupal\ckeditor\CKEditorPluginManager
* @see hook_ckeditor_plugin_info_alter()
* @see plugin_api
*
* @Annotation

View file

@ -17,11 +17,11 @@ use Drupal\editor\Entity\Editor;
* than the one provided by Drupal core is used. Most CKEditor plugins don't
* need to provide additional settings forms.
*
* This base assumes that your plugin has buttons that you want to be enabled
* through the toolbar builder UI. It is still possible to also implement the
* CKEditorPluginContextualInterface (for contextual enabling) and
* This base class assumes that your plugin has buttons that you want to be
* enabled through the toolbar builder UI. It is still possible to also
* implement the CKEditorPluginContextualInterface (for contextual enabling) and
* CKEditorPluginConfigurableInterface interfaces (for configuring plugin
* settings) though.
* settings).
*
* NOTE: the Drupal plugin ID should correspond to the CKEditor plugin name.
*

View file

@ -298,8 +298,11 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
);
// Finally, set Drupal-specific CKEditor settings.
$root_relative_file_url = function ($uri) {
return file_url_transform_relative(file_create_url($uri));
};
$settings += array(
'drupalExternalPlugins' => array_map('file_create_url', $external_plugin_files),
'drupalExternalPlugins' => array_map($root_relative_file_url, $external_plugin_files),
);
// Parse all CKEditor plugin JavaScript files for translations.
@ -421,6 +424,7 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
$this->moduleHandler->alter('ckeditor_css', $css, $editor);
$css = array_merge($css, _ckeditor_theme_css());
$css = array_map('file_create_url', $css);
$css = array_map('file_url_transform_relative', $css);
return array_values($css);
}

View file

@ -90,8 +90,8 @@ class CKEditorTest extends KernelTestBase {
'language' => 'en',
'stylesSet' => FALSE,
'drupalExternalPlugins' => array(
'drupalimage' => file_create_url('core/modules/ckeditor/js/plugins/drupalimage/plugin.js'),
'drupallink' => file_create_url('core/modules/ckeditor/js/plugins/drupallink/plugin.js'),
'drupalimage' => file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupalimage/plugin.js')),
'drupallink' => file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupallink/plugin.js')),
),
);
$expected_config = $this->castSafeStrings($expected_config);
@ -114,9 +114,9 @@ class CKEditorTest extends KernelTestBase {
$expected_config['toolbar'][0]['items'][] = 'Format';
$expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6';
$expected_config['extraPlugins'] .= ',llama_contextual,llama_contextual_and_button';
$expected_config['drupalExternalPlugins']['llama_contextual'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js');
$expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js');
$expected_config['contentsCss'][] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
$expected_config['drupalExternalPlugins']['llama_contextual'] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js'));
$expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js'));
$expected_config['contentsCss'][] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css'));
ksort($expected_config);
$this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for customized configuration.');
@ -261,15 +261,15 @@ class CKEditorTest extends KernelTestBase {
// Enable the editor_test module, which implements hook_ckeditor_css_alter().
$this->enableModules(array('ckeditor_test'));
$expected[] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
$expected[] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css'));
$this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a hook_ckeditor_css_alter() implementation exists.');
// Enable the Bartik theme, which specifies a CKEditor stylesheet.
\Drupal::service('theme_handler')->install(['bartik']);
$this->config('system.theme')->set('default', 'bartik')->save();
$expected[] = file_create_url('core/themes/bartik/css/base/elements.css');
$expected[] = file_create_url('core/themes/bartik/css/components/captions.css');
$expected[] = file_create_url('core/themes/bartik/css/components/table.css');
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/base/elements.css'));
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/captions.css'));
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/table.css'));
$this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a theme providing a CKEditor stylesheet exists.');
}
@ -478,8 +478,8 @@ class CKEditorTest extends KernelTestBase {
protected function getDefaultContentsCssConfig() {
return array(
file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css'),
file_create_url('core/modules/system/css/components/align.module.css'),
file_url_transform_relative(file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css')),
file_url_transform_relative(file_create_url('core/modules/system/css/components/align.module.css')),
);
}

View file

@ -57,7 +57,6 @@ class CKEditorToolbarButtonTest extends WebTestBase {
* Method tests CKEditor image buttons.
*/
public function testImageButtonDisplay() {
global $base_url;
$this->drupalLogin($this->admin_user);
// Install the Arabic language (which is RTL) and configure as the default.
@ -75,7 +74,7 @@ class CKEditorToolbarButtonTest extends WebTestBase {
$json_encode = function($html) {
return trim(Json::encode($html), '"');
};
$markup = $json_encode($base_url . '/core/modules/ckeditor/js/plugins/drupalimage/image.png');
$markup = $json_encode(file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupalimage/image.png')));
$this->assertRaw($markup);
}

View file

@ -126,7 +126,7 @@ function color_block_view_pre_render(array $build) {
// Override logo.
$logo = $config->get('logo');
if ($logo && $build['content']['site_logo'] && preg_match('!' . $theme_key . '/logo.svg$!', $build['content']['site_logo']['#uri'])) {
$build['content']['site_logo']['#uri'] = file_create_url($logo);
$build['content']['site_logo']['#uri'] = file_url_transform_relative(file_create_url($logo));
}
return $build;

View file

@ -121,7 +121,7 @@ class ColorTest extends WebTestBase {
$this->drupalGet('<front>');
$stylesheets = $this->config('color.theme.' . $theme)->get('stylesheets');
foreach ($stylesheets as $stylesheet) {
$this->assertPattern('|' . file_create_url($stylesheet) . '|', 'Make sure the color stylesheet is included in the content. (' . $theme . ')');
$this->assertPattern('|' . file_url_transform_relative(file_create_url($stylesheet)) . '|', 'Make sure the color stylesheet is included in the content. (' . $theme . ')');
$stylesheet_content = join("\n", file($stylesheet));
$this->assertTrue(strpos($stylesheet_content, 'color: #123456') !== FALSE, 'Make sure the color we changed is in the color stylesheet. (' . $theme . ')');
}
@ -191,7 +191,7 @@ class ColorTest extends WebTestBase {
// Ensure that the overridden logo is present in Bartik, which is colorable.
$this->drupalGet('admin/appearance/settings/bartik');
$this->assertIdentical($GLOBALS['base_url'] . '/' . 'core/misc/druplicon.png', $this->getDrupalSettings()['color']['logo']);
$this->assertIdentical($GLOBALS['base_path'] . 'core/misc/druplicon.png', $this->getDrupalSettings()['color']['logo']);
}
/**

View file

@ -1,14 +0,0 @@
<?php
/**
* @file
* Provide test color module.
*/
/**
* Implements hook_system_theme_info().
*/
function color_test_system_theme_info() {
$themes['color_test_theme'] = drupal_get_path('module', 'color_test') . '/themes/color_test_theme/color_test_theme.info.yml';
return $themes;
}

View file

@ -563,7 +563,7 @@ function comment_preview(CommentInterface $comment, FormStateInterface $form_sta
if (!$form_state->getErrors()) {
$comment->in_preview = TRUE;
$comment_build = comment_view($comment);
$comment_build = \Drupal::entityTypeManager()->getViewBuilder('comment')->view($comment);
$comment_build['#weight'] = -100;
$preview_build['comment_preview'] = $comment_build;
@ -573,7 +573,7 @@ function comment_preview(CommentInterface $comment, FormStateInterface $form_sta
$build = array();
$parent = $comment->getParentComment();
if ($parent && $parent->isPublished()) {
$build = comment_view($parent);
$build = \Drupal::entityTypeManager()->getViewBuilder('comment')->view($parent);
}
}
else {

View file

@ -104,7 +104,7 @@ class Rss extends RssPluginBase {
// The comment gets built and modules add to or modify
// $comment->rss_elements and $comment->rss_namespaces.
$build = comment_view($comment, 'rss');
$build = $this->entityManager->getViewBuilder('comment')->view($comment, 'rss');
unset($build['#theme']);
if (!empty($comment->rss_namespaces)) {

View file

@ -33,7 +33,6 @@ class ConfigEventsTest extends KernelTestBase {
$config = new Config($name, \Drupal::service('config.storage'), \Drupal::service('event_dispatcher'), \Drupal::service('config.typed'));
$config->set('key', 'initial');
\Drupal::state()->get('config_events_test.event', FALSE);
$this->assertIdentical(\Drupal::state()->get('config_events_test.event', array()), array(), 'No events fired by creating a new configuration object');
$config->save();

View file

@ -506,4 +506,70 @@ class ConfigSchemaTest extends KernelTestBase {
$this->assertEqual($definitions['config_schema_test.hook']['additional_metadata'], 'new schema info');
}
/**
* Tests saving config when the type is wrapped by a dynamic type.
*/
public function testConfigSaveWithWrappingSchema() {
$untyped_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'plugin_id' => 'wrapper:foo',
'internal_value' => 100,
],
],
];
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'plugin_id' => 'wrapper:foo',
'internal_value' => '100',
],
],
];
// Save config which has a schema that enforces types.
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.plugin_types')
->setData($untyped_values)
->save();
$this->assertIdentical(\Drupal::config('wrapping.config_schema_test.plugin_types')
->get(), $typed_values);
}
/**
* Tests dynamic config schema type with multiple sub-key references.
*/
public function testConfigSaveWithWrappingSchemaDoubleBrackets() {
$untyped_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'cat',
'bar' => 'dog',
'another_key' => 100,
],
],
];
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'cat',
'bar' => 'dog',
'another_key' => '100',
],
],
];
// Save config which has a schema that enforces types.
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
->setData($untyped_values)
->save();
$this->assertIdentical(\Drupal::config('wrapping.config_schema_test.double_brackets')
->get(), $typed_values);
}
}

View file

@ -9,6 +9,7 @@ namespace Drupal\config\Tests\Storage;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
/**
* Tests FileStorage operations.
@ -76,4 +77,19 @@ class FileStorageTest extends ConfigStorageTestBase {
$this->assertIdentical($config_files, $expected_files, 'Absolute path, two config files found.');
}
/**
* Test UnsupportedDataTypeConfigException displays path of
* erroneous file during read.
*/
public function testReadUnsupportedDataTypeConfigException() {
file_put_contents($this->storage->getFilePath('core.extension'), PHP_EOL . 'foo : [bar}', FILE_APPEND);
try {
$config_parsed = $this->storage->read('core.extension');
}
catch (UnsupportedDataTypeConfigException $e) {
$this->pass('Exception thrown when trying to read a field containing invalid data type.');
$this->assertTrue((strpos($e->getMessage(), $this->storage->getFilePath('core.extension')) !== FALSE), 'Erroneous file path is displayed.');
}
}
}

View file

@ -32,10 +32,10 @@ class EventSubscriber implements EventSubscriberInterface {
}
/**
* Reacts to the ConfigEvents::COLLECTION_NAMES event.
* Reacts to the ConfigEvents::COLLECTION_INFO event.
*
* @param \Drupal\Core\Config\ConfigCollectionInfo $collection_info
* The configuration collection names event.
* The configuration collection info event.
*/
public function addCollections(ConfigCollectionInfo $collection_info) {
$collections = $this->state->get('config_collection_install_test.collection_names', array());

View file

@ -213,3 +213,50 @@ config_test.dynamic.*.third_party.config_schema_test:
type: integer
string:
type: string
wrapping.config_schema_test.plugin_types:
type: config_object
mapping:
tests:
type: sequence
sequence:
- type: wrapping.test.plugin_types.[plugin_id]
wrapping.test.plugin_types.*:
type: test.plugin_types.[plugin_id]
mapping:
wrapper_value:
type: string
test.plugin_types.wrapper:*:
type: test.plugin_types
mapping:
internal_value:
type: string
wrapping.config_schema_test.double_brackets:
type: config_object
mapping:
tests:
type: sequence
sequence:
- type: wrapping.test.double_brackets.[another_key]
wrapping.test.double_brackets.*:
type: test.double_brackets.[foo].[bar]
mapping:
wrapper_value:
type: string
test.double_brackets.cat.dog:
type: test.double_brackets
mapping:
another_key:
type: string
foo:
type: string
bar:
type: string
test.double_brackets.*:
type: mapping

View file

@ -35,9 +35,6 @@ function dblog_views_data() {
'sort' => array(
'id' => 'standard',
),
'search' => array(
'id' => 'standard',
),
);
$data['watchdog']['uid'] = array(
@ -52,9 +49,6 @@ function dblog_views_data() {
'argument' => array(
'id' => 'numeric',
),
'search' => array(
'id' => 'standard',
),
'relationship' => array(
'title' => t('User'),
'help' => t('The user on which the log entry as written.'),

View file

@ -12,6 +12,7 @@ use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTest;
@ -76,6 +77,7 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
$this->installEntitySchema('file');
$this->installSchema('comment', ['comment_entity_statistics']);
$this->installSchema('node', ['node_access']);
$this->vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => $this->randomMachineName(),
@ -100,7 +102,7 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_term', 'Test content entity reference', 'taxonomy_term');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_entity_test_string_id', 'Test content entity reference with string ID', 'entity_test_string_id');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_vocabulary', 'Test config entity reference', 'taxonomy_vocabulary');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_node', 'Test node entity reference', 'node');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_node', 'Test node entity reference', 'node', 'default', [], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_user', 'Test user entity reference', 'user');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_comment', 'Test comment entity reference', 'comment');
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_file', 'Test file entity reference', 'file');
@ -406,6 +408,64 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
$errors = $entity->validate();
$this->assertEqual(0, count($errors));
// Test with a mix of valid and invalid nodes.
$unsaved_unpublished_node_title = $this->randomString();
$unsaved_unpublished_node = Node::create([
'title' => $unsaved_unpublished_node_title,
'type' => 'node',
'status' => NODE_NOT_PUBLISHED,
]);
$saved_unpublished_node_title = $this->randomString();
$saved_unpublished_node = Node::create([
'title' => $saved_unpublished_node_title,
'type' => 'node',
'status' => NODE_NOT_PUBLISHED,
]);
$saved_unpublished_node->save();
$saved_published_node_title = $this->randomString();
$saved_published_node = Node::create([
'title' => $saved_published_node_title,
'type' => 'node',
'status' => NODE_PUBLISHED,
]);
$saved_published_node->save();
$entity = EntityTest::create([
'field_test_node' => [
[
'entity' => $unsaved_unpublished_node,
],
[
'target_id' => $saved_unpublished_node->id(),
],
[
'target_id' => $saved_published_node->id(),
],
],
]);
$errors = $entity->validate();
$this->assertEqual(2, count($errors));
$this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $unsaved_unpublished_node_title]));
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
$this->assertEqual($errors[1]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $saved_unpublished_node->id()]));
$this->assertEqual($errors[1]->getPropertyPath(), 'field_test_node.1.target_id');
// Publish one of the nodes and try again.
$saved_unpublished_node->setPublished(TRUE);
$saved_unpublished_node->save();
$errors = $entity->validate();
$this->assertEqual(1, count($errors));
$this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $unsaved_unpublished_node_title]));
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
// Publish the last invalid node and try again.
$unsaved_unpublished_node->setPublished(TRUE);
$errors = $entity->validate();
$this->assertEqual(0, count($errors));
// Test with an unpublished and unsaved comment.
$title = $this->randomString();
$comment = Comment::create([

View file

@ -977,7 +977,12 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta
break;
case 'url':
// Ideally, this would use file_url_transform_relative(), but because
// tokens are also often used in e-mails, it's better to keep absolute
// file URLs. The 'url.site' cache context is associated to ensure the
// correct absolute URL is used in case of a multisite setup.
$replacements[$original] = file_create_url($file->getFileUri());
$bubbleable_metadata->addCacheContexts(['url.site']);
break;
// These tokens are default variations on the chained tokens handled below.
@ -1228,7 +1233,13 @@ function template_preprocess_file_link(&$variables) {
$options = array();
$file_entity = ($file instanceof File) ? $file : File::load($file->fid);
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. As a work-around, we currently add the 'url.site' cache context
// to ensure different file URLs are generated for different sites in a
// multisite setup, including HTTP and HTTPS versions of the same site.
// Fix in https://www.drupal.org/node/2646744.
$url = file_create_url($file_entity->getFileUri());
$variables['#cache']['contexts'][] = 'url.site';
$mime_type = $file->getMimeType();
// Set options as per anchor format described at

View file

@ -70,6 +70,8 @@ class File extends ContentEntityBase implements FileInterface {
/**
* {@inheritdoc}
*
* @see file_url_transform_relative()
*/
public function url($rel = 'canonical', $options = array()) {
return file_create_url($this->getFileUri());

View file

@ -52,6 +52,8 @@ abstract class BaseFieldFileFormatterBase extends FormatterBase {
$url = NULL;
// Add support to link to the entity itself.
if ($this->getSetting('link_to_file')) {
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. See below.
$url = file_create_url($items->getEntity()->uri->value);
}
@ -63,6 +65,16 @@ abstract class BaseFieldFileFormatterBase extends FormatterBase {
'#type' => 'link',
'#title' => $view_value,
'#url' => Url::fromUri($url),
// @todo Remove the 'url.site' cache context by using a relative file
// URL (file_url_transform_relative()). This is currently impossible
// because #type => link requires a Url object, and Url objects do not
// support relative URLs: they require fully qualified URLs. Fix in
// https://www.drupal.org/node/2646744.
'#cache' => [
'contexts' => [
'url.site',
],
],
];
}
else {

View file

@ -55,6 +55,9 @@ class FileUriFormatter extends BaseFieldFileFormatterBase {
protected function viewValue(FieldItemInterface $item) {
$value = $item->value;
if ($this->getSetting('file_download_path')) {
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. See BaseFieldFileFormatterBase::viewElements(). Fix in
// https://www.drupal.org/node/2646744.
$value = file_create_url($value);
}
return $value;

View file

@ -33,6 +33,9 @@ class RSSEnclosureFormatter extends FileFormatterBase {
$entity->rss_elements[] = array(
'key' => 'enclosure',
'attributes' => array(
// In RSS feeds, it is necessary to use absolute URLs. The 'url.site'
// cache context is already associated with RSS feed responses, so it
// does not need to be specified here.
'url' => file_create_url($file->getFileUri()),
'length' => $file->getSize(),
'type' => $file->getMimeType(),

View file

@ -30,7 +30,7 @@ class UrlPlainFormatter extends FileFormatterBase {
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) {
$elements[$delta] = array(
'#markup' => file_create_url($file->getFileUri()),
'#markup' => file_url_transform_relative(file_create_url($file->getFileUri())),
'#cache' => array(
'tags' => $file->getCacheTags(),
),

View file

@ -56,10 +56,12 @@ class FileFieldItemList extends EntityReferenceFieldItemList {
$original_ids = array();
$langcode = $this->getLangcode();
$original = $entity->original;
$original_items = $original->hasTranslation($langcode) ? $original->getTranslation($langcode)->{$field_name} : $original->{$field_name};
if ($original->hasTranslation($langcode)) {
$original_items = $original->getTranslation($langcode)->{$field_name};
foreach ($original_items as $item) {
$original_ids[] = $item->target_id;
}
}
// Decrement file usage by 1 for files that were removed from the field.
$removed_ids = array_filter(array_diff($original_ids, $ids));

View file

@ -186,7 +186,6 @@ class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
$elements['#description'] = $description;
$elements['#field_name'] = $field_name;
$elements['#language'] = $items->getLangcode();
$elements['#display_field'] = (bool) $this->getFieldSetting('display_field');
// The field settings include defaults for the field type. However, this
// widget is a base class for other widgets (e.g., ImageWidget) that may
// act on field types without these expected settings.

View file

@ -2,10 +2,10 @@
/**
* @file
* Contains \Drupal\file\Plugin\migrate\cckfield\FileField.
* Contains \Drupal\file\Plugin\migrate\cckfield\d6\FileField.
*/
namespace Drupal\file\Plugin\migrate\cckfield;
namespace Drupal\file\Plugin\migrate\cckfield\d6;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\file\Plugin\migrate\cckfield\d7\FileField.
*/
namespace Drupal\file\Plugin\migrate\cckfield\d7;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "file",
* )
*/
class FileField extends CckFieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return [
'filefield_widget' => 'file_generic',
];
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'default' => 'file_default',
'url_plain' => 'file_url_plain',
'path_plain' => 'file_url_plain',
'image_plain' => 'image',
'image_nodelink' => 'image',
'image_imagelink' => 'image',
];
}
/**
* {@inheritdoc}
*/
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'iterator',
'source' => $field_name,
'process' => [
'target_id' => 'fid',
'display' => 'display',
'description' => 'description',
],
];
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldType(Row $row) {
return $row->getSourceProperty('widget_type') == 'imagefield_widget' ? 'image' : 'file';
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\file\Plugin\migrate\cckfield\d7\ImageField.
*/
namespace Drupal\file\Plugin\migrate\cckfield\d7;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "image"
* )
*/
class ImageField extends CckFieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return array();
}
/**
* {@inheritdoc}
*/
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'iterator',
'source' => $field_name,
'process' => [
'target_id' => 'fid',
'alt' => 'alt',
'title' => 'title',
'width' => 'width',
'height' => 'height',
],
];
$migration->mergeProcessOfProperty($field_name, $process);
}
}

View file

@ -69,6 +69,12 @@ class File extends FieldPluginBase {
protected function renderLink($data, ResultRow $values) {
if (!empty($this->options['link_to_file']) && $data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. As a work-around, we could add the 'url.site' cache context
// to ensure different file URLs are generated for different sites in a
// multisite setup, including HTTP and HTTPS versions of the same site.
// But unfortunately it's impossible to bubble a cache context here.
// Fix in https://www.drupal.org/node/2646744.
$this->options['alter']['path'] = file_create_url($this->getValue($values, 'uri'));
}

View file

@ -0,0 +1,208 @@
<?php
/**
* @file
* Contains \Drupal\file\Tests\FileOnTranslatedEntityTest.
*/
namespace Drupal\file\Tests;
use Drupal\file\Entity\File;
use Drupal\node\Entity\Node;
/**
* Uploads files to translated nodes.
*
* @group file
*/
class FileOnTranslatedEntityTest extends FileFieldTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('language', 'content_translation');
/**
* The name of the file field used in the test.
*
* @var string
*/
protected $fieldName;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create the "Basic page" node type.
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
// Create a file field on the "Basic page" node type.
$this->fieldName = strtolower($this->randomMachineName());
$this->createFileField($this->fieldName, 'node', 'page');
// Create and login user.
$permissions = array(
'access administration pages',
'administer content translation',
'administer content types',
'administer languages',
'create content translations',
'create page content',
'edit any page content',
'translate any entity',
'delete any page content',
);
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
// Add a second and third language.
$edit = array();
$edit['predefined_langcode'] = 'fr';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
$edit = array();
$edit['predefined_langcode'] = 'nl';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Enable translation for "Basic page" nodes.
$edit = array(
'entity_types[node]' => 1,
'settings[node][page][translatable]' => 1,
"settings[node][page][fields][$this->fieldName]" => 1,
);
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
\Drupal::entityManager()->clearCachedDefinitions();
}
/**
* Tests synced file fields on translated nodes.
*/
public function testSyncedFiles() {
// Verify that the file field on the "Basic page" node type is translatable.
$definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
$this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node file field is translatable.');
// Create a default language node.
$default_language_node = $this->drupalCreateNode(array('type' => 'page', 'title' => 'Lost in translation'));
// Edit the node to upload a file.
$edit = array();
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[0]->uri);
$this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
$first_fid = $this->getLastFileId();
// Translate the node into French: remove the existing file.
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/fr', array(), t('Remove'));
// Upload a different file.
$edit = array();
$edit['title[0][value]'] = 'Bill Murray';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[1]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
// This inspects the HTML after the post of the translation, the file
// should be displayed on the original node.
$this->assertRaw('file--mime-text-plain');
$second_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
/* @var $file \Drupal\file\FileInterface */
// Ensure the file status of the first file permanent.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the second file is permanent.
$file = File::load($second_fid);
$this->assertTrue($file->isPermanent());
// Translate the node into dutch: remove the existing file.
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/nl', array(), t('Remove'));
// Upload a different file.
$edit = array();
$edit['title[0][value]'] = 'Scarlett Johansson';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[2]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$third_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first file is untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
// This inspects the HTML after the post of the translation, the file
// should be displayed on the original node.
$this->assertRaw('file--mime-text-plain');
// Ensure the file status of the second file is permanent.
$file = File::load($second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the third file is permanent.
$file = File::load($third_fid);
$this->assertTrue($file->isPermanent());
// Edit the second translation: remove the existing file.
$this->drupalPostForm('fr/node/' . $default_language_node->id() . '/edit', array(), t('Remove'));
// Upload a different file.
$edit = array();
$edit['title[0][value]'] = 'David Bowie';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[3]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$replaced_second_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first and third files are untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
$file = File::load($third_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the replaced second file is permanent.
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the old second file is now temporary.
$file = File::load($second_fid);
$this->assertTrue($file->isTemporary());
// Delete the third translation.
$this->drupalPostForm('nl/node/' . $default_language_node->id() . '/delete', array(), t('Delete Dutch translation'));
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first and replaced second files are untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the third file is now temporary.
$file = File::load($third_fid);
$this->assertTrue($file->isTemporary());
// Delete the all translations.
$this->drupalPostForm('node/' . $default_language_node->id() . '/delete', array(), t('Delete all translations'));
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the file status of the all files are now temporary.
$file = File::load($first_fid);
$this->assertTrue($file->isTemporary(), 'First file still exists and is temporary.');
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isTemporary());
}
}

View file

@ -66,7 +66,8 @@ class FileTokenReplaceTest extends FileFieldTestBase {
$metadata_tests['[file:path]'] = $base_bubbleable_metadata;
$metadata_tests['[file:mime]'] = $base_bubbleable_metadata;
$metadata_tests['[file:size]'] = $base_bubbleable_metadata;
$metadata_tests['[file:url]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[file:url]'] = $bubbleable_metadata->addCacheContexts(['url.site']);
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[file:created]'] = $bubbleable_metadata->addCacheTags(['rendered']);
$metadata_tests['[file:created:short]'] = $bubbleable_metadata;

View file

@ -72,16 +72,18 @@ class FilterCaption extends FilterBase {
$altered_html = drupal_render($filter_caption);
// Load the altered HTML into a new DOMDocument and retrieve the element.
$updated_node = Html::load($altered_html)->getElementsByTagName('body')
$updated_nodes = Html::load($altered_html)->getElementsByTagName('body')
->item(0)
->childNodes
->item(0);
->childNodes;
foreach ($updated_nodes as $updated_node) {
// Import the updated node from the new DOMDocument into the original
// one, importing also the child nodes of the updated node.
$updated_node = $dom->importNode($updated_node, TRUE);
// Finally, replace the original node with the new node.
$node->parentNode->replaceChild($updated_node, $node);
$node->parentNode->insertBefore($updated_node, $node);
}
// Finally, remove the original data-caption node.
$node->parentNode->removeChild($node);
}
$result->setProcessedText(Html::serialize($dom))

View file

@ -49,7 +49,7 @@ class FilterHtml extends FilterBase {
'#type' => 'textfield',
'#title' => $this->t('Allowed HTML tags'),
'#default_value' => $this->settings['allowed_html'],
'#maxlength' => 1024,
'#maxlength' => 2048,
'#description' => $this->t('A list of HTML tags that can be used. By default only the <em>lang</em> and <em>dir</em> attributes are allowed for all HTML tags. Each HTML tag may have attributes which are treated as allowed attribute names for that HTML tag. Each attribute may allow all values, or only allow specific values. Attribute names or values may be written as a prefix and wildcard like <em>jump-*</em>. JavaScript event attributes, JavaScript URLs, and CSS are always stripped.'),
'#size' => 250,
'#attached' => array(

View file

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\filter\Tests\FilterCaptionTwigDebugTest.
*/
namespace Drupal\filter\Tests;
use Drupal\Core\Render\RenderContext;
use Drupal\simpletest\WebTestBase;
use Drupal\filter\FilterPluginCollection;
/**
* Tests the caption filter with Twig debugging on.
*
* @group filter
*/
class FilterCaptionTwigDebugTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['system', 'filter'];
/**
* @var \Drupal\filter\Plugin\FilterInterface[]
*/
protected $filters;
/**
* Enables Twig debugging.
*/
protected function debugOn() {
// Enable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
if (!$parameters['debug']) {
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
}
}
/**
* Disables Twig debugging.
*/
protected function debugOff() {
// Disable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
if ($parameters['debug']) {
$parameters['debug'] = FALSE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
}
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->debugOn();
$manager = $this->container->get('plugin.manager.filter');
$bag = new FilterPluginCollection($manager, []);
$this->filters = $bag->getAll();
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
$this->debugOff();
}
/**
* Test the caption filter with Twig debugging on.
*/
function testCaptionFilter() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$filter = $this->filters['filter_caption'];
$test = function ($input) use ($filter, $renderer) {
return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $filter) {
return $filter->process($input, 'und');
});
};
// No data-caption attribute.
$input = '<img src="llama.jpg" />';
$expected = $input;
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Data-caption attribute.
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" />';
$expected = '<img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption>';
$output = $test($input);
$output = $output->getProcessedText();
$this->assertTrue(strpos($output, $expected) !== FALSE, "\"$output\" contains \"$expected\"");
$this->assertTrue(strpos($output, '<!-- THEME HOOK: \'filter_caption\' -->') !== FALSE, 'filter_caption theme hook debug comment is present.');
}
}

View file

@ -501,7 +501,12 @@ function template_preprocess_forums(&$variables) {
}
$row[] = array(
'data' => $topic->comment_count . $new_replies,
'data' => [
[
'#prefix' => $topic->comment_count,
'#markup' => $new_replies,
],
],
'class' => array('forum__replies'),
);
$row[] = array(

View file

@ -514,6 +514,9 @@ class ForumTest extends WebTestBase {
// Check that forum renders properly.
$this->drupalGet("forum/{$this->forum['tid']}");
$this->assertResponse(200);
// Verify there is no unintentional HTML tag escaping.
$this->assertNoEscaped('<', '');
}
/**

View file

@ -34,7 +34,7 @@ function template_preprocess_image_style_preview(&$variables) {
$original_path = \Drupal::config('image.settings')->get('preview_image');
$original_image = $image_factory->get($original_path);
$variables['original'] = array(
'url' => file_create_url($original_path),
'url' => file_url_transform_relative(file_create_url($original_path)),
'width' => $original_image->getWidth(),
'height' => $original_image->getHeight(),
);
@ -55,7 +55,7 @@ function template_preprocess_image_style_preview(&$variables) {
}
$preview_image = $image_factory->get($preview_file);
$variables['derivative'] = array(
'url' => file_create_url($preview_file),
'url' => file_url_transform_relative(file_create_url($preview_file)),
'width' => $preview_image->getWidth(),
'height' => $preview_image->getHeight(),
);

View file

@ -0,0 +1,22 @@
<?php
/**
* @file
* Post-update functions for Image.
*/
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
/**
* Saves the image style dependencies into form and view display entities.
*/
function image_post_update_image_style_dependencies() {
// Merge view and form displays. Use array_values() to avoid key collisions.
$displays = array_merge(array_values(EntityViewDisplay::loadMultiple()), array_values(EntityFormDisplay::loadMultiple()));
/** @var \Drupal\Core\Entity\Display\EntityDisplayInterface[] $displays */
foreach ($displays as $display) {
// Re-save each config entity to add missed dependencies.
$display->save();
}
}

View file

@ -36,6 +36,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
* "flush" = "Drupal\image\Form\ImageStyleFlushForm"
* },
* "list_builder" = "Drupal\image\ImageStyleListBuilder",
* "storage" = "Drupal\image\ImageStyleStorage",
* },
* admin_permission = "administer image styles",
* config_prefix = "style",
@ -58,13 +59,6 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
*/
class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, EntityWithPluginCollectionInterface {
/**
* The name of the image style to use as replacement upon delete.
*
* @var string
*/
protected $replacementID;
/**
* The name of the image style.
*
@ -128,17 +122,13 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
/** @var \Drupal\image\ImageStyleInterface[] $entities */
foreach ($entities as $style) {
// Flush cached media for the deleted style.
$style->flush();
// Check whether field settings need to be updated.
// In case no replacement style was specified, all image fields that are
// using the deleted style are left in a broken state.
if (!$style->isSyncing() && $new_id = $style->getReplacementID()) {
// The deleted ID is still set as originalID.
$style->setName($new_id);
static::replaceImageStyle($style);
}
// Clear the replacement ID, if one has been previously stored.
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage->clearReplacementId($style->id());
}
}
@ -380,7 +370,9 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
* {@inheritdoc}
*/
public function getReplacementID() {
return $this->get('replacementID');
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId());
return $storage->getReplacementId($this->id());
}
/**

View file

@ -15,6 +15,13 @@ use Drupal\Core\Form\FormStateInterface;
*/
class ImageStyleDeleteForm extends EntityDeleteForm {
/**
* Replacement options.
*
* @var array
*/
protected $replacementOptions;
/**
* {@inheritdoc}
*/
@ -25,20 +32,28 @@ class ImageStyleDeleteForm extends EntityDeleteForm {
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted.');
if (count($this->getReplacementOptions()) > 1) {
return $this->t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.');
}
return $this->t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.');
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$replacement_styles = array_diff_key(image_style_options(), array($this->entity->id() => ''));
$form['replacement'] = array(
'#title' => $this->t('Replacement style'),
$replacement_styles = $this->getReplacementOptions();
// If there are non-empty options in the list, allow the user to optionally
// pick up a replacement.
if (count($replacement_styles) > 1) {
$form['replacement'] = [
'#type' => 'select',
'#title' => $this->t('Replacement style'),
'#options' => $replacement_styles,
'#empty_option' => $this->t('No replacement, just delete'),
);
'#empty_option' => $this->t('- No replacement -'),
'#weight' => -5,
];
}
return parent::form($form, $form_state);
}
@ -47,9 +62,27 @@ class ImageStyleDeleteForm extends EntityDeleteForm {
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->set('replacementID', $form_state->getValue('replacement'));
// Save a selected replacement in the image style storage. It will be used
// later, in the same request, when resolving dependencies.
if ($replacement = $form_state->getValue('replacement')) {
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage($this->entity->getEntityTypeId());
$storage->setReplacementId($this->entity->id(), $replacement);
}
parent::submitForm($form, $form_state);
}
/**
* Returns a list of image style replacement options.
*
* @return array
* An option list suitable for the form select '#options'.
*/
protected function getReplacementOptions() {
if (!isset($this->replacementOptions)) {
$this->replacementOptions = array_diff_key(image_style_options(), [$this->getEntity()->id() => '']);
}
return $this->replacementOptions;
}
}

View file

@ -17,8 +17,14 @@ interface ImageStyleInterface extends ConfigEntityInterface {
/**
* Returns the replacement ID.
*
* @return string
* The name of the image style to use as replacement upon delete.
* @return string|null
* The replacement image style ID or NULL if no replacement has been
* selected.
*
* @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.x. Use
* \Drupal\image\ImageStyleStorageInterface::getReplacementId() instead.
*
* @see \Drupal\image\ImageStyleStorageInterface::getReplacementId()
*/
public function getReplacementID();
@ -70,6 +76,7 @@ interface ImageStyleInterface extends ConfigEntityInterface {
* in an <img> tag. Requesting the URL will cause the image to be created.
*
* @see \Drupal\image\Controller\ImageStyleDownloadController::deliver()
* @see file_url_transform_relative()
*/
public function buildUrl($path, $clean_urls = NULL);

View file

@ -91,9 +91,9 @@ class ImageStyleListBuilder extends ConfigEntityListBuilder {
*/
public function render() {
$build = parent::render();
$build['#empty'] = $this->t('There are currently no styles. <a href=":url">Add a new one</a>.', array(
$build['table']['#empty'] = $this->t('There are currently no styles. <a href=":url">Add a new one</a>.', [
':url' => $this->urlGenerator->generateFromRoute('image.style_add'),
));
]);
return $build;
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\image\ImageStyleStorage.
*/
namespace Drupal\image;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
/**
* Storage controller class for "image style" configuration entities.
*/
class ImageStyleStorage extends ConfigEntityStorage implements ImageStyleStorageInterface {
/**
* Image style replacement memory storage.
*
* This value is not stored in the backend. It's used during the deletion of
* an image style to save the replacement image style in the same request. The
* value is used later, when resolving dependencies.
*
* @var string[]
*
* @see \Drupal\image\Form\ImageStyleDeleteForm::submitForm()
*/
protected $replacement = [];
/**
* {@inheritdoc}
*/
public function setReplacementId($name, $replacement) {
$this->replacement[$name] = $replacement;
}
/**
* {@inheritdoc}
*/
public function getReplacementId($name) {
return isset($this->replacement[$name]) ? $this->replacement[$name] : NULL;
}
/**
* {@inheritdoc}
*/
public function clearReplacementId($name) {
unset($this->replacement[$name]);
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\image\ImageStyleStorageInterface.
*/
namespace Drupal\image;
/**
* Interface for storage controller for "image style" configuration entities.
*/
interface ImageStyleStorageInterface {
/**
* Stores a replacement ID for an image style being deleted.
*
* The method stores a replacement style to be used by the configuration
* dependency system when a image style is deleted. The replacement style is
* replacing the deleted style in other configuration entities that are
* depending on the image style being deleted.
*
* @param string $name
* The ID of the image style to be deleted.
* @param string $replacement
* The ID of the image style used as replacement.
*/
public function setReplacementId($name, $replacement);
/**
* Retrieves the replacement ID of a deleted image style.
*
* The method is retrieving the value stored by ::setReplacementId().
*
* @param string $name
* The ID of the image style to be replaced.
*
* @return string|null
* The ID of the image style used as replacement, if there's any, or NULL.
*
* @see \Drupal\image\ImageStyleStorageInterface::setReplacementId()
*/
public function getReplacementId($name);
/**
* Clears a replacement ID from the storage.
*
* The method clears the value previously stored with ::setReplacementId().
*
* @param string $name
* The ID of the image style to be replaced.
*
* @see \Drupal\image\ImageStyleStorageInterface::setReplacementId()
*/
public function clearReplacementId($name);
}

View file

@ -14,6 +14,7 @@ use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\image\Entity\ImageStyle;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Cache\Cache;
@ -41,7 +42,7 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
/**
* The image style entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
* @var \Drupal\image\ImageStyleStorageInterface
*/
protected $imageStyleStorage;
@ -199,9 +200,16 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
}
foreach ($files as $delta => $file) {
$cache_contexts = array();
if (isset($link_file)) {
$image_uri = $file->getFileUri();
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. As a work-around, we currently add the 'url.site' cache
// context to ensure different file URLs are generated for different
// sites in a multisite setup, including HTTP and HTTPS versions of the
// same site. Fix in https://www.drupal.org/node/2646744.
$url = Url::fromUri(file_create_url($image_uri));
$cache_contexts[] = 'url.site';
}
$cache_tags = Cache::mergeTags($cache_tags, $file->getCacheTags());
@ -219,6 +227,7 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
'#url' => $url,
'#cache' => array(
'tags' => $cache_tags,
'contexts' => $cache_contexts,
),
);
}
@ -226,4 +235,41 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
return $elements;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$style_id = $this->getSetting('image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
// If this formatter uses a valid image style to display the image, add
// the image style configuration entity as dependency of this formatter.
$dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = parent::onDependencyRemoval($dependencies);
$style_id = $this->getSetting('image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
$replacement_id = $this->imageStyleStorage->getReplacementId($style_id);
// If a valid replacement has been provided in the storage, replace the
// image style with the replacement and signal that the formatter plugin
// settings were updated.
if ($replacement_id && ImageStyle::load($replacement_id)) {
$this->setSetting('image_style', $replacement_id);
$changed = TRUE;
}
}
}
return $changed;
}
}

View file

@ -12,6 +12,7 @@ use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
use Drupal\image\Entity\ImageStyle;
/**
* Plugin implementation of the 'image_image' widget.
@ -273,4 +274,49 @@ class ImageWidget extends FileWidget {
}
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$style_id = $this->getSetting('preview_image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
// If this widget uses a valid image style to display the preview of the
// uploaded image, add that image style configuration entity as dependency
// of this widget.
$dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = parent::onDependencyRemoval($dependencies);
$style_id = $this->getSetting('preview_image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage = \Drupal::entityManager()->getStorage($style->getEntityTypeId());
$replacement_id = $storage->getReplacementId($style_id);
// If a valid replacement has been provided in the storage, replace the
// preview image style with the replacement.
if ($replacement_id && ImageStyle::load($replacement_id)) {
$this->setSetting('preview_image_style', $replacement_id);
}
// If there's no replacement or the replacement is invalid, disable the
// image preview.
else {
$this->setSetting('preview_image_style', '');
}
// Signal that the formatter plugin settings were updated.
$changed = TRUE;
}
}
return $changed;
}
}

View file

@ -8,6 +8,7 @@
namespace Drupal\image\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\ImageStyleInterface;
use Drupal\node\Entity\Node;
@ -273,6 +274,19 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$this->assertFalse(ImageStyle::load($style_name), format_string('Image style %style successfully deleted.', array('%style' => $style->label())));
// Test empty text when there are no image styles.
// Delete all image styles.
foreach (ImageStyle::loadMultiple() as $image_style) {
$image_style->delete();
}
// Confirm that the empty text is correct on the image styles page.
$this->drupalGet($admin_path);
$this->assertRaw(t('There are currently no styles. <a href=":url">Add a new one</a>.', [
':url' => \Drupal::url('image.style_add'),
]));
}
/**
@ -307,7 +321,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
// Test that image is displayed using newly created style.
$this->drupalGet('node/' . $nid);
$this->assertRaw($style->buildUrl($original_uri), format_string('Image displayed using style @style.', array('@style' => $style_name)));
$this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), format_string('Image displayed using style @style.', array('@style' => $style_name)));
// Rename the style and make sure the image field is updated.
$new_style_name = strtolower($this->randomMachineName(10));
@ -322,7 +336,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
// Reload the image style using the new name.
$style = ImageStyle::load($new_style_name);
$this->assertRaw($style->buildUrl($original_uri), 'Image displayed using style replacement style.');
$this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), 'Image displayed using style replacement style.');
// Delete the style and choose a replacement style.
$edit = array(
@ -334,7 +348,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$replacement_style = ImageStyle::load('thumbnail');
$this->drupalGet('node/' . $nid);
$this->assertRaw($replacement_style->buildUrl($original_uri), 'Image displayed using style replacement style.');
$this->assertRaw(file_url_transform_relative($replacement_style->buildUrl($original_uri)), 'Image displayed using style replacement style.');
}
/**
@ -441,11 +455,16 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
// Test that image is displayed using newly created style.
$this->drupalGet('node/' . $nid);
$this->assertRaw($style->buildUrl($original_uri), format_string('Image displayed using style @style.', array('@style' => $style_name)));
$this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), format_string('Image displayed using style @style.', array('@style' => $style_name)));
// Copy config to sync, and delete the image style.
$sync = $this->container->get('config.storage.sync');
$active = $this->container->get('config.storage');
// Remove the image field from the display, to avoid a dependency error
// during import.
EntityViewDisplay::load('node.article.default')
->removeComponent($field_name)
->save();
$this->copyConfig($active, $sync);
$sync->delete('image.style.' . $style_name);
$this->configImporter()->import();

View file

@ -41,7 +41,7 @@ class ImageDimensionsTest extends WebTestBase {
$style = entity_create('image_style', array('name' => 'test', 'label' => 'Test'));
$style->save();
$generated_uri = 'public://styles/test/public/'. \Drupal::service('file_system')->basename($original_uri);
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
$variables = array(
'#theme' => 'image_style',
@ -70,7 +70,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="120" height="60" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);
@ -91,7 +91,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="60" height="120" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);
@ -113,7 +113,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);
@ -135,7 +135,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);
@ -153,7 +153,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);
@ -174,7 +174,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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.');
@ -194,7 +194,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="30" height="30" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);
@ -215,7 +215,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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.');
@ -251,10 +251,10 @@ class ImageDimensionsTest extends WebTestBase {
];
// PNG original image. Should be resized to 100x100.
$generated_uri = 'public://styles/test_uri/public/'. \Drupal::service('file_system')->basename($original_uri);
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="100" height="100" alt="" class="image-style-test-uri" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);
@ -264,11 +264,11 @@ class ImageDimensionsTest extends WebTestBase {
$file = $files[1];
$original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
$generated_uri = 'public://styles/test_uri/public/'. \Drupal::service('file_system')->basename($original_uri);
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
$variables['#uri'] = $original_uri;
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="50" height="50" alt="" class="image-style-test-uri" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$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);

View file

@ -127,6 +127,8 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$default_output = '<a href="' . file_create_url($image_uri) . '">' . $renderer->renderRoot($image) . '</a>';
$this->drupalGet('node/' . $nid);
$this->assertCacheTag($file->getCacheTags()[0]);
// @todo Remove in https://www.drupal.org/node/2646744.
$this->assertCacheContext('url.site');
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
$this->assertRaw($default_output, 'Image linked to file formatter displaying correctly on full node view.');
@ -165,7 +167,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
'//a[@href=:path]/img[@src=:url and @alt=:alt and @width=:width and @height=:height]',
array(
':path' => $node->url(),
':url' => file_create_url($image['#uri']),
':url' => file_url_transform_relative(file_create_url($image['#uri'])),
':width' => $image['#width'],
':height' => $image['#height'],
':alt' => $alt,
@ -256,7 +258,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$node = $node_storage->load($nid);
$file = $node->{$field_name}->entity;
$url = file_create_url(ImageStyle::load('medium')->buildUrl($file->getFileUri()));
$url = file_url_transform_relative(file_create_url(ImageStyle::load('medium')->buildUrl($file->getFileUri())));
$this->assertTrue($this->cssSelect('img[width=40][height=20][class=image-style-medium][src="' . $url . '"]'));
// Add alt/title fields to the image and verify that they are displayed.

View file

@ -66,9 +66,11 @@ abstract class ImageFieldTestBase extends WebTestBase {
* @param array $field_settings
* A list of instance settings that will be added to the instance defaults.
* @param array $widget_settings
* A list of widget settings that will be added to the widget defaults.
* Widget settings to be added to the widget defaults.
* @param array $formatter_settings
* Formatter settings to be added to the formatter defaults.
*/
function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array()) {
function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array()) {
entity_create('field_storage_config', array(
'field_name' => $name,
'entity_type' => 'node',
@ -95,7 +97,10 @@ abstract class ImageFieldTestBase extends WebTestBase {
->save();
entity_get_display('node', $type_name, 'default')
->setComponent($name)
->setComponent($name, array(
'type' => 'image',
'settings' => $formatter_settings,
))
->save();
return $field_config;

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains \Drupal\image\Tests\ImageStyleDeleteTest.
*/
namespace Drupal\image\Tests;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
/**
* Tests image style deletion using the UI.
*
* @group image
*/
class ImageStyleDeleteTest extends ImageFieldTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create an image field 'foo' having the image style 'medium' as widget
// preview and as formatter.
$this->createImageField('foo', 'page', [], [], ['preview_image_style' => 'medium'], ['image_style' => 'medium']);
}
/**
* Tests image style deletion.
*/
public function testDelete() {
$this->drupalGet('admin/config/media/image-styles/manage/medium/delete');
// Checks that the 'replacement' select element is displayed.
$this->assertFieldByName('replacement');
// Checks that UI messages are correct.
$this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
$this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
// Delete 'medium' image style but replace it with 'thumbnail'. This style
// is involved in 'node.page.default' display view and form.
$this->drupalPostForm(NULL, ['replacement' => 'thumbnail'], t('Delete'));
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
$view_display = EntityViewDisplay::load('node.page.default');
// Checks that the formatter setting is replaced.
if ($this->assertNotNull($component = $view_display->getComponent('foo'))) {
$this->assertIdentical($component['settings']['image_style'], 'thumbnail');
}
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
$form_display = EntityFormDisplay::load('node.page.default');
// Check that the widget setting is replaced.
if ($this->assertNotNull($component = $form_display->getComponent('foo'))) {
$this->assertIdentical($component['settings']['preview_image_style'], 'thumbnail');
}
$this->drupalGet('admin/config/media/image-styles/manage/thumbnail/delete');
// Checks that the 'replacement' select element is displayed.
$this->assertFieldByName('replacement');
// Checks that UI messages are correct.
$this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
$this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
// Delete 'thumbnail' image style. Provide no replacement.
$this->drupalPostForm(NULL, [], t('Delete'));
$view_display = EntityViewDisplay::load('node.page.default');
// Checks that the formatter setting is disabled.
$this->assertNull($view_display->getComponent('foo'));
$this->assertNotNull($view_display->get('hidden')['foo']);
// Checks that widget setting is preserved with the image preview disabled.
$form_display = EntityFormDisplay::load('node.page.default');
$this->assertNotNull($widget = $form_display->getComponent('foo'));
$this->assertIdentical($widget['settings']['preview_image_style'], '');
// Now, there's only one image style configured on the system: 'large'.
$this->drupalGet('admin/config/media/image-styles/manage/large/delete');
// Checks that the 'replacement' select element is not displayed.
$this->assertNoFieldByName('replacement');
// Checks that UI messages are correct.
$this->assertNoRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
$this->assertRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
}
}

Some files were not shown because too many files have changed in this diff Show more