Update to drupal 8.0.0-rc1. For more information, see https://www.drupal.org/node/2582663

This commit is contained in:
Greg Anderson 2015-10-08 11:40:12 -07:00
parent eb34d130a8
commit f32e58e4b1
8476 changed files with 211648 additions and 170042 deletions

View file

@ -57,7 +57,7 @@ class Handle {
if (version_compare(PHP_VERSION, '7.0.0-dev') < 0) {
// PHP 5 - create a handler to throw the exception directly.
assert_options(ASSERT_CALLBACK, function($file, $line, $code, $message) {
assert_options(ASSERT_CALLBACK, function($file = '', $line = 0, $code = '', $message = '') {
if (empty($message)) {
$message = $code;
}

View file

@ -71,6 +71,9 @@ class Inspector {
/**
* Asserts that all members are strings.
*
* Use this only if it is vital that the members not be objects, otherwise
* test with ::assertAllStringable().
*
* @param mixed $traversable
* Variable to be examined.
*
@ -94,7 +97,7 @@ class Inspector {
public static function assertAllStringable($traversable) {
if (static::assertTraversable($traversable)) {
foreach ($traversable as $member) {
if (!(is_string($member) || (is_object($member) && method_exists($member, '__toString')))) {
if (!static::assertStringable($member)) {
return FALSE;
}
}
@ -103,6 +106,22 @@ class Inspector {
return FALSE;
}
/**
* Asserts argument is a string or an object castable to a string.
*
* Use this instead of is_string() alone unless the argument being an object
* in any way will cause a problem.
*
* @param mixed string
* Variable to be examined
*
* @return bool
* TRUE if $string is a string or an object castable to a string.
*/
public static function assertStringable($string) {
return is_string($string) || (is_object($string) && method_exists($string, '__toString'));
}
/**
* Asserts that all members are arrays.
*

View file

@ -5,6 +5,7 @@
* Contains \Drupal\Component\Datetime\DateTimePlus.
*/
namespace Drupal\Component\Datetime;
use Drupal\Component\Utility\ToStringTrait;
/**
* Wraps DateTime().
@ -28,6 +29,8 @@ namespace Drupal\Component\Datetime;
*/
class DateTimePlus {
use ToStringTrait;
const FORMAT = 'Y-m-d H:i:s';
/**
@ -271,16 +274,12 @@ class DateTimePlus {
}
/**
* Implements __toString() for dates.
* Renders the timezone name.
*
* The DateTime class does not implement this.
*
* @see https://bugs.php.net/bug.php?id=62911
* @see http://www.serverphorums.com/read.php?7,555645
* @return string
*/
public function __toString() {
$format = static::FORMAT;
return $this->format($format) . ' ' . $this->getTimeZone()->getName();
public function render() {
return $this->format(static::FORMAT) . ' ' . $this->getTimeZone()->getName();
}
/**

View file

@ -153,4 +153,13 @@ class FileCache implements FileCacheInterface {
}
}
/**
* Resets the static cache.
*
* @todo Replace this once https://www.drupal.org/node/2260187 is in.
*/
public static function reset() {
static::$cached = [];
}
}

View file

@ -193,7 +193,7 @@ class PoItem {
strpos($this->_source, LOCALE_PLURAL_DELIMITER) !== FALSE) {
$this->setSource(explode(LOCALE_PLURAL_DELIMITER, $this->_source));
$this->setTranslation(explode(LOCALE_PLURAL_DELIMITER, $this->_translation));
$this->setPlural(count($this->_translation) > 1);
$this->setPlural(count($this->_source) > 1);
}
}

View file

@ -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;
}
/**
@ -74,13 +70,6 @@ class Context implements ContextInterface {
return (bool) $this->contextValue || (bool) $this->getContextDefinition()->getDefaultValue();
}
/**
* {@inheritdoc}
*/
public function setContextDefinition(ContextDefinitionInterface $context_definition) {
$this->contextDefinition = $context_definition;
}
/**
* Implements \Drupal\Component\Plugin\Context\ContextInterface::getContextDefinition().
*/

View file

@ -12,16 +12,6 @@ namespace Drupal\Component\Plugin\Context;
*/
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.
*
@ -38,15 +28,6 @@ interface ContextInterface {
*/
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.
*

View file

@ -41,17 +41,30 @@ abstract class ContextAwarePluginBase extends PluginBase implements ContextAware
* 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 @@ abstract class ContextAwarePluginBase extends PluginBase implements ContextAware
* {@inheritdoc}
*/
public function setContextValue($name, $value) {
$this->getContext($name)->setContextValue($value);
$this->context[$name] = new Context($this->getContextDefinition($name), $value);
return $this;
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\Component\Plugin\PluginDefinitionInterface.
*/
namespace Drupal\Component\Plugin\Definition;
/**
* Defines a plugin definition.
*
* Object-based plugin definitions MUST implement this interface.
*
* @ingroup Plugin
*/
interface PluginDefinitionInterface {
/**
* Sets the class.
*
* @param string $class
* A fully qualified class name.
*
* @return static
*
* @throws \InvalidArgumentException
* If the class is invalid.
*/
public function setClass($class);
/**
* Gets the class.
*
* @return string
* A fully qualified class name.
*/
public function getClass();
}

View file

@ -6,6 +6,7 @@
namespace Drupal\Component\Plugin\Factory;
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Component\Plugin\Exception\PluginException;
@ -63,7 +64,7 @@ class DefaultFactory implements FactoryInterface {
*
* @param string $plugin_id
* The id of a plugin.
* @param mixed $plugin_definition
* @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface|mixed[] $plugin_definition
* The plugin definition associated with the plugin ID.
* @param string $required_interface
* (optional) THe required plugin interface.
@ -77,18 +78,32 @@ class DefaultFactory implements FactoryInterface {
*
*/
public static function getPluginClass($plugin_id, $plugin_definition = NULL, $required_interface = NULL) {
if (empty($plugin_definition['class'])) {
throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id));
}
$missing_class_message = sprintf('The plugin (%s) did not specify an instance class.', $plugin_id);
if (is_array($plugin_definition)) {
if (empty($plugin_definition['class'])) {
throw new PluginException($missing_class_message);
}
$class = $plugin_definition['class'];
$class = $plugin_definition['class'];
}
elseif ($plugin_definition instanceof PluginDefinitionInterface) {
if (!$plugin_definition->getClass()) {
throw new PluginException($missing_class_message);
}
$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;

View file

@ -0,0 +1,253 @@
<?php
/**
* @file
* Contains Drupal\Component\Render\FormattableMarkup.
*/
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
/**
* Formats a string for HTML display by replacing variable placeholders.
*
* When cast to a string, this object replaces variable placeholders in the
* string with the arguments passed in during construction and escapes the
* values so they can be safely displayed as HTML. See the documentation of
* \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for details
* on the supported placeholders and how to use them securely. Incorrect use of
* this class can result in security vulnerabilities.
*
* In most cases, you should use TranslatableMarkup or PluralTranslatableMarkup
* rather than this object, since they will translate the text (on
* non-English-only sites) in addition to formatting it. Variables concatenated
* without the insertion of language-specific words or punctuation are some
* examples where translation is not applicable and using this class directly
* directly is appropriate.
*
* This class is designed for formatting messages that are mostly text, not as
* an HTML template language. As such:
* - The passed in string should contain no (or minimal) HTML.
* - Variable placeholders should not be used within the "<" and ">" of an
* HTML tag, such as in HTML attribute values. This would be a security
* risk. Examples:
* @code
* // Insecure (placeholder within "<" and ">"):
* $this->placeholderFormat('<@variable>text</@variable>', ['@variable' => $variable]);
* // Insecure (placeholder within "<" and ">"):
* $this->placeholderFormat('<a @variable>link text</a>', ['@variable' => $variable]);
* // Insecure (placeholder within "<" and ">"):
* $this->placeholderFormat('<a title="@variable">link text</a>', ['@variable' => $variable]);
* @endcode
* Only the "href" attribute is supported via the special ":variable"
* placeholder, to allow simple links to be inserted:
* @code
* // Secure (usage of ":variable" placeholder for href attribute):
* $this->placeholderFormat('<a href=":variable">link text</a>', [':variable' , $variable]);
* // Secure (usage of ":variable" placeholder for href attribute):
* $this->placeholderFormat('<a href=":variable" title="static text">link text</a>', [':variable' => $variable]);
* // Insecure (the "@variable" placeholder does not filter dangerous
* // protocols):
* $this->placeholderFormat('<a href="@variable">link text</a>', ['@variable' => $variable]);
* // Insecure ("@variable" placeholder within "<" and ">"):
* $this->placeholderFormat('<a href=":url" title="@variable">link text</a>', [':url' => $url, '@variable' => $variable]);
* @endcode
* To build non-minimal HTML, use an HTML template language such as Twig,
* rather than this class.
*
* @ingroup sanitization
*
* @see \Drupal\Core\StringTranslation\TranslatableMarkup
* @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
*/
class FormattableMarkup implements MarkupInterface {
/**
* The arguments to replace placeholders with.
*
* @var array
*/
protected $arguments = [];
/**
* Constructs a new class instance.
*
* @param string $string
* A string containing placeholders. The string itself will not be escaped,
* any unsafe content must be in $args and inserted via placeholders.
* @param array $arguments
* An array with placeholder replacements, keyed by placeholder. See
* \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for
* additional information about placeholders.
*
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
*/
public function __construct($string, array $arguments) {
$this->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();
}
/**
* Replaces placeholders in a string with values.
*
* @param string $string
* A string containing placeholders. The string itself is expected to be
* safe and correct HTML. Any unsafe content must be in $args and
* inserted via placeholders.
* @param array $args
* An associative array of replacements. Each array key should be the same
* as a placeholder in $string. The corresponding value should be a string
* or an object that implements
* \Drupal\Component\Render\MarkupInterface. The value replaces the
* placeholder in $string. Sanitization and formatting will be done before
* replacement. The type of sanitization and formatting depends on the first
* character of the key:
* - @variable: When the placeholder replacement value is:
* - A string, the replaced value in the returned string will be sanitized
* using \Drupal\Component\Utility\Html::escape().
* - A MarkupInterface object, the replaced value in the returned string
* will not be sanitized.
* - A MarkupInterface object cast to a string, the replaced value in the
* returned string be forcibly sanitized using
* \Drupal\Component\Utility\Html::escape().
* @code
* $this->placeholderFormat('This will force HTML-escaping of the replacement value: @text', ['@text' => (string) $safe_string_interface_object));
* @endcode
* Use this placeholder as the default choice for anything displayed on
* the site, but not within HTML attributes, JavaScript, or CSS. Doing so
* is a security risk.
* - %variable: Use when the replacement value is to be wrapped in <em>
* tags.
* A call like:
* @code
* $string = "%output_text";
* $arguments = ['output_text' => 'text output here.'];
* $this->placeholderFormat($string, $arguments);
* @endcode
* makes the following HTML code:
* @code
* <em class="placeholder">text output here.</em>
* @endcode
* As with @variable, do not use this within HTML attributes, JavaScript,
* or CSS. Doing so is a security risk.
* - :variable: Return value is escaped with
* \Drupal\Component\Utility\Html::escape() and filtered for dangerous
* protocols using UrlHelper::stripDangerousProtocols(). Use this when
* using the "href" attribute, ensuring the attribute value is always
* wrapped in quotes:
* @code
* // Secure (with quotes):
* $this->placeholderFormat('<a href=":url">@variable</a>', [':url' => $url, @variable => $variable]);
* // Insecure (without quotes):
* $this->placeholderFormat('<a href=:url>@variable</a>', [':url' => $url, @variable => $variable]);
* @endcode
* 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.
*
* @return string
* A formatted HTML string with the placeholders replaced.
*
* @ingroup sanitization
*
* @see \Drupal\Core\StringTranslation\TranslatableMarkup
* @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup
* @see \Drupal\Component\Utility\Html::escape()
* @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
* @see \Drupal\Core\Url::fromUri()
*/
protected static function placeholderFormat($string, array $args) {
// Transform arguments before inserting them.
foreach ($args as $key => $value) {
switch ($key[0]) {
case '@':
// Escape if the value is not an object from a class that implements
// \Drupal\Component\Render\MarkupInterface, for example strings will
// be escaped.
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() may
// return TRUE for content that is safe within HTML fragments, but not
// within other contexts, so this placeholder type must not be used
// within HTML attributes, JavaScript, or CSS.
$args[$key] = static::placeholderEscape($value);
break;
case ':':
// Strip URL protocols that can be XSS vectors.
$value = UrlHelper::stripDangerousProtocols($value);
// Escape unconditionally, without checking
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe(). This
// forces characters that are unsafe for use in an "href" HTML
// attribute to be encoded. If a caller wants to pass a value that is
// extracted from HTML and therefore is already HTML encoded, it must
// invoke
// \Drupal\Component\Render\OutputStrategyInterface::renderFromHtml()
// on it prior to passing it in as a placeholder value of this type.
// @todo Add some advice and stronger warnings.
// https://www.drupal.org/node/2569041.
$args[$key] = Html::escape($value);
break;
case '%':
default:
// Similarly to @, escape non-safe values. Also, add wrapping markup
// in order to render as a placeholder. Not for use within attributes,
// per the warning above about
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() and also
// due to the wrapping markup.
$args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>';
break;
}
}
return strtr($string, $args);
}
/**
* Escapes a placeholder replacement value if needed.
*
* @param string|\Drupal\Component\Render\MarkupInterface $value
* A placeholder replacement value.
*
* @return string
* The properly escaped replacement value.
*/
protected static function placeholderEscape($value) {
return SafeMarkup::isSafe($value) ? (string) $value : Html::escape($value);
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\Component\Render\HtmlEscapedText.
*/
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
/**
* Escapes HTML syntax characters to HTML entities for display in markup.
*
* This class can be used to provide theme engine-like late escaping
* functionality.
*
* @ingroup sanitization
*/
class HtmlEscapedText implements MarkupInterface {
/**
* The string to escape.
*
* @var string
*/
protected $string;
/**
* Constructs an HtmlEscapedText object.
*
* @param $string
* The string to escape. This value will be cast to a string.
*/
public function __construct($string) {
$this->string = (string) $string;
}
/**
* {@inheritdoc}
*/
public function __toString() {
return Html::escape($this->string);
}
/**
* {@inheritdoc}
*/
public function count() {
return Unicode::strlen($this->string);
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() {
return $this->__toString();
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\Component\Render\MarkupInterface.
*/
namespace Drupal\Component\Render;
/**
* Marks an object's __toString() method as returning markup.
*
* Objects that implement this interface will not be automatically XSS filtered
* by the render system or automatically escaped by the theme engine.
*
* If there is any risk of the object's __toString() method returning
* user-entered data that has not been filtered first, it must not be used. If
* the object that implements this does not perform automatic escaping or
* filtering itself, then it must be marked as "@internal". For example, Views
* has the internal ViewsRenderPipelineMarkup object to provide a custom render
* pipeline in order to render JSON and to fast render fields. By contrast,
* FormattableMarkup and TranslatableMarkup always sanitize their output when
* used correctly.
*
* If the object is going to be used directly in Twig templates it should
* implement \Countable so it can be used in if statements.
*
* @see \Drupal\Component\Render\MarkupTrait
* @see \Drupal\Component\Utility\SafeMarkup::isSafe()
* @see \Drupal\Core\Template\TwigExtension::escapeFilter()
* @see \Drupal\Component\Render\FormattableMarkup
* @see \Drupal\Core\StringTranslation\TranslatableMarkup
* @see \Drupal\views\Render\ViewsRenderPipelineMarkup
* @see twig_render_template()
* @see sanitization
* @see theme_render
*/
interface MarkupInterface extends \JsonSerializable {
/**
* Returns markup.
*
* @return string
* The markup.
*/
public function __toString();
}

View file

@ -2,17 +2,19 @@
/**
* @file
* Contains \Drupal\Component\Utility\SafeStringTrait.
* Contains \Drupal\Component\Render\MarkupTrait.
*/
namespace Drupal\Component\Utility;
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Unicode;
/**
* Implements SafeStringInterface and Countable for rendered objects.
* Implements MarkupInterface and Countable for rendered objects.
*
* @see \Drupal\Component\Utility\SafeStringInterface
* @see \Drupal\Component\Render\MarkupInterface
*/
trait SafeStringTrait {
trait MarkupTrait {
/**
* The safe string.
@ -22,20 +24,20 @@ trait SafeStringTrait {
protected $string;
/**
* Creates a SafeString object if necessary.
* Creates a Markup object if necessary.
*
* If $string is equal to a blank string then it is not necessary to create a
* SafeString object. If $string is an object that implements
* SafeStringInterface it is returned unchanged.
* Markup object. If $string is an object that implements MarkupInterface it
* is returned unchanged.
*
* @param mixed $string
* The string to mark as safe. This value will be cast to a string.
*
* @return string|\Drupal\Component\Utility\SafeStringInterface
* @return string|\Drupal\Component\Render\MarkupInterface
* A safe string.
*/
public static function create($string) {
if ($string instanceof SafeStringInterface) {
if ($string instanceof MarkupInterface) {
return $string;
}
$string = (string) $string;
@ -48,7 +50,7 @@ trait SafeStringTrait {
}
/**
* Returns the string version of the SafeString object.
* Returns the string version of the Markup object.
*
* @return string
* The safe string content.

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Component\Render\OutputStrategyInterface.
*/
namespace Drupal\Component\Render;
/**
* Provides an output strategy that formats HTML strings for a given context.
*
* Output strategies assist in transforming HTML strings into strings that are
* appropriate for a given context (e.g. plain-text), through performing the
* relevant formatting. No santization is applied.
*/
interface OutputStrategyInterface {
/**
* Transforms a given HTML string into to a context-appropriate output string.
*
* This transformation consists of performing the formatting appropriate to
* a given output context (e.g., plain-text email subjects, HTML attribute
* values).
*
* @param string|object $string
* An HTML string or an object with a ::__toString() magic method returning
* HTML markup. The source HTML markup is considered ready for output into
* HTML fragments and thus already properly escaped and sanitized.
*
* @return string
* A new string that is formatted according to the output strategy.
*/
public static function renderFromHtml($string);
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Component\Render\PlainTextOutput.
*/
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Html;
/**
* Provides an output strategy for transforming HTML into simple plain text.
*
* Use this when rendering a given HTML string into a plain text string that
* does not need special formatting, such as a label or an email subject.
*
* Returns a string with HTML tags stripped and HTML entities decoded suitable
* for email or other non-HTML contexts.
*/
class PlainTextOutput implements OutputStrategyInterface {
/**
* {@inheritdoc}
*/
public static function renderFromHtml($string) {
return Html::decodeEntities(strip_tags((string) $string));
}
}

View file

@ -78,12 +78,24 @@ class Html {
public static function cleanCssIdentifier($identifier, array $filter = array(
' ' => '-',
'_' => '-',
'__' => '__',
'/' => '-',
'[' => '-',
']' => ''
']' => '',
)) {
$identifier = strtr($identifier, $filter);
// We could also use strtr() here but its much slower than str_replace(). In
// order to keep '__' to stay '__' we first replace it with a different
// placeholder after checking that it is not defined as a filter.
$double_underscore_replacements = 0;
if (!isset($filter['__'])) {
$identifier = str_replace('__', '##', $identifier, $double_underscore_replacements);
}
$identifier = str_replace(array_keys($filter), array_values($filter), $identifier);
// Replace temporary placeholder '##' with '__' only if the original
// $identifier contained '__'.
if ($double_underscore_replacements > 0) {
$identifier = str_replace('##', '__', $identifier);
}
// Valid characters in a CSS identifier are:
// - the hyphen (U+002D)
// - a-z (U+0030 - U+0039)
@ -250,9 +262,9 @@ class Html {
<body>!html</body>
</html>
EOD;
// PHP's \DOMDocument serialization adds straw whitespace in case the markup
// of the wrapping document contains newlines, so ensure to remove all
// newlines before injecting the actual HTML body to process.
// PHP's \DOMDocument serialization adds extra whitespace when the markup
// of the wrapping document contains newlines, so ensure we remove all
// newlines before injecting the actual HTML body to be processed.
$document = strtr($document, array("\n" => '', '!html' => $html));
$dom = new \DOMDocument();
@ -280,14 +292,16 @@ EOD;
$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;
}

View file

@ -7,23 +7,16 @@
namespace Drupal\Component\Utility;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Render\MarkupInterface;
/**
* Manages known safe strings for rendering at the theme layer.
* Contains deprecated functionality related to sanitization of markup.
*
* The Twig theme engine autoescapes string variables in the template, so it
* is possible for a string of markup to become double-escaped. SafeMarkup
* provides a store for known safe strings and methods to manage them
* throughout the page request.
*
* Strings sanitized by self::checkPlain() and self::escape() are automatically
* marked safe, as are markup strings created from @link theme_render render
* arrays @endlink via drupal_render().
*
* This class should be limited to internal use only. Module developers should
* instead use the appropriate
* @link sanitization sanitization functions @endlink or the
* @link theme_render theme and render systems @endlink so that the output can
* can be themed, escaped, and altered properly.
* @deprecated Will be removed before Drupal 9.0.0. Use the appropriate
* @link sanitization sanitization functions @endlink or the @link theme_render theme and render systems @endlink
* so that the output can can be themed, escaped, and altered properly.
*
* @see TwigExtension::escapeFilter()
* @see twig_render_template()
@ -32,100 +25,23 @@ namespace Drupal\Component\Utility;
*/
class SafeMarkup {
/**
* The list of safe strings.
*
* Strings in this list are marked as secure for the entire page render, not
* just the code or element that set it. Therefore, only valid HTML should be
* marked as safe (never partial markup). For example, you should never mark
* string such as '<' or '<script>' safe.
*
* @var array
*/
protected static $safeStrings = array();
/**
* Checks if a string is safe to output.
*
* @param string|\Drupal\Component\Utility\SafeStringInterface $string
* @param string|\Drupal\Component\Render\MarkupInterface $string
* The content to be checked.
* @param string $strategy
* The escaping strategy. Defaults to 'html'. Two escaping strategies are
* supported by default:
* - 'html': (default) The string is safe for use in HTML code.
* - 'all': The string is safe for all use cases.
* See the
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
* for more information on escaping strategies in Twig.
* (optional) This value is ignored.
*
* @return bool
* TRUE if the string has been marked secure, FALSE otherwise.
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
* Instead, you should just check if a variable is an instance of
* \Drupal\Component\Render\MarkupInterface.
*/
public static function isSafe($string, $strategy = 'html') {
// Do the instanceof checks first to save unnecessarily casting the object
// to a string.
return $string instanceOf SafeStringInterface || isset(static::$safeStrings[(string) $string][$strategy]) ||
isset(static::$safeStrings[(string) $string]['all']);
}
/**
* Adds previously retrieved known safe strings to the safe string list.
*
* This method is for internal use. Do not use it to prevent escaping of
* markup; instead, use the appropriate
* @link sanitization sanitization functions @endlink or the
* @link theme_render theme and render systems @endlink so that the output
* can be themed, escaped, and altered properly.
*
* This marks strings as secure for the entire page render, not just the code
* or element that set it. Therefore, only valid HTML should be
* marked as safe (never partial markup). For example, you should never do:
* @code
* SafeMarkup::setMultiple(['<' => ['html' => TRUE]]);
* @endcode
* or:
* @code
* SafeMarkup::setMultiple(['<script>' => ['all' => TRUE]]);
* @endcode
* @param array $safe_strings
* A list of safe strings as previously retrieved by self::getAll().
* Every string in this list will be represented by a multidimensional
* array in which the keys are the string and the escaping strategy used for
* this string, and in which the value is the boolean TRUE.
* See self::isSafe() for the list of supported escaping strategies.
*
* @throws \UnexpectedValueException
*
* @internal This is called by FormCache, StringTranslation and the Batch API.
* It should not be used anywhere else.
*/
public static function setMultiple(array $safe_strings) {
foreach ($safe_strings as $string => $strategies) {
foreach ($strategies as $strategy => $value) {
$string = (string) $string;
if ($value === TRUE) {
static::$safeStrings[$string][$strategy] = TRUE;
}
else {
// Danger - something is very wrong.
throw new \UnexpectedValueException('Only the value TRUE is accepted for safe strings');
}
}
}
}
/**
* Gets all strings currently marked as safe.
*
* This is useful for the batch and form APIs, where it is important to
* preserve the safe markup state across page requests.
*
* @return array
* An array of strings currently marked safe.
*/
public static function getAll() {
return static::$safeStrings;
return $string instanceOf MarkupInterface;
}
/**
@ -137,13 +53,10 @@ class SafeMarkup {
* @param string $text
* The text to be checked or processed.
*
* @return string
* An HTML safe version of $text, or an empty string if $text is not valid
* UTF-8.
* @return \Drupal\Component\Render\HtmlEscapedText
* An HtmlEscapedText object that escapes when rendered to string.
*
* @ingroup sanitization
*
* @deprecated Will be removed before Drupal 8.0.0. Rely on Twig's
* @deprecated Will be removed before Drupal 9.0.0. Rely on Twig's
* auto-escaping feature, or use the @link theme_render #plain_text @endlink
* key when constructing a render array that contains plain text in order to
* use the renderer's auto-escaping feature. If neither of these are
@ -153,91 +66,32 @@ class SafeMarkup {
* @see drupal_validate_utf8()
*/
public static function checkPlain($text) {
$string = Html::escape($text);
static::$safeStrings[$string]['html'] = TRUE;
return $string;
return new HtmlEscapedText($text);
}
/**
* Formats a string for HTML display by replacing variable placeholders.
*
* This function 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).
*
* 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 self::escape(). Use this as the
* default choice for anything displayed on a page on the site.
* - %variable: Escaped to HTML wrapped in <em> tags, which makes the
* following HTML code:
* @code
* <em class="placeholder">text output here.</em>
* @endcode
* - !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\Render\FormattableMarkup::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\Render\MarkupInterface
* The formatted string, which is an instance of MarkupInterface unless
* sanitization of an unsafe argument was suppressed (see above).
*
* @ingroup sanitization
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
* @see \Drupal\Component\Render\FormattableMarkup
*
* @see t()
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
* Use \Drupal\Component\Render\FormattableMarkup.
*/
public static function format($string, array $args) {
$safe = TRUE;
// Transform arguments before inserting them.
foreach ($args as $key => $value) {
switch ($key[0]) {
case '@':
// Escaped only.
if (!SafeMarkup::isSafe($value)) {
$args[$key] = Html::escape($value);
}
break;
case '%':
default:
// Escaped and placeholder.
if (!SafeMarkup::isSafe($value)) {
$value = Html::escape($value);
}
$args[$key] = '<em class="placeholder">' . $value . '</em>';
break;
case '!':
// Pass-through.
if (!static::isSafe($value)) {
$safe = FALSE;
}
}
}
$output = strtr($string, $args);
if ($safe) {
static::$safeStrings[$output]['html'] = TRUE;
}
return $output;
return new FormattableMarkup($string, $args);
}
}

View file

@ -1,45 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Component\Utility\SafeStringInterface.
*/
namespace Drupal\Component\Utility;
/**
* Marks an object's __toString() method as returning safe markup.
*
* All objects that implement this interface should be marked @internal.
*
* This interface should only be used on objects that emit known safe strings
* from their __toString() method. If there is any risk of the method returning
* user-entered data that has not been filtered first, it must not be used.
*
* If the object is going to be used directly in Twig templates it should
* implement \Countable so it can be used in if statements.
*
* @internal
* This interface is marked as internal because it should only be used by
* objects used during rendering. This interface should be used by modules if
* they interrupt the render pipeline and explicitly deal with SafeString
* objects created by the render system. Additionally, if a module reuses the
* regular render pipeline internally and passes processed data into it. For
* example, Views implements a custom render pipeline in order to render JSON
* and to fast render fields.
*
* @see \Drupal\Component\Utility\SafeStringTrait
* @see \Drupal\Component\Utility\SafeMarkup::isSafe()
* @see \Drupal\Core\Template\TwigExtension::escapeFilter()
*/
interface SafeStringInterface extends \JsonSerializable {
/**
* Returns a safe string.
*
* @return string
* The safe string.
*/
public function __toString();
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\Component\Utility\ToStringTrait.
*/
namespace Drupal\Component\Utility;
/**
* Wraps __toString in a trait to avoid some fatals.
*/
trait ToStringTrait {
/**
* Implements the magic __toString() method.
*/
public function __toString() {
try {
return (string) $this->render();
}
catch (\Exception $e) {
// User errors in __toString() methods are considered fatal in the Drupal
// error handler.
trigger_error(get_class($e) . ' thrown while calling __toString on a ' . get_class($this) . ' object in ' . $e->getFile() . ' on line ' . $e->getLine() . ': ' . $e->getMessage(), E_USER_ERROR);
// In case we are using another error handler that did not fatal on the
// E_USER_ERROR, we terminate execution. However, for test purposes allow
// a return value.
return $this->_die();
}
}
/**
* For test purposes, wrap die() in an overridable method.
*/
protected function _die() {
die();
}
/**
* Renders the object as a string.
*
* @return string|object
* The rendered string or an object implementing __toString().
*/
abstract public function render();
}

View file

@ -412,8 +412,8 @@ EOD;
// Find the starting byte offset.
$bytes = 0;
if ($start > 0) {
// Count all the continuation bytes from the start until we have found
// $start characters or the end of the string.
// Count all the characters except continuation bytes from the start
// until we have found $start characters or the end of the string.
$bytes = -1; $chars = -1;
while ($bytes < $strlen - 1 && $chars < $start) {
$bytes++;
@ -424,8 +424,8 @@ EOD;
}
}
elseif ($start < 0) {
// Count all the continuation bytes from the end until we have found
// abs($start) characters.
// Count all the characters except continuation bytes from the end
// until we have found abs($start) characters.
$start = abs($start);
$bytes = $strlen; $chars = 0;
while ($bytes > 0 && $chars < $start) {
@ -443,9 +443,9 @@ EOD;
$iend = $strlen;
}
elseif ($length > 0) {
// Count all the continuation bytes from the starting index until we have
// found $length characters or reached the end of the string, then
// backtrace one byte.
// Count all the characters except continuation bytes from the starting
// index until we have found $length characters or reached the end of
// the string, then backtrace one byte.
$iend = $istart - 1;
$chars = -1;
$last_real = FALSE;
@ -458,15 +458,15 @@ EOD;
$last_real = TRUE;
}
}
// Backtrace one byte if the last character we found was a real character
// and we don't need it.
// Backtrace one byte if the last character we found was a real
// character and we don't need it.
if ($last_real && $chars >= $length) {
$iend--;
}
}
elseif ($length < 0) {
// Count all the continuation bytes from the end until we have found
// abs($start) characters, then backtrace one byte.
// Count all the characters except continuation bytes from the end
// until we have found abs($start) characters, then backtrace one byte.
$length = abs($length);
$iend = $strlen; $chars = 0;
while ($iend > 0 && $chars < $length) {
@ -697,4 +697,33 @@ EOD;
return (preg_match('/^./us', $text) == 1);
}
/**
* Finds the position of the first occurrence of a string in another string.
*
* @param string $haystack
* The string to search in.
* @param string $needle
* The string to find in $haystack.
* @param int $offset
* If specified, start the search at this number of characters from the
* beginning (default 0).
*
* @return int|false
* The position where $needle occurs in $haystack, always relative to the
* beginning (independent of $offset), or FALSE if not found. Note that
* a return value of 0 is not the same as FALSE.
*/
public static function strpos($haystack, $needle, $offset = 0) {
if (static::getStatus() == static::STATUS_MULTIBYTE) {
return mb_strpos($haystack, $needle, $offset);
}
else {
// Remove Unicode continuation characters, to be compatible with
// Unicode::strlen() and Unicode::substr().
$haystack = preg_replace("/[\x80-\xBF]/", '', $haystack);
$needle = preg_replace("/[\x80-\xBF]/", '', $needle);
return strpos($haystack, $needle, $offset);
}
}
}

View file

@ -304,7 +304,22 @@ class UrlHelper {
* \Drupal\Component\Utility\Xss::filter(), but those functions return an
* HTML-encoded string, so this function can be called independently when the
* output needs to be a plain-text string for passing to functions that will
* call \Drupal\Component\Utility\SafeMarkup::checkPlain() separately.
* call Html::escape() separately. The exact behavior depends on the value:
* - If the value is a well-formed (per RFC 3986) relative URL or
* absolute URL that does not use a dangerous protocol (like
* "javascript:"), then the URL remains unchanged. This includes all
* URLs generated via Url::toString() and UrlGeneratorTrait::url().
* - If the value is a well-formed absolute URL with a dangerous protocol,
* the protocol is stripped. This process is repeated on the remaining URL
* until it is stripped down to a safe protocol.
* - If the value is not a well-formed URL, the same sanitization behavior as
* for well-formed URLs will be invoked, which strips most substrings that
* precede a ":". The result can be used in URL attributes such as "href"
* or "src" (only after calling Html::escape() separately), but this may not
* produce valid HTML (e.g., malformed URLs within "href" attributes fail
* HTML validation). This can be avoided by using
* Url::fromUri($possibly_not_a_url)->toString(), which either throws an
* exception or returns a well-formed URL.
*
* @param string $uri
* A plain-text URI that might contain dangerous protocols.
@ -314,6 +329,11 @@ class UrlHelper {
* strings, this return value must not be output to an HTML page without
* being sanitized first. However, it can be passed to functions
* expecting plain-text strings.
*
* @see \Drupal\Component\Utility\Html::escape()
* @see \Drupal\Core\Url::toString()
* @see \Drupal\Core\Routing\UrlGeneratorTrait::url()
* @see \Drupal\Core\Url::fromUri()
*/
public static function stripDangerousProtocols($uri) {
$allowed_protocols = array_flip(static::$allowedProtocols);

View file

@ -106,7 +106,7 @@ class Xss {
*
* Use only for fields where it is impractical to use the
* whole filter system, but where some (mainly inline) mark-up
* is desired (so \Drupal\Component\Utility\SafeMarkup::checkPlain() is
* is desired (so \Drupal\Component\Utility\Html::escape() is
* not acceptable).
*
* Allows all tags that can be used inside an HTML body, save