diff --git a/core/core.services.yml b/core/core.services.yml index c56c778..e791572 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1450,6 +1450,7 @@ services: calls: - [setUrlGenerator, ['@url_generator']] - [setThemeManager, ['@theme.manager']] + - [setDateFormatter, ['@date.formatter']] # @todo Figure out what to do about debugging functions. # @see https://www.drupal.org/node/1804998 twig.extension.debug: diff --git a/core/lib/Drupal/Component/Plugin/Context/Context.php b/core/lib/Drupal/Component/Plugin/Context/Context.php index c891e62..852876a 100644 --- a/core/lib/Drupal/Component/Plugin/Context/Context.php +++ b/core/lib/Drupal/Component/Plugin/Context/Context.php @@ -31,20 +31,16 @@ class Context implements ContextInterface { protected $contextDefinition; /** - * Sets the contextDefinition for us without needing to call the setter. + * Create a context object. * * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition * The context definition. + * @param mixed|null $context_value + * The value of the context. */ - public function __construct(ContextDefinitionInterface $context_definition) { + public function __construct(ContextDefinitionInterface $context_definition, $context_value = NULL) { $this->contextDefinition = $context_definition; - } - - /** - * Implements \Drupal\Component\Plugin\Context\ContextInterface::setContextValue(). - */ - public function setContextValue($value) { - $this->contextValue = $value; + $this->contextValue = $context_value; } /** @@ -75,13 +71,6 @@ public function hasContextValue() { } /** - * {@inheritdoc} - */ - public function setContextDefinition(ContextDefinitionInterface $context_definition) { - $this->contextDefinition = $context_definition; - } - - /** * Implements \Drupal\Component\Plugin\Context\ContextInterface::getContextDefinition(). */ public function getContextDefinition() { diff --git a/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php b/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php index 04948f9..2cfbc6d 100644 --- a/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php +++ b/core/lib/Drupal/Component/Plugin/Context/ContextInterface.php @@ -13,16 +13,6 @@ interface ContextInterface { /** - * Sets the context value. - * - * @param mixed $value - * The value of this context, matching the context definition. - * - * @see \Drupal\Component\Plugin\Context\ContextInterface::setContextDefinition(). - */ - public function setContextValue($value); - - /** * Gets the context value. * * @return mixed @@ -39,15 +29,6 @@ public function getContextValue(); public function hasContextValue(); /** - * Sets the definition that the context must conform to. - * - * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition - * A defining characteristic representation of the context against which - * that context can be validated. - */ - public function setContextDefinition(ContextDefinitionInterface $context_definition); - - /** * Gets the provided definition that the context must conform to. * * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface diff --git a/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php index d9c9c8a..4f58956 100644 --- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php +++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php @@ -41,17 +41,30 @@ * The plugin implementation definition. */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { - $context = array(); - if (isset($configuration['context'])) { - $context = $configuration['context']; - unset($configuration['context']); - } + $context_configuration = isset($configuration['context']) ? $configuration['context'] : []; + unset($configuration['context']); + parent::__construct($configuration, $plugin_id, $plugin_definition); - foreach ($context as $key => $value) { + + $this->contexts = $this->createContextFromConfiguration($context_configuration); + } + + /** + * Creates context objects from any context mappings in configuration. + * + * @param array $context_configuration + * An associative array of context names and values. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of context objects. + */ + protected function createContextFromConfiguration(array $context_configuration) { + $contexts = []; + foreach ($context_configuration as $key => $value) { $context_definition = $this->getContextDefinition($key); - $this->context[$key] = new Context($context_definition); - $this->context[$key]->setContextValue($value); + $contexts[$key] = new Context($context_definition, $value); } + return $contexts; } /** @@ -124,7 +137,7 @@ public function getContextValue($name) { * {@inheritdoc} */ public function setContextValue($name, $value) { - $this->getContext($name)->setContextValue($value); + $this->context[$name] = new Context($this->getContextDefinition($name), $value); return $this; } diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php new file mode 100644 index 0000000..3aeeaec --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php @@ -0,0 +1,40 @@ +getClass()) { + throw new PluginException($missing_class_message); + } - $class = $plugin_definition['class']; + $class = $plugin_definition->getClass(); + } + else { + $plugin_definition_type = is_object($plugin_definition) ? get_class($plugin_definition) : gettype($plugin_definition); + throw new PluginException(sprintf('%s can only handle plugin definitions that are arrays or that implement %s, but %s given.', __CLASS__, PluginDefinitionInterface::class, $plugin_definition_type)); + } if (!class_exists($class)) { throw new PluginException(sprintf('Plugin (%s) instance class "%s" does not exist.', $plugin_id, $class)); } - if ($required_interface && !is_subclass_of($plugin_definition['class'], $required_interface)) { - throw new PluginException(sprintf('Plugin "%s" (%s) must implement interface %s.', $plugin_id, $plugin_definition['class'], $required_interface)); + if ($required_interface && !is_subclass_of($class, $required_interface)) { + throw new PluginException(sprintf('Plugin "%s" (%s) must implement interface %s.', $plugin_id, $class, $required_interface)); } return $class; diff --git a/core/lib/Drupal/Component/Utility/FormattableString.php b/core/lib/Drupal/Component/Utility/FormattableString.php new file mode 100644 index 0000000..ea7861c --- /dev/null +++ b/core/lib/Drupal/Component/Utility/FormattableString.php @@ -0,0 +1,92 @@ +string = (string) $string; + $this->arguments = $arguments; + } + + /** + * {@inheritdoc} + */ + public function __toString() { + return static::placeholderFormat($this->string, $this->arguments); + } + + /** + * Returns the string length. + * + * @return int + * The length of the string. + */ + public function count() { + return Unicode::strlen($this->string); + } + + /** + * Returns a representation of the object for use in JSON serialization. + * + * @return string + * The safe string content. + */ + public function jsonSerialize() { + return $this->__toString(); + } + +} diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php index 76e0541..69adbeb 100644 --- a/core/lib/Drupal/Component/Utility/Html.php +++ b/core/lib/Drupal/Component/Utility/Html.php @@ -292,14 +292,16 @@ public static function serialize(\DOMDocument $document) { $body_node = $document->getElementsByTagName('body')->item(0); $html = ''; - foreach ($body_node->getElementsByTagName('script') as $node) { - static::escapeCdataElement($node); - } - foreach ($body_node->getElementsByTagName('style') as $node) { - static::escapeCdataElement($node, '/*', '*/'); - } - foreach ($body_node->childNodes as $node) { - $html .= $document->saveXML($node); + if ($body_node !== NULL) { + foreach ($body_node->getElementsByTagName('script') as $node) { + static::escapeCdataElement($node); + } + foreach ($body_node->getElementsByTagName('style') as $node) { + static::escapeCdataElement($node, '/*', '*/'); + } + foreach ($body_node->childNodes as $node) { + $html .= $document->saveXML($node); + } } return $html; } diff --git a/core/lib/Drupal/Component/Utility/PlaceholderTrait.php b/core/lib/Drupal/Component/Utility/PlaceholderTrait.php index 5922b3f..f5bcb39 100644 --- a/core/lib/Drupal/Component/Utility/PlaceholderTrait.php +++ b/core/lib/Drupal/Component/Utility/PlaceholderTrait.php @@ -15,18 +15,62 @@ /** * Formats a string by replacing variable placeholders. * + * This trait is not intended for passing arbitrary user input into any HTML + * attribute value, as only URL attributes such as "src" and "href" are + * supported (using ":variable"). Never use this method on unsafe HTML + * attributes such as "on*" and "style" and take care when using this with + * unsupported attributes such as "title" or "alt" as this can lead to + * unexpected and unsafe output. + * * @param string $string * A string containing placeholders. * @param array $args - * An associative array of replacements to make. + * An associative array of replacements to make. Occurrences in $string of + * any key in $args are replaced with the corresponding value, after + * optional sanitization and formatting. The type of sanitization and + * formatting depends on the first character of the key: + * - @variable: Escaped to HTML using Html::escape() unless the value is + * already HTML-safe. Use this as the default choice for anything + * displayed on a page on the site, but not within HTML attributes. + * - %variable: Escaped to HTML just like @variable, but also wrapped in + * tags, which makes the following HTML code: + * @code + * text output here. + * @endcode + * As with @variable, do not use this within HTML attributes. + * - :variable: Escaped to HTML using Html::escape() and filtered for + * dangerous protocols using UrlHelper::stripDangerousProtocols(). Use + * this when passing in a URL, such as when using the "src" or "href" + * attributes, ensuring the value is always wrapped in quotes: + * - Secure: @variable + * - Insecure: @variable + * When ":variable" comes from arbitrary user input, the result is secure, + * but not guaranteed to be a valid URL (which means the resulting output + * could fail HTML validation). To guarantee a valid URL, use + * Url::fromUri($user_input)->toString() (which either throws an exception + * or returns a well-formed URL) before passing the result into a + * ":variable" placeholder. + * - !variable: Inserted as is, with no sanitization or formatting. Only + * use this when the resulting string is being generated for one of: + * - Non-HTML usage, such as a plain-text email. + * - Non-direct HTML output, such as a plain-text variable that will be + * printed as an HTML attribute value and therefore formatted with + * self::checkPlain() as part of that. + * - Some other special reason for suppressing sanitization. * @param bool &$safe * A boolean indicating whether the string is safe or not (optional). * * @return string * The string with the placeholders replaced. * - * @see \Drupal\Component\Utility\SafeMarkup::format() - * @see \Drupal\Core\StringTranslation\TranslatableString::render() + * @ingroup sanitization + * + * @see \Drupal\Component\Utility\FormattableString + * @see \Drupal\Core\StringTranslation\TranslatableString + * @see \Drupal\Core\StringTranslation\PluralTranslatableString + * @see \Drupal\Component\Utility\Html::escape() + * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() + * @see \Drupal\Core\Url::fromUri() */ protected static function placeholderFormat($string, array $args, &$safe = TRUE) { // Transform arguments before inserting them. diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php index c1c86f5..a8c9576 100644 --- a/core/lib/Drupal/Component/Utility/SafeMarkup.php +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php @@ -162,79 +162,41 @@ public static function checkPlain($text) { /** * Formats a string for HTML display by replacing variable placeholders. * - * This method replaces variable placeholders in a string with the requested - * values and escapes the values so they can be safely displayed as HTML. It - * should be used on any unknown text that is intended to be printed to an - * HTML page (especially text that may have come from untrusted users, since - * in that case it prevents cross-site scripting and other security problems). - * - * This method is not intended for passing arbitrary user input into any - * HTML attribute value, as only URL attributes such as "src" and "href" are - * supported (using ":variable"). Never use this method on unsafe HTML - * attributes such as "on*" and "style" and take care when using this with - * unsupported attributes such as "title" or "alt" as this can lead to - * unexpected output. - * - * In most cases, you should use t() rather than calling this function - * directly, since it will translate the text (on non-English-only sites) in - * addition to formatting it. - * * @param string $string - * A string containing placeholders. The string itself is not escaped, any - * unsafe content must be in $args and inserted via placeholders. + * A string containing placeholders. The string itself will not be escaped, + * any unsafe content must be in $args and inserted via placeholders. * @param array $args - * An associative array of replacements to make. Occurrences in $string of - * any key in $args are replaced with the corresponding value, after - * optional sanitization and formatting. The type of sanitization and - * formatting depends on the first character of the key: - * - @variable: Escaped to HTML using Html::escape() unless the value is - * already HTML-safe. Use this as the default choice for anything - * displayed on a page on the site, but not within HTML attributes. - * - %variable: Escaped to HTML just like @variable, but also wrapped in - * tags, which makes the following HTML code: - * @code - * text output here. - * @endcode - * As with @variable, do not use this within HTML attributes. - * - :variable: Escaped to HTML using Html::escape() and filtered for - * dangerous protocols using UrlHelper::stripDangerousProtocols(). Use - * this when passing in a URL, such as when using the "src" or "href" - * attributes, ensuring the value is always wrapped in quotes: - * - Secure: @variable - * - Insecure: @variable - * When ":variable" comes from arbitrary user input, the result is secure, - * but not guaranteed to be a valid URL (which means the resulting output - * could fail HTML validation). To guarantee a valid URL, use - * Url::fromUri($user_input)->toString() (which either throws an exception - * or returns a well-formed URL) before passing the result into a - * ":variable" placeholder. - * - !variable: Inserted as is, with no sanitization or formatting. Only - * use this when the resulting string is being generated for one of: - * - Non-HTML usage, such as a plain-text email. - * - Non-direct HTML output, such as a plain-text variable that will be - * printed as an HTML attribute value and therefore formatted with - * self::checkPlain() as part of that. - * - Some other special reason for suppressing sanitization. + * An array with placeholder replacements, keyed by placeholder. See + * \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() for + * additional information about placeholders. * - * @return string - * The formatted string, which is marked as safe unless sanitization of an - * unsafe argument was suppressed (see above). + * @return string|\Drupal\Component\Utility\SafeStringInterface + * The formatted string, which is an instance of SafeStringInterface unless + * sanitization of an unsafe argument was suppressed (see above). * * @ingroup sanitization * - * @see t() - * @see \Drupal\Component\Utility\Html::escape() - * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() - * @see \Drupal\Core\Url::fromUri() + * @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() + * @see \Drupal\Component\Utility\FormattableString + * + * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. + * Use \Drupal\Component\Utility\FormattableString. */ public static function format($string, array $args) { + // If the string has arguments that start with '!' we consider it unsafe + // and return a string instead of an object for backward compatibility + // purposes. + // @todo https://www.drupal.org/node/2571695 remove this temporary + // workaround. $safe = TRUE; - $output = static::placeholderFormat($string, $args, $safe); - if ($safe) { - static::$safeStrings[$output]['html'] = TRUE; + foreach ($args as $key => $value) { + if ($key[0] == '!' && !static::isSafe($value)) { + $safe = FALSE; + } } - return $output; + $safe_string = new FormattableString($string, $args); + return $safe ? $safe_string : (string) $safe_string; } } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyDeleteFormTrait.php b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyDeleteFormTrait.php index cd28d19..1f2660b 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyDeleteFormTrait.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyDeleteFormTrait.php @@ -53,8 +53,7 @@ protected function addDependencyListsToForm(array &$form, $type, array $names, C '#type' => 'details', '#title' => $this->t('Configuration updates'), '#description' => $this->t('The listed configuration will be updated.'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, + '#open' => TRUE, '#access' => FALSE, ); @@ -72,7 +71,7 @@ protected function addDependencyListsToForm(array &$form, $type, array $names, C '#items' => array(), ); } - $form['entity_updates'][$entity_type_id]['#items'][] = $entity->label() ?: $entity->id(); + $form['entity_updates'][$entity_type_id]['#items'][$entity->id()] = $entity->label() ?: $entity->id(); } if (!empty($dependent_entities['update'])) { $form['entity_updates']['#access'] = TRUE; @@ -83,7 +82,7 @@ protected function addDependencyListsToForm(array &$form, $type, array $names, C foreach ($entity_types as $entity_type_id => $label) { $form['entity_updates'][$entity_type_id]['#weight'] = $weight; // Sort the list of entity labels alphabetically. - sort($form['entity_updates'][$entity_type_id]['#items'], SORT_FLAG_CASE); + ksort($form['entity_updates'][$entity_type_id]['#items'], SORT_FLAG_CASE); $weight++; } } @@ -92,8 +91,7 @@ protected function addDependencyListsToForm(array &$form, $type, array $names, C '#type' => 'details', '#title' => $this->t('Configuration deletions'), '#description' => $this->t('The listed configuration will be deleted.'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, + '#open' => TRUE, '#access' => FALSE, ); @@ -110,7 +108,7 @@ protected function addDependencyListsToForm(array &$form, $type, array $names, C '#items' => array(), ); } - $form['entity_deletes'][$entity_type_id]['#items'][] = $entity->label() ?: $entity->id(); + $form['entity_deletes'][$entity_type_id]['#items'][$entity->id()] = $entity->label() ?: $entity->id(); } if (!empty($dependent_entities['delete'])) { $form['entity_deletes']['#access'] = TRUE; @@ -119,10 +117,12 @@ protected function addDependencyListsToForm(array &$form, $type, array $names, C asort($entity_types, SORT_FLAG_CASE); $weight = 0; foreach ($entity_types as $entity_type_id => $label) { - $form['entity_deletes'][$entity_type_id]['#weight'] = $weight; - // Sort the list of entity labels alphabetically. - sort($form['entity_deletes'][$entity_type_id]['#items'], SORT_FLAG_CASE); - $weight++; + if (isset($form['entity_deletes'][$entity_type_id])) { + $form['entity_deletes'][$entity_type_id]['#weight'] = $weight; + // Sort the list of entity labels alphabetically. + ksort($form['entity_deletes'][$entity_type_id]['#items'], SORT_FLAG_CASE); + $weight++; + } } } diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php index 5c932e7..27884cd 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php @@ -249,7 +249,7 @@ public function buildMultiple(array $entities) { $items = $grouped_items[$id]; /** @var \Drupal\Core\Access\AccessResultInterface $field_access */ $field_access = $items->access('view', NULL, TRUE); - $build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items) : []; + $build_list[$id][$name] = $field_access->isAllowed() ? $formatter->view($items, $entity->language()->getId()) : []; // Apply the field access cacheability metadata to the render array. $this->renderer->addCacheableDependency($build_list[$id][$name], $field_access); } diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteForm.php b/core/lib/Drupal/Core/Entity/EntityDeleteForm.php index 6d0ef79..5ca89c7 100644 --- a/core/lib/Drupal/Core/Entity/EntityDeleteForm.php +++ b/core/lib/Drupal/Core/Entity/EntityDeleteForm.php @@ -34,7 +34,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { if (!($entity instanceof ConfigEntityInterface)) { return $form; } - $this->addDependencyListsToForm($form, $entity->getConfigDependencyKey(), [$entity->getConfigDependencyName()], $this->getConfigManager(), $this->entityManager); + $this->addDependencyListsToForm($form, $entity->getConfigDependencyKey(), $this->getConfigNamesToDelete($entity), $this->getConfigManager(), $this->entityManager); return $form; } @@ -49,4 +49,17 @@ protected function getConfigManager() { return \Drupal::service('config.manager'); } + /** + * Returns config names to delete for the deletion confirmation form. + * + * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity + * The entity being deleted. + * + * @return string[] + * A list of configuration names that will be deleted by this form. + */ + protected function getConfigNamesToDelete(ConfigEntityInterface $entity) { + return [$entity->getConfigDependencyName()]; + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php index c8a2e65..82b4556 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Entity; +use Drupal\Component\Plugin\Definition\PluginDefinitionInterface; + /** * Provides an interface for an entity type and its metadata. * @@ -15,7 +17,7 @@ * implemented to alter existing data and fill-in defaults. Module-specific * properties should be documented in the hook implementations defining them. */ -interface EntityTypeInterface { +interface EntityTypeInterface extends PluginDefinitionInterface { /** * The maximum length of ID, in characters. @@ -67,14 +69,6 @@ public function id(); public function getProvider(); /** - * Gets the name of the entity type class. - * - * @return string - * The name of the entity type class. - */ - public function getClass(); - - /** * Gets the name of the original entity type class. * * In case the class name was changed with setClass(), this will return @@ -171,16 +165,6 @@ public function isRenderCacheable(); public function isPersistentlyCacheable(); /** - * Sets the name of the entity type class. - * - * @param string $class - * The name of the entity type class. - * - * @return $this - */ - public function setClass($class); - - /** * Determines if there is a handler for a given type. * * @param string $handler_type diff --git a/core/lib/Drupal/Core/Field/FormatterBase.php b/core/lib/Drupal/Core/Field/FormatterBase.php index a44b8a9..7b29951 100644 --- a/core/lib/Drupal/Core/Field/FormatterBase.php +++ b/core/lib/Drupal/Core/Field/FormatterBase.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Field; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\Element; /** @@ -76,8 +77,12 @@ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInter /** * {@inheritdoc} */ - public function view(FieldItemListInterface $items) { - $elements = $this->viewElements($items); + public function view(FieldItemListInterface $items, $langcode = NULL) { + // Default the language to the current content language. + if (empty($langcode)) { + $langcode = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); + } + $elements = $this->viewElements($items, $langcode); // If there are actual renderable children, use #theme => field, otherwise, // let access cacheability metadata pass through for correct bubbling. diff --git a/core/lib/Drupal/Core/Field/FormatterInterface.php b/core/lib/Drupal/Core/Field/FormatterInterface.php index 196fb89..a60ffa6 100644 --- a/core/lib/Drupal/Core/Field/FormatterInterface.php +++ b/core/lib/Drupal/Core/Field/FormatterInterface.php @@ -69,23 +69,28 @@ public function prepareView(array $entities_items); * * @param \Drupal\Core\Field\FieldItemListInterface $items * The field values to be rendered. + * @param string $langcode + * (optional) The language that should be used to render the field. Defaults + * to the current content language. * * @return array * A renderable array for a themed field with its label and all its values. */ - public function view(FieldItemListInterface $items); + public function view(FieldItemListInterface $items, $langcode = NULL); /** * Builds a renderable array for a field value. * * @param \Drupal\Core\Field\FieldItemListInterface $items * The field values to be rendered. + * @param string $langcode + * The language that should be used to render the field. * * @return array * A renderable array for $items, as an array of child elements keyed by * consecutive numeric indexes starting from 0. */ - public function viewElements(FieldItemListInterface $items); + public function viewElements(FieldItemListInterface $items, $langcode); /** * Returns if the formatter can be used for the provided field. diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BasicStringFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BasicStringFormatter.php index 9d2b730..30edc11 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BasicStringFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BasicStringFormatter.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; -use Drupal\Component\Utility\Html; use Drupal\Core\Field\FormatterBase; use Drupal\Core\Field\FieldItemListInterface; @@ -31,13 +30,17 @@ class BasicStringFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { - $elements = array(); + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; foreach ($items as $delta => $item) { // The text value has no text format assigned to it, so the user input // should equal the output, including newlines. - $elements[$delta] = array('#markup' => nl2br(Html::escape($item->value))); + $elements[$delta] = [ + '#type' => 'inline_template', + '#template' => '{{ value|nl2br }}', + '#context' => ['value' => $item->value], + ]; } return $elements; diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BooleanFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BooleanFormatter.php index b1d2d8b..2822905 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BooleanFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BooleanFormatter.php @@ -116,7 +116,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; $formats = $this->getOutputFormats(); diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php index 1328f65..b98fc45 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php @@ -10,6 +10,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -119,11 +120,11 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $view_mode = $this->getSetting('view_mode'); $elements = array(); - foreach ($this->getEntitiesToView($items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { // Protect ourselves from recursive rendering. static $depth = 0; $depth++; diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php index b9a339f..6d20774 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFormatterBase.php @@ -36,24 +36,25 @@ * * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items * The item list. + * @param string $langcode + * The language code of the referenced entities to display. * * @return \Drupal\Core\Entity\EntityInterface[] * The array of referenced entities to display, keyed by delta. * * @see ::prepareView() */ - protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items) { + protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) { $entities = array(); - $parent_entity_langcode = $items->getEntity()->language()->getId(); foreach ($items as $delta => $item) { // Ignore items where no entity could be loaded in prepareView(). if (!empty($item->_loaded)) { $entity = $item->entity; // Set the entity in the correct language for display. - if ($entity instanceof TranslatableInterface && $entity->hasTranslation($parent_entity_langcode)) { - $entity = $entity->getTranslation($parent_entity_langcode); + if ($entity instanceof TranslatableInterface) { + $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode); } $access = $this->checkAccess($entity); @@ -76,8 +77,8 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item * @see ::prepareView() * @see ::getEntitiestoView() */ - public function view(FieldItemListInterface $items) { - $elements = parent::view($items); + public function view(FieldItemListInterface $items, $langcode = NULL) { + $elements = parent::view($items, $langcode); $field_level_access_cacheability = new CacheableMetadata(); diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php index 3f73939..3b58c68 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Language\LanguageInterface; /** * Plugin implementation of the 'entity reference ID' formatter. @@ -26,10 +27,10 @@ class EntityReferenceIdFormatter extends EntityReferenceFormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - foreach ($this->getEntitiesToView($items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { if ($entity->id()) { $elements[$delta] = array( '#plain_text' => $entity->id(), diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php index 68d28ad..eb005bc 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php @@ -59,11 +59,11 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); $output_as_link = $this->getSetting('link'); - foreach ($this->getEntitiesToView($items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { $label = $entity->label(); // If the link is to be displayed and the entity has a uri, display a // link. diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php index 19311b5..94ab462 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/MailToFormatter.php @@ -27,7 +27,7 @@ class MailToFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php index 01a748f..0320cd5 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php @@ -66,7 +66,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); $settings = $this->getFieldSettings(); diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericUnformattedFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericUnformattedFormatter.php index 55d9dbf..47e591b 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericUnformattedFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericUnformattedFormatter.php @@ -28,7 +28,7 @@ class NumericUnformattedFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php index fd5c3bb..dffa667 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php @@ -7,9 +7,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; -use Drupal\Component\Utility\Html; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Entity\RevisionableInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FormatterBase; @@ -119,7 +117,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); $url = NULL; if ($this->getSetting('link_to_entity')) { @@ -137,7 +135,7 @@ public function viewElements(FieldItemListInterface $items) { ]; } else { - $elements[$delta] = is_array($view_value) ? $view_value : ['#markup' => $view_value]; + $elements[$delta] = $view_value; } } return $elements; @@ -156,7 +154,9 @@ protected function viewValue(FieldItemInterface $item) { // The text value has no text format assigned to it, so the user input // should equal the output, including newlines. return [ - '#markup' => nl2br(Html::escape($item->value)) + '#type' => 'inline_template', + '#template' => '{{ value|nl2br }}', + '#context' => ['value' => $item->value], ]; } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php index 6443669..26b01db 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php @@ -153,7 +153,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php index 032e1d1..fbd44ac 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampFormatter.php @@ -165,7 +165,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); $date_format = $this->getSetting('date_format'); diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php index 51e0c3b..bcf0a50 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/UriLinkFormatter.php @@ -27,7 +27,7 @@ class UriLinkFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index adf4c86..721ee92 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -679,6 +679,15 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { $form['#method'] = 'get'; } + // GET forms should not use a CSRF token. + if (isset($form['#method']) && $form['#method'] === 'get') { + // Merges in a default, this means if you've explicitly set #token to the + // the $form_id on a GET form, which we don't recommend, it will work. + $form += [ + '#token' => FALSE, + ]; + } + // Generate a new #build_id for this form, if none has been set already. // The form_build_id is used as key to cache a particular build of the form. // For multi-step forms, this allows the user to go back to an earlier @@ -1307,7 +1316,11 @@ protected function buttonWasClicked($element, FormStateInterface &$form_state) { // long as $form['#name'] puts the value at the top level of the tree of // \Drupal::request()->request data. $input = $form_state->getUserInput(); - if (isset($input[$element['#name']]) && $input[$element['#name']] == $element['#value']) { + // The input value attribute is treated as CDATA by browsers. This means + // that they replace character entities with characters. Therefore, we need + // to decode the value in $element['#value']. For more details see + // http://www.w3.org/TR/html401/types.html#type-cdata. + if (isset($input[$element['#name']]) && $input[$element['#name']] == Html::decodeEntities($element['#value'])) { return TRUE; } // When image buttons are clicked, browsers do NOT pass the form element diff --git a/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php b/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php index 94f39fd..4d23e7b 100644 --- a/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php +++ b/core/lib/Drupal/Core/Language/ContextProvider/CurrentLanguageContext.php @@ -57,8 +57,7 @@ public function getRuntimeContexts(array $unqualified_context_ids) { $result = []; foreach ($language_types as $type_key) { if (isset($info[$type_key]['name'])) { - $context = new Context(new ContextDefinition('language', $info[$type_key]['name'])); - $context->setContextValue($this->languageManager->getCurrentLanguage($type_key)); + $context = new Context(new ContextDefinition('language', $info[$type_key]['name']), $this->languageManager->getCurrentLanguage($type_key)); $cacheability = new CacheableMetadata(); $cacheability->setCacheContexts(['languages:' . $type_key]); diff --git a/core/lib/Drupal/Core/Menu/menu.api.php b/core/lib/Drupal/Core/Menu/menu.api.php index 014ea65..a403cda 100644 --- a/core/lib/Drupal/Core/Menu/menu.api.php +++ b/core/lib/Drupal/Core/Menu/menu.api.php @@ -428,7 +428,7 @@ function hook_contextual_links_plugins_alter(array &$contextual_links) { */ function hook_system_breadcrumb_alter(\Drupal\Core\Breadcrumb\Breadcrumb &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) { // Add an item to the end of the breadcrumb. - $breadcrumb->addLink(Drupal::l(t('Text'), 'example_route_name')); + $breadcrumb->addLink(\Drupal\Core\Link::createFromRoute(t('Text'), 'example_route_name')); } /** diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php index 837d601..7b66dcb 100644 --- a/core/lib/Drupal/Core/Plugin/Context/Context.php +++ b/core/lib/Drupal/Core/Plugin/Context/Context.php @@ -43,11 +43,19 @@ class Context extends ComponentContext implements ContextInterface { protected $cacheabilityMetadata; /** - * {@inheritdoc} + * Create a context object. + * + * @param \Drupal\Core\Plugin\Context\ContextDefinitionInterface $context_definition + * The context definition. + * @param mixed $context_value|NULL + * The context value object. */ - public function __construct(ContextDefinitionInterface $context_definition) { - parent::__construct($context_definition); + public function __construct(ContextDefinitionInterface $context_definition, $context_value = NULL) { + parent::__construct($context_definition, NULL); $this->cacheabilityMetadata = new CacheableMetadata(); + if (!is_null($context_value)) { + $this->setContextValue($context_value); + } } /** @@ -80,19 +88,22 @@ public function hasContextValue() { } /** - * {@inheritdoc} + * Sets the context value. + * + * @param mixed $value + * The value of this context, matching the context definition. */ - public function setContextValue($value) { + protected function setContextValue($value) { // Add the value as a cacheable dependency only if implements the interface // to prevent it from disabling caching with a max-age 0. if ($value instanceof CacheableDependencyInterface) { $this->addCacheableDependency($value); } if ($value instanceof TypedDataInterface) { - return $this->setContextData($value); + $this->contextData = $value; } else { - return $this->setContextData($this->getTypedDataManager()->create($this->contextDefinition->getDataDefinition(), $value)); + $this->contextData = $this->getTypedDataManager()->create($this->contextDefinition->getDataDefinition(), $value); } } @@ -119,13 +130,6 @@ public function getContextData() { return $this->contextData; } - /** - * {@inheritdoc} - */ - public function setContextData(TypedDataInterface $data) { - $this->contextData = $data; - return $this; - } /** * {@inheritdoc} @@ -170,4 +174,16 @@ public function getCacheMaxAge() { return $this->cacheabilityMetadata->getCacheMaxAge(); } + /** + * {@inheritdoc} + */ + public static function createFromContext(ContextInterface $old_context, $value) { + $context = new static($old_context->getContextDefinition(), $value); + $context->addCacheableDependency($old_context); + if (method_exists($old_context, 'getTypedDataManager')) { + $context->setTypedDataManager($old_context->getTypedDataManager()); + } + return $context; + } + } diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php index 373c73c..8c31371 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php @@ -72,6 +72,7 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface * {@inheritdoc} */ public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) { + /** @var $contexts \Drupal\Core\Plugin\Context\ContextInterface[] */ $mappings += $plugin->getContextMapping(); // Loop through each of the expected contexts. @@ -94,7 +95,7 @@ public function applyContextMapping(ContextAwarePluginInterface $plugin, $contex // Pass the value to the plugin if there is one. if ($contexts[$context_id]->hasContextValue()) { - $plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextValue()); + $plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextData()); } elseif ($plugin_context_definition->isRequired()) { // Collect required contexts that exist but are missing a value. diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php index 2f1b785..14dd889 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php @@ -68,7 +68,7 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface * * @param \Drupal\Core\Plugin\ContextAwarePluginInterface $plugin * A plugin about to be evaluated. - * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts + * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts * An array of contexts to set on the plugin. They will only be set if they * match the plugin's context definitions. * @param array $mappings diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextInterface.php index 5b1e6bd..067e76a 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextInterface.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextInterface.php @@ -9,7 +9,6 @@ use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface; use Drupal\Core\Cache\CacheableDependencyInterface; -use Drupal\Core\TypedData\TypedDataInterface; /** * Interface for context. @@ -17,21 +16,18 @@ interface ContextInterface extends ComponentContextInterface, CacheableDependencyInterface { /** - * Gets the context value as typed data object. + * {@inheritdoc} * - * @return \Drupal\Core\TypedData\TypedDataInterface + * @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface */ - public function getContextData(); + public function getContextDefinition(); /** - * Sets the context value as typed data object. - * - * @param \Drupal\Core\TypedData\TypedDataInterface $data - * The context value as a typed data object. + * Gets the context value as typed data object. * - * @return $this + * @return \Drupal\Core\TypedData\TypedDataInterface */ - public function setContextData(TypedDataInterface $data); + public function getContextData(); /** * Adds a dependency on an object: merges its cacheability metadata. @@ -51,4 +47,17 @@ public function setContextData(TypedDataInterface $data); */ public function addCacheableDependency($dependency); + /** + * Creates a new context with a different value. + * + * @param \Drupal\Core\Plugin\Context\ContextInterface $old_context + * The context object used to create a new object. Cacheability metadata + * will be copied over. + * @param mixed $value + * The value of the new context object. + * + * @return static + */ + public static function createFromContext(ContextInterface $old_context, $value); + } diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php index 4f6b5ef..2c3c4e6 100644 --- a/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php +++ b/core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php @@ -32,8 +32,7 @@ * $node = ... * * // Set that specific node as the value of the 'node' context. - * $context = new Context(new ContextDefinition('entity:node')); - * $context->setContextValue($node); + * $context = new Context(new ContextDefinition('entity:node'), $node); * return ['node' => $context]; * @endcode * diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php index 51a39d7..21ee311 100644 --- a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php @@ -30,6 +30,25 @@ /** * {@inheritdoc} * + * @return \Drupal\Core\Plugin\Context\ContextInterface[] + */ + protected function createContextFromConfiguration(array $context_configuration) { + // This method is overridden so that it will use + // \Drupal\Core\Plugin\Context\Context instead. + $contexts = []; + foreach ($context_configuration as $key => $value) { + $context_definition = $this->getContextDefinition($key); + $contexts[$key] = new Context($context_definition, $value); + } + return $contexts; + } + + /** + * {@inheritdoc} + * + * @return \Drupal\Core\Plugin\Context\ContextInterface + * The context object. + * * This code is identical to the Component in order to pick up a different * Context class. */ @@ -55,6 +74,14 @@ public function setContext($name, ComponentContextInterface $context) { /** * {@inheritdoc} */ + public function setContextValue($name, $value) { + $this->context[$name] = Context::createFromContext($this->getContext($name), $value); + return $this; + } + + /** + * {@inheritdoc} + */ public function getContextMapping() { $configuration = $this instanceof ConfigurablePluginInterface ? $this->getConfiguration() : $this->configuration; return isset($configuration['context_mapping']) ? $configuration['context_mapping'] : []; @@ -85,6 +112,15 @@ public function getContextDefinitions() { } /** + * {@inheritdoc} + * + * @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface + */ + public function getContextDefinition($name) { + return parent::getContextDefinition($name); + } + + /** * Wraps the context handler. * * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface diff --git a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableString.php b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableString.php index f29d6e7..b9e79e5 100644 --- a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableString.php +++ b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableString.php @@ -63,18 +63,18 @@ class PluralTranslatableString extends TranslatableString { * ease translation. Use @count in place of the item count, as in * "@count new comments". * @param array $args - * (optional) An associative array of replacements to make after - * translation. Instances of any key in this array are replaced with the - * corresponding value. Based on the first character of the key, the value - * is escaped and/or themed. See - * \Drupal\Component\Utility\SafeMarkup::format(). Note that you do not need - * to include @count in this array; this replacement is done automatically + * (optional) An array with placeholder replacements, keyed by placeholder. + * See \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() for + * additional information about placeholders. Note that you do not need to + * include @count in this array; this replacement is done automatically * for the plural cases. * @param array $options * (optional) An associative array of additional options. See t() for * allowed keys. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation * (optional) The string translation service. + * + * @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() */ public function __construct($count, $singular, $plural, array $args = [], array $options = [], TranslationInterface $string_translation = NULL) { $this->count = $count; diff --git a/core/lib/Drupal/Core/StringTranslation/TranslatableString.php b/core/lib/Drupal/Core/StringTranslation/TranslatableString.php index 92345b7..0dbb3a6 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslatableString.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslatableString.php @@ -73,10 +73,14 @@ class TranslatableString implements SafeStringInterface { * The string that is to be translated. * @param array $arguments * (optional) An array with placeholder replacements, keyed by placeholder. + * See \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() for + * additional information about placeholders. * @param array $options * (optional) An array of additional options. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation * (optional) The string translation service. + * + * @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() */ public function __construct($string, array $arguments = array(), array $options = array(), TranslationInterface $string_translation = NULL) { $this->string = $string; diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php index 3629cda..8784025 100644 --- a/core/lib/Drupal/Core/Template/Attribute.php +++ b/core/lib/Drupal/Core/Template/Attribute.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Template; +use Drupal\Component\Utility\PlainTextOutput; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\SafeStringInterface; /** @@ -52,7 +54,18 @@ * // Produces * @endcode * + * The attribute values are considered plain text and are treated as such. If a + * safe HTML string is detected, it is converted to plain text with + * PlainTextOutput::renderFromHtml() before being escaped. For example: + * @code + * $value = t('Highlight the @tag tag', ['@tag' => '']); + * $attributes = new Attribute(['value' => $value]); + * echo ''; + * // Produces + * @endcode + * * @see \Drupal\Component\Utility\Html::escape() + * @see \Drupal\Component\Utility\PlainTextOutput::renderFromHtml() * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() */ class Attribute implements \ArrayAccess, \IteratorAggregate, SafeStringInterface { @@ -111,17 +124,27 @@ protected function createAttributeValue($name, $value) { } // An array value or 'class' attribute name are forced to always be an // AttributeArray value for consistency. - if (is_array($value) || $name == 'class') { + if ($name == 'class' && !is_array($value)) { + // Cast the value to string in case it implements SafeStringInterface. + $value = [(string) $value]; + } + if (is_array($value)) { // Cast the value to an array if the value was passed in as a string. // @todo Decide to fix all the broken instances of class as a string // in core or cast them. - $value = new AttributeArray($name, (array) $value); + $value = new AttributeArray($name, $value); } elseif (is_bool($value)) { $value = new AttributeBoolean($name, $value); } // As a development aid, we allow the value to be a safe string object. - elseif (!is_object($value) || $value instanceof SafeStringInterface) { + elseif (SafeMarkup::isSafe($value)) { + // Attributes are not supposed to display HTML markup, so we just convert + // the value to plain text. + $value = PlainTextOutput::renderFromHtml($value); + $value = new AttributeString($name, $value); + } + elseif (!is_object($value)) { $value = new AttributeString($name, $value); } return $value; diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 13a7d16..360eed8 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -15,6 +15,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\SafeStringInterface; +use Drupal\Core\Datetime\DateFormatter; use Drupal\Core\Render\RenderableInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\UrlGeneratorInterface; @@ -52,6 +53,13 @@ class TwigExtension extends \Twig_Extension { protected $themeManager; /** + * The date formatter. + * + * @var \Drupal\Core\Datetime\DateFormatter + */ + protected $dateFormatter; + + /** * Constructs \Drupal\Core\Template\TwigExtension. * * @param \Drupal\Core\Render\RendererInterface $renderer @@ -103,6 +111,19 @@ public function setThemeManager(ThemeManagerInterface $theme_manager) { } /** + * Sets the date formatter. + * + * @param \Drupal\Core\Datetime\DateFormatter $date_formatter + * The date formatter. + * + * @return $this + */ + public function setDateFormatter(DateFormatter $date_formatter) { + $this->dateFormatter = $date_formatter; + return $this; + } + + /** * {@inheritdoc} */ public function getFunctions() { @@ -152,6 +173,7 @@ public function getFilters() { new \Twig_SimpleFilter('clean_id', '\Drupal\Component\Utility\Html::getId'), // This filter will render a renderable array to use the string results. new \Twig_SimpleFilter('render', array($this, 'renderVar')), + new \Twig_SimpleFilter('format_date', array($this->dateFormatter, 'format')), ); } diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php index c23c996..4bd6172 100644 --- a/core/lib/Drupal/Core/Updater/Updater.php +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -235,9 +235,6 @@ public function update(&$filetransfer, $overrides = array()) { // Make sure the installation parent directory exists and is writable. $this->prepareInstallDirectory($filetransfer, $args['install_dir']); - // Note: If the project is installed in the top-level, it will not be - // deleted. It will be installed in sites/default as that will override - // the top-level reference and not break other sites which are using it. if (is_dir($args['install_dir'] . '/' . $this->name)) { // Remove the existing installed file. $filetransfer->removeDirectory($args['install_dir'] . '/' . $this->name); diff --git a/core/misc/states.js b/core/misc/states.js index c87bf58..589a370 100644 --- a/core/misc/states.js +++ b/core/misc/states.js @@ -607,7 +607,8 @@ $(document).on('state:required', function (e) { if (e.trigger) { if (e.value) { - var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find('label'); + var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : ''); + var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label); // Avoids duplicate required markers on initialization. if (!$label.hasClass('js-form-required').length) { $label.addClass('js-form-required form-required'); diff --git a/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php b/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php index 6882aed..4de369d 100644 --- a/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php +++ b/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorTitleFormatter.php @@ -54,7 +54,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; if ($items->getEntity()->getEntityTypeId() == 'aggregator_feed') { diff --git a/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorXSSFormatter.php b/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorXSSFormatter.php index 4f3f601..0edb460 100644 --- a/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorXSSFormatter.php +++ b/core/modules/aggregator/src/Plugin/Field/FieldFormatter/AggregatorXSSFormatter.php @@ -30,7 +30,7 @@ class AggregatorXSSFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; foreach ($items as $delta => $item) { diff --git a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php index 641ac89..afbffcc 100644 --- a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php @@ -135,7 +135,7 @@ public function challengeException(Request $request, \Exception $previous) { $challenge = SafeMarkup::format('Basic realm="@realm"', array( '@realm' => !empty($site_name) ? $site_name : 'Access restricted', )); - return new UnauthorizedHttpException($challenge, 'No authentication credentials provided.', $previous); + return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous); } } diff --git a/core/modules/block/src/Plugin/migrate/process/BlockRegion.php b/core/modules/block/src/Plugin/migrate/process/BlockRegion.php index d51dabd..4cf77ac 100644 --- a/core/modules/block/src/Plugin/migrate/process/BlockRegion.php +++ b/core/modules/block/src/Plugin/migrate/process/BlockRegion.php @@ -7,30 +7,68 @@ namespace Drupal\block\Plugin\migrate\process; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; use Drupal\migrate\Row; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * @MigrateProcessPlugin( * id = "block_region" * ) */ -class BlockRegion extends ProcessPluginBase { +class BlockRegion extends ProcessPluginBase implements ContainerFactoryPluginInterface { /** - * {@inheritdoc} + * List of regions, keyed by theme. + * + * @var array[] + */ + protected $regions; + + /** + * Constructs a BlockRegion plugin instance. * - * Set the destination block region, based on the source region and theme as - * well as the current destination default theme. + * @param array $configuration + * The plugin configuration. + * @param string $plugin_id + * The plugin ID. + * @param mixed $plugin_definition + * The plugin definition. + * @param array $regions + * Array of region maps, keyed by theme. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, array $regions) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->regions = $regions; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $regions = array(); + foreach ($container->get('theme_handler')->listInfo() as $key => $theme) { + $regions[$key] = $theme->info['regions']; + } + return new static($configuration, $plugin_id, $plugin_definition, $regions); + } + + /** + * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + // Set the destination region, based on the source region and theme as well + // as the current destination default theme. list($region, $source_theme, $destination_theme) = $value; - // Theme is the same on both source and destination, we will assume they - // have the same regions. + // Theme is the same on both source and destination, so ensure that the + // region exists in the destination theme. if (strtolower($source_theme) == strtolower($destination_theme)) { - return $region; + if (isset($this->regions[$destination_theme][$region])) { + return $region; + } } // If the source and destination theme are different, try to use the diff --git a/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php b/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php index 214c6d3..4222734 100644 --- a/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php +++ b/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php @@ -154,7 +154,7 @@ public function testBlockMigration() { $visibility['request_path']['id'] = 'request_path'; $visibility['request_path']['negate'] = FALSE; $visibility['request_path']['pages'] = '/node'; - $this->assertEntity('block_1', $visibility, 'right', 'bluemarine', -4); + $this->assertEntity('block_1', $visibility, 'sidebar_second', 'bluemarine', -4); $visibility = []; $this->assertEntity('block_2', $visibility, 'right', 'test_theme', -7); diff --git a/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php b/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php index 75f00ce..b0dff81 100644 --- a/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php +++ b/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php @@ -23,7 +23,7 @@ class MigrateBlockTest extends MigrateDrupal7TestBase { * * @var array */ - static $modules = array( + static $modules = [ 'block', 'views', 'comment', @@ -33,7 +33,7 @@ class MigrateBlockTest extends MigrateDrupal7TestBase { 'text', 'filter', 'user', - ); + ]; /** * {@inheritdoc} @@ -50,7 +50,7 @@ protected function setUp() { $config->save(); // Install one of D8's test themes. - \Drupal::service('theme_handler')->install(array('test_theme')); + \Drupal::service('theme_handler')->install(['bartik']); $this->executeMigration('d7_filter_format'); $this->executeMigration('d7_user_role'); @@ -117,7 +117,7 @@ public function testBlockMigration() { // Assert that disabled blocks (or enabled blocks whose plugin IDs could // be resolved) did not migrate. - $non_existent_blocks = array( + $non_existent_blocks = [ 'bartik_system_navigation', 'bartik_system_help', 'seven_user_new', @@ -155,7 +155,7 @@ public function testBlockMigration() { 'seven_menu_menu-test-menu', 'seven_statistics_popular', 'seven_block_1', - ); + ]; $this->assertTrue(empty(Block::loadMultiple($non_existent_blocks))); } diff --git a/core/modules/block/tests/modules/block_test/src/ContextProvider/MultipleStaticContext.php b/core/modules/block/tests/modules/block_test/src/ContextProvider/MultipleStaticContext.php index fea7f6d..eb35eb3 100644 --- a/core/modules/block/tests/modules/block_test/src/ContextProvider/MultipleStaticContext.php +++ b/core/modules/block/tests/modules/block_test/src/ContextProvider/MultipleStaticContext.php @@ -52,11 +52,9 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e public function getRuntimeContexts(array $unqualified_context_ids) { $current_user = $this->userStorage->load($this->account->id()); - $context1 = new Context(new ContextDefinition('entity:user', 'User 1')); - $context1->setContextValue($current_user); + $context1 = new Context(new ContextDefinition('entity:user', 'User 1'), $current_user); - $context2 = new Context(new ContextDefinition('entity:user', 'User 2')); - $context2->setContextValue($current_user); + $context2 = new Context(new ContextDefinition('entity:user', 'User 2'), $current_user); $cacheability = new CacheableMetadata(); $cacheability->setCacheContexts(['user']); diff --git a/core/modules/block/tests/src/Unit/Plugin/migrate/process/BlockRegionTest.php b/core/modules/block/tests/src/Unit/Plugin/migrate/process/BlockRegionTest.php new file mode 100644 index 0000000..6f85e50 --- /dev/null +++ b/core/modules/block/tests/src/Unit/Plugin/migrate/process/BlockRegionTest.php @@ -0,0 +1,69 @@ +prophesize(MigrateExecutableInterface::class)->reveal(); + if (empty($row)) { + $row = $this->prophesize(Row::class)->reveal(); + } + + $regions = array( + 'bartik' => array( + 'triptych_first' => 'Triptych first', + 'triptych_second' => 'Triptych second', + 'triptych_third' => 'Triptych third', + ), + ); + $plugin = new BlockRegion(['region_map' => []], 'block_region', [], $regions); + return $plugin->transform($value, $executable, $row, 'foo'); + } + + /** + * If the source and destination themes are identical, the region should only + * be passed through if it actually exists in the destination theme. + * + * @covers ::transform + */ + public function testTransformSameThemeRegionExists() { + $this->assertSame('triptych_second', $this->transform(['triptych_second', 'bartik', 'bartik'])); + } + + /** + * If the source and destination themes are identical, the region should be + * changed to 'content' if it doesn't exist in the destination theme. + * + * @covers ::transform + */ + public function testTransformSameThemeRegionNotExists() { + $this->assertSame('content', $this->transform(['footer', 'bartik', 'bartik'])); + } + +} diff --git a/core/modules/comment/src/Plugin/Field/FieldFormatter/AuthorNameFormatter.php b/core/modules/comment/src/Plugin/Field/FieldFormatter/AuthorNameFormatter.php index 6772f9b..1d10dd0 100644 --- a/core/modules/comment/src/Plugin/Field/FieldFormatter/AuthorNameFormatter.php +++ b/core/modules/comment/src/Plugin/Field/FieldFormatter/AuthorNameFormatter.php @@ -28,7 +28,7 @@ class AuthorNameFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php index 5aa5f4f..32b3ca1 100644 --- a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php +++ b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php @@ -137,7 +137,7 @@ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInter /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); $output = array(); diff --git a/core/modules/config/src/Form/ConfigSingleExportForm.php b/core/modules/config/src/Form/ConfigSingleExportForm.php index 440de7f..2426b90 100644 --- a/core/modules/config/src/Form/ConfigSingleExportForm.php +++ b/core/modules/config/src/Form/ConfigSingleExportForm.php @@ -105,7 +105,6 @@ public function buildForm(array $form, FormStateInterface $form_state, $config_t '#type' => 'select', '#options' => $this->findConfiguration($default_type), '#default_value' => $config_name, - '#required' => TRUE, '#prefix' => '
', '#suffix' => '
', '#ajax' => array( @@ -118,7 +117,6 @@ public function buildForm(array $form, FormStateInterface $form_state, $config_t '#title' => $this->t('Here is your configuration:'), '#type' => 'textarea', '#rows' => 24, - '#required' => TRUE, '#prefix' => '
', '#suffix' => '
', ); diff --git a/core/modules/config/src/Tests/ConfigExportUITest.php b/core/modules/config/src/Tests/ConfigExportUITest.php index 3c672da..f6840ef 100644 --- a/core/modules/config/src/Tests/ConfigExportUITest.php +++ b/core/modules/config/src/Tests/ConfigExportUITest.php @@ -82,6 +82,10 @@ function testExport() { $file_contents = file_get_contents(file_directory_temp() . '/' . 'system.maintenance.yml'); $exported = Yaml::decode($file_contents); $this->assertNotIdentical($exported['message'], 'Foo'); + + // Check the single export form doesn't have "form-required" elements. + $this->drupalGet('admin/config/development/configuration/single/export'); + $this->assertNoRaw('js-form-required form-required', 'No form required fields are found.'); } } diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php index 002cf72..af6fd37 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php @@ -36,7 +36,7 @@ public static function defaultSettings() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php index 9154037..9b2735c 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeDefaultFormatter.php @@ -36,7 +36,7 @@ public static function defaultSettings() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php index b92c07c..c22445e 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php @@ -25,7 +25,7 @@ class DateTimePlainFormatter extends DateTimeFormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php index 0bc56c6..851f59b 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeTimeAgoFormatter.php @@ -107,7 +107,7 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php index f72a4de..1b7ae33 100644 --- a/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php +++ b/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php @@ -17,6 +17,14 @@ * @group entity_reference */ class EntityReferenceFieldTranslatedReferenceViewTest extends WebTestBase { + + /** + * Flag indicating whether the field is translatable. + * + * @var bool + */ + protected $translatable = TRUE; + /** * The langcode of the source language. * @@ -188,7 +196,7 @@ protected function setUpEntityReferenceField() { 'entity_type' => $this->testEntityTypeName, 'type' => 'entity_reference', 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, - 'translatable' => TRUE, + 'translatable' => $this->translatable, 'settings' => array( 'allowed_values' => array( array( diff --git a/core/modules/field/src/Tests/String/UuidFormatterTest.php b/core/modules/field/src/Tests/String/UuidFormatterTest.php index 4d849cd..92554bf 100644 --- a/core/modules/field/src/Tests/String/UuidFormatterTest.php +++ b/core/modules/field/src/Tests/String/UuidFormatterTest.php @@ -46,13 +46,19 @@ public function testUuidStringFormatter() { $uuid_field = $entity->get('uuid'); + // Verify default render. $render_array = $uuid_field->view([]); - $this->assertIdentical($render_array[0]['#markup'], $entity->uuid(), 'The rendered UUID matches the entity UUID.'); + $this->assertIdentical($render_array[0]['#context']['value'], $entity->uuid(), 'The rendered UUID matches the entity UUID.'); + $this->assertTrue(strpos($this->render($render_array), $entity->uuid()), 'The rendered UUID found.'); + // Verify customized render. $render_array = $uuid_field->view(['settings' => ['link_to_entity' => TRUE]]); $this->assertIdentical($render_array[0]['#type'], 'link'); - $this->assertIdentical($render_array[0]['#title']['#markup'], $entity->uuid()); + $this->assertIdentical($render_array[0]['#title']['#context']['value'], $entity->uuid()); $this->assertIdentical($render_array[0]['#url']->toString(), $entity->url()); + $rendered = $this->render($render_array); + $this->assertTrue(strpos($rendered, $entity->uuid()), 'The rendered UUID found.'); + $this->assertTrue(strpos($rendered, $entity->url()), 'The rendered entity URL found.'); } } diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldApplicableFormatter.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldApplicableFormatter.php index 24704e1..c7bc49c 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldApplicableFormatter.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldApplicableFormatter.php @@ -38,7 +38,7 @@ public static function isApplicable(FieldDefinitionInterface $field_definition) /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { return array('#markup' => 'Nothing to see here'); } } diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldDefaultFormatter.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldDefaultFormatter.php index 1bf2260..1fa8bfb 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldDefaultFormatter.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldDefaultFormatter.php @@ -62,7 +62,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptyFormatter.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptyFormatter.php index bee7d50..cab0c17 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptyFormatter.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptyFormatter.php @@ -35,7 +35,7 @@ public static function defaultSettings() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); if ($items->isEmpty()) { diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptySettingFormatter.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptySettingFormatter.php index 1f076f7..b9bfdd7 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptySettingFormatter.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldEmptySettingFormatter.php @@ -63,7 +63,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); if (!empty($items)) { diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldMultipleFormatter.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldMultipleFormatter.php index 20f7796..40b6558 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldMultipleFormatter.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldMultipleFormatter.php @@ -63,7 +63,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); if (!empty($items)) { diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldNoSettingsFormatter.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldNoSettingsFormatter.php index 4ab58f1..d01ef8b 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldNoSettingsFormatter.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldNoSettingsFormatter.php @@ -27,7 +27,7 @@ class TestFieldNoSettingsFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldPrepareViewFormatter.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldPrepareViewFormatter.php index aaf2bf2..708a404 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldPrepareViewFormatter.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldFormatter/TestFieldPrepareViewFormatter.php @@ -75,7 +75,7 @@ public function prepareView(array $entities_items) { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/field/tests/modules/field_test_views/test_views/views.view.test_view_field_delete.yml b/core/modules/field/tests/modules/field_test_views/test_views/views.view.test_view_field_delete.yml new file mode 100644 index 0000000..16e9cb1 --- /dev/null +++ b/core/modules/field/tests/modules/field_test_views/test_views/views.view.test_view_field_delete.yml @@ -0,0 +1,52 @@ +langcode: en +status: true +dependencies: + module: + - node + - user + config: + - field.storage.node.field_test +id: test_view_field_delete +label: test_view_field_delete +module: views +description: '' +tag: default +base_table: node +base_field: nid +core: '8' +display: + default: + display_options: + access: + type: perm + fields: + nid: + field: nid + id: nid + table: node + plugin_id: field + entity_type: node + entity_field: nid + field_test: + id: field_test + table: node__field_test + field: field_test + plugin_id: field + entity_type: node + entity_field: field_test + cache: + type: tag + exposed_form: + type: basic + pager: + type: full + query: + type: views_query + style: + type: default + row: + type: fields + display_plugin: default + display_title: Master + id: default + position: 0 diff --git a/core/modules/field_ui/src/Form/FieldConfigDeleteForm.php b/core/modules/field_ui/src/Form/FieldConfigDeleteForm.php index 2aacd88..c38544d 100644 --- a/core/modules/field_ui/src/Form/FieldConfigDeleteForm.php +++ b/core/modules/field_ui/src/Form/FieldConfigDeleteForm.php @@ -7,9 +7,11 @@ namespace Drupal\field_ui\Form; +use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityDeleteForm; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element; use Drupal\field_ui\FieldUI; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -47,6 +49,44 @@ public static function create(ContainerInterface $container) { /** * {@inheritdoc} */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + + // If we are adding the field storage as a dependency to delete, then that + // will list the field as a dependency. That is confusing, so remove it. + // Also remove the entity type and the whole entity deletions details + // element if nothing else is in there. + if (isset($form['entity_deletes']['field_config']['#items']) && isset($form['entity_deletes']['field_config']['#items'][$this->entity->id()])) { + unset($form['entity_deletes']['field_config']['#items'][$this->entity->id()]); + if (empty($form['entity_deletes']['field_config']['#items'])) { + unset($form['entity_deletes']['field_config']); + if (!Element::children($form['entity_deletes'])) { + $form['entity_deletes']['#access'] = FALSE; + } + } + } + return $form; + } + + /** + * {@inheritdoc} + */ + protected function getConfigNamesToDelete(ConfigEntityInterface $entity) { + /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */ + $field_storage = $entity->getFieldStorageDefinition(); + $config_names = [$entity->getConfigDependencyName()]; + + // If there is only one bundle left for this field storage, it will be + // deleted too, notify the user about dependencies. + if (count($field_storage->getBundles()) <= 1) { + $config_names[] = $field_storage->getConfigDependencyName(); + } + return $config_names; + } + + /** + * {@inheritdoc} + */ public function getCancelUrl() { return FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle()); } diff --git a/core/modules/field_ui/src/Tests/FieldUIDeleteTest.php b/core/modules/field_ui/src/Tests/FieldUIDeleteTest.php new file mode 100644 index 0000000..6fe6649 --- /dev/null +++ b/core/modules/field_ui/src/Tests/FieldUIDeleteTest.php @@ -0,0 +1,115 @@ +drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('local_tasks_block'); + + // Create a test user. + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access')); + $this->drupalLogin($admin_user); + } + + /** + * Tests that deletion removes field storages and fields as expected. + */ + function testDeleteField() { + $field_label = $this->randomMachineName(); + $field_name_input = 'test'; + $field_name = 'field_test'; + + // Create an additional node type. + $type_name1 = strtolower($this->randomMachineName(8)) . '_test'; + $type1 = $this->drupalCreateContentType(array('name' => $type_name1, 'type' => $type_name1)); + $type_name1 = $type1->id(); + + // Create a new field. + $bundle_path1 = 'admin/structure/types/manage/' . $type_name1; + $this->fieldUIAddNewField($bundle_path1, $field_name_input, $field_label); + + // Create an additional node type. + $type_name2 = strtolower($this->randomMachineName(8)) . '_test'; + $type2 = $this->drupalCreateContentType(array('name' => $type_name2, 'type' => $type_name2)); + $type_name2 = $type2->id(); + + // Add a field to the second node type. + $bundle_path2 = 'admin/structure/types/manage/' . $type_name2; + $this->fieldUIAddExistingField($bundle_path2, $field_name, $field_label); + + \Drupal::service('module_installer')->install(['views']); + ViewTestData::createTestViews(get_class($this), array('field_test_views')); + + // Check the config dependencies of the first field, the field storage must + // not be shown as being deleted yet. + $this->drupalGet("$bundle_path1/fields/node.$type_name1.$field_name/delete"); + $this->assertNoText(t('The listed configuration will be deleted.')); + $this->assertNoText(t('View')); + $this->assertNoText('test_view_field_delete'); + + // Delete the first field. + $this->fieldUIDeleteField($bundle_path1, "node.$type_name1.$field_name", $field_label, $type_name1); + + // Check that the field was deleted. + $this->assertNull(FieldConfig::loadByName('node', $type_name1, $field_name), 'Field was deleted.'); + // Check that the field storage was not deleted + $this->assertNotNull(FieldStorageConfig::loadByName('node', $field_name), 'Field storage was not deleted.'); + + // Check the config dependencies of the first field. + $this->drupalGet("$bundle_path2/fields/node.$type_name2.$field_name/delete"); + $this->assertText(t('The listed configuration will be deleted.')); + $this->assertText(t('View')); + $this->assertText('test_view_field_delete'); + + $xml = $this->cssSelect('#edit-entity-deletes'); + // Remove the wrapping HTML. + $this->assertIdentical(FALSE, strpos($xml[0]->asXml(), $field_label), 'The currently being deleted field is not shown in the entity deletions.'); + + // Delete the second field. + $this->fieldUIDeleteField($bundle_path2, "node.$type_name2.$field_name", $field_label, $type_name2); + + // Check that the field was deleted. + $this->assertNull(FieldConfig::loadByName('node', $type_name2, $field_name), 'Field was deleted.'); + // Check that the field storage was deleted too. + $this->assertNull(FieldStorageConfig::loadByName('node', $field_name), 'Field storage was deleted.'); + } + +} diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php b/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php index 31d5e05..fbe14f0 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php @@ -46,7 +46,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; $url = NULL; diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php b/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php index 27973da..fcf74c6 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php @@ -34,7 +34,7 @@ public static function isApplicable(FieldDefinitionInterface $field_definition) /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; foreach ($items as $delta => $item) { diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php index f370037..a157d96 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/GenericFileFormatter.php @@ -25,10 +25,10 @@ class GenericFileFormatter extends FileFormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - foreach ($this->getEntitiesToView($items) as $delta => $file) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) { $item = $file->_referringItem; $elements[$delta] = array( '#theme' => 'file_link', diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php index 2706e2f..ad3418f 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/RSSEnclosureFormatter.php @@ -25,11 +25,11 @@ class RSSEnclosureFormatter extends FileFormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $entity = $items->getEntity(); // Add the first file as an enclosure to the RSS item. RSS allows only one // enclosure per item. See: http://en.wikipedia.org/wiki/RSS_enclosure - foreach ($this->getEntitiesToView($items) as $delta => $file) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) { $entity->rss_elements[] = array( 'key' => 'enclosure', 'attributes' => array( diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php index 9bf55ee..fda843e 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php @@ -25,10 +25,10 @@ class TableFormatter extends FileFormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - if ($files = $this->getEntitiesToView($items)) { + if ($files = $this->getEntitiesToView($items, $langcode)) { $header = array(t('Attachment'), t('Size')); $rows = array(); foreach ($files as $delta => $file) { diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php index f61a98d..6f2264e 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php @@ -25,10 +25,10 @@ class UrlPlainFormatter extends FileFormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - foreach ($this->getEntitiesToView($items) as $delta => $file) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) { $elements[$delta] = array( '#markup' => file_create_url($file->getFileUri()), '#cache' => array( diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index 3069721..aa24a95 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -169,9 +169,9 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - $files = $this->getEntitiesToView($items); + $files = $this->getEntitiesToView($items, $langcode); // Early opt-out if the field is empty. if (empty($files)) { diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php index 6a0d3ed..6f79497 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php @@ -8,6 +8,7 @@ namespace Drupal\image\Plugin\Field\FieldFormatter; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\field\FieldConfigInterface; use Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase; @@ -19,7 +20,7 @@ /** * {@inheritdoc} */ - protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items) { + protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) { // Add the default image if needed. if ($items->isEmpty()) { $default_image = $this->getFieldSetting('default_image'); @@ -28,7 +29,6 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item if (empty($default_image['uuid']) && $this->fieldDefinition instanceof FieldConfigInterface) { $default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image'); } - if (!empty($default_image['uuid']) && $file = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid'])) { // Clone the FieldItemList into a runtime-only object for the formatter, // so that the fallback image can be rendered without affecting the @@ -48,7 +48,7 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item } } - return parent::getEntitiesToView($items); + return parent::getEntitiesToView($items, $langcode); } } diff --git a/core/modules/language/src/Form/LanguageAddForm.php b/core/modules/language/src/Form/LanguageAddForm.php index 7d2b083..d43927b 100644 --- a/core/modules/language/src/Form/LanguageAddForm.php +++ b/core/modules/language/src/Form/LanguageAddForm.php @@ -46,6 +46,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['predefined_submit'] = array( '#type' => 'submit', '#value' => $this->t('Add language'), + '#name' => 'add_language', '#limit_validation_errors' => array(array('predefined_langcode'), array('predefined_submit')), '#states' => array( 'invisible' => array( @@ -76,6 +77,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['custom_language']['submit'] = array( '#type' => 'submit', '#value' => $this->t('Add custom language'), + '#name' => 'add_custom_language', '#validate' => array('::validateCustom'), '#submit' => array('::submitForm', '::save'), ); diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php index ed80376..d978058 100644 --- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php +++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php @@ -176,7 +176,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $element = array(); $entity = $items->getEntity(); $settings = $this->getSettings(); diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php index 3ea2770..5e3f458 100644 --- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php +++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php @@ -42,7 +42,7 @@ public static function defaultSettings() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $element = array(); $entity = $items->getEntity(); $settings = $this->getSettings(); diff --git a/core/modules/link/tests/src/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php similarity index 98% rename from core/modules/link/tests/src/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php rename to core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php index a3711ec..82db60c 100644 --- a/core/modules/link/tests/src/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php @@ -5,7 +5,7 @@ * Contains \Drupal\Tests\link\Plugin\Validation\Constraint\LinkAccessConstraintValidatorTest. */ -namespace Drupal\Tests\link\Plugin\Validation\Constraint; +namespace Drupal\Tests\link\Unit\Plugin\Validation\Constraint; use Drupal\Core\Url; use Drupal\link\Plugin\Validation\Constraint\LinkAccessConstraint; diff --git a/core/modules/link/tests/src/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php similarity index 98% rename from core/modules/link/tests/src/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php rename to core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php index 2b92971..4fa9292 100644 --- a/core/modules/link/tests/src/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php @@ -5,7 +5,7 @@ * Contains \Drupal\Tests\link\Plugin\Validation\Constraint\LinkExternalProtocolsConstraintValidatorTest. */ -namespace Drupal\Tests\link\Plugin\Validation\Constraint; +namespace Drupal\Tests\link\Unit\Plugin\Validation\Constraint; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Url; diff --git a/core/modules/link/tests/src/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php similarity index 98% rename from core/modules/link/tests/src/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php rename to core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php index 545437c..2325306 100644 --- a/core/modules/link/tests/src/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php @@ -5,7 +5,7 @@ * Contains \Drupal\Tests\link\Plugin\Validation\Constraint\LinkNotExistingInternalConstraintValidatorTest. */ -namespace Drupal\Tests\link\Plugin\Validation\Constraint; +namespace Drupal\Tests\link\Unit\Plugin\Validation\Constraint; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Url; diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index c0e2b65..83bdebb 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -52,18 +52,8 @@ function locale_translation_manual_status() { * @see \Drupal\locale\Form\TranslationStatusForm */ function template_preprocess_locale_translation_update_info(array &$variables) { - // Build output for available updates. - if (isset($variables['updates'])) { - $variables['available_updates'] = []; - if ($variables['updates']) { - foreach ($variables['updates'] as $update) { - $variables['modules'][] = $update['name']; - // Format date for Twig template. - $release = $update; - $release['date'] = \Drupal::service('date.formatter')->format($update['timestamp'], 'html_date'); - $variables['available_updates'][] = $release; - } - } + foreach ($variables['updates'] as $update) { + $variables['modules'][] = $update['name']; } } diff --git a/core/modules/locale/templates/locale-translation-update-info.html.twig b/core/modules/locale/templates/locale-translation-update-info.html.twig index f26883e..74cbffc 100644 --- a/core/modules/locale/templates/locale-translation-update-info.html.twig +++ b/core/modules/locale/templates/locale-translation-update-info.html.twig @@ -7,7 +7,7 @@ * * Available variables: * - modules: A list of modules names that have available translation updates. - * - available_updates: A list of available translation updates. + * - updates: A list of available translation updates. * - not_found: A list of modules missing translation updates. * * @see template_preprocess_locale_translation_update_info() @@ -29,12 +29,12 @@ {%- endtrans -%} {% endif %} - {% if available_updates or not_found %} + {% if updates or not_found %}
- {% if available_updates %} + {% if updates %}
    - {% for update in available_updates %} -
  • {{ update.name }} ({{ update.date }})
  • + {% for update in updates %} +
  • {{ update.name }} ({{ update.timestamp|format_date('html_date') }})
  • {% endfor %}
{% endif %} @@ -43,7 +43,7 @@ Prefix the missing updates list if there is an available updates lists before it. #} - {% if available_updates %} + {% if updates %} {{ 'Missing translations for:'|t }} {% endif %} {% if not_found %} diff --git a/core/modules/node/src/ContextProvider/NodeRouteContext.php b/core/modules/node/src/ContextProvider/NodeRouteContext.php index 6f55b08..485245e 100644 --- a/core/modules/node/src/ContextProvider/NodeRouteContext.php +++ b/core/modules/node/src/ContextProvider/NodeRouteContext.php @@ -44,18 +44,22 @@ public function __construct(RouteMatchInterface $route_match) { */ public function getRuntimeContexts(array $unqualified_context_ids) { $result = []; - $context = new Context(new ContextDefinition('entity:node', NULL, FALSE)); + $context_definition = new ContextDefinition('entity:node', NULL, FALSE); + $value = NULL; if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) { if ($node = $this->routeMatch->getParameter('node')) { - $context->setContextValue($node); + $value = $node; } } elseif ($this->routeMatch->getRouteName() == 'node.add') { $node_type = $this->routeMatch->getParameter('node_type'); - $context->setContextValue(Node::create(array('type' => $node_type->id()))); + $value = Node::create(array('type' => $node_type->id())); } + $cacheability = new CacheableMetadata(); $cacheability->setCacheContexts(['route']); + + $context = new Context($context_definition, $value); $context->addCacheableDependency($cacheability); $result['node'] = $context; diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php index 37db17d..3f3420c 100644 --- a/core/modules/node/src/NodeViewBuilder.php +++ b/core/modules/node/src/NodeViewBuilder.php @@ -41,7 +41,6 @@ public function buildComponents(array &$build, array $entities, array $displays, $langcode, !empty($entity->in_preview), ]], - '#create_placeholder' => TRUE, ); } diff --git a/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php b/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php index 54f5d9e..81b299f 100644 --- a/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php +++ b/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsDefaultFormatter.php @@ -33,7 +33,7 @@ class OptionsDefaultFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); // Only collect allowed options if there are actually items to display. diff --git a/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsKeyFormatter.php b/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsKeyFormatter.php index 69776f8..138d709 100644 --- a/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsKeyFormatter.php +++ b/core/modules/options/src/Plugin/Field/FieldFormatter/OptionsKeyFormatter.php @@ -32,7 +32,7 @@ class OptionsKeyFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); foreach ($items as $delta => $item) { diff --git a/core/modules/options/src/Tests/OptionsFormattersTest.php b/core/modules/options/src/Tests/OptionsFormattersTest.php index e8c6f76..8d97ab4 100644 --- a/core/modules/options/src/Tests/OptionsFormattersTest.php +++ b/core/modules/options/src/Tests/OptionsFormattersTest.php @@ -11,8 +11,8 @@ * Tests the Options field type formatters. * * @group options - * @see \Drupal\options\Plugin\field\formatter\OptionsDefaultFormatter - * @see \Drupal\options\Plugin\field\formatter\OptionsKeyFormatter + * @see \Drupal\options\Plugin\Field\FieldFormatter\OptionsDefaultFormatter + * @see \Drupal\options\Plugin\Field\FieldFormatter\OptionsKeyFormatter */ class OptionsFormattersTest extends OptionsFieldUnitTestBase { diff --git a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php index 7749452..d1290cb 100644 --- a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php +++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php @@ -192,9 +192,9 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - $files = $this->getEntitiesToView($items); + $files = $this->getEntitiesToView($items, $langcode); // Early opt-out if the field is empty. if (empty($files)) { diff --git a/core/modules/responsive_image/tests/modules/responsive_image_test_module/src/Plugin/Field/FieldFormatter/ResponsiveImageTestFormatter.php b/core/modules/responsive_image/tests/modules/responsive_image_test_module/src/Plugin/Field/FieldFormatter/ResponsiveImageTestFormatter.php index eeb5b09..ac9644c 100644 --- a/core/modules/responsive_image/tests/modules/responsive_image_test_module/src/Plugin/Field/FieldFormatter/ResponsiveImageTestFormatter.php +++ b/core/modules/responsive_image/tests/modules/responsive_image_test_module/src/Plugin/Field/FieldFormatter/ResponsiveImageTestFormatter.php @@ -26,8 +26,8 @@ class ResponsiveImageTestFormatter extends ResponsiveImageFormatter { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { - $elements = parent::viewElements($items); + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = parent::viewElements($items, $langcode); // Unset #item_attributes to test that the theme function can handle that. foreach ($elements as &$element) { if (isset($element['#item_attributes'])) { diff --git a/core/modules/search/src/Form/SearchBlockForm.php b/core/modules/search/src/Form/SearchBlockForm.php index 2345c80..243c641 100644 --- a/core/modules/search/src/Form/SearchBlockForm.php +++ b/core/modules/search/src/Form/SearchBlockForm.php @@ -89,7 +89,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { $route = 'search.view_' . $entity_id; $form['#action'] = $this->url($route); - $form['#token'] = FALSE; $form['#method'] = 'get'; $form['keys'] = array( diff --git a/core/modules/statistics/config/install/statistics.settings.yml b/core/modules/statistics/config/install/statistics.settings.yml index e7ae1fd..f788bec 100644 --- a/core/modules/statistics/config/install/statistics.settings.yml +++ b/core/modules/statistics/config/install/statistics.settings.yml @@ -2,3 +2,4 @@ access_log: enabled: false max_lifetime: 259200 count_content_views: 0 +display_max_age: 3600 diff --git a/core/modules/statistics/config/schema/statistics.schema.yml b/core/modules/statistics/config/schema/statistics.schema.yml index 81ed438..d5e6200 100644 --- a/core/modules/statistics/config/schema/statistics.schema.yml +++ b/core/modules/statistics/config/schema/statistics.schema.yml @@ -17,6 +17,9 @@ statistics.settings: count_content_views: type: integer label: 'Count content views' + display_max_age: + type: integer + label: 'How long any statistics may be cached, i.e. the refresh interval' block.settings.statistics_popular_block: type: block_settings diff --git a/core/modules/statistics/src/Tests/StatisticsAdminTest.php b/core/modules/statistics/src/Tests/StatisticsAdminTest.php index 7c4f583..65ff2f0 100644 --- a/core/modules/statistics/src/Tests/StatisticsAdminTest.php +++ b/core/modules/statistics/src/Tests/StatisticsAdminTest.php @@ -47,6 +47,9 @@ class StatisticsAdminTest extends WebTestBase { protected function setUp() { parent::setUp(); + // Set the max age to 0 to simplify testing. + $this->config('statistics.settings')->set('display_max_age', 0)->save(); + // Create Basic page node type. if ($this->profile != 'standard') { $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); @@ -89,6 +92,16 @@ function testStatisticsSettings() { $this->drupalGet('node/' . $this->testNode->id()); $this->client->post($stats_path, array('form_params' => $post)); $this->assertText('2 views', 'Node is viewed 2 times.'); + + // Increase the max age to test that nodes are no longer immediately + // updated, visit the node once more to populate the cache. + $this->config('statistics.settings')->set('display_max_age', 3600)->save(); + $this->drupalGet('node/' . $this->testNode->id()); + $this->assertText('3 views', 'Node is viewed 3 times.'); + + $this->client->post($stats_path, array('form_params' => $post)); + $this->drupalGet('node/' . $this->testNode->id()); + $this->assertText('3 views', 'Views counter was not updated.'); } /** diff --git a/core/modules/statistics/statistics.install b/core/modules/statistics/statistics.install index 4e2b267..11c72f4 100644 --- a/core/modules/statistics/statistics.install +++ b/core/modules/statistics/statistics.install @@ -71,3 +71,11 @@ function statistics_update_8001() { } } } + +/** + * Disable the Statistics module if the node module is not enabled. + */ +function statistics_update_8002() { + // Set the new configuration setting for max age to the initial value. + \Drupal::configFactory()->getEditable('statistics.settings')->set('display_max_age', 3600)->save(); +} diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index a1d4acd..910f7c6 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -50,6 +50,7 @@ function statistics_node_view(array &$build, EntityInterface $node, EntityViewDi */ function statistics_node_links_alter(array &$node_links, NodeInterface $entity, array &$context) { if ($context['view_mode'] != 'rss') { + $node_links['#cache']['contexts'][] = 'user.permissions'; if (\Drupal::currentUser()->hasPermission('view post access counter')) { $statistics = statistics_get($entity->id()); if ($statistics) { @@ -60,6 +61,7 @@ function statistics_node_links_alter(array &$node_links, NodeInterface $entity, '#attributes' => array('class' => array('links', 'inline')), ); } + $node_links['#cache']['max-age'] = \Drupal::config('statistics.settings')->get('display_max_age'); } } } diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php index 389d23e..3ce775d 100644 --- a/core/modules/system/src/Tests/Form/FormTest.php +++ b/core/modules/system/src/Tests/Form/FormTest.php @@ -295,6 +295,18 @@ public function testInputWithInvalidToken() { } /** + * CSRF tokens for GET forms should not be added by default. + */ + public function testGetFormsCsrfToken() { + // We need to be logged in to have CSRF tokens. + $account = $this->createUser(); + $this->drupalLogin($account); + + $this->drupalGet(Url::fromRoute('form_test.get_form')); + $this->assertNoRaw('form_token'); + } + + /** * Tests validation for required textfield element without title. * * Submits a test form containing a textfield form element without title. diff --git a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php index 3508689..05c4c7e 100644 --- a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php +++ b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php @@ -9,6 +9,7 @@ use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\node\Entity\NodeType; use Drupal\plugin_test\Plugin\MockBlockManager; use Drupal\simpletest\KernelTestBase; @@ -26,6 +27,10 @@ class ContextPluginTest extends KernelTestBase { */ function testContext() { $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installEntitySchema('node_type'); + $type = NodeType::create(['type' => 'page', 'name' => 'Page']); + $type->save(); $name = $this->randomMachineName(); $manager = new MockBlockManager(); diff --git a/core/modules/system/src/Tests/Theme/EnginePhpTemplateTest.php b/core/modules/system/src/Tests/Theme/EngineNyanCatTest.php similarity index 62% rename from core/modules/system/src/Tests/Theme/EnginePhpTemplateTest.php rename to core/modules/system/src/Tests/Theme/EngineNyanCatTest.php index 37ab343..bfaa1bb 100644 --- a/core/modules/system/src/Tests/Theme/EnginePhpTemplateTest.php +++ b/core/modules/system/src/Tests/Theme/EngineNyanCatTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\system\Tests\Theme\EnginePhpTemplateTest. + * Contains \Drupal\system\Tests\Theme\EngineNyanCatTest. */ namespace Drupal\system\Tests\Theme; @@ -10,11 +10,11 @@ use Drupal\simpletest\WebTestBase; /** - * Tests theme functions with PHPTemplate. + * Tests the multi theme engine support. * * @group Theme */ -class EnginePhpTemplateTest extends WebTestBase { +class EngineNyanCatTest extends WebTestBase { /** * Modules to enable. @@ -25,7 +25,7 @@ class EnginePhpTemplateTest extends WebTestBase { protected function setUp() { parent::setUp(); - \Drupal::service('theme_handler')->install(array('test_theme_phptemplate')); + \Drupal::service('theme_handler')->install(array('test_theme_nyan_cat_engine')); } /** @@ -33,10 +33,10 @@ protected function setUp() { */ function testTemplateOverride() { $this->config('system.theme') - ->set('default', 'test_theme_phptemplate') + ->set('default', 'test_theme_nyan_cat_engine') ->save(); $this->drupalGet('theme-test/template-test'); - $this->assertText('Success: Template overridden with PHPTemplate theme.', 'Template overridden by PHPTemplate file.'); + $this->assertText('Success: Template overridden with Nyan Cat theme. All of them', 'Template overridden by Nyan Cat file.'); } } diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 11b72a5..47ac437 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -277,83 +277,63 @@ function theme_system_modules_details($variables) { } /** - * Returns HTML for a table of currently disabled modules. + * Prepares variables for the module uninstall template. * * @param $variables * An associative array containing: - * - form: A render element representing the form. + * - form: A render element representing the form. Child elements of the form + * are individual modules. Each module is an associative array containing + * the following elements: + * - #module_name: The name of the module as a string. + * - name: The name of the module in a renderable array. + * - description: A description of the module. + * - #required_by: (optional) A list of modules that require the module. + * - #validation_reasons: (optional) Additional reasons why the module + * cannot be disabled. + * - #attributes: A list of attributes for the module wrapper. * * @ingroup themeable */ -function theme_system_modules_uninstall($variables) { +function template_preprocess_system_modules_uninstall(&$variables) { $form = $variables['form']; + $variables['modules'] = []; - // No theming for the confirm form. - if (isset($form['confirm'])) { - return drupal_render($form); - } + $variables['empty'] = t('No modules are available to uninstall.'); - // Table headers. - $header = array(t('Uninstall'), - t('Name'), - t('Description'), - ); + // Iterate through all the modules, which are children of this element. + foreach (Element::children($form['modules']) as $key) { + $module = $form['modules'][$key]; + $module['plain_name'] = $module['#module_name']; + $module['checkbox'] = $form['uninstall'][$key]; + $module['checkbox_id'] = $form['uninstall'][$key]['#id']; - // Display table. - $rows = array(); - foreach (Element::children($form['modules']) as $module) { - $disabled_header = ''; - $disabled_reasons = ''; + if (!empty($module['#validation_reasons'])) { + $module['validation_reasons'] = $module['#validation_reasons']; + $module['reasons_count'] = count($module['validation_reasons']); + } else { + $module['reasons_count'] = 0; + } + if (!empty($module['#required_by'])) { + $module['required_by'] = $module['#required_by']; + $module['reasons_count'] = $module['reasons_count'] + 1; + } +/* + krumo($module); // Add the modules requiring the module in question as a validation reason. - if (!empty($form['modules'][$module]['#required_by'])) { - $form['modules'][$module]['#validation_reasons'][] = \Drupal::translation()->translate('Required by: @modules', array('@modules' => implode(', ',$form['modules'][$module]['#required_by']))); + if (!empty($module['#required_by'])) { + $module['#validation_reasons'][] = \Drupal::translation() + ->translate('Required by: @modules', array('@modules' => implode(', ', $module['#required_by']))); } - if (!empty($form['modules'][$module]['#validation_reasons'])) { - $disabled_reasons = [ + if (!empty($module['#validation_reasons'])) { + $module['disabled_reasons'] = [ '#theme' => 'item_list', - '#items' => $form['modules'][$module]['#validation_reasons'], + '#items' => $module['#validation_reasons'], ]; - $disabled_reasons = drupal_render($disabled_reasons); - $disabled_header = \Drupal::translation()->formatPlural(count($form['modules'][$module]['#validation_reasons']), - 'The following reason prevents @module from being uninstalled:', - 'The following reasons prevents @module from being uninstalled:', - array('@module' => $form['modules'][$module]['#module_name'])); - } - $rows[] = array( - array('data' => drupal_render($form['uninstall'][$module]), 'align' => 'center'), - array( - 'data' => array( - '#type' => 'inline_template', - '#template' => '', - '#context' => array('module_id' => $form['uninstall'][$module]['#id'], 'module_name' => drupal_render($form['modules'][$module]['name'])), - ) - ), - array( - 'data' => array( - '#type' => 'inline_template', - '#template' => '{{ module_description }}{% if disabled_header is not empty %}
{{ disabled_header }}{{ disabled_reasons }}
{% endif %}', - '#context' => array( - 'module_description' => drupal_render($form['modules'][$module]['description']), - 'disabled_header' => $disabled_header, - 'disabled_reasons' => $disabled_reasons, - ), - ), - 'class' => array('description'), - ), - ); - } - $table = array( - '#type' => 'table', - '#header' => $header, - '#rows' => $rows, - '#empty' => t('No modules are available to uninstall.'), - ); - $output = drupal_render($form['filters']); - $output .= drupal_render($table); - $output .= drupal_render_children($form); - - return $output; + }*/ + $module['attributes'] = new Attribute($module['#attributes']); + $variables['modules'][] = $module; + } } /** diff --git a/core/modules/system/system.module b/core/modules/system/system.module index a8477d3..80af03d 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -191,7 +191,6 @@ function system_theme() { 'system_modules_uninstall' => array( 'render element' => 'form', 'file' => 'system.admin.inc', - 'function' => 'theme_system_modules_uninstall', ), 'status_report' => array( 'variables' => array('requirements' => NULL), diff --git a/core/modules/system/templates/system-modules-uninstall.html.twig b/core/modules/system/templates/system-modules-uninstall.html.twig new file mode 100644 index 0000000..89597af --- /dev/null +++ b/core/modules/system/templates/system-modules-uninstall.html.twig @@ -0,0 +1,84 @@ +{# +/** + * @file + * Default theme implementation for the modules uninstall page. + * + * Available variables: + * - form: The modules uninstall form. + * - modules: Contains multiple module instances. Each module contains: + * - attributes: Attributes on the row. + * - plain_name: The plain-text name of the module. + * - checkbox: A checkbox for uninstalling the module. + * - checkbox_id: A unique id for interacting with the checkbox element. + * - name: The human-readable name of the module. + * - description: The description of the module. + * - disabled_reasons: (optional) A list of reasons why this module cannot be + * uninstalled. + * + * @see template_preprocess_system_modules_uninstall() + * + * @ingroup themeable + */ +#} + +{{ form.filters }} + + + + + + + + + + + {% for module in modules %} + {% set zebra = cycle(['odd', 'even'], loop.index0) %} + + + + + {% else %} + + + + {% endfor %} + +
+ {{ 'Uninstall'|t }} + + {{ 'Name'|t }} + + {{ 'Description'|t }} +
+ {{ module.checkbox }} + + + + {{ module.description }} + {% if module.reasons_count > 0 %} +
+ {% trans %} + The following reason prevents {{ module.plain_name }} from being uninstalled: + {% plural module.reasons_count %} + The following reasons prevent {{ module.plain_name }} from being uninstalled: + {% endtrans %} +
+
    + {% for reason in module.validation_reasons %} +
  • {{ reason }}
  • + {% endfor %} + {% if module.required_by %} +
  • {{ 'Required by:'|t }} {{ module.required_by|safe_join(', ') }}
  • + {% endif %} +
+
+
+ {% endif %} +
{{ empty }}
+ +{{ form|without( + 'filters', + 'modules', + 'uninstall' +) }} diff --git a/core/modules/system/tests/modules/condition_test/src/Tests/ConditionTestDualUserTest.php b/core/modules/system/tests/modules/condition_test/src/Tests/ConditionTestDualUserTest.php index ab498ba..8c80467 100644 --- a/core/modules/system/tests/modules/condition_test/src/Tests/ConditionTestDualUserTest.php +++ b/core/modules/system/tests/modules/condition_test/src/Tests/ConditionTestDualUserTest.php @@ -72,7 +72,7 @@ protected function doTestIdenticalUser() { 'user2' => 'anonymous', ]); $definition = new ContextDefinition('entity:user'); - $contexts['anonymous'] = (new Context($definition))->setContextValue($this->anonymous); + $contexts['anonymous'] = new Context($definition, $this->anonymous); \Drupal::service('context.handler')->applyContextMapping($condition, $contexts); $this->assertTrue($condition->execute()); } @@ -89,8 +89,8 @@ protected function doTestDifferentUser() { 'user2' => 'authenticated', ]); $definition = new ContextDefinition('entity:user'); - $contexts['anonymous'] = (new Context($definition))->setContextValue($this->anonymous); - $contexts['authenticated'] = (new Context($definition))->setContextValue($this->authenticated); + $contexts['anonymous'] = new Context($definition, $this->anonymous); + $contexts['authenticated'] = new Context($definition, $this->authenticated); \Drupal::service('context.handler')->applyContextMapping($condition, $contexts); $this->assertFalse($condition->execute()); } diff --git a/core/modules/system/tests/modules/condition_test/src/Tests/OptionalContextConditionTest.php b/core/modules/system/tests/modules/condition_test/src/Tests/OptionalContextConditionTest.php index f455905..d2b4ed0 100644 --- a/core/modules/system/tests/modules/condition_test/src/Tests/OptionalContextConditionTest.php +++ b/core/modules/system/tests/modules/condition_test/src/Tests/OptionalContextConditionTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\node\Entity\Node; +use Drupal\node\Entity\NodeType; use Drupal\simpletest\KernelTestBase; /** @@ -58,6 +59,7 @@ protected function testContextNoValue() { * Tests with both contexts mapped to the same user. */ protected function testContextAvailable() { + NodeType::create(['type' => 'example', 'name' => 'Example'])->save(); /** @var \Drupal\Core\Condition\ConditionPluginBase $condition */ $condition = \Drupal::service('plugin.manager.condition') ->createInstance('condition_test_optional_context') @@ -66,7 +68,7 @@ protected function testContextAvailable() { ]); $definition = new ContextDefinition('entity:node'); $node = Node::create(['type' => 'example']); - $contexts['node'] = (new Context($definition))->setContextValue($node); + $contexts['node'] = new Context($definition, $node); \Drupal::service('context.handler')->applyContextMapping($condition, $contexts); $this->assertFalse($condition->execute()); } diff --git a/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php b/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php index 3eca4d6..4c3d0b6 100644 --- a/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php +++ b/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php @@ -29,8 +29,7 @@ public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $eve $event->setPluginConfiguration(['required_configuration' => 'A very important, required value.']); $event->addCacheTags(['custom_cache_tag']); - $context = new Context(new ContextDefinition('string', NULL, TRUE)); - $context->setContextValue('Explicitly passed in context.'); + $context = new Context(new ContextDefinition('string', NULL, TRUE), 'Explicitly passed in context.'); $event->setContexts(['context' => $context]); } diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml index a37990b..d8c5833 100644 --- a/core/modules/system/tests/modules/form_test/form_test.routing.yml +++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml @@ -473,3 +473,10 @@ form_test.form_storage_page_cache: _title: 'Form storage with page cache test' requirements: _access: 'TRUE' + +form_test.get_form: + path: '/form-test/get-form' + defaults: + _form: '\Drupal\form_test\Form\FormTestGetForm' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestGetForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestGetForm.php new file mode 100644 index 0000000..9bcb55e --- /dev/null +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestGetForm.php @@ -0,0 +1,44 @@ + 'submit', + '#value' => 'Save', + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + drupal_set_message('The form_test_get_form form has been submitted successfully.'); + } + +} diff --git a/core/modules/system/tests/themes/engines/nyan_cat/nyan_cat.engine b/core/modules/system/tests/themes/engines/nyan_cat/nyan_cat.engine new file mode 100644 index 0000000..a1da728 --- /dev/null +++ b/core/modules/system/tests/themes/engines/nyan_cat/nyan_cat.engine @@ -0,0 +1,52 @@ +load(); +} + +/** + * Implements hook_theme(). + */ +function nyan_cat_theme($existing, $type, $theme, $path) { + $templates = drupal_find_theme_functions($existing, array($theme)); + $templates += drupal_find_theme_templates($existing, '.nyan-cat.html', $path); + return $templates; +} + +/** + * Implements hook_extension(). + */ +function nyan_cat_extension() { + return '.nyan-cat.html'; +} + +/** + * Implements hook_render_template(). + * + * @param string $template_file + * The filename of the template to render. + * @param mixed[] $variables + * A keyed array of variables that will appear in the output. + * + * @return string + * The output generated by the template. + */ +function nyan_cat_render_template($template_file, $variables) { + $output = str_replace('div', 'nyancat', file_get_contents(\Drupal::root() . '/' . $template_file)); + foreach ($variables as $key => $variable) { + if (strpos($output, '9' . $key) !== FALSE) { + $output = str_replace('9' . $key, theme_render_and_autoescape($variable), $output); + } + } + return $output; +} diff --git a/core/themes/engines/phptemplate/phptemplate.info.yml b/core/modules/system/tests/themes/engines/nyan_cat/nyan_cat.info.yml similarity index 76% rename from core/themes/engines/phptemplate/phptemplate.info.yml rename to core/modules/system/tests/themes/engines/nyan_cat/nyan_cat.info.yml index 2bea7a0..342191c 100644 --- a/core/themes/engines/phptemplate/phptemplate.info.yml +++ b/core/modules/system/tests/themes/engines/nyan_cat/nyan_cat.info.yml @@ -1,5 +1,5 @@ type: theme_engine -name: PHPTemplate +name: Nyan cat core: 8.x version: VERSION package: Core diff --git a/core/modules/system/tests/themes/test_theme_nyan_cat_engine/test_theme_nyan_cat_engine.info.yml b/core/modules/system/tests/themes/test_theme_nyan_cat_engine/test_theme_nyan_cat_engine.info.yml new file mode 100644 index 0000000..a911bd0 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme_nyan_cat_engine/test_theme_nyan_cat_engine.info.yml @@ -0,0 +1,6 @@ +name: 'Test theme for Nyan Cat engine' +type: theme +description: 'Theme for testing the theme system with the Nyan Cat theme engine' +version: VERSION +core: 8.x +engine: nyan_cat diff --git a/core/modules/system/tests/themes/test_theme_nyan_cat_engine/test_theme_nyan_cat_engine.theme b/core/modules/system/tests/themes/test_theme_nyan_cat_engine/test_theme_nyan_cat_engine.theme new file mode 100644 index 0000000..5b69a12 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme_nyan_cat_engine/test_theme_nyan_cat_engine.theme @@ -0,0 +1,8 @@ + -Node Content Dummy diff --git a/core/modules/system/tests/themes/test_theme_phptemplate/test_theme_phptemplate.info.yml b/core/modules/system/tests/themes/test_theme_phptemplate/test_theme_phptemplate.info.yml deleted file mode 100644 index da076b1..0000000 --- a/core/modules/system/tests/themes/test_theme_phptemplate/test_theme_phptemplate.info.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: 'Test theme PHPTemplate' -type: theme -description: 'Theme for testing the theme system with the PHPTemplate engine' -version: VERSION -core: 8.x -engine: phptemplate diff --git a/core/modules/system/tests/themes/test_theme_phptemplate/test_theme_phptemplate.theme b/core/modules/system/tests/themes/test_theme_phptemplate/test_theme_phptemplate.theme deleted file mode 100644 index b3d9bbc..0000000 --- a/core/modules/system/tests/themes/test_theme_phptemplate/test_theme_phptemplate.theme +++ /dev/null @@ -1 +0,0 @@ - - diff --git a/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php b/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php index 0433066..ba5158e 100644 --- a/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php +++ b/core/modules/taxonomy/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermRssFormatter.php @@ -28,11 +28,11 @@ class EntityReferenceTaxonomyTermRssFormatter extends EntityReferenceFormatterBa /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $parent_entity = $items->getEntity(); $elements = array(); - foreach ($this->getEntitiesToView($items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { $parent_entity->rss_elements[] = array( 'key' => 'category', 'value' => $entity->label(), diff --git a/core/modules/taxonomy/src/Plugin/views/field/TermName.php b/core/modules/taxonomy/src/Plugin/views/field/TermName.php index c2eba0f..45246d1 100644 --- a/core/modules/taxonomy/src/Plugin/views/field/TermName.php +++ b/core/modules/taxonomy/src/Plugin/views/field/TermName.php @@ -28,7 +28,8 @@ public function getItems(ResultRow $values) { foreach ($items as &$item) { // Replace spaces with hyphens. $name = $item['raw']->get('value')->getValue(); - $item['rendered']['#markup'] = str_replace(' ', '-', $name); + // @todo Add link support https://www.drupal.org/node/2567745 + $item['rendered']['#context']['value'] = str_replace(' ', '-', $name); } } return $items; diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php b/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php index 4b828cf..93f2f83 100644 --- a/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php +++ b/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php @@ -83,7 +83,7 @@ protected function enableTranslation() { * (optional) If TRUE, create a translatable term reference field. Defaults * to FALSE. */ - protected function setUpTermReferenceField($translatable = FALSE) { + protected function setUpTermReferenceField() { $handler_settings = array( 'target_bundles' => array( $this->vocabulary->id() => $this->vocabulary->id(), @@ -92,7 +92,7 @@ protected function setUpTermReferenceField($translatable = FALSE) { ); $this->createEntityReferenceField('node', 'article', $this->termFieldName, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); $field_storage = FieldStorageConfig::loadByName('node', $this->termFieldName); - $field_storage->setTranslatable($translatable); + $field_storage->setTranslatable(FALSE); $field_storage->save(); entity_get_form_display('node', 'article', 'default') diff --git a/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php b/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php index e9d2afe..d807d5e 100644 --- a/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php +++ b/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php @@ -50,7 +50,7 @@ protected function setUp() { $this->vocabulary = $this->createVocabulary(); $this->enableTranslation(); $this->setUpTerm(); - $this->setUpTermReferenceField(TRUE); + $this->setUpTermReferenceField(); $this->setUpNode(); } diff --git a/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php b/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php index c0b8337..8b3749b 100644 --- a/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php +++ b/core/modules/telephone/src/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php @@ -67,7 +67,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $element = array(); $title_setting = $this->getSetting('title'); diff --git a/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php b/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php index 87861e8..2990c99 100644 --- a/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php +++ b/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php @@ -31,7 +31,7 @@ class TextDefaultFormatter extends FormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); // The ProcessedText element already handles cache context & tag bubbling. diff --git a/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php b/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php index 09404d6..9cb3038 100644 --- a/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php +++ b/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php @@ -70,7 +70,7 @@ public function settingsSummary() { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); $render_as_summary = function (&$element) { diff --git a/core/modules/toolbar/js/toolbar.menu.js b/core/modules/toolbar/js/toolbar.menu.js index f3c2301..60b9ea0 100644 --- a/core/modules/toolbar/js/toolbar.menu.js +++ b/core/modules/toolbar/js/toolbar.menu.js @@ -157,15 +157,15 @@ } } - // Bind event handlers. - $(document) - .on('click.toolbar', '.toolbar-box', toggleClickHandler) - .on('click.toolbar', '.toolbar-box a', linkClickHandler); - // Return the jQuery object. return this.each(function (selector) { var $menu = $(this).once('toolbar-menu'); if ($menu.length) { + // Bind event handlers. + $($menu) + .on('click.toolbar', '.toolbar-box', toggleClickHandler) + .on('click.toolbar', '.toolbar-box a', linkClickHandler); + $menu.addClass('root'); initItems($menu); markListLevels($menu); diff --git a/core/modules/user/src/ContextProvider/CurrentUserContext.php b/core/modules/user/src/ContextProvider/CurrentUserContext.php index 1a0f26c..0c66761 100644 --- a/core/modules/user/src/ContextProvider/CurrentUserContext.php +++ b/core/modules/user/src/ContextProvider/CurrentUserContext.php @@ -55,8 +55,7 @@ public function __construct(AccountInterface $account, EntityManagerInterface $e public function getRuntimeContexts(array $unqualified_context_ids) { $current_user = $this->userStorage->load($this->account->id()); - $context = new Context(new ContextDefinition('entity:user', $this->t('Current user'))); - $context->setContextValue($current_user); + $context = new Context(new ContextDefinition('entity:user', $this->t('Current user')), $current_user); $cacheability = new CacheableMetadata(); $cacheability->setCacheContexts(['user']); $context->addCacheableDependency($cacheability); diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php index 21c81a2..8c9c7db 100644 --- a/core/modules/user/src/Controller/UserController.php +++ b/core/modules/user/src/Controller/UserController.php @@ -103,7 +103,7 @@ public function resetPass($uid, $timestamp, $hash) { } else { // Invalid one-time link specifies an unknown user. - drupal_set_message($this->t('The one-time login link you clicked is invalid.')); + drupal_set_message($this->t('The one-time login link you clicked is invalid.'), 'error'); } return $this->redirect(''); } @@ -209,7 +209,7 @@ public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass return batch_process(''); } else { - drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.')); + drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error'); return $this->redirect('entity.user.cancel_form', ['user' => $user->id()], ['absolute' => TRUE]); } } diff --git a/core/modules/user/src/Plugin/Field/FieldFormatter/AuthorFormatter.php b/core/modules/user/src/Plugin/Field/FieldFormatter/AuthorFormatter.php index c880066..fddefe4 100644 --- a/core/modules/user/src/Plugin/Field/FieldFormatter/AuthorFormatter.php +++ b/core/modules/user/src/Plugin/Field/FieldFormatter/AuthorFormatter.php @@ -30,10 +30,10 @@ class AuthorFormatter extends EntityReferenceFormatterBase { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = array(); - foreach ($this->getEntitiesToView($items) as $delta => $entity) { + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { /** @var $referenced_user \Drupal\user\UserInterface */ $elements[$delta] = array( '#theme' => 'username', diff --git a/core/modules/user/src/Plugin/Field/FieldFormatter/UserNameFormatter.php b/core/modules/user/src/Plugin/Field/FieldFormatter/UserNameFormatter.php index 1886ed9..7c8df16 100644 --- a/core/modules/user/src/Plugin/Field/FieldFormatter/UserNameFormatter.php +++ b/core/modules/user/src/Plugin/Field/FieldFormatter/UserNameFormatter.php @@ -54,7 +54,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { + public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; foreach ($items as $delta => $item) { diff --git a/core/modules/user/user.module b/core/modules/user/user.module index a19acc6..084b991 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1051,17 +1051,6 @@ function user_user_role_delete(RoleInterface $role) { * value. */ function user_roles($membersonly = FALSE, $permission = NULL) { - $user_roles = &drupal_static(__FUNCTION__); - - // Do not cache roles for specific permissions. This data is not requested - // frequently enough to justify the additional memory use. - if (empty($permission)) { - $cid = $membersonly ? RoleInterface::AUTHENTICATED_ID : RoleInterface::ANONYMOUS_ID; - if (isset($user_roles[$cid])) { - return $user_roles[$cid]; - } - } - $roles = Role::loadMultiple(); if ($membersonly) { unset($roles[RoleInterface::ANONYMOUS_ID]); @@ -1073,10 +1062,6 @@ function user_roles($membersonly = FALSE, $permission = NULL) { }); } - if (empty($permission)) { - $user_roles[$cid] = $roles; - } - return $roles; } diff --git a/core/modules/views/src/Plugin/views/access/AccessPluginBase.php b/core/modules/views/src/Plugin/views/access/AccessPluginBase.php index 57481fa..5891364 100644 --- a/core/modules/views/src/Plugin/views/access/AccessPluginBase.php +++ b/core/modules/views/src/Plugin/views/access/AccessPluginBase.php @@ -29,6 +29,24 @@ /** * The base plugin to handle access control. + * + * Access plugins are responsible for controlling a user's access to the view. + * Views includes plugins for checking user roles and individual permissions. + * + * To define an access control plugin, extend this base class. Your access + * plugin should have an annotation that includes the plugin's metadata, for + * example: + * @Plugin( + * id = "denyall", + * title = @Translation("No Access"), + * help = @Translation("Will not be accessible.") + * ) + * The definition should include the following keys: + * - id: The unique identifier of your access plugin. + * - title: The human-readable name for your access plugin. + * - help: A short help message for your plugin. + * + * @see \Drupal\views\Plugin\ViewsPluginManager */ abstract class AccessPluginBase extends PluginBase { diff --git a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php index 5df5465..8906cfd 100644 --- a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php +++ b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php @@ -232,7 +232,6 @@ public function testExposedSortAndItemsPerPage() { 'entity_test_view_grants', 'theme', 'url.query_args', - 'user.roles:authenticated', 'languages:language_content' ]; diff --git a/core/modules/views/src/Views.php b/core/modules/views/src/Views.php index 6f11b4a..06b835e 100644 --- a/core/modules/views/src/Views.php +++ b/core/modules/views/src/Views.php @@ -155,7 +155,7 @@ public static function fetchPluginNames($type, $key = NULL, array $base = array( } if (empty($plugin['no_ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) { - $plugins[$id] = (string) $plugin['title']; + $plugins[$id] = $plugin['title']; } } diff --git a/core/modules/views/tests/modules/views_test_formatter/src/Plugin/Field/FieldFormatter/AttachmentTestFormatter.php b/core/modules/views/tests/modules/views_test_formatter/src/Plugin/Field/FieldFormatter/AttachmentTestFormatter.php index 002105e..86600c6 100644 --- a/core/modules/views/tests/modules/views_test_formatter/src/Plugin/Field/FieldFormatter/AttachmentTestFormatter.php +++ b/core/modules/views/tests/modules/views_test_formatter/src/Plugin/Field/FieldFormatter/AttachmentTestFormatter.php @@ -28,8 +28,8 @@ class AttachmentTestFormatter extends NumericUnformattedFormatter { /** * {@inheritdoc} */ - public function viewElements(FieldItemListInterface $items) { - $elements = parent::viewElements($items); + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = parent::viewElements($items, $langcode); // Add dummy attachments. $entity_id = $items->getEntity()->id(); diff --git a/core/modules/views_ui/src/Tests/DisplayTest.php b/core/modules/views_ui/src/Tests/DisplayTest.php index 535cd8e..89096d0 100644 --- a/core/modules/views_ui/src/Tests/DisplayTest.php +++ b/core/modules/views_ui/src/Tests/DisplayTest.php @@ -9,9 +9,9 @@ use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\SafeMarkup; - -use Drupal\views\Views; use Drupal\Core\Template\Attribute; +use Drupal\views\Entity\View; +use Drupal\views\Views; /** * Tests the display UI. @@ -215,6 +215,35 @@ public function testViewStatus() { } /** + * Ensures that no XSS is possible for buttons. + */ + public function testDisplayTitleInButtonsXss() { + $xss_markup = '">'; + $view = $this->randomView(); + $view = View::load($view['id']); + \Drupal::configFactory()->getEditable('views.settings')->set('ui.show.master_display', TRUE)->save(); + + foreach ([$xss_markup, '">'] as $input) { + $display =& $view->getDisplay('page_1'); + $display['display_title'] = $input; + $view->save(); + + $this->drupalGet("admin/structure/views/view/{$view->id()}"); + $escaped = views_ui_truncate($input, 25); + $this->assertEscaped($escaped); + $this->assertNoRaw($xss_markup); + + $this->drupalGet("admin/structure/views/view/{$view->id()}/edit/page_1"); + $this->assertEscaped("View $escaped"); + $this->assertNoRaw("View $xss_markup"); + $this->assertEscaped("Duplicate $escaped"); + $this->assertNoRaw("Duplicate $xss_markup"); + $this->assertEscaped("Delete $escaped"); + $this->assertNoRaw("Delete $xss_markup"); + } + } + + /** * Tests the action links on the edit display UI. */ public function testActionLinks() { @@ -244,4 +273,5 @@ public function testActionLinks() { $this->assertEscaped($display_title); $this->assertNoRaw($display_title); } + } diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php index 818984a..f27776c 100644 --- a/core/modules/views_ui/src/ViewEditForm.php +++ b/core/modules/views_ui/src/ViewEditForm.php @@ -8,23 +8,20 @@ namespace Drupal\views_ui; use Drupal\Component\Utility\Html; -use Drupal\Component\Utility\UrlHelper; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\Xss; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\HtmlCommand; use Drupal\Core\Ajax\ReplaceCommand; use Drupal\Core\Datetime\DateFormatter; -use Drupal\Component\Utility\NestedArray; -use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Url; use Drupal\user\SharedTempStoreFactory; use Drupal\views\Views; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; /** * Form controller for the Views edit form. @@ -402,7 +399,7 @@ public function getDisplayDetails($view, $display) { if (!$is_enabled) { $build['top']['actions']['enable'] = array( '#type' => 'submit', - '#value' => $this->t('Enable !display_title', array('!display_title' => $display_title)), + '#value' => $this->t('Enable @display_title', ['@display_title' => $display_title]), '#limit_validation_errors' => array(), '#submit' => array('::submitDisplayEnable', '::submitDelayDestination'), '#prefix' => '
  • ', @@ -424,7 +421,7 @@ public function getDisplayDetails($view, $display) { } $build['top']['actions']['path'] = array( '#type' => 'link', - '#title' => $this->t('View !display_title', array('!display_title' => $display_title)), + '#title' => $this->t('View @display_title', ['@display_title' => $display_title]), '#options' => array('alt' => array($this->t("Go to the real page for this display"))), '#url' => $url, '#prefix' => '
  • ', @@ -435,7 +432,7 @@ public function getDisplayDetails($view, $display) { if (!$is_default) { $build['top']['actions']['duplicate'] = array( '#type' => 'submit', - '#value' => $this->t('Duplicate !display_title', array('!display_title' => $display_title)), + '#value' => $this->t('Duplicate @display_title', ['@display_title' => $display_title]), '#limit_validation_errors' => array(), '#submit' => array('::submitDisplayDuplicate', '::submitDelayDestination'), '#prefix' => '
  • ', @@ -445,7 +442,7 @@ public function getDisplayDetails($view, $display) { // Always allow a display to be deleted. $build['top']['actions']['delete'] = array( '#type' => 'submit', - '#value' => $this->t('Delete !display_title', array('!display_title' => $display_title)), + '#value' => $this->t('Delete @display_title', ['@display_title' => $display_title]), '#limit_validation_errors' => array(), '#submit' => array('::submitDisplayDelete', '::submitDelayDestination'), '#prefix' => '
  • ', @@ -459,7 +456,7 @@ public function getDisplayDetails($view, $display) { $build['top']['actions']['duplicate_as'][$type] = array( '#type' => 'submit', - '#value' => $this->t('Duplicate as !type', array('!type' => $label)), + '#value' => $this->t('Duplicate as @type', ['@type' => $label]), '#limit_validation_errors' => array(), '#submit' => array('::submitDuplicateDisplayAsType', '::submitDelayDestination'), '#prefix' => '
  • ', @@ -470,7 +467,7 @@ public function getDisplayDetails($view, $display) { else { $build['top']['actions']['undo_delete'] = array( '#type' => 'submit', - '#value' => $this->t('Undo delete of !display_title', array('!display_title' => $display_title)), + '#value' => $this->t('Undo delete of @display_title', ['@display_title' => $display_title]), '#limit_validation_errors' => array(), '#submit' => array('::submitDisplayUndoDelete', '::submitDelayDestination'), '#prefix' => '
  • ', @@ -480,7 +477,7 @@ public function getDisplayDetails($view, $display) { if ($is_enabled) { $build['top']['actions']['disable'] = array( '#type' => 'submit', - '#value' => $this->t('Disable !display_title', array('!display_title' => $display_title)), + '#value' => $this->t('Disable @display_title', ['@display_title' => $display_title]), '#limit_validation_errors' => array(), '#submit' => array('::submitDisplayDisable', '::submitDelayDestination'), '#prefix' => '
  • ', diff --git a/core/phpcs.xml.dist b/core/phpcs.xml.dist new file mode 100644 index 0000000..870e5fb --- /dev/null +++ b/core/phpcs.xml.dist @@ -0,0 +1,81 @@ + + + Default PHP CodeSniffer configuration for Drupal core. + . + + + + ./assets/vendor/* + ./vendor/* + + + ./modules/system/tests/fixtures/HtaccessTest + + + ./lib/Drupal/Core/Cache/Context/OriginalRequestCacheContext.php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index a9ebf6f..085009c 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -406,17 +406,17 @@ protected function config($name) { protected function getDatabaseConnectionInfo() { // If the test is run with argument dburl then use it. $db_url = getenv('SIMPLETEST_DB'); - if (!empty($db_url)) { + if (empty($db_url)) { + $this->markTestSkipped('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh. See https://www.drupal.org/node/2116263#skipped-tests for more information.'); + } + else { $database = Database::convertDbUrlToConnectionInfo($db_url, $this->root); Database::addConnectionInfo('default', 'default', $database); } // Clone the current connection and replace the current prefix. $connection_info = Database::getConnectionInfo('default'); - if (is_null($connection_info)) { - throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable, like "sqlite://localhost//tmp/test.sqlite", to run PHPUnit based functional tests outside of run-tests.sh.'); - } - else { + if (!empty($connection_info)) { Database::renameConnection('default', 'simpletest_original_default'); foreach ($connection_info as $target => $value) { // Replace the full table prefix definition to ensure that no table diff --git a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php index a23b690..d02deee 100644 --- a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php +++ b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php @@ -7,7 +7,11 @@ namespace Drupal\Tests\Component\Plugin; +use Drupal\Component\Plugin\Definition\PluginDefinitionInterface; use Drupal\Component\Plugin\Factory\DefaultFactory; +use Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry; +use Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface; +use Drupal\plugin_test\Plugin\plugin_test\fruit\Kale; use Drupal\Tests\UnitTestCase; /** @@ -17,41 +21,110 @@ class DefaultFactoryTest extends UnitTestCase { /** - * Tests getPluginClass() with a valid plugin. + * Tests getPluginClass() with a valid array plugin definition. + * + * @covers ::getPluginClass */ - public function testGetPluginClassWithValidPlugin() { - $plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry'; + public function testGetPluginClassWithValidArrayPluginDefinition() { + $plugin_class = Cherry::class; $class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class]); $this->assertEquals($plugin_class, $class); } /** + * Tests getPluginClass() with a valid object plugin definition. + * + * @covers ::getPluginClass + */ + public function testGetPluginClassWithValidObjectPluginDefinition() { + $plugin_class = Cherry::class; + $plugin_definition = $this->getMock(PluginDefinitionInterface::class); + $plugin_definition->expects($this->atLeastOnce()) + ->method('getClass') + ->willReturn($plugin_class); + $class = DefaultFactory::getPluginClass('cherry', $plugin_definition); + + $this->assertEquals($plugin_class, $class); + } + + /** * Tests getPluginClass() with a missing class definition. * + * @covers ::getPluginClass + * * @expectedException \Drupal\Component\Plugin\Exception\PluginException * @expectedExceptionMessage The plugin (cherry) did not specify an instance class. */ - public function testGetPluginClassWithMissingClass() { + public function testGetPluginClassWithMissingClassWithArrayPluginDefinition() { DefaultFactory::getPluginClass('cherry', []); } /** + * Tests getPluginClass() with a missing class definition. + * + * @covers ::getPluginClass + * + * @expectedException \Drupal\Component\Plugin\Exception\PluginException + * @expectedExceptionMessage The plugin (cherry) did not specify an instance class. + */ + public function testGetPluginClassWithMissingClassWithObjectPluginDefinition() { + $plugin_definition = $this->getMock(PluginDefinitionInterface::class); + DefaultFactory::getPluginClass('cherry', $plugin_definition); + } + + /** * Tests getPluginClass() with a not existing class definition. * + * @covers ::getPluginClass + * * @expectedException \Drupal\Component\Plugin\Exception\PluginException * @expectedExceptionMessage Plugin (kiwifruit) instance class "\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit" does not exist. */ - public function testGetPluginClassWithNotExistingClass() { + public function testGetPluginClassWithNotExistingClassWithArrayPluginDefinition() { DefaultFactory::getPluginClass('kiwifruit', ['class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit']); } /** + * Tests getPluginClass() with a not existing class definition. + * + * @covers ::getPluginClass + * + * @expectedException \Drupal\Component\Plugin\Exception\PluginException + */ + public function testGetPluginClassWithNotExistingClassWithObjectPluginDefinition() { + $plugin_class = '\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit'; + $plugin_definition = $this->getMock(PluginDefinitionInterface::class); + $plugin_definition->expects($this->atLeastOnce()) + ->method('getClass') + ->willReturn($plugin_class); + DefaultFactory::getPluginClass('kiwifruit', $plugin_definition); + } + + /** + * Tests getPluginClass() with a required interface. + * + * @covers ::getPluginClass + */ + public function testGetPluginClassWithInterfaceWithArrayPluginDefinition() { + $plugin_class = Cherry::class; + $class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class], FruitInterface::class); + + $this->assertEquals($plugin_class, $class); + } + + /** * Tests getPluginClass() with a required interface. + * + * @covers ::getPluginClass */ - public function testGetPluginClassWithInterface() { - $plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry'; - $class = DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class], '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface'); + public function testGetPluginClassWithInterfaceWithObjectPluginDefinition() { + $plugin_class = Cherry::class; + $plugin_definition = $this->getMock(PluginDefinitionInterface::class); + $plugin_definition->expects($this->atLeastOnce()) + ->method('getClass') + ->willReturn($plugin_class); + $class = DefaultFactory::getPluginClass('cherry', $plugin_definition, FruitInterface::class); $this->assertEquals($plugin_class, $class); } @@ -59,12 +132,30 @@ public function testGetPluginClassWithInterface() { /** * Tests getPluginClass() with a required interface but no implementation. * + * @covers ::getPluginClass + * + * @expectedException \Drupal\Component\Plugin\Exception\PluginException + * @expectedExceptionMessage Plugin "cherry" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) must implement interface Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface. + */ + public function testGetPluginClassWithInterfaceAndInvalidClassWithArrayPluginDefinition() { + $plugin_class = Kale::class; + DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class, 'provider' => 'core'], FruitInterface::class); + } + + /** + * Tests getPluginClass() with a required interface but no implementation. + * + * @covers ::getPluginClass + * * @expectedException \Drupal\Component\Plugin\Exception\PluginException - * @expectedExceptionMessage Plugin "cherry" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) must implement interface \Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface. */ - public function testGetPluginClassWithInterfaceAndInvalidClass() { - $plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale'; - DefaultFactory::getPluginClass('cherry', ['class' => $plugin_class, 'provider' => 'core'], '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface'); + public function testGetPluginClassWithInterfaceAndInvalidClassWithObjectPluginDefinition() { + $plugin_class = Kale::class; + $plugin_definition = $this->getMock(PluginDefinitionInterface::class); + $plugin_definition->expects($this->atLeastOnce()) + ->method('getClass') + ->willReturn($plugin_class); + DefaultFactory::getPluginClass('cherry', $plugin_definition, FruitInterface::class); } } diff --git a/core/tests/Drupal/Tests/Component/Utility/FormattableStringTest.php b/core/tests/Drupal/Tests/Component/Utility/FormattableStringTest.php new file mode 100644 index 0000000..670b6e5 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Utility/FormattableStringTest.php @@ -0,0 +1,43 @@ + 'kitten']); + $text = (string) $formattable_string; + $this->assertEquals('Can I please have a kitten', $text); + $text = $formattable_string->jsonSerialize(); + $this->assertEquals('Can I please have a kitten', $text); + } + + /** + * @covers ::count + */ + public function testCount() { + $string = 'Can I please have a @replacement'; + $formattable_string = new FormattableString($string, ['@replacement' => 'kitten']); + $this->assertEquals(strlen($string), $formattable_string->count()); + } + +} diff --git a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php index bca2623..42984c4 100644 --- a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php @@ -312,4 +312,17 @@ public function testDecodeEntitiesAndEscape() { $this->assertSame('<em>répété</em>', $escaped); } -} + /** + * Tests Html::serialize(). + * + * Resolves an issue by where an empty DOMDocument object sent to serialization would + * cause errors in getElementsByTagName() in the serialization function. + * + * @covers ::serialize + */ + public function testSerialize() { + $document = new \DOMDocument(); + $result = Html::serialize($document); + $this->assertSame('', $result); + } +} \ No newline at end of file diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index d95e3bc..e5f2963 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -7,9 +7,10 @@ namespace Drupal\Tests\Core\Form; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Access\AccessResult; -use Drupal\Core\Access\AccessResultForbidden; +use Drupal\Core\Cache\Context\CacheContextsManager; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Form\EnforcedResponseException; use Drupal\Core\Form\FormBuilder; @@ -19,11 +20,10 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountProxyInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Drupal\Core\Cache\Context\CacheContextsManager; -use Symfony\Component\DependencyInjection\ContainerBuilder; /** * @coversDefaultClass \Drupal\Core\Form\FormBuilder @@ -307,6 +307,53 @@ public function testBuildFormWithObject() { } /** + * Tests whether the triggering element is properly identified. + * + * @param string $element_value + * The input element "#value" value. + * @param string $input_value + * The corresponding submitted input value. + * + * @covers ::buildForm + * + * @dataProvider providerTestBuildFormWithTriggeringElement + */ + public function testBuildFormWithTriggeringElement($element_value, $input_value) { + $form_id = 'test_form_id'; + $expected_form = $form_id(); + + $expected_form['actions']['other_submit'] = [ + '#type' => 'submit', + '#value' => $element_value, + ]; + + $form_arg = $this->getMockForm($form_id, $expected_form, 2); + $form_state = new FormState(); + $form_state->setProcessInput(); + $form_state->setUserInput(['form_id' => $form_id, 'op' => $input_value]); + $this->request->setMethod('POST'); + $this->formBuilder->buildForm($form_arg, $form_state); + + $this->assertEquals($expected_form['actions']['other_submit']['#value'], $form_state->getTriggeringElement()['#value']); + } + + /** + * Data provider for ::testBuildFormWithTriggeringElement(). + */ + public function providerTestBuildFormWithTriggeringElement() { + $plain_text = 'Other submit value'; + $markup = 'Other submit value'; + return [ + 'plain-text' => [$plain_text, $plain_text], + 'markup' => [$markup, $markup], + // Note: The input is always decoded, see + // \Drupal\Core\Form\FormBuilder::buttonWasClicked, so we do not need to + // escape the input. + 'escaped-markup' => [Html::escape($markup), $markup], + ]; + } + + /** * Tests the rebuildForm() method for a POST submission. */ public function testRebuildForm() { @@ -773,7 +820,7 @@ public function providerTestInvalidToken() { * * @dataProvider providerTestFormTokenCacheability */ - function testFormTokenCacheability($token, $is_authenticated, $expected_form_cacheability, $expected_token_cacheability) { + public function testFormTokenCacheability($token, $is_authenticated, $expected_form_cacheability, $expected_token_cacheability, $method) { $user = $this->prophesize(AccountProxyInterface::class); $user->isAuthenticated() ->willReturn($is_authenticated); @@ -782,6 +829,7 @@ function testFormTokenCacheability($token, $is_authenticated, $expected_form_cac $form_id = 'test_form_id'; $form = $form_id(); + $form['#method'] = $method; if (isset($token)) { $form['#token'] = $token; @@ -797,7 +845,7 @@ function testFormTokenCacheability($token, $is_authenticated, $expected_form_cac $form_state = new FormState(); $built_form = $this->formBuilder->buildForm($form_arg, $form_state); - if (!isset($expected_form_cacheability)) { + if (!isset($expected_form_cacheability) || ($method == 'get' && !is_string($token))) { $this->assertFalse(isset($built_form['#cache'])); } else { @@ -820,10 +868,12 @@ function testFormTokenCacheability($token, $is_authenticated, $expected_form_cac */ function providerTestFormTokenCacheability() { return [ - 'token:none,authenticated:true' => [NULL, TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0]], - 'token:false,authenticated:true' => [FALSE, TRUE, NULL, NULL], - 'token:none,authenticated:false' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL], - 'token:false,authenticated:false' => [FALSE, FALSE, NULL, NULL], + 'token:none,authenticated:true' => [NULL, TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'post'], + 'token:none,authenticated:false' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'post'], + 'token:false,authenticated:false' => [FALSE, FALSE, NULL, NULL, 'post'], + 'token:false,authenticated:true' => [FALSE, TRUE, NULL, NULL, 'post'], + 'token:none,authenticated:false,method:get' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'get'], + 'token:test_form_id,authenticated:false,method:get' => ['test_form_id', TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'get'], ]; } diff --git a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php index 1cef375..156a125 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php @@ -427,9 +427,9 @@ public function testSetCacheAuthUser() { * @covers ::setCache */ public function testSetCacheWithSafeStrings() { - // A call to SafeMarkup::format() is appropriate in this test as a way to - // add a string to the safe list in the simplest way possible. - SafeMarkup::format('@value', ['@value' => 'a_safe_string']); + SafeMarkup::setMultiple([ + 'a_safe_string' => ['html' => TRUE], + ]); $form_build_id = 'the_form_build_id'; $form = [ '#form_id' => 'the_form_id' diff --git a/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTest.php b/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTest.php index b59ed8a..892b574 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTest.php @@ -83,10 +83,8 @@ public function testSetContextValueTypedData() { ->setMethods(array('getDefaultValue', 'getDataDefinition')) ->getMockForAbstractClass(); - $context = new Context($this->contextDefinition); - $context->setTypedDataManager($this->typedDataManager); $typed_data = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); - $context->setContextValue($typed_data); + $context = new Context($this->contextDefinition, $typed_data); $this->assertSame($typed_data, $context->getContextData()); } @@ -120,7 +118,7 @@ public function testSetContextValueCacheableDependency() { ->method('getCacheMaxAge') ->willReturn(60); - $context->setContextValue($cacheable_dependency); + $context = Context::createFromContext($context, $cacheable_dependency); $this->assertSame($cacheable_dependency, $context->getContextData()); $this->assertEquals(['node:1'], $context->getCacheTags()); $this->assertEquals(['route'], $context->getCacheContexts()); diff --git a/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTypedDataTest.php b/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTypedDataTest.php index 49335a6..e684dd1 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTypedDataTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/Context/ContextTypedDataTest.php @@ -50,11 +50,10 @@ public function testGetContextValue() { \Drupal::setContainer($container); $definition = new ContextDefinition('any'); - $context = new Context($definition); $data_definition = DataDefinition::create('string'); $this->typedData = new StringData($data_definition); $this->typedData->setValue('example string'); - $context->setContextData($this->typedData); + $context = new Context($definition, $this->typedData); $value = $context->getContextValue(); $this->assertSame($value, $this->typedData->getValue()); } diff --git a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php index 8900fdd..981f4c2 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php @@ -11,6 +11,8 @@ use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\ContextHandler; use Drupal\Core\Plugin\ContextAwarePluginInterface; +use Drupal\Core\TypedData\DataDefinition; +use Drupal\Core\TypedData\Plugin\DataType\StringData; use Drupal\Tests\UnitTestCase; /** @@ -229,16 +231,20 @@ public function providerTestFilterPluginDefinitionsByContexts() { * @covers ::applyContextMapping */ public function testApplyContextMapping() { + $context_hit_data = StringData::createInstance(DataDefinition::create('string')); + $context_hit_data->setValue('foo'); $context_hit = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface'); $context_hit->expects($this->atLeastOnce()) - ->method('getContextValue') - ->will($this->returnValue(array('foo'))); + ->method('getContextData') + ->will($this->returnValue($context_hit_data)); + $context_miss_data = StringData::createInstance(DataDefinition::create('string')); + $context_miss_data->setValue('bar'); $context_hit->expects($this->atLeastOnce()) ->method('hasContextValue') ->willReturn(TRUE); $context_miss = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface'); $context_miss->expects($this->never()) - ->method('getContextValue'); + ->method('getContextData'); $contexts = array( 'hit' => $context_hit, @@ -256,7 +262,7 @@ public function testApplyContextMapping() { ->will($this->returnValue(array('hit' => $context_definition))); $plugin->expects($this->once()) ->method('setContextValue') - ->with('hit', array('foo')); + ->with('hit', $context_hit_data); // Make sure that the cacheability metadata is passed to the plugin context. $plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface'); @@ -416,10 +422,12 @@ public function testApplyContextMappingNoValueNonRequired() { * @covers ::applyContextMapping */ public function testApplyContextMappingConfigurableAssigned() { + $context_data = StringData::createInstance(DataDefinition::create('string')); + $context_data->setValue('foo'); $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface'); $context->expects($this->atLeastOnce()) - ->method('getContextValue') - ->will($this->returnValue(array('foo'))); + ->method('getContextData') + ->will($this->returnValue($context_data)); $context->expects($this->atLeastOnce()) ->method('hasContextValue') ->willReturn(TRUE); @@ -439,7 +447,7 @@ public function testApplyContextMappingConfigurableAssigned() { ->will($this->returnValue(array('hit' => $context_definition))); $plugin->expects($this->once()) ->method('setContextValue') - ->with('hit', array('foo')); + ->with('hit', $context_data); // Make sure that the cacheability metadata is passed to the plugin context. $plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface'); diff --git a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php index b4a192d..fba7786 100644 --- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php +++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php @@ -7,10 +7,13 @@ namespace Drupal\Tests\Core\Template; +use Drupal\Component\Utility\Html; +use Drupal\Core\Render\SafeString; use Drupal\Core\Template\Attribute; use Drupal\Core\Template\AttributeArray; use Drupal\Core\Template\AttributeString; use Drupal\Tests\UnitTestCase; +use Drupal\Component\Utility\SafeStringInterface; /** * @coversDefaultClass \Drupal\Core\Template\Attribute @@ -30,6 +33,18 @@ public function testConstructor() { $attribute = new Attribute(['selected' => TRUE, 'checked' => FALSE]); $this->assertTrue($attribute['selected']->value()); $this->assertFalse($attribute['checked']->value()); + + // Test that non-array values with name "class" are cast to array. + $attribute = new Attribute(array('class' => 'example-class')); + $this->assertTrue(isset($attribute['class'])); + $this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']); + + // Test that safe string objects work correctly. + $safe_string = $this->prophesize(SafeStringInterface::class); + $safe_string->__toString()->willReturn('example-class'); + $attribute = new Attribute(array('class' => $safe_string->reveal())); + $this->assertTrue(isset($attribute['class'])); + $this->assertEquals(new AttributeArray('class', array('example-class')), $attribute['class']); } /** @@ -341,6 +356,27 @@ public function testPrint() { } /** + * @covers ::createAttributeValue + * @dataProvider providerTestAttributeValues + */ + public function testAttributeValues(array $attributes, $expected) { + $this->assertEquals($expected, (new Attribute($attributes))->__toString()); + } + + public function providerTestAttributeValues() { + $data = []; + + $string = '"> "'; + $data['safe-object-xss1'] = [['title' => SafeString::create($string)], ' title=""> alert(123)""']; + $data['non-safe-object-xss1'] = [['title' => $string], ' title="' . Html::escape($string) . '"']; + $string = '">'; + $data['safe-object-xss2'] = [['title' => SafeString::create($string)], ' title="">alert(123)"']; + $data['non-safe-object-xss2'] = [['title' => $string], ' title="' . Html::escape($string) . '"']; + + return $data; + } + + /** * Checks that the given CSS class is present in the given HTML snippet. * * @param string $class diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php index 3e78e91..d01641b 100644 --- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Render\RenderableInterface; use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Template\Loader\StringLoader; use Drupal\Core\Template\TwigEnvironment; use Drupal\Core\Template\TwigExtension; use Drupal\Tests\UnitTestCase; @@ -104,6 +105,27 @@ public function testActiveTheme() { } /** + * Tests the format_date filter. + */ + public function testFormatDate() { + $date_formatter = $this->getMockBuilder('\Drupal\Core\Datetime\DateFormatter') + ->disableOriginalConstructor() + ->getMock(); + $date_formatter->expects($this->exactly(2)) + ->method('format') + ->willReturn('1978-11-19'); + $renderer = $this->getMock('\Drupal\Core\Render\RendererInterface'); + $extension = new TwigExtension($renderer); + $extension->setDateFormatter($date_formatter); + + $loader = new StringLoader(); + $twig = new \Twig_Environment($loader); + $twig->addExtension($extension); + $result = $twig->render('{{ time|format_date("html_date") }}'); + $this->assertEquals($date_formatter->format('html_date'), $result); + } + + /** * Tests the escaping of objects implementing SafeStringInterface. * * @covers ::escapeFilter diff --git a/core/themes/bartik/bartik.libraries.yml b/core/themes/bartik/bartik.libraries.yml index 3bfa3a6..fd82367 100644 --- a/core/themes/bartik/bartik.libraries.yml +++ b/core/themes/bartik/bartik.libraries.yml @@ -20,6 +20,7 @@ global-styling: css/components/forum.css: {} css/components/header.css: {} css/components/help.css: {} + css/components/highlighted.css: {} css/components/item-list.css: {} css/components/list-group.css: {} css/components/list.css: {} diff --git a/core/themes/bartik/css/components/highlighted.css b/core/themes/bartik/css/components/highlighted.css new file mode 100644 index 0000000..d80bb86 --- /dev/null +++ b/core/themes/bartik/css/components/highlighted.css @@ -0,0 +1,12 @@ +/** + * @file + * Styles for Bartik's highlighted component. + */ + +.has-featured-top .region-highlighted { + background: #f0f0f0; + background: rgba(30, 50, 10, 0.08); +} +.region-highlighted { + margin: 0 15px; +} diff --git a/core/themes/bartik/css/components/messages.css b/core/themes/bartik/css/components/messages.css index aad710f..15a550d 100644 --- a/core/themes/bartik/css/components/messages.css +++ b/core/themes/bartik/css/components/messages.css @@ -1,18 +1,13 @@ -/* ---------------- Messages ----------------- */ +/** + * @file + * Styles for Bartik's messages. + */ -#messages { - padding: 20px 0 5px; - margin: 0 auto; +.messages__wrapper { + padding: 20px 0 5px 8px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 8px 0; } -.has-featured-top .highlighted { - background: #f0f0f0; - background: rgba(30, 50, 10, 0.08); -} -div.messages { - margin: 8px 15px 8px 23px; /* LTR */ -} -[dir="rtl"] div.messages { - margin-right: 23px; - margin-left: 15px; +[dir="rtl"] .messages__wrapper { + padding: 20px 8px 5px 0; } diff --git a/core/themes/bartik/css/maintenance-page.css b/core/themes/bartik/css/maintenance-page.css index dd473d4..1196a22 100644 --- a/core/themes/bartik/css/maintenance-page.css +++ b/core/themes/bartik/css/maintenance-page.css @@ -17,8 +17,7 @@ body.maintenance-page { .maintenance-page #main-wrapper { min-height: inherit; } -.maintenance-page #header, -.maintenance-page #messages { +.maintenance-page #header { width: auto; } .maintenance-page #main { @@ -54,13 +53,6 @@ body.maintenance-page { line-height: 1em; margin-top: 0; } -.maintenance-page #messages { - padding: 0; - margin-top: 30px; -} -.maintenance-page #messages div.messages { - margin: 0; -} @media all and (min-width: 800px) { .maintenance-page #page-wrapper { width: 800px; @@ -69,10 +61,6 @@ body.maintenance-page { .maintenance-page #main { width: 700px; } - .maintenance-page #messages div.section { - padding: 0; - width: auto; - } } @media all and (min-width: 600px) { /* @TODO find the proper breakpoint */ .maintenance-page #page { diff --git a/core/themes/bartik/templates/status-messages.html.twig b/core/themes/bartik/templates/status-messages.html.twig index 96188d3..62dc635 100644 --- a/core/themes/bartik/templates/status-messages.html.twig +++ b/core/themes/bartik/templates/status-messages.html.twig @@ -25,10 +25,8 @@ {% block messages %} {% if message_list is not empty %} {{ attach_library('bartik/messages') }} -
    -
    - {{ parent() }} -
    +
    + {{ parent() }}
    {% endif %} {% endblock messages %} diff --git a/core/themes/engines/phptemplate/phptemplate.engine b/core/themes/engines/phptemplate/phptemplate.engine deleted file mode 100644 index 232f78d..0000000 --- a/core/themes/engines/phptemplate/phptemplate.engine +++ /dev/null @@ -1,58 +0,0 @@ -load(); -} - -/** - * Implements hook_theme(). - */ -function phptemplate_theme($existing, $type, $theme, $path) { - $templates = drupal_find_theme_functions($existing, array($theme)); - $templates += drupal_find_theme_templates($existing, '.tpl.php', $path); - return $templates; -} - -/** - * Implements hook_extension(). - */ -function phptemplate_extension() { - return '.tpl.php'; -} - -/** - * Implements hook_render_template(). - * - * Renders a system default template, which is essentially a PHP template. - * - * @param $template_file - * The filename of the template to render. - * @param $variables - * A keyed array of variables that will appear in the output. - * - * @return - * The output generated by the template. - */ -function phptemplate_render_template($template_file, $variables) { - // Extract the variables to a local namespace - extract($variables, EXTR_SKIP); - - // Start output buffering - ob_start(); - - // Include the template file - include \Drupal::root() . '/' . $template_file; - - // End buffering and return its contents - return ob_get_clean(); -}