diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 06ba271..4ce12c5 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -12,10 +12,12 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Utility\SafeStringInterface; use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Xss; use Drupal\Core\Config\Config; use Drupal\Core\Config\StorageException; +use Drupal\Core\Render\RenderableInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Theme\ThemeSettings; use Drupal\Component\Utility\NestedArray; @@ -358,6 +360,75 @@ function theme_get_setting($setting_name, $theme = NULL) { } /** + * Escapes and renders variables for theme functions. + * + * This method is used in theme functions to ensure that the result is safe for + * output inside HTML fragments. This mimics the behavior of the auto-escape + * functionality in Twig. + * + * Note: This function should be kept in sync with + * \Drupal\Core\Template\TwigExtension::escapeFilter(). + * + * @param mixed $arg + * The string, object, or render array to escape if needed. + * + * @return string + * The rendered string, safe for use in HTML. The string is not safe when used + * as any part of an HTML attribute name or value. + * + * @throws \Exception + * Thrown when an object is passed in which cannot be printed. + * + * @see \Drupal\Core\Template\TwigExtension::escapeFilter() + * + * @todo Discuss deprecating this in https://www.drupal.org/node/2575081. + * @todo Refactor this to keep it in sync with Twig filtering in + * https://www.drupal.org/node/2575065 + */ +function theme_render_and_autoescape($arg) { + if ($arg instanceOf SafeStringInterface) { + return (string) $arg; + } + $return = NULL; + + if (is_scalar($arg)) { + $return = (string) $arg; + } + elseif (is_object($arg)) { + if ($arg instanceof RenderableInterface) { + $arg = $arg->toRenderable(); + } + elseif (method_exists($arg, '__toString')) { + $return = (string) $arg; + } + // You can't throw exceptions in the magic PHP __toString methods, see + // http://php.net/manual/en/language.oop5.magic.php#object.tostring so + // we also support a toString method. + elseif (method_exists($arg, 'toString')) { + $return = $arg->toString(); + } + else { + throw new \Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg)))); + } + } + + // We have a string or an object converted to a string: Escape it! + if (isset($return)) { + return SafeMarkup::isSafe($return, 'html') ? $return : Html::escape($return); + } + + // This is a normal render array, which is safe by definition, with special + // simple cases already handled. + + // Early return if this element was pre-rendered (no need to re-render). + if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) { + return (string) $arg['#markup']; + } + $arg['#printed'] = FALSE; + return (string) \Drupal::service('renderer')->render($arg); +} + +/** * Converts theme settings to configuration. * * @see system_theme_settings_submit() 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/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/Field/Plugin/Field/FieldFormatter/BasicStringFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/BasicStringFormatter.php index 9d2b730..b1dde5b 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; @@ -32,12 +31,16 @@ class BasicStringFormatter extends FormatterBase { * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items) { - $elements = array(); + $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/StringFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php index fd5c3bb..3bc804e 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; @@ -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/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/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php index 3139224..bf95672 100644 --- a/core/lib/Drupal/Core/Render/theme.api.php +++ b/core/lib/Drupal/Core/Render/theme.api.php @@ -70,7 +70,10 @@ * hook_theme() implementations can also specify that a theme hook * implementation is a theme function, but that is uncommon. It is only used for * special cases, for performance reasons, because rendering using theme - * functions is somewhat faster than theme templates. + * functions is somewhat faster than theme templates. Note that while Twig + * templates will auto-escape variables, theme functions must explicitly escape + * any variables by using theme_render_and_autoescape(). Failure to do so is + * likely to result in security vulnerabilities. * * @section sec_overriding_theme_hooks Overriding Theme Hooks * Themes may register new theme hooks within a hook_theme() implementation, but @@ -93,6 +96,9 @@ * bartik_search_result() in the bartik.theme file, if the search_result hook * implementation was a function instead of a template). Normally, copying the * default function is again a good starting point for overriding its behavior. + * Again, note that theme functions (unlike templates) must explicitly escape + * variables using theme_render_and_autoescape() or risk security + * vulnerabilities. * * @section sec_preprocess_templates Preprocessing for Template Files * If the theme implementation is a template file, several functions are called 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 735a434..13a7d16 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -350,6 +350,9 @@ public function escapePlaceholder($env, $string) { * * Replacement function for Twig's escape filter. * + * Note: This function should be kept in sync with + * theme_render_and_autoescape(). + * * @param \Twig_Environment $env * A Twig_Environment instance. * @param mixed $arg @@ -364,6 +367,9 @@ public function escapePlaceholder($env, $string) { * * @return string|null * The escaped, rendered output, or NULL if there is no valid output. + * + * @todo Refactor this to keep it in sync with theme_render_and_autoescape() + * in https://www.drupal.org/node/2575065 */ public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $charset = NULL, $autoescape = FALSE) { // Check for a numeric zero int or float. 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/config/config.info.yml b/core/modules/config/config.info.yml index 7a527bf..0544746 100644 --- a/core/modules/config/config.info.yml +++ b/core/modules/config/config.info.yml @@ -1,6 +1,6 @@ name: 'Configuration Manager' type: module -description: 'Allows adminstrators to import and export configuration changes.' +description: 'Allows administrators to import and export configuration changes.' package: Core version: VERSION core: 8.x diff --git a/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig b/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig index 777514f..672f567 100644 --- a/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig +++ b/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig @@ -15,10 +15,10 @@ */ #}
-
+
{{ element.source }}
-
+
{{ element.translation }}
diff --git a/core/modules/content_translation/content_translation.info.yml b/core/modules/content_translation/content_translation.info.yml index ff744ad..749cb45 100644 --- a/core/modules/content_translation/content_translation.info.yml +++ b/core/modules/content_translation/content_translation.info.yml @@ -1,6 +1,6 @@ name: 'Content Translation' type: module -description: 'Allows users to translate content entities.' +description: 'Allows users to translate content.' dependencies: - language package: Multilingual diff --git a/core/modules/datetime/datetime.info.yml b/core/modules/datetime/datetime.info.yml index dce0395..3660c2b 100644 --- a/core/modules/datetime/datetime.info.yml +++ b/core/modules/datetime/datetime.info.yml @@ -1,6 +1,6 @@ name: Datetime type: module -description: 'Defines Date fields that store dates and times.' +description: 'Defines fields that store dates and times.' package: Field types version: VERSION core: 8.x diff --git a/core/modules/dynamic_page_cache/dynamic_page_cache.info.yml b/core/modules/dynamic_page_cache/dynamic_page_cache.info.yml index 1ec7b2c..5d8f3fa 100644 --- a/core/modules/dynamic_page_cache/dynamic_page_cache.info.yml +++ b/core/modules/dynamic_page_cache/dynamic_page_cache.info.yml @@ -1,6 +1,6 @@ name: 'Internal Dynamic Page Cache' type: module -description: 'Caches pages incl. those with dynamic content for all users.' +description: 'Caches pages including those with dynamic content for all users.' package: Core version: VERSION core: 8.x diff --git a/core/modules/editor/editor.info.yml b/core/modules/editor/editor.info.yml index 5ace284..981c7a8 100644 --- a/core/modules/editor/editor.info.yml +++ b/core/modules/editor/editor.info.yml @@ -1,6 +1,6 @@ name: 'Text Editor' type: module -description: 'Provides ways to show toolbars or other means to format content.' +description: 'Provides a framework for other modules to add toolbars or other means to format content.' package: Core version: VERSION core: 8.x diff --git a/core/modules/entity_reference/entity_reference.info.yml b/core/modules/entity_reference/entity_reference.info.yml index 43ef6b5..5306c2e 100644 --- a/core/modules/entity_reference/entity_reference.info.yml +++ b/core/modules/entity_reference/entity_reference.info.yml @@ -1,6 +1,6 @@ name: 'Entity Reference' type: module -description: 'Defines fields that contain links to other entities within the site.' +description: 'Defines fields that contain links to other content, users etc. within the site.' package: Field types version: VERSION core: 8.x 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_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/help/src/Controller/HelpController.php b/core/modules/help/src/Controller/HelpController.php index dcb50bd..4ebd4c8 100644 --- a/core/modules/help/src/Controller/HelpController.php +++ b/core/modules/help/src/Controller/HelpController.php @@ -77,7 +77,7 @@ protected function helpLinksAsList() { $column = array( '#type' => 'container', 'links' => array('#theme' => 'item_list'), - '#attributes' => array('class' => array('layout-column', 'layout-column--quarter')), + '#attributes' => array('class' => array('layout-column', 'quarter')), ); $output = array( '#prefix' => '
', diff --git a/core/modules/history/history.info.yml b/core/modules/history/history.info.yml index 63f9e8e..944eb01 100644 --- a/core/modules/history/history.info.yml +++ b/core/modules/history/history.info.yml @@ -1,6 +1,6 @@ name: History type: module -description: 'Record which content a user has read and marks it as new or updated.' +description: 'Records which content a user has viewed and marks it as new or updated.' package: Core version: VERSION core: 8.x diff --git a/core/modules/image/image.info.yml b/core/modules/image/image.info.yml index 2763a4f..90394b2 100644 --- a/core/modules/image/image.info.yml +++ b/core/modules/image/image.info.yml @@ -1,6 +1,6 @@ name: Image type: module -description: 'Defines fields that contain image files, and provides tools to configure their display.' +description: 'Defines fields that contain image files and provides tools to configure their display.' package: Field types version: VERSION core: 8.x diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index 3f860de..9373672 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -201,5 +201,5 @@ function template_preprocess_language_content_settings_table(&$variables) { * @ingroup themeable */ function theme_language_content_settings_table($variables) { - return '

' . $variables['build']['#title'] . '

' . drupal_render($variables['build']); + return '

' . theme_render_and_autoescape($variables['build']['#title']) . '

' . theme_render_and_autoescape($variables['build']); } 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/link.info.yml b/core/modules/link/link.info.yml index ce26581..9e542b2 100644 --- a/core/modules/link/link.info.yml +++ b/core/modules/link/link.info.yml @@ -1,6 +1,6 @@ name: Link type: module -description: 'Defines fields that contain internal or external URLs and optional link text.' +description: 'Defines fields that contain internal or external URLs.' core: 8.x package: Field types version: VERSION diff --git a/core/modules/path/path.info.yml b/core/modules/path/path.info.yml index 8f38a2a..670cdef 100644 --- a/core/modules/path/path.info.yml +++ b/core/modules/path/path.info.yml @@ -1,6 +1,6 @@ name: Path type: module -description: 'Allows users to specify a custom URL for an existing internal system path.' +description: 'Allows users to specify a custom URL for an existing internal path.' package: Core version: VERSION core: 8.x diff --git a/core/modules/rdf/rdf.info.yml b/core/modules/rdf/rdf.info.yml index c48bf0c..99e4a43 100644 --- a/core/modules/rdf/rdf.info.yml +++ b/core/modules/rdf/rdf.info.yml @@ -1,6 +1,6 @@ name: RDF type: module -description: 'Enriches content with metadata (relationships, attributes etc.) for other applications (search engines, aggregators etc.).' +description: 'Enriches content with metadata that external applications (such as search engines or aggregators) can use.' package: Core version: VERSION core: 8.x 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/serialization/serialization.info.yml b/core/modules/serialization/serialization.info.yml index be8d340..6c7b366 100644 --- a/core/modules/serialization/serialization.info.yml +++ b/core/modules/serialization/serialization.info.yml @@ -1,6 +1,6 @@ name: Serialization type: module -description: 'Provides a service for (de)serializing data to/from formats such as JSON and XML.' +description: 'Provides a service to converts data structure to and from formats such as JSON and XML.' package: Web services version: VERSION core: 8.x diff --git a/core/modules/shortcut/shortcut.info.yml b/core/modules/shortcut/shortcut.info.yml index 0953c4e..ddbec6a 100644 --- a/core/modules/shortcut/shortcut.info.yml +++ b/core/modules/shortcut/shortcut.info.yml @@ -1,6 +1,6 @@ name: Shortcut type: module -description: 'Allows users to create sets of shortcut links to pages of the site.' +description: 'Allows users to create sets of shortcut links to pages within the site.' package: Core version: VERSION core: 8.x diff --git a/core/modules/system/css/system.admin.css b/core/modules/system/css/system.admin.css index 1d9a51c..b01e405 100644 --- a/core/modules/system/css/system.admin.css +++ b/core/modules/system/css/system.admin.css @@ -33,13 +33,13 @@ padding-right: 10px; padding-left: 0; } - .layout-column--half { + .layout-column.half { width: 50%; } - .layout-column--quarter { + .layout-column.quarter { width: 25%; } - .layout-column--three-quarter { + .layout-column.three-quarter { width: 75%; } } 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/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/templates/admin-page.html.twig b/core/modules/system/templates/admin-page.html.twig index b3808e4..038b1af 100644 --- a/core/modules/system/templates/admin-page.html.twig +++ b/core/modules/system/templates/admin-page.html.twig @@ -17,7 +17,7 @@
{{ system_compact_link }} {% for container in containers %} -
+
{% for block in container.blocks %} {{ block }} {% endfor %} 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/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/update/templates/update-version.html.twig b/core/modules/update/templates/update-version.html.twig index 2d5520f..612f5d4 100644 --- a/core/modules/update/templates/update-version.html.twig +++ b/core/modules/update/templates/update-version.html.twig @@ -19,12 +19,12 @@ #}
-
{{ title }}
-
+
{{ title }}
+
{{ version.version }} ({{ version.date|date('Y-M-d') }})
-
+