Update to drupal 8.0.0-rc1. For more information, see https://www.drupal.org/node/2582663
This commit is contained in:
parent
eb34d130a8
commit
f32e58e4b1
8476 changed files with 211648 additions and 170042 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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().
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
253
core/lib/Drupal/Component/Render/FormattableMarkup.php
Normal file
253
core/lib/Drupal/Component/Render/FormattableMarkup.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
61
core/lib/Drupal/Component/Render/HtmlEscapedText.php
Normal file
61
core/lib/Drupal/Component/Render/HtmlEscapedText.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
48
core/lib/Drupal/Component/Render/MarkupInterface.php
Normal file
48
core/lib/Drupal/Component/Render/MarkupInterface.php
Normal 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();
|
||||
|
||||
}
|
|
@ -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.
|
36
core/lib/Drupal/Component/Render/OutputStrategyInterface.php
Normal file
36
core/lib/Drupal/Component/Render/OutputStrategyInterface.php
Normal 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);
|
||||
|
||||
}
|
29
core/lib/Drupal/Component/Render/PlainTextOutput.php
Normal file
29
core/lib/Drupal/Component/Render/PlainTextOutput.php
Normal 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
48
core/lib/Drupal/Component/Utility/ToStringTrait.php
Normal file
48
core/lib/Drupal/Component/Utility/ToStringTrait.php
Normal 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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
52
core/lib/Drupal/Core/Ajax/BaseCommand.php
Normal file
52
core/lib/Drupal/Core/Ajax/BaseCommand.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\BaseCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Base command that only exists to simplify AJAX commands.
|
||||
*/
|
||||
class BaseCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* The name of the command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $command;
|
||||
|
||||
/**
|
||||
* The data to pass on to the client side.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Constructs a BaseCommand object.
|
||||
*
|
||||
* @param string $command
|
||||
* The name of the command.
|
||||
* @param string $data
|
||||
* The data to pass on to the client side.
|
||||
*/
|
||||
public function __construct($command, $data) {
|
||||
$this->command = $command;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return array(
|
||||
'command' => $this->command,
|
||||
'data' => $this->data,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -29,7 +29,7 @@ trait CommandWithAttachedAssetsTrait {
|
|||
* If content is a render array, it may contain attached assets to be
|
||||
* processed.
|
||||
*
|
||||
* @return string|\Drupal\Component\Utility\SafeStringInterface
|
||||
* @return string|\Drupal\Component\Render\MarkupInterface
|
||||
* HTML rendered content.
|
||||
*/
|
||||
protected function getRenderedContent() {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* @defgroup plugin_context Annotation for context definition
|
||||
|
@ -113,7 +113,7 @@ class ContextDefinition extends Plugin {
|
|||
// used in the classes they pass to.
|
||||
foreach (['label', 'description'] as $key) {
|
||||
// @todo Remove this workaround in https://www.drupal.org/node/2362727.
|
||||
if (isset($values[$key]) && $values[$key] instanceof TranslationWrapper) {
|
||||
if (isset($values[$key]) && $values[$key] instanceof TranslatableMarkup) {
|
||||
$values[$key] = (string) $values[$key]->get();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationBase;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* @defgroup plugin_translatable Annotation for translatable text
|
||||
|
@ -60,7 +60,7 @@ class Translation extends AnnotationBase {
|
|||
/**
|
||||
* The string translation object.
|
||||
*
|
||||
* @var \Drupal\Core\StringTranslation\TranslationWrapper
|
||||
* @var \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
*/
|
||||
protected $translation;
|
||||
|
||||
|
@ -86,7 +86,7 @@ class Translation extends AnnotationBase {
|
|||
'context' => $values['context'],
|
||||
);
|
||||
}
|
||||
$this->translation = new TranslationWrapper($string, $arguments, $options);
|
||||
$this->translation = new TranslatableMarkup($string, $arguments, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -166,6 +166,7 @@ class AssetResolver implements AssetResolverInterface {
|
|||
uasort($css, 'static::sort');
|
||||
|
||||
// Allow themes to remove CSS files by CSS files full path and file name.
|
||||
// @todo Remove in Drupal 9.0.x.
|
||||
if ($stylesheet_remove = $theme_info->getStyleSheetsRemove()) {
|
||||
foreach ($css as $key => $options) {
|
||||
if (isset($stylesheet_remove[$key])) {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\Exception\InvalidLibrariesExtendSpecificationException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Defines a custom exception for an invalid libraries-extend specification.
|
||||
*/
|
||||
class InvalidLibrariesExtendSpecificationException extends \RuntimeException {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\Exception\InvalidLibrariesOverrideSpecificationException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Defines a custom exception if a definition refers to a non-existent library.
|
||||
*/
|
||||
class InvalidLibrariesOverrideSpecificationException extends \RuntimeException {
|
||||
|
||||
}
|
|
@ -9,8 +9,6 @@ namespace Drupal\Core\Asset;
|
|||
|
||||
use Drupal\Core\Cache\CacheCollectorInterface;
|
||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
|
||||
/**
|
||||
* Discovers available asset libraries in Drupal.
|
||||
|
@ -87,6 +85,8 @@ class LibraryDiscovery implements LibraryDiscoveryInterface {
|
|||
*/
|
||||
public function clearCachedDefinitions() {
|
||||
$this->cacheTagInvalidator->invalidateTags(['library_info']);
|
||||
$this->libraryDefinitions = [];
|
||||
$this->collector->clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Asset\Exception\InvalidLibrariesExtendSpecificationException;
|
||||
use Drupal\Core\Asset\Exception\InvalidLibrariesOverrideSpecificationException;
|
||||
use Drupal\Core\Cache\CacheCollector;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
|
@ -79,9 +82,94 @@ class LibraryDiscoveryCollector extends CacheCollector {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
protected function resolveCacheMiss($key) {
|
||||
$this->storage[$key] = $this->discoveryParser->buildByExtension($key);
|
||||
$this->storage[$key] = $this->getLibraryDefinitions($key);
|
||||
$this->persist($key);
|
||||
|
||||
return $this->storage[$key];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the library definitions for a given extension.
|
||||
*
|
||||
* This also implements libraries-overrides for entire libraries that have
|
||||
* been specified by the LibraryDiscoveryParser.
|
||||
*
|
||||
* @param string $extension
|
||||
* The name of the extension for which library definitions will be returned.
|
||||
*
|
||||
* @return array
|
||||
* The library definitions for $extension with overrides applied.
|
||||
*
|
||||
* @throws \Drupal\Core\Asset\Exception\InvalidLibrariesOverrideSpecificationException
|
||||
*/
|
||||
protected function getLibraryDefinitions($extension) {
|
||||
$libraries = $this->discoveryParser->buildByExtension($extension);
|
||||
foreach ($libraries as $name => $definition) {
|
||||
// Handle libraries that are marked for override or removal.
|
||||
// @see \Drupal\Core\Asset\LibraryDiscoveryParser::applyLibrariesOverride()
|
||||
if (isset($definition['override'])) {
|
||||
if ($definition['override'] === FALSE) {
|
||||
// Remove the library definition if FALSE is given.
|
||||
unset($libraries[$name]);
|
||||
}
|
||||
else {
|
||||
// Otherwise replace with existing library definition if it exists.
|
||||
// Throw an exception if it doesn't.
|
||||
list($replacement_extension, $replacement_name) = explode('/', $definition['override']);
|
||||
$replacement_definition = $this->get($replacement_extension);
|
||||
if (isset($replacement_definition[$replacement_name])) {
|
||||
$libraries[$name] = $replacement_definition[$replacement_name];
|
||||
}
|
||||
else {
|
||||
throw new InvalidLibrariesOverrideSpecificationException(sprintf('The specified library %s does not exist.', $definition['override']));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If libraries are not overridden, then apply libraries-extend.
|
||||
$libraries[$name] = $this->applyLibrariesExtend($extension, $name, $definition);
|
||||
}
|
||||
}
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the libraries-extend specified by the active theme.
|
||||
*
|
||||
* This extends the library definitions with the those specified by the
|
||||
* libraries-extend specifications for the active theme.
|
||||
*
|
||||
* @param string $extension
|
||||
* The name of the extension for which library definitions will be extended.
|
||||
* @param string $library_name
|
||||
* The name of the library whose definitions is to be extended.
|
||||
* @param $library_definition
|
||||
* The library definition to be extended.
|
||||
*
|
||||
* @return array
|
||||
* The library definition extended as specified by libraries-extend.
|
||||
*
|
||||
* @throws \Drupal\Core\Asset\Exception\InvalidLibrariesExtendSpecificationException
|
||||
*/
|
||||
protected function applyLibrariesExtend($extension, $library_name, $library_definition) {
|
||||
$libraries_extend = $this->themeManager->getActiveTheme()->getLibrariesExtend();
|
||||
if (!empty($libraries_extend["$extension/$library_name"])) {
|
||||
foreach ($libraries_extend["$extension/$library_name"] as $library_extend_name) {
|
||||
if (!is_string($library_extend_name)) {
|
||||
// Only string library names are allowed.
|
||||
throw new InvalidLibrariesExtendSpecificationException('The libraries-extend specification for each library must be a list of strings.');
|
||||
}
|
||||
list($new_extension, $new_library_name) = explode('/', $library_extend_name, 2);
|
||||
$new_libraries = $this->get($new_extension);
|
||||
if (isset($new_libraries[$new_library_name])) {
|
||||
$library_definition = NestedArray::mergeDeep($library_definition, $new_libraries[$new_library_name]);
|
||||
}
|
||||
else {
|
||||
throw new InvalidLibrariesExtendSpecificationException(sprintf('The specified library "%s" does not exist.', $library_extend_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $library_definition;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException;
|
||||
use Drupal\Core\Asset\Exception\InvalidLibrariesOverrideSpecificationException;
|
||||
use Drupal\Core\Asset\Exception\InvalidLibraryFileException;
|
||||
use Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
|
||||
|
@ -88,6 +90,7 @@ class LibraryDiscoveryParser {
|
|||
}
|
||||
|
||||
$libraries = $this->parseLibraryInfo($extension, $path);
|
||||
$libraries = $this->applyLibrariesOverride($libraries, $extension);
|
||||
|
||||
foreach ($libraries as $id => &$library) {
|
||||
if (!isset($library['js']) && !isset($library['css']) && !isset($library['drupalSettings'])) {
|
||||
|
@ -185,6 +188,13 @@ class LibraryDiscoveryParser {
|
|||
elseif ($this->fileValidUri($source)) {
|
||||
$options['data'] = $source;
|
||||
}
|
||||
// A regular URI (e.g., http://example.com/example.js) without
|
||||
// 'external' explicitly specified, which may happen if, e.g.
|
||||
// libraries-override is used.
|
||||
elseif ($this->isValidUri($source)) {
|
||||
$options['type'] = 'external';
|
||||
$options['data'] = $source;
|
||||
}
|
||||
// By default, file paths are relative to the registering extension.
|
||||
else {
|
||||
$options['data'] = $path . '/' . $source;
|
||||
|
@ -313,6 +323,70 @@ class LibraryDiscoveryParser {
|
|||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply libraries overrides specified for the current active theme.
|
||||
*
|
||||
* @param array $libraries
|
||||
* The libraries definitions.
|
||||
* @param string $extension
|
||||
* The extension in which these libraries are defined.
|
||||
*
|
||||
* @return array
|
||||
* The modified libraries definitions.
|
||||
*/
|
||||
protected function applyLibrariesOverride($libraries, $extension) {
|
||||
$active_theme = $this->themeManager->getActiveTheme();
|
||||
// ActiveTheme::getLibrariesOverride() returns libraries-overrides for the
|
||||
// current theme as well as all its base themes.
|
||||
$all_libraries_overrides = $active_theme->getLibrariesOverride();
|
||||
foreach ($all_libraries_overrides as $theme_path => $libraries_overrides) {
|
||||
foreach ($libraries as $library_name => $library) {
|
||||
// Process libraries overrides.
|
||||
if (isset($libraries_overrides["$extension/$library_name"])) {
|
||||
// Active theme defines an override for this library.
|
||||
$override_definition = $libraries_overrides["$extension/$library_name"];
|
||||
if (is_string($override_definition) || $override_definition === FALSE) {
|
||||
// A string or boolean definition implies an override (or removal)
|
||||
// for the whole library. Use the override key to specify that this
|
||||
// library will be overridden when it is called.
|
||||
// @see \Drupal\Core\Asset\LibraryDiscovery::getLibraryByName()
|
||||
if ($override_definition) {
|
||||
$libraries[$library_name]['override'] = $override_definition;
|
||||
}
|
||||
else {
|
||||
$libraries[$library_name]['override'] = FALSE;
|
||||
}
|
||||
}
|
||||
elseif (is_array($override_definition)) {
|
||||
// An array definition implies an override for an asset within this
|
||||
// library.
|
||||
foreach ($override_definition as $sub_key => $value) {
|
||||
// Throw an exception if the asset is not properly specified.
|
||||
if (!is_array($value)) {
|
||||
throw new InvalidLibrariesOverrideSpecificationException(sprintf('Library asset %s is not correctly specified. It should be in the form "extension/library_name/sub_key/path/to/asset.js".', "$extension/$library_name/$sub_key"));
|
||||
}
|
||||
if ($sub_key === 'drupalSettings') {
|
||||
// drupalSettings may not be overridden.
|
||||
throw new InvalidLibrariesOverrideSpecificationException(sprintf('drupalSettings may not be overridden in libraries-override. Trying to override %s. Use hook_library_info_alter() instead.', "$extension/$library_name/$sub_key"));
|
||||
}
|
||||
elseif ($sub_key === 'css') {
|
||||
// SMACSS category should be incorporated into the asset name.
|
||||
foreach ($value as $category => $overrides) {
|
||||
$this->setOverrideValue($libraries[$library_name], [$sub_key, $category], $overrides, $theme_path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->setOverrideValue($libraries[$library_name], [$sub_key], $value, $theme_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps drupal_get_path().
|
||||
*/
|
||||
|
@ -327,4 +401,67 @@ class LibraryDiscoveryParser {
|
|||
return file_valid_uri($source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the supplied string is a valid URI.
|
||||
*/
|
||||
protected function isValidUri($string) {
|
||||
return count(explode('://', $string)) === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the specified library asset.
|
||||
*
|
||||
* @param array $library
|
||||
* The containing library definition.
|
||||
* @param array $sub_key
|
||||
* An array containing the sub-keys specifying the library asset, e.g.
|
||||
* @code['js']@endcode or @code['css', 'component']@endcode
|
||||
* @param array $overrides
|
||||
* Specifies the overrides, this is an array where the key is the asset to
|
||||
* be overridden while the value is overriding asset.
|
||||
*/
|
||||
protected function setOverrideValue(array &$library, array $sub_key, array $overrides, $theme_path) {
|
||||
foreach ($overrides as $original => $replacement) {
|
||||
// Get the attributes of the asset to be overridden. If the key does
|
||||
// not exist, then throw an exception.
|
||||
$key_exists = NULL;
|
||||
$parents = array_merge($sub_key, [$original]);
|
||||
// Save the attributes of the library asset to be overridden.
|
||||
$attributes = NestedArray::getValue($library, $parents, $key_exists);
|
||||
if ($key_exists) {
|
||||
// Remove asset to be overridden.
|
||||
NestedArray::unsetValue($library, $parents);
|
||||
// No need to replace if FALSE is specified, since that is a removal.
|
||||
if ($replacement) {
|
||||
// Ensure the replacement path is relative to drupal root.
|
||||
$replacement = $this->resolveThemeAssetPath($theme_path, $replacement);
|
||||
$new_parents = array_merge($sub_key, [$replacement]);
|
||||
// Replace with an override if specified.
|
||||
NestedArray::setValue($library, $new_parents, $attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that a full path is returned for an overriding theme asset.
|
||||
*
|
||||
* @param string $theme_path
|
||||
* The theme or base theme.
|
||||
* @param string $overriding_asset
|
||||
* The overriding library asset.
|
||||
*
|
||||
* @return string
|
||||
* A fully resolved theme asset path relative to the Drupal directory.
|
||||
*/
|
||||
protected function resolveThemeAssetPath($theme_path, $overriding_asset) {
|
||||
if ($overriding_asset[0] !== '/' && !$this->isValidUri($overriding_asset)) {
|
||||
// The destination is not an absolute path and it's not a URI (e.g.
|
||||
// public://generated_js/example.js or http://example.com/js/my_js.js), so
|
||||
// it's relative to the theme.
|
||||
return '/' . $theme_path . '/' . $overriding_asset;
|
||||
}
|
||||
return $overriding_asset;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,13 +9,12 @@ namespace Drupal\Core\Block;
|
|||
|
||||
use Drupal\block\BlockInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginBase;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Component\Transliteration\TransliterationInterface;
|
||||
|
||||
|
@ -30,6 +29,8 @@ use Drupal\Component\Transliteration\TransliterationInterface;
|
|||
*/
|
||||
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface {
|
||||
|
||||
use ContextAwarePluginAssignmentTrait;
|
||||
|
||||
/**
|
||||
* The transliteration service.
|
||||
*
|
||||
|
@ -47,7 +48,7 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
|
|||
|
||||
$definition = $this->getPluginDefinition();
|
||||
// Cast the admin label to a string since it is an object.
|
||||
// @see \Drupal\Core\StringTranslation\TranslationWrapper
|
||||
// @see \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
return (string) $definition['admin_label'];
|
||||
}
|
||||
|
||||
|
@ -89,10 +90,6 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
|
|||
'label' => '',
|
||||
'provider' => $this->pluginDefinition['provider'],
|
||||
'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE,
|
||||
'cache' => array(
|
||||
// Blocks are cacheable by default.
|
||||
'max_age' => Cache::PERMANENT,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -180,24 +177,10 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
|
|||
'#default_value' => ($this->configuration['label_display'] === BlockInterface::BLOCK_LABEL_VISIBLE),
|
||||
'#return_value' => BlockInterface::BLOCK_LABEL_VISIBLE,
|
||||
);
|
||||
// Identical options to the ones for page caching.
|
||||
// @see \Drupal\system\Form\PerformanceForm::buildForm()
|
||||
$period = array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400);
|
||||
$period = array_map(array(\Drupal::service('date.formatter'), 'formatInterval'), array_combine($period, $period));
|
||||
$period[0] = '<' . $this->t('no caching') . '>';
|
||||
$period[\Drupal\Core\Cache\Cache::PERMANENT] = $this->t('Forever');
|
||||
$form['cache'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Cache settings'),
|
||||
);
|
||||
$form['cache']['max_age'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Maximum age'),
|
||||
'#description' => $this->t('The maximum time this block may be cached.'),
|
||||
'#default_value' => $this->configuration['cache']['max_age'],
|
||||
'#options' => $period,
|
||||
);
|
||||
|
||||
// Add context mapping UI form elements.
|
||||
$contexts = $form_state->getTemporaryValue('gathered_contexts') ?: [];
|
||||
$form['context_mapping'] = $this->addContextAssignmentElement($this, $contexts);
|
||||
// Add plugin-specific settings for this block type.
|
||||
$form += $this->blockForm($form, $form_state);
|
||||
return $form;
|
||||
|
@ -244,7 +227,6 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
|
|||
$this->configuration['label'] = $form_state->getValue('label');
|
||||
$this->configuration['label_display'] = $form_state->getValue('label_display');
|
||||
$this->configuration['provider'] = $form_state->getValue('provider');
|
||||
$this->configuration['cache'] = $form_state->getValue('cache');
|
||||
$this->blockSubmit($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
@ -272,16 +254,6 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
|
|||
return $transliterated;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
$max_age = parent::getCacheMaxAge();
|
||||
// @todo Configurability of this will be removed in
|
||||
// https://www.drupal.org/node/2458763.
|
||||
return Cache::mergeMaxAges($max_age, (int) $this->configuration['cache']['max_age']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the transliteration service.
|
||||
*
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Drupal\Core\Block;
|
|||
/**
|
||||
* The interface for "main page content" blocks.
|
||||
*
|
||||
* A main page content block represents the content returns by the controller.
|
||||
* A main page content block represents the content returned by the controller.
|
||||
*
|
||||
* @ingroup block_api
|
||||
*/
|
||||
|
|
55
core/lib/Drupal/Core/Block/Plugin/Block/PageTitleBlock.php
Normal file
55
core/lib/Drupal/Core/Block/Plugin/Block/PageTitleBlock.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Plugin\Block\PageTitleBlock.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Block\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Block\TitleBlockPluginInterface;
|
||||
|
||||
/**
|
||||
* Provides a block to display the page title.
|
||||
*
|
||||
* @Block(
|
||||
* id = "page_title_block",
|
||||
* admin_label = @Translation("Page title"),
|
||||
* )
|
||||
*/
|
||||
class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface {
|
||||
|
||||
/**
|
||||
* The page title: a string (plain title) or a render array (formatted title).
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return ['label_display' => FALSE];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
return [
|
||||
'#type' => 'page_title',
|
||||
'#title' => $this->title,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
30
core/lib/Drupal/Core/Block/TitleBlockPluginInterface.php
Normal file
30
core/lib/Drupal/Core/Block/TitleBlockPluginInterface.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Block\TitleBlockPluginInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Block;
|
||||
|
||||
/**
|
||||
* The interface for "title" blocks.
|
||||
*
|
||||
* A title block shows the title returned by the controller.
|
||||
*
|
||||
* @ingroup block_api
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\PageTitle
|
||||
*/
|
||||
interface TitleBlockPluginInterface extends BlockPluginInterface {
|
||||
|
||||
/**
|
||||
* Sets the title.
|
||||
*
|
||||
* @param string|array $title
|
||||
* The page title: either a string for plain titles or a render array for
|
||||
* formatted titles.
|
||||
*/
|
||||
public function setTitle($title);
|
||||
|
||||
}
|
|
@ -7,15 +7,17 @@
|
|||
|
||||
namespace Drupal\Core\Breadcrumb;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Render\RenderableInterface;
|
||||
|
||||
/**
|
||||
* Used to return generated breadcrumbs with associated cacheability metadata.
|
||||
*
|
||||
* @todo implement RenderableInterface once https://www.drupal.org/node/2529560 lands.
|
||||
*/
|
||||
class Breadcrumb extends CacheableMetadata {
|
||||
class Breadcrumb implements RenderableInterface, RefinableCacheableDependencyInterface {
|
||||
|
||||
use RefinableCacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* An ordered list of links for the breadcrumb.
|
||||
|
@ -68,4 +70,24 @@ class Breadcrumb extends CacheableMetadata {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toRenderable() {
|
||||
$build = [
|
||||
'#cache' => [
|
||||
'contexts' => $this->cacheContexts,
|
||||
'tags' => $this->cacheTags,
|
||||
'max-age' => $this->cacheMaxAge,
|
||||
],
|
||||
];
|
||||
if (!empty($this->links)) {
|
||||
$build += [
|
||||
'#theme' => 'breadcrumb',
|
||||
'#links' => $this->links,
|
||||
];
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ class ApcuBackend implements CacheBackendInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {
|
||||
Cache::validateTags($tags);
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
|
||||
$tags = array_unique($tags);
|
||||
$cache = new \stdClass();
|
||||
$cache->cid = $cid;
|
||||
|
|
|
@ -34,7 +34,7 @@ class Cache {
|
|||
*/
|
||||
public static function mergeContexts(array $a = [], array $b = []) {
|
||||
$cache_contexts = array_unique(array_merge($a, $b));
|
||||
\Drupal::service('cache_contexts_manager')->validateTokens($cache_contexts);
|
||||
assert('\Drupal::service(\'cache_contexts_manager\')->assertValidTokens($cache_contexts)');
|
||||
sort($cache_contexts);
|
||||
return $cache_contexts;
|
||||
}
|
||||
|
@ -59,8 +59,9 @@ class Cache {
|
|||
* The merged array of cache tags.
|
||||
*/
|
||||
public static function mergeTags(array $a = [], array $b = []) {
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($a) && \Drupal\Component\Assertion\Inspector::assertAllStrings($b)', 'Cache tags must be valid strings');
|
||||
|
||||
$cache_tags = array_unique(array_merge($a, $b));
|
||||
static::validateTags($cache_tags);
|
||||
sort($cache_tags);
|
||||
return $cache_tags;
|
||||
}
|
||||
|
@ -99,6 +100,9 @@ class Cache {
|
|||
* @param string[] $tags
|
||||
* An array of cache tags.
|
||||
*
|
||||
* @deprecated
|
||||
* Use assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)');
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public static function validateTags(array $tags) {
|
||||
|
|
|
@ -115,7 +115,7 @@ abstract class CacheCollector implements CacheCollectorInterface, DestructableIn
|
|||
* (optional) The tags to specify for the cache item.
|
||||
*/
|
||||
public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, array $tags = array()) {
|
||||
Cache::validateTags($tags);
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
|
||||
$this->cid = $cid;
|
||||
$this->cache = $cache;
|
||||
$this->tags = $tags;
|
||||
|
|
|
@ -27,8 +27,7 @@ class CacheTagsInvalidator implements CacheTagsInvalidatorInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function invalidateTags(array $tags) {
|
||||
// Validate the tags.
|
||||
Cache::validateTags($tags);
|
||||
assert('Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
|
||||
|
||||
// Notify all added cache tags invalidators.
|
||||
foreach ($this->invalidators as $invalidator) {
|
||||
|
|
|
@ -103,11 +103,9 @@ class CacheContextsManager {
|
|||
* The ContextCacheKeys object containing the converted cache keys and
|
||||
* cacheability metadata.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* Thrown if any of the context tokens or parameters are not valid.
|
||||
*/
|
||||
public function convertTokensToKeys(array $context_tokens) {
|
||||
$this->validateTokens($context_tokens);
|
||||
assert('$this->assertValidTokens($context_tokens)');
|
||||
$cacheable_metadata = new CacheableMetadata();
|
||||
$optimized_tokens = $this->optimizeTokens($context_tokens);
|
||||
// Iterate over cache contexts that have been optimized away and get their
|
||||
|
@ -299,4 +297,33 @@ class CacheContextsManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the context tokens are valid
|
||||
*
|
||||
* Similar to ::validateTokens, this method returns boolean TRUE when the
|
||||
* context tokens are valid, and FALSE when they are not instead of returning
|
||||
* NULL when they are valid and throwing a \LogicException when they are not.
|
||||
* This function should be used with the assert() statement.
|
||||
*
|
||||
* @param mixed $context_tokens
|
||||
* Variable to be examined - should be array of context_tokens.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if context_tokens is an array of valid tokens.
|
||||
*/
|
||||
public function assertValidTokens($context_tokens) {
|
||||
if (!is_array($context_tokens)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validateTokens($context_tokens);
|
||||
}
|
||||
catch (\LogicException $e) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -199,7 +199,7 @@ class DatabaseBackend implements CacheBackendInterface {
|
|||
'tags' => array(),
|
||||
);
|
||||
|
||||
Cache::validateTags($item['tags']);
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($item[\'tags\'])', 'Cache Tags must be strings.');
|
||||
$item['tags'] = array_unique($item['tags']);
|
||||
// Sort the cache tags so that they are stored consistently in the DB.
|
||||
sort($item['tags']);
|
||||
|
|
|
@ -107,7 +107,7 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf
|
|||
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
|
||||
*/
|
||||
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
|
||||
Cache::validateTags($tags);
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
|
||||
$tags = array_unique($tags);
|
||||
// Sort the cache tags so that they are stored consistently in the database.
|
||||
sort($tags);
|
||||
|
|
|
@ -148,7 +148,7 @@ class PhpBackend implements CacheBackendInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
|
||||
Cache::validateTags($tags);
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
|
||||
$item = (object) array(
|
||||
'cid' => $cid,
|
||||
'data' => $data,
|
||||
|
|
|
@ -33,6 +33,27 @@ trait RefinableCacheableDependencyTrait {
|
|||
*/
|
||||
protected $cacheMaxAge = Cache::PERMANENT;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->cacheTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return $this->cacheContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->cacheMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
65
core/lib/Drupal/Core/Command/DbCommandBase.php
Normal file
65
core/lib/Drupal/Core/Command/DbCommandBase.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Command\DbCommandBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Command;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/**
|
||||
* Base command that abstracts handling of database connection arguments.
|
||||
*/
|
||||
class DbCommandBase extends Command {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure() {
|
||||
$this->addOption('database', NULL, InputOption::VALUE_OPTIONAL, 'The database connection name to use.', 'default')
|
||||
->addOption('database-url', 'db-url', InputOption::VALUE_OPTIONAL, 'A database url to parse and use as the database connection.')
|
||||
->addOption('prefix', NULL, InputOption::VALUE_OPTIONAL, 'Override or set the table prefix used in the database connection.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse input options decide on a database.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input
|
||||
* Input object.
|
||||
* @return \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected function getDatabaseConnection(InputInterface $input) {
|
||||
// Load connection from a url.
|
||||
if ($input->getOption('database-url')) {
|
||||
// @todo this could probably be refactored to not use a global connection.
|
||||
// Ensure database connection isn't set.
|
||||
if (Database::getConnectionInfo('db-tools')) {
|
||||
throw new \RuntimeException('Database "db-tools" is already defined. Cannot define database provided.');
|
||||
}
|
||||
$info = Database::convertDbUrlToConnectionInfo($input->getOption('database-url'), \Drupal::root());
|
||||
Database::addConnectionInfo('db-tools', 'default', $info);
|
||||
$key = 'db-tools';
|
||||
}
|
||||
else {
|
||||
$key = $input->getOption('database');
|
||||
}
|
||||
|
||||
// If they supplied a prefix, replace it in the connection information.
|
||||
$prefix = $input->getOption('prefix');
|
||||
if ($prefix) {
|
||||
$info = Database::getConnectionInfo($key)['default'];
|
||||
$info['prefix']['default'] = $prefix;
|
||||
|
||||
Database::removeConnection($key);
|
||||
Database::addConnectionInfo($key, 'default', $info);
|
||||
}
|
||||
|
||||
return Database::getConnection('default', $key);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Command;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
|
@ -17,34 +15,6 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
*/
|
||||
class DbDumpApplication extends Application {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Construct the application.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
function __construct(Connection $connection, ModuleHandlerInterface $module_handler) {
|
||||
$this->connection = $connection;
|
||||
$this->moduleHandler = $module_handler;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -58,7 +28,7 @@ class DbDumpApplication extends Application {
|
|||
protected function getDefaultCommands() {
|
||||
// Even though this is a single command, keep the HelpCommand (--help).
|
||||
$default_commands = parent::getDefaultCommands();
|
||||
$default_commands[] = new DbDumpCommand($this->connection, $this->moduleHandler);
|
||||
$default_commands[] = new DbDumpCommand();
|
||||
return $default_commands;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,8 @@ namespace Drupal\Core\Command;
|
|||
|
||||
use Drupal\Component\Utility\Variable;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
|
@ -30,21 +29,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*
|
||||
* @see \Drupal\Core\Command\DbDumpApplication
|
||||
*/
|
||||
class DbDumpCommand extends Command {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection $connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
class DbDumpCommand extends DbCommandBase {
|
||||
|
||||
/**
|
||||
* An array of table patterns to exclude completely.
|
||||
|
@ -55,81 +40,78 @@ class DbDumpCommand extends Command {
|
|||
*/
|
||||
protected $excludeTables = ['simpletest.+'];
|
||||
|
||||
/**
|
||||
* Table patterns for which to only dump the schema, no data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $schemaOnly = ['cache.*', 'sessions', 'watchdog'];
|
||||
|
||||
/**
|
||||
* Construct the database dump command.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to use.
|
||||
*/
|
||||
function __construct(Connection $connection, ModuleHandlerInterface $module_handler) {
|
||||
// Check this is MySQL.
|
||||
if ($connection->databaseType() !== 'mysql') {
|
||||
throw new \RuntimeException('This script can only be used with MySQL database backends.');
|
||||
}
|
||||
|
||||
$this->connection = $connection;
|
||||
$this->moduleHandler = $module_handler;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure() {
|
||||
$this->setName('dump-database-d8-mysql')
|
||||
->setDescription('Dump the current database to a generation script');
|
||||
->setDescription('Dump the current database to a generation script')
|
||||
->addOption('schema-only', NULL, InputOption::VALUE_OPTIONAL, 'A comma separated list of tables to only export the schema without data.', 'cache.*,sessions,watchdog');
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$connection = $this->getDatabaseConnection($input);
|
||||
|
||||
// If not explicitly set, disable ANSI which will break generated php.
|
||||
if ($input->hasParameterOption(['--ansi']) !== TRUE) {
|
||||
$output->setDecorated(FALSE);
|
||||
}
|
||||
|
||||
$output->writeln($this->generateScript());
|
||||
$schema_tables = $input->getOption('schema-only');
|
||||
$schema_tables = explode(',', $schema_tables);
|
||||
|
||||
$output->writeln($this->generateScript($connection, $schema_tables), OutputInterface::OUTPUT_RAW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the database script.
|
||||
*
|
||||
* @return string
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param array $schema_only
|
||||
* Table patterns for which to only dump the schema, no data.
|
||||
* @return string The PHP script.
|
||||
* The PHP script.
|
||||
*/
|
||||
protected function generateScript() {
|
||||
protected function generateScript(Connection $connection, array $schema_only = []) {
|
||||
$tables = '';
|
||||
foreach ($this->getTables() as $table) {
|
||||
$schema = $this->getTableSchema($table);
|
||||
$data = $this->getTableData($table);
|
||||
|
||||
$schema_only_patterns = [];
|
||||
foreach ($schema_only as $match) {
|
||||
$schema_only_patterns[] = '/^' . $match . '$/';
|
||||
}
|
||||
|
||||
foreach ($this->getTables($connection) as $table) {
|
||||
$schema = $this->getTableSchema($connection, $table);
|
||||
// Check for schema only.
|
||||
if (empty($schema_only_patterns) || preg_replace($schema_only_patterns, '', $table)) {
|
||||
$data = $this->getTableData($connection, $table);
|
||||
}
|
||||
else {
|
||||
$data = [];
|
||||
}
|
||||
$tables .= $this->getTableScript($table, $schema, $data);
|
||||
}
|
||||
$script = $this->getTemplate();
|
||||
// Substitute in the tables.
|
||||
$script = str_replace('{{TABLES}}', trim($tables), $script);
|
||||
// Modules.
|
||||
$script = str_replace('{{MODULES}}', $this->getModulesScript(), $script);
|
||||
return trim($script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tables, not including those set to be excluded.
|
||||
*
|
||||
* @return array
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @return array An array of table names.
|
||||
* An array of table names.
|
||||
*/
|
||||
protected function getTables() {
|
||||
$tables = array_values($this->connection->schema()->findTables('%'));
|
||||
protected function getTables(Connection $connection) {
|
||||
$tables = array_values($connection->schema()->findTables('%'));
|
||||
|
||||
foreach ($tables as $key => $table) {
|
||||
// Remove any explicitly excluded tables.
|
||||
|
@ -146,6 +128,8 @@ class DbDumpCommand extends Command {
|
|||
/**
|
||||
* Returns a schema array for a given table.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param string $table
|
||||
* The table name.
|
||||
*
|
||||
|
@ -154,14 +138,19 @@ class DbDumpCommand extends Command {
|
|||
*
|
||||
* @todo This implementation is hard-coded for MySQL.
|
||||
*/
|
||||
protected function getTableSchema($table) {
|
||||
$query = $this->connection->query("SHOW FULL COLUMNS FROM {" . $table . "}");
|
||||
protected function getTableSchema(Connection $connection, $table) {
|
||||
// Check this is MySQL.
|
||||
if ($connection->databaseType() !== 'mysql') {
|
||||
throw new \RuntimeException('This script can only be used with MySQL database backends.');
|
||||
}
|
||||
|
||||
$query = $connection->query("SHOW FULL COLUMNS FROM {" . $table . "}");
|
||||
$definition = [];
|
||||
while (($row = $query->fetchAssoc()) !== FALSE) {
|
||||
$name = $row['Field'];
|
||||
// Parse out the field type and meta information.
|
||||
preg_match('@([a-z]+)(?:\((\d+)(?:,(\d+))?\))?\s*(unsigned)?@', $row['Type'], $matches);
|
||||
$type = $this->fieldTypeMap($matches[1]);
|
||||
$type = $this->fieldTypeMap($connection, $matches[1]);
|
||||
if ($row['Extra'] === 'auto_increment') {
|
||||
// If this is an auto increment, then the type is 'serial'.
|
||||
$type = 'serial';
|
||||
|
@ -170,7 +159,7 @@ class DbDumpCommand extends Command {
|
|||
'type' => $type,
|
||||
'not null' => $row['Null'] === 'NO',
|
||||
];
|
||||
if ($size = $this->fieldSizeMap($matches[1])) {
|
||||
if ($size = $this->fieldSizeMap($connection, $matches[1])) {
|
||||
$definition['fields'][$name]['size'] = $size;
|
||||
}
|
||||
if (isset($matches[2]) && $type === 'numeric') {
|
||||
|
@ -216,10 +205,10 @@ class DbDumpCommand extends Command {
|
|||
}
|
||||
|
||||
// Set primary key, unique keys, and indexes.
|
||||
$this->getTableIndexes($table, $definition);
|
||||
$this->getTableIndexes($connection, $table, $definition);
|
||||
|
||||
// Set table collation.
|
||||
$this->getTableCollation($table, $definition);
|
||||
$this->getTableCollation($connection, $table, $definition);
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
@ -227,15 +216,17 @@ class DbDumpCommand extends Command {
|
|||
/**
|
||||
* Adds primary key, unique keys, and index information to the schema.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param string $table
|
||||
* The table to find indexes for.
|
||||
* @param array &$definition
|
||||
* The schema definition to modify.
|
||||
*/
|
||||
protected function getTableIndexes($table, &$definition) {
|
||||
protected function getTableIndexes(Connection $connection, $table, &$definition) {
|
||||
// Note, this query doesn't support ordering, so that is worked around
|
||||
// below by keying the array on Seq_in_index.
|
||||
$query = $this->connection->query("SHOW INDEX FROM {" . $table . "}");
|
||||
$query = $connection->query("SHOW INDEX FROM {" . $table . "}");
|
||||
while (($row = $query->fetchAssoc()) !== FALSE) {
|
||||
$index_name = $row['Key_name'];
|
||||
$column = $row['Column_name'];
|
||||
|
@ -262,13 +253,15 @@ class DbDumpCommand extends Command {
|
|||
/**
|
||||
* Set the table collation.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param string $table
|
||||
* The table to find indexes for.
|
||||
* @param array &$definition
|
||||
* The schema definition to modify.
|
||||
*/
|
||||
protected function getTableCollation($table, &$definition) {
|
||||
$query = $this->connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
|
||||
protected function getTableCollation(Connection $connection, $table, &$definition) {
|
||||
$query = $connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
|
||||
$data = $query->fetchAssoc();
|
||||
|
||||
// Set `mysql_character_set`. This will be ignored by other backends.
|
||||
|
@ -280,21 +273,17 @@ class DbDumpCommand extends Command {
|
|||
*
|
||||
* If a table is set to be schema only, and empty array is returned.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param string $table
|
||||
* The table to query.
|
||||
*
|
||||
* @return array
|
||||
* The data from the table as an array.
|
||||
*/
|
||||
protected function getTableData($table) {
|
||||
// Check for schema only.
|
||||
foreach ($this->schemaOnly as $schema_only) {
|
||||
if (preg_match('/^' . $schema_only . '$/', $table)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
$order = $this->getFieldOrder($table);
|
||||
$query = $this->connection->query("SELECT * FROM {" . $table . "} " . $order );
|
||||
protected function getTableData(Connection $connection, $table) {
|
||||
$order = $this->getFieldOrder($connection, $table);
|
||||
$query = $connection->query("SELECT * FROM {" . $table . "} " . $order);
|
||||
$results = [];
|
||||
while (($row = $query->fetchAssoc()) !== FALSE) {
|
||||
$results[] = $row;
|
||||
|
@ -305,6 +294,8 @@ class DbDumpCommand extends Command {
|
|||
/**
|
||||
* Given a database field type, return a Drupal type.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param string $type
|
||||
* The MySQL field type.
|
||||
*
|
||||
|
@ -312,9 +303,9 @@ class DbDumpCommand extends Command {
|
|||
* The Drupal schema field type. If there is no mapping, the original field
|
||||
* type is returned.
|
||||
*/
|
||||
protected function fieldTypeMap($type) {
|
||||
protected function fieldTypeMap(Connection $connection, $type) {
|
||||
// Convert everything to lowercase.
|
||||
$map = array_map('strtolower', $this->connection->schema()->getFieldTypeMap());
|
||||
$map = array_map('strtolower', $connection->schema()->getFieldTypeMap());
|
||||
$map = array_flip($map);
|
||||
|
||||
// The MySql map contains type:size. Remove the size part.
|
||||
|
@ -324,15 +315,17 @@ class DbDumpCommand extends Command {
|
|||
/**
|
||||
* Given a database field type, return a Drupal size.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param string $type
|
||||
* The MySQL field type.
|
||||
*
|
||||
* @return string
|
||||
* The Drupal schema field size.
|
||||
*/
|
||||
protected function fieldSizeMap($type) {
|
||||
protected function fieldSizeMap(Connection $connection, $type) {
|
||||
// Convert everything to lowercase.
|
||||
$map = array_map('strtolower', $this->connection->schema()->getFieldTypeMap());
|
||||
$map = array_map('strtolower', $connection->schema()->getFieldTypeMap());
|
||||
$map = array_flip($map);
|
||||
|
||||
$schema_type = explode(':', $map[$type])[0];
|
||||
|
@ -346,24 +339,26 @@ class DbDumpCommand extends Command {
|
|||
/**
|
||||
* Gets field ordering for a given table.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @param string $table
|
||||
* The table name.
|
||||
*
|
||||
* @return string
|
||||
* The order string to append to the query.
|
||||
*/
|
||||
protected function getFieldOrder($table) {
|
||||
protected function getFieldOrder(Connection $connection, $table) {
|
||||
// @todo this is MySQL only since there are no Database API functions for
|
||||
// table column data.
|
||||
// @todo this code is duplicated in `core/scripts/migrate-db.sh`.
|
||||
$connection_info = $this->connection->getConnectionOptions();
|
||||
$connection_info = $connection->getConnectionOptions();
|
||||
// Order by primary keys.
|
||||
$order = '';
|
||||
$query = "SELECT `COLUMN_NAME` FROM `information_schema`.`COLUMNS`
|
||||
WHERE (`TABLE_SCHEMA` = '" . $connection_info['database'] . "')
|
||||
AND (`TABLE_NAME` = '{" . $table . "}') AND (`COLUMN_KEY` = 'PRI')
|
||||
ORDER BY COLUMN_NAME";
|
||||
$results = $this->connection->query($query);
|
||||
$results = $connection->query($query);
|
||||
while (($row = $results->fetchAssoc()) !== FALSE) {
|
||||
$order .= $row['COLUMN_NAME'] . ', ';
|
||||
}
|
||||
|
@ -384,12 +379,9 @@ class DbDumpCommand extends Command {
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Filled installation of Drupal 8.0, for test purposes.
|
||||
* A database agnostic dump for testing purposes.
|
||||
*
|
||||
* This file was generated by the dump-database-d8.php script, from an
|
||||
* installation of Drupal 8. It has the following modules installed:
|
||||
*
|
||||
{{MODULES}}
|
||||
* This file was generated by the Drupal 8.0 db-tools.php script.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
@ -431,20 +423,4 @@ ENDOFSCRIPT;
|
|||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of modules enabled for insertion into the script docblock.
|
||||
*
|
||||
* @return string
|
||||
* The formatted list of enabled modules.
|
||||
*/
|
||||
protected function getModulesScript() {
|
||||
$output = '';
|
||||
$modules = $this->moduleHandler->getModuleList();
|
||||
ksort($modules);
|
||||
foreach ($modules as $module => $filename) {
|
||||
$output .= " * - $module\n";
|
||||
}
|
||||
return rtrim($output, "\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
75
core/lib/Drupal/Core/Command/DbImportCommand.php
Normal file
75
core/lib/Drupal/Core/Command/DbImportCommand.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Command\DbDumpCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Command;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\SchemaObjectExistsException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Provides a command to import the current database from a script.
|
||||
*
|
||||
* This script runs on databases exported using using one of the database dump
|
||||
* commands and imports it into the current database connection.
|
||||
*
|
||||
* @see \Drupal\Core\Command\DbImportApplication
|
||||
*/
|
||||
class DbImportCommand extends DbCommandBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
$this->setName('import')
|
||||
->setDescription('Import database from a generation script.')
|
||||
->addArgument('script', InputOption::VALUE_REQUIRED, 'Import script');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$script = $input->getArgument('script');
|
||||
if (!is_file($script)) {
|
||||
$output->writeln('File must exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $this->getDatabaseConnection($input);
|
||||
$this->runScript($connection, $script);
|
||||
$output->writeln('Import completed successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database script.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* Connection used by the script when included.
|
||||
* @param string $script
|
||||
* Path to dump script.
|
||||
*/
|
||||
protected function runScript(Connection $connection, $script) {
|
||||
$old_key = Database::setActiveConnection($connection->getKey());
|
||||
|
||||
if (substr($script, -3) == '.gz') {
|
||||
$script = "compress.zlib://$script";
|
||||
}
|
||||
try {
|
||||
require $script;
|
||||
}
|
||||
catch (SchemaObjectExistsException $e) {
|
||||
throw new \RuntimeException('An existing Drupal installation exists at this location. Try removing all tables or changing the database prefix in your settings.php file.');
|
||||
}
|
||||
Database::setActiveConnection($old_key);
|
||||
}
|
||||
|
||||
}
|
34
core/lib/Drupal/Core/Command/DbToolsApplication.php
Normal file
34
core/lib/Drupal/Core/Command/DbToolsApplication.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Command\DbToolsApplication.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Command;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
/**
|
||||
* Provides a command to import a database generation script.
|
||||
*/
|
||||
class DbToolsApplication extends Application {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct('Database Tools', '8.0.x');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDefaultCommands() {
|
||||
$default_commands = parent::getDefaultCommands();
|
||||
$default_commands[] = new DbDumpCommand();
|
||||
$default_commands[] = new DbImportCommand();
|
||||
return $default_commands;
|
||||
}
|
||||
|
||||
}
|
|
@ -48,7 +48,15 @@ class BootstrapConfigStorageFactory {
|
|||
/**
|
||||
* Returns a File-based configuration storage implementation.
|
||||
*
|
||||
* If there is no active configuration directory calling this method will
|
||||
* result in an error.
|
||||
*
|
||||
* @return \Drupal\Core\Config\FileStorage
|
||||
*
|
||||
* @deprecated in Drupal 8.0.x and will be removed before 9.0.0. Drupal core
|
||||
* no longer creates an active directory.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getFileStorage() {
|
||||
return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
|
||||
|
|
|
@ -108,8 +108,8 @@ class Config extends StorableConfigBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setData(array $data, $validate_keys = TRUE) {
|
||||
parent::setData($data, $validate_keys);
|
||||
public function setData(array $data) {
|
||||
parent::setData($data);
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
|
@ -153,10 +154,6 @@ abstract class ConfigBase implements RefinableCacheableDependencyInterface {
|
|||
*
|
||||
* @param array $data
|
||||
* The new configuration data.
|
||||
* @param bool $validate_keys
|
||||
* (optional) Whether the data should be verified for valid keys. Set to
|
||||
* FALSE if the $data is known to be valid already (for example, being
|
||||
* loaded from the config storage).
|
||||
*
|
||||
* @return $this
|
||||
* The configuration object.
|
||||
|
@ -164,10 +161,9 @@ abstract class ConfigBase implements RefinableCacheableDependencyInterface {
|
|||
* @throws \Drupal\Core\Config\ConfigValueException
|
||||
* If any key in $data in any depth contains a dot.
|
||||
*/
|
||||
public function setData(array $data, $validate_keys = TRUE) {
|
||||
if ($validate_keys) {
|
||||
$this->validateKeys($data);
|
||||
}
|
||||
public function setData(array $data) {
|
||||
$data = $this->castSafeStrings($data);
|
||||
$this->validateKeys($data);
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
@ -187,6 +183,7 @@ abstract class ConfigBase implements RefinableCacheableDependencyInterface {
|
|||
* If $value is an array and any of its keys in any depth contains a dot.
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
$value = $this->castSafeStrings($value);
|
||||
// The dot/period is a reserved character; it may appear between keys, but
|
||||
// not within keys.
|
||||
if (is_array($value)) {
|
||||
|
@ -280,4 +277,27 @@ abstract class ConfigBase implements RefinableCacheableDependencyInterface {
|
|||
return $this->cacheMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts any objects that implement MarkupInterface to string.
|
||||
*
|
||||
* @param mixed $data
|
||||
* The configuration data.
|
||||
*
|
||||
* @return mixed
|
||||
* The data with any safe strings cast to string.
|
||||
*/
|
||||
protected function castSafeStrings($data) {
|
||||
if ($data instanceof MarkupInterface) {
|
||||
$data = (string) $data;
|
||||
}
|
||||
else if (is_array($data)) {
|
||||
array_walk_recursive($data, function (&$value) {
|
||||
if ($value instanceof MarkupInterface) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -367,8 +367,8 @@ class ConfigImporter {
|
|||
$current_extensions = $this->storageComparer->getTargetStorage()->read('core.extension');
|
||||
$new_extensions = $this->storageComparer->getSourceStorage()->read('core.extension');
|
||||
|
||||
// If there is no extension information in staging then exit. This is
|
||||
// probably due to an empty staging directory.
|
||||
// If there is no extension information in sync then exit. This is probably
|
||||
// due to an empty sync directory.
|
||||
if (!$new_extensions) {
|
||||
return;
|
||||
}
|
||||
|
@ -718,11 +718,11 @@ class ConfigImporter {
|
|||
$old_entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']);
|
||||
$new_entity_type_id = $this->configManager->getEntityTypeIdByName($names['new_name']);
|
||||
if ($old_entity_type_id != $new_entity_type_id) {
|
||||
$this->logError($this->t('Entity type mismatch on rename. !old_type not equal to !new_type for existing configuration !old_name and staged configuration !new_name.', array('old_type' => $old_entity_type_id, 'new_type' => $new_entity_type_id, 'old_name' => $names['old_name'], 'new_name' => $names['new_name'])));
|
||||
$this->logError($this->t('Entity type mismatch on rename. @old_type not equal to @new_type for existing configuration @old_name and staged configuration @new_name.', array('@old_type' => $old_entity_type_id, '@new_type' => $new_entity_type_id, '@old_name' => $names['old_name'], '@new_name' => $names['new_name'])));
|
||||
}
|
||||
// Has to be a configuration entity.
|
||||
if (!$old_entity_type_id) {
|
||||
$this->logError($this->t('Rename operation for simple configuration. Existing configuration !old_name and staged configuration !new_name.', array('old_name' => $names['old_name'], 'new_name' => $names['new_name'])));
|
||||
$this->logError($this->t('Rename operation for simple configuration. Existing configuration @old_name and staged configuration @new_name.', array('@old_name' => $names['old_name'], '@new_name' => $names['new_name'])));
|
||||
}
|
||||
}
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this));
|
||||
|
@ -762,7 +762,7 @@ class ConfigImporter {
|
|||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->logError($this->t('Unexpected error during import with operation @op for @name: !message', array('@op' => $op, '@name' => $name, '!message' => $e->getMessage())));
|
||||
$this->logError($this->t('Unexpected error during import with operation @op for @name: @message', array('@op' => $op, '@name' => $name, '@message' => $e->getMessage())));
|
||||
// Error for that operation was logged, mark it as processed so that
|
||||
// the import can continue.
|
||||
$this->setProcessedConfiguration($collection, $op, $name);
|
||||
|
@ -780,7 +780,7 @@ class ConfigImporter {
|
|||
* The name of the extension to process.
|
||||
*/
|
||||
protected function processExtension($type, $op, $name) {
|
||||
// Set the config installer to use the staging directory instead of the
|
||||
// Set the config installer to use the sync directory instead of the
|
||||
// extensions own default config directories.
|
||||
\Drupal::service('config.installer')
|
||||
->setSyncing(TRUE)
|
||||
|
|
|
@ -155,11 +155,15 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
// Check for new or removed files.
|
||||
if ($source_data === array('false')) {
|
||||
// Added file.
|
||||
$source_data = array($this->t('File added'));
|
||||
// Cast the result of t() to a string, as the diff engine doesn't know
|
||||
// about objects.
|
||||
$source_data = array((string) $this->t('File added'));
|
||||
}
|
||||
if ($target_data === array('false')) {
|
||||
// Deleted file.
|
||||
$target_data = array($this->t('File removed'));
|
||||
// Cast the result of t() to a string, as the diff engine doesn't know
|
||||
// about objects.
|
||||
$target_data = array((string) $this->t('File removed'));
|
||||
}
|
||||
|
||||
return new Diff($source_data, $target_data);
|
||||
|
@ -316,7 +320,8 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
}
|
||||
if ($this->callOnDependencyRemoval($dependent, $original_dependencies, $type, $names)) {
|
||||
// Recalculate dependencies and update the dependency graph data.
|
||||
$dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->calculateDependencies());
|
||||
$dependent->calculateDependencies();
|
||||
$dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->getDependencies());
|
||||
// Based on the updated data rebuild the list of dependents.
|
||||
$dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
// Ensure that the dependency has actually been fixed. It is possible
|
||||
|
@ -454,6 +459,9 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
if (isset($config_data['dependencies']['content'])) {
|
||||
$content_dependencies = array_merge($content_dependencies, $config_data['dependencies']['content']);
|
||||
}
|
||||
if (isset($config_data['dependencies']['enforced']['content'])) {
|
||||
$content_dependencies = array_merge($content_dependencies, $config_data['dependencies']['enforced']['content']);
|
||||
}
|
||||
}
|
||||
foreach (array_unique($content_dependencies) as $content_dependency) {
|
||||
// Format of the dependency is entity_type:bundle:uuid.
|
||||
|
|
|
@ -53,8 +53,7 @@ trait ConfigDependencyDeleteFormTrait {
|
|||
'#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 @@ trait ConfigDependencyDeleteFormTrait {
|
|||
'#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 @@ trait ConfigDependencyDeleteFormTrait {
|
|||
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 @@ trait ConfigDependencyDeleteFormTrait {
|
|||
'#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 @@ trait ConfigDependencyDeleteFormTrait {
|
|||
'#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 @@ trait ConfigDependencyDeleteFormTrait {
|
|||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,11 +58,11 @@ use Drupal\Component\Utility\SortArray;
|
|||
* Configuration entity classes usually extend
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityBase. The base class provides a
|
||||
* generic implementation of the calculateDependencies() method that can
|
||||
* discover dependencies due to enforced dependencies, plugins, and third party
|
||||
* settings. If the configuration entity has dependencies that cannot be
|
||||
* discovered by the base class's implementation, then it needs to implement
|
||||
* discover dependencies due to plugins, and third party settings. If the
|
||||
* configuration entity has dependencies that cannot be discovered by the base
|
||||
* class's implementation, then it needs to implement
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() to
|
||||
* calculate (and return) the dependencies. In this method, use
|
||||
* calculate the dependencies. In this method, use
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency() to add
|
||||
* dependencies. Implementations should call the base class implementation to
|
||||
* inherit the generic functionality.
|
||||
|
@ -85,9 +85,9 @@ use Drupal\Component\Utility\SortArray;
|
|||
* configuration object so that they can be checked without the module that
|
||||
* provides the configuration entity class being installed. This is important
|
||||
* for configuration synchronization, which needs to be able to validate
|
||||
* configuration in the staging directory before the synchronization has
|
||||
* occurred. Also, if you have a configuration entity object and you want to
|
||||
* get the current dependencies without recalculation, you can use
|
||||
* configuration in the sync directory before the synchronization has occurred.
|
||||
* Also, if you have a configuration entity object and you want to get the
|
||||
* current dependencies (without recalculation), you can use
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies().
|
||||
*
|
||||
* When uninstalling a module or a theme, configuration entities that are
|
||||
|
@ -115,6 +115,7 @@ use Drupal\Component\Utility\SortArray;
|
|||
* module dependency in the sub-module only.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies()
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency()
|
||||
* @see \Drupal\Core\Config\ConfigInstallerInterface::installDefaultConfig()
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Config\ConfigException;
|
||||
use Drupal\Core\Config\Schema\SchemaIncompleteException;
|
||||
|
@ -341,7 +342,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this entity already exists with UUID '{$original->uuid()}'");
|
||||
}
|
||||
}
|
||||
if (!$this->isSyncing() && !$this->trustedData) {
|
||||
if (!$this->isSyncing()) {
|
||||
// Ensure the correct dependencies are present. If the configuration is
|
||||
// being written during a configuration synchronization then there is no
|
||||
// need to recalculate the dependencies.
|
||||
|
@ -353,16 +354,9 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
// Dependencies should be recalculated on every save. This ensures stale
|
||||
// dependencies are never saved.
|
||||
if (isset($this->dependencies['enforced'])) {
|
||||
$dependencies = $this->dependencies['enforced'];
|
||||
$this->dependencies = $dependencies;
|
||||
$this->dependencies['enforced'] = $dependencies;
|
||||
}
|
||||
else {
|
||||
$this->dependencies = array();
|
||||
}
|
||||
// All dependencies should be recalculated on every save apart from enforced
|
||||
// dependencies. This ensures stale dependencies are never saved.
|
||||
$this->dependencies = array_intersect_key($this->dependencies, ['enforced' => '']);
|
||||
if ($this instanceof EntityWithPluginCollectionInterface) {
|
||||
// Configuration entities need to depend on the providers of any plugins
|
||||
// that they store the configuration for.
|
||||
|
@ -379,13 +373,15 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
$this->addDependency('module', $provider);
|
||||
}
|
||||
}
|
||||
return $this->dependencies;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function urlInfo($rel = 'edit-form', array $options = []) {
|
||||
// Unless language was already provided, avoid setting an explicit language.
|
||||
$options += ['language' => NULL];
|
||||
return parent::urlInfo($rel, $options);
|
||||
}
|
||||
|
||||
|
@ -436,7 +432,14 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDependencies() {
|
||||
return $this->dependencies;
|
||||
$dependencies = $this->dependencies;
|
||||
if (isset($dependencies['enforced'])) {
|
||||
// Merge the enforced dependencies into the list of dependencies.
|
||||
$enforced_dependencies = $dependencies['enforced'];
|
||||
unset($dependencies['enforced']);
|
||||
$dependencies = NestedArray::mergeDeep($dependencies, $enforced_dependencies);
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Config\ConfigNameException;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
|
||||
/**
|
||||
|
@ -19,31 +18,6 @@ use Drupal\Core\Entity\EntityStorageInterface;
|
|||
*/
|
||||
abstract class ConfigEntityBundleBase extends ConfigEntityBase {
|
||||
|
||||
/**
|
||||
* Renames displays when a bundle is renamed.
|
||||
*/
|
||||
protected function renameDisplays() {
|
||||
// Rename entity displays.
|
||||
if ($this->getOriginalId() !== $this->id()) {
|
||||
foreach ($this->loadDisplays('entity_view_display') as $display) {
|
||||
$new_id = $this->getEntityType()->getBundleOf() . '.' . $this->id() . '.' . $display->getMode();
|
||||
$display->set('id', $new_id);
|
||||
$display->setTargetBundle($this->id());
|
||||
$display->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Rename entity form displays.
|
||||
if ($this->getOriginalId() !== $this->id()) {
|
||||
foreach ($this->loadDisplays('entity_form_display') as $form_display) {
|
||||
$new_id = $this->getEntityType()->getBundleOf() . '.' . $this->id() . '.' . $form_display->getMode();
|
||||
$form_display->set('id', $new_id);
|
||||
$form_display->setTargetBundle($this->id());
|
||||
$form_display->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes display if a bundle is deleted.
|
||||
*/
|
||||
|
@ -80,12 +54,6 @@ abstract class ConfigEntityBundleBase extends ConfigEntityBase {
|
|||
}
|
||||
// Entity bundle field definitions may depend on bundle settings.
|
||||
$entity_manager->clearCachedFieldDefinitions();
|
||||
|
||||
if ($this->getOriginalId() != $this->id()) {
|
||||
// If the entity was renamed, update the displays.
|
||||
$this->renameDisplays();
|
||||
$entity_manager->onBundleRename($this->getOriginalId(), $this->id(), $bundle_of);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,6 +69,33 @@ abstract class ConfigEntityBundleBase extends ConfigEntityBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts on an entity before the presave hook is invoked.
|
||||
*
|
||||
* Used before the entity is saved and before invoking the presave hook.
|
||||
*
|
||||
* Ensure that config entities which are bundles of other entities cannot have
|
||||
* their ID changed.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The entity storage object.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigNameException
|
||||
* Thrown when attempting to rename a bundle entity.
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
|
||||
// Only handle renames, not creations.
|
||||
if (!$this->isNew() && $this->getOriginalId() !== $this->id()) {
|
||||
$bundle_type = $this->getEntityType();
|
||||
$bundle_of = $bundle_type->getBundleOf();
|
||||
if (!empty($bundle_of)) {
|
||||
throw new ConfigNameException("The machine name of the '{$bundle_type->getLabel()}' bundle cannot be changed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns view or form displays for this bundle.
|
||||
*
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
|
||||
/**
|
||||
* Provides a value object to discover configuration dependencies.
|
||||
*
|
||||
|
@ -26,7 +28,7 @@ class ConfigEntityDependency {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dependencies;
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* Constructs the configuration entity dependency from the entity values.
|
||||
|
@ -36,13 +38,16 @@ class ConfigEntityDependency {
|
|||
* @param array $values
|
||||
* (optional) The configuration entity's values.
|
||||
*/
|
||||
public function __construct($name, $values = array()) {
|
||||
public function __construct($name, $values = []) {
|
||||
$this->name = $name;
|
||||
if (isset($values['dependencies'])) {
|
||||
$this->dependencies = $values['dependencies'];
|
||||
if (isset($values['dependencies']) && isset($values['dependencies']['enforced'])) {
|
||||
// Merge the enforced dependencies into the list of dependencies.
|
||||
$enforced_dependencies = $values['dependencies']['enforced'];
|
||||
unset($values['dependencies']['enforced']);
|
||||
$this->dependencies = NestedArray::mergeDeep($values['dependencies'], $enforced_dependencies);
|
||||
}
|
||||
else {
|
||||
$this->dependencies = array();
|
||||
elseif (isset($values['dependencies'])) {
|
||||
$this->dependencies = $values['dependencies'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,8 +144,7 @@ interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInter
|
|||
/**
|
||||
* Calculates dependencies and stores them in the dependency property.
|
||||
*
|
||||
* @return array
|
||||
* An array of dependencies grouped by type (module, theme, entity).
|
||||
* @return $this
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*/
|
||||
|
|
|
@ -16,18 +16,21 @@ class FileStorageFactory {
|
|||
* Returns a FileStorage object working with the active config directory.
|
||||
*
|
||||
* @return \Drupal\Core\Config\FileStorage FileStorage
|
||||
*
|
||||
* @deprecated in Drupal 8.0.x and will be removed before 9.0.0. Drupal core
|
||||
* no longer creates an active directory.
|
||||
*/
|
||||
static function getActive() {
|
||||
return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FileStorage object working with the staging config directory.
|
||||
* Returns a FileStorage object working with the sync config directory.
|
||||
*
|
||||
* @return \Drupal\Core\Config\FileStorage FileStorage
|
||||
*/
|
||||
static function getStaging() {
|
||||
return new FileStorage(config_get_config_directory(CONFIG_STAGING_DIRECTORY));
|
||||
static function getSync() {
|
||||
return new FileStorage(config_get_config_directory(CONFIG_SYNC_DIRECTORY));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,27 +7,10 @@
|
|||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\TypedData\TypedData;
|
||||
|
||||
/**
|
||||
* Defines a generic configuration element that contains multiple properties.
|
||||
*/
|
||||
abstract class ArrayElement extends TypedData implements \IteratorAggregate, TypedConfigInterface {
|
||||
|
||||
/**
|
||||
* The typed config manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedConfig;
|
||||
|
||||
/**
|
||||
* The configuration value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
abstract class ArrayElement extends Element implements \IteratorAggregate, TypedConfigInterface {
|
||||
|
||||
/**
|
||||
* Parsed elements.
|
||||
|
@ -152,7 +135,7 @@ abstract class ArrayElement extends TypedData implements \IteratorAggregate, Typ
|
|||
* @return \Drupal\Core\TypedData\TypedDataInterface
|
||||
*/
|
||||
protected function createElement($definition, $value, $key) {
|
||||
return $this->typedConfig->create($definition, $value, $key, $this);
|
||||
return $this->getTypedDataManager()->create($definition, $value, $key, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,20 +153,17 @@ abstract class ArrayElement extends TypedData implements \IteratorAggregate, Typ
|
|||
* @return \Drupal\Core\TypedData\DataDefinitionInterface
|
||||
*/
|
||||
protected function buildDataDefinition($definition, $value, $key) {
|
||||
return $this->typedConfig->buildDataDefinition($definition, $value, $key, $this);
|
||||
return $this->getTypedDataManager()->buildDataDefinition($definition, $value, $key, $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the typed config manager on the instance.
|
||||
* Determines if this element allows NULL as a value.
|
||||
*
|
||||
* This must be called immediately after construction to enable
|
||||
* self::parseElement() and self::buildDataDefinition() to work.
|
||||
*
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
||||
* @return bool
|
||||
* TRUE if NULL is a valid value, FALSE otherwise.
|
||||
*/
|
||||
public function setTypedConfig(TypedConfigManagerInterface $typed_config) {
|
||||
$this->typedConfig = $typed_config;
|
||||
public function isNullable() {
|
||||
return isset($this->definition['nullable']) && $this->definition['nullable'] == TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\TypedData\TypedData;
|
||||
use Drupal\Core\TypedData\TypedDataManagerInterface;
|
||||
|
||||
/**
|
||||
* Defines a generic configuration element.
|
||||
|
@ -21,4 +23,41 @@ abstract class Element extends TypedData {
|
|||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Gets the typed configuration manager.
|
||||
*
|
||||
* Overrides \Drupal\Core\TypedData\TypedDataTrait::getTypedDataManager() to
|
||||
* ensure the typed configuration manager is returned.
|
||||
*
|
||||
* @return \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
* The typed configuration manager.
|
||||
*/
|
||||
public function getTypedDataManager() {
|
||||
if (empty($this->typedDataManager)) {
|
||||
$this->setTypedDataManager(\Drupal::service('config.typed'));
|
||||
}
|
||||
|
||||
return $this->typedDataManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the typed config manager.
|
||||
*
|
||||
* Overrides \Drupal\Core\TypedData\TypedDataTrait::setTypedDataManager() to
|
||||
* ensure that only typed configuration manager can be used.
|
||||
*
|
||||
* @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
|
||||
* The typed config manager. This must be an instance of
|
||||
* \Drupal\Core\Config\TypedConfigManagerInterface. If it is not, then this
|
||||
* method will error when assertions are enabled. We can not narrow the
|
||||
* typehint as this will cause PHP errors.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTypedDataManager(TypedDataManagerInterface $typed_data_manager) {
|
||||
assert($typed_data_manager instanceof TypedConfigManagerInterface, '$typed_data_manager should be an instance of \Drupal\Core\Config\TypedConfigManagerInterface.');
|
||||
$this->typedDataManager = $typed_data_manager;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -106,9 +106,13 @@ trait SchemaCheckTrait {
|
|||
(($type == 'double' || $type == 'integer') && $element instanceof FloatInterface) ||
|
||||
($type == 'boolean' && $element instanceof BooleanInterface) ||
|
||||
($type == 'string' && $element instanceof StringInterface) ||
|
||||
// Null values are allowed for all types.
|
||||
// Null values are allowed for all primitive types.
|
||||
($value === NULL);
|
||||
}
|
||||
// Array elements can also opt-in for allowing a NULL value.
|
||||
elseif ($element instanceof ArrayElement && $element->isNullable() && $value === NULL) {
|
||||
$success = TRUE;
|
||||
}
|
||||
$class = get_class($element);
|
||||
if (!$success) {
|
||||
return array($error_key => "variable type is $type but applied schema class is $class");
|
||||
|
|
|
@ -98,7 +98,7 @@ abstract class StorableConfigBase extends ConfigBase {
|
|||
*/
|
||||
public function initWithData(array $data) {
|
||||
$this->isNew = FALSE;
|
||||
$this->setData($data, FALSE);
|
||||
$this->data = $data;
|
||||
$this->originalData = $this->data;
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -71,13 +71,7 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
|
||||
|
||||
/**
|
||||
* Gets typed configuration data.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration object name.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Schema\TypedConfigInterface
|
||||
* Typed configuration data.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($name) {
|
||||
$data = $this->configStorage->read($name);
|
||||
|
@ -282,8 +276,12 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
return $value;
|
||||
}
|
||||
elseif (!$parts) {
|
||||
$value = $data[$name];
|
||||
if (is_bool($value)) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
// If no more parts left, this is the final property.
|
||||
return (string)$data[$name];
|
||||
return (string) $value;
|
||||
}
|
||||
else {
|
||||
// Get nested value and continue processing.
|
||||
|
@ -338,17 +336,4 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstance($data_type, array $configuration = array()) {
|
||||
$instance = parent::createInstance($data_type, $configuration);
|
||||
// Enable elements to construct their own definitions using the typed config
|
||||
// manager.
|
||||
if ($instance instanceof ArrayElement) {
|
||||
$instance->setTypedConfig($this);
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\TypedData\DataDefinitionInterface;
|
||||
use Drupal\Core\TypedData\TypedDataManagerInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for managing config schema type plugins.
|
||||
|
@ -19,7 +17,7 @@ use Drupal\Core\TypedData\DataDefinitionInterface;
|
|||
* @see hook_config_schema_info_alter()
|
||||
* @see https://www.drupal.org/node/1905070
|
||||
*/
|
||||
Interface TypedConfigManagerInterface extends PluginManagerInterface, CachedDiscoveryInterface {
|
||||
Interface TypedConfigManagerInterface extends TypedDataManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets typed configuration data.
|
||||
|
@ -32,48 +30,6 @@ Interface TypedConfigManagerInterface extends PluginManagerInterface, CachedDisc
|
|||
*/
|
||||
public function get($name);
|
||||
|
||||
/**
|
||||
* Instantiates a typed configuration object.
|
||||
*
|
||||
* @param string $data_type
|
||||
* The data type, for which a typed configuration object should be
|
||||
* instantiated.
|
||||
* @param array $configuration
|
||||
* The plugin configuration array, i.e. an array with the following keys:
|
||||
* - data definition: The data definition object, i.e. an instance of
|
||||
* \Drupal\Core\TypedData\DataDefinitionInterface.
|
||||
* - name: (optional) If a property or list item is to be created, the name
|
||||
* of the property or the delta of the list item.
|
||||
* - parent: (optional) If a property or list item is to be created, the
|
||||
* parent typed data object implementing either the ListInterface or the
|
||||
* ComplexDataInterface.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Schema\Element
|
||||
* The instantiated typed configuration object.
|
||||
*/
|
||||
public function createInstance($data_type, array $configuration = array());
|
||||
|
||||
/**
|
||||
* Creates a new typed configuration object instance.
|
||||
*
|
||||
* @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
|
||||
* The data definition of the typed data object.
|
||||
* @param mixed $value
|
||||
* The data value. If set, it has to match one of the supported
|
||||
* data type format as documented for the data type classes.
|
||||
* @param string $name
|
||||
* (optional) If a property or list item is to be created, the name of the
|
||||
* property or the delta of the list item.
|
||||
* @param mixed $parent
|
||||
* (optional) If a property or list item is to be created, the parent typed
|
||||
* data object implementing either the ListInterface or the
|
||||
* ComplexDataInterface.
|
||||
*
|
||||
* @return \Drupal\Core\Config\Schema\Element
|
||||
* The instantiated typed data object.
|
||||
*/
|
||||
public function create(DataDefinitionInterface $definition, $value, $name = NULL, $parent = NULL);
|
||||
|
||||
/**
|
||||
* Creates a new data definition object from a type definition array and
|
||||
* actual configuration data. Since type definitions may contain variables
|
||||
|
|
|
@ -33,7 +33,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
*
|
||||
* @see \Drupal\Core\DependencyInjection\ContainerInjectionInterface
|
||||
*
|
||||
* @ingroup menu
|
||||
* @ingroup routing
|
||||
*/
|
||||
abstract class ControllerBase implements ContainerInjectionInterface {
|
||||
|
||||
|
|
|
@ -17,18 +17,20 @@ interface TitleResolverInterface {
|
|||
/**
|
||||
* Returns a static or dynamic title for the route.
|
||||
*
|
||||
* The returned title string must be safe to output in HTML. For example, an
|
||||
* implementation should call \Drupal\Component\Utility\SafeMarkup::checkPlain()
|
||||
* or \Drupal\Component\Utility\Xss::filterAdmin() on the string, or use
|
||||
* appropriate placeholders to sanitize dynamic content inside a localized
|
||||
* string before returning it. The title may contain HTML such as EM tags.
|
||||
* If the returned title can contain HTML that should not be escaped it should
|
||||
* return a render array, for example:
|
||||
* @code
|
||||
* ['#markup' => 'title', '#allowed_tags' => ['em']]
|
||||
* @endcode
|
||||
* If the method returns a string and it is not marked safe then it will be
|
||||
* auto-escaped.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object passed to the title callback.
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route information of the route to fetch the title.
|
||||
*
|
||||
* @return string|null
|
||||
* @return array|string|null
|
||||
* The title for the route.
|
||||
*/
|
||||
public function getTitle(Request $request, Route $route);
|
||||
|
|
|
@ -24,7 +24,7 @@ use Drupal\Core\DependencyInjection\ServiceProviderInterface;
|
|||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\RegisterEventSubscribersPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
|
||||
use Drupal\Core\Plugin\PluginManagerPass;
|
||||
|
@ -82,7 +82,7 @@ class CoreServiceProvider implements ServiceProviderInterface {
|
|||
$container->addCompilerPass(new TwigExtensionPass());
|
||||
|
||||
// Add a compiler pass for registering event subscribers.
|
||||
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$container->addCompilerPass(new RegisterEventSubscribersPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
|
||||
$container->addCompilerPass(new RegisterAccessChecksPass());
|
||||
$container->addCompilerPass(new RegisterLazyRouteEnhancers());
|
||||
|
|
|
@ -239,6 +239,12 @@ abstract class Connection {
|
|||
* further up the call chain can take an appropriate action. To suppress
|
||||
* that behavior and simply return NULL on failure, set this option to
|
||||
* FALSE.
|
||||
* - allow_delimiter_in_query: By default, queries which have the ; delimiter
|
||||
* any place in them will cause an exception. This reduces the chance of SQL
|
||||
* injection attacks that terminate the original query and add one or more
|
||||
* additional queries (such as inserting new user accounts). In rare cases,
|
||||
* such as creating an SQL function, a ; is needed and can be allowed by
|
||||
* changing this option to TRUE.
|
||||
*
|
||||
* @return array
|
||||
* An array of default query options.
|
||||
|
@ -249,6 +255,7 @@ abstract class Connection {
|
|||
'fetch' => \PDO::FETCH_OBJ,
|
||||
'return' => Database::RETURN_STATEMENT,
|
||||
'throw_exception' => TRUE,
|
||||
'allow_delimiter_in_query' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -491,7 +498,7 @@ abstract class Connection {
|
|||
return '';
|
||||
|
||||
// Flatten the array of comments.
|
||||
$comment = implode('; ', $comments);
|
||||
$comment = implode('. ', $comments);
|
||||
|
||||
// Sanitize the comment string so as to avoid SQL injection attacks.
|
||||
return '/* ' . $this->filterComment($comment) . ' */ ';
|
||||
|
@ -516,7 +523,7 @@ abstract class Connection {
|
|||
*
|
||||
* Would result in the following SQL statement being generated:
|
||||
* @code
|
||||
* "/ * Exploit * / DROP TABLE node; -- * / UPDATE example SET field2=..."
|
||||
* "/ * Exploit * / DROP TABLE node. -- * / UPDATE example SET field2=..."
|
||||
* @endcode
|
||||
*
|
||||
* Unless the comment is sanitised first, the SQL server would drop the
|
||||
|
@ -529,7 +536,8 @@ abstract class Connection {
|
|||
* A sanitized version of the query comment string.
|
||||
*/
|
||||
protected function filterComment($comment = '') {
|
||||
return strtr($comment, ['*' => ' * ']);
|
||||
// Change semicolons to period to avoid triggering multi-statement check.
|
||||
return strtr($comment, ['*' => ' * ', ';' => '.']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -593,6 +601,16 @@ abstract class Connection {
|
|||
}
|
||||
else {
|
||||
$this->expandArguments($query, $args);
|
||||
// To protect against SQL injection, Drupal only supports executing one
|
||||
// statement at a time. Thus, the presence of a SQL delimiter (the
|
||||
// semicolon) is not allowed unless the option is set. Allowing
|
||||
// semicolons should only be needed for special cases like defining a
|
||||
// function or stored procedure in SQL. Trim any trailing delimiter to
|
||||
// minimize false positives.
|
||||
$query = rtrim($query, "; \t\n\r\0\x0B");
|
||||
if (strpos($query, ';') !== FALSE && empty($options['allow_delimiter_in_query'])) {
|
||||
throw new \InvalidArgumentException('; is not supported in SQL strings. Use only one statement at a time.');
|
||||
}
|
||||
$stmt = $this->prepareQuery($query);
|
||||
$stmt->execute($args, $options);
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ class Tasks extends InstallTasks {
|
|||
catch (\Exception $e) {
|
||||
// Detect utf8mb4 incompability.
|
||||
if ($e->getCode() == Connection::UNSUPPORTED_CHARSET) {
|
||||
$this->fail(t('Your MySQL server and PHP MySQL driver must support utf8mb4 character encoding. Make sure to use a database system that supports this (such as MySQL/MariaDB/Percona 5.5.3 and up), and that the utf8mb4 character set is compiled in. See the <a href="@documentation" target="_blank">MySQL documentation</a> for more information.', array('@documentation' => 'https://dev.mysql.com/doc/refman/5.0/en/cannot-initialize-character-set.html')));
|
||||
$this->fail(t('Your MySQL server and PHP MySQL driver must support utf8mb4 character encoding. Make sure to use a database system that supports this (such as MySQL/MariaDB/Percona 5.5.3 and up), and that the utf8mb4 character set is compiled in. See the <a href=":documentation" target="_blank">MySQL documentation</a> for more information.', array(':documentation' => 'https://dev.mysql.com/doc/refman/5.0/en/cannot-initialize-character-set.html')));
|
||||
$info = Database::getConnectionInfo();
|
||||
$info_copy = $info;
|
||||
// Set a flag to fall back to utf8. Note: this flag should only be
|
||||
|
@ -164,13 +164,13 @@ class Tasks extends InstallTasks {
|
|||
// The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
|
||||
$version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
|
||||
if (version_compare($version, self::MYSQLND_MINIMUM_VERSION, '<')) {
|
||||
$this->fail(t("The MySQLnd driver version %version is less than the minimum required version. Upgrade to MySQLnd version %mysqlnd_minimum_version or up, or alternatively switch mysql drivers to libmysqlclient version %libmysqlclient_minimum_version or up.", array('%version' => Database::getConnection()->version(), '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION, '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION)));
|
||||
$this->fail(t("The MySQLnd driver version %version is less than the minimum required version. Upgrade to MySQLnd version %mysqlnd_minimum_version or up, or alternatively switch mysql drivers to libmysqlclient version %libmysqlclient_minimum_version or up.", array('%version' => $version, '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION, '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
|
||||
if (version_compare($version, self::LIBMYSQLCLIENT_MINIMUM_VERSION, '<')) {
|
||||
$this->fail(t("The libmysqlclient driver version %version is less than the minimum required version. Upgrade to libmysqlclient version %libmysqlclient_minimum_version or up, or alternatively switch mysql drivers to MySQLnd version %mysqlnd_minimum_version or up.", array('%version' => Database::getConnection()->version(), '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION, '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION)));
|
||||
$this->fail(t("The libmysqlclient driver version %version is less than the minimum required version. Upgrade to libmysqlclient version %libmysqlclient_minimum_version or up, or alternatively switch mysql drivers to MySQLnd version %mysqlnd_minimum_version or up.", array('%version' => $version, '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION, '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -539,7 +539,8 @@ class Schema extends DatabaseSchema {
|
|||
// Add table prefixes before truncating.
|
||||
$comment = Unicode::truncate($this->connection->prefixTables($comment), $length, TRUE, TRUE);
|
||||
}
|
||||
|
||||
// Remove semicolons to avoid triggering multi-statement check.
|
||||
$comment = strtr($comment, array(';' => '.'));
|
||||
return $this->connection->quote($comment);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,10 @@ class Tasks extends InstallTasks {
|
|||
'function' => 'checkBinaryOutput',
|
||||
'arguments' => array(),
|
||||
);
|
||||
$this->tasks[] = array(
|
||||
'function' => 'checkStandardConformingStrings',
|
||||
'arguments' => array(),
|
||||
);
|
||||
$this->tasks[] = array(
|
||||
'function' => 'initializeDatabase',
|
||||
'arguments' => array(),
|
||||
|
@ -118,10 +122,9 @@ class Tasks extends InstallTasks {
|
|||
$this->pass(t('Database is encoded in UTF-8'));
|
||||
}
|
||||
else {
|
||||
$this->fail(t('The %driver database must use %encoding encoding to work with Drupal. Recreate the database with %encoding encoding. See !link for more details.', array(
|
||||
$this->fail(t('The %driver database must use %encoding encoding to work with Drupal. Recreate the database with %encoding encoding. See <a href="INSTALL.pgsql.txt">INSTALL.pgsql.txt</a> for more details.', array(
|
||||
'%encoding' => 'UTF8',
|
||||
'%driver' => $this->name(),
|
||||
'!link' => '<a href="INSTALL.pgsql.txt">INSTALL.pgsql.txt</a>'
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -168,9 +171,9 @@ class Tasks extends InstallTasks {
|
|||
'%setting' => 'bytea_output',
|
||||
'%current_value' => 'hex',
|
||||
'%needed_value' => 'escape',
|
||||
'!query' => "<code>" . $query . "</code>",
|
||||
'@query' => $query,
|
||||
);
|
||||
$this->fail(t("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: !query", $replacements));
|
||||
$this->fail(t("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: <code>@query</code>", $replacements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +187,58 @@ class Tasks extends InstallTasks {
|
|||
return ($bytea_output == 'escape');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures standard_conforming_strings setting is 'on'.
|
||||
*
|
||||
* When standard_conforming_strings setting is 'on' string literals ('...')
|
||||
* treat backslashes literally, as specified in the SQL standard. This allows
|
||||
* Drupal to convert between bytea, text and varchar columns.
|
||||
*/
|
||||
public function checkStandardConformingStrings() {
|
||||
$database_connection = Database::getConnection();
|
||||
if (!$this->checkStandardConformingStringsSuccess()) {
|
||||
// First try to alter the database. If it fails, raise an error telling
|
||||
// the user to do it themselves.
|
||||
$connection_options = $database_connection->getConnectionOptions();
|
||||
// It is safe to include the database name directly here, because this
|
||||
// code is only called when a connection to the database is already
|
||||
// established, thus the database name is guaranteed to be a correct
|
||||
// value.
|
||||
$query = "ALTER DATABASE \"" . $connection_options['database'] . "\" SET standard_conforming_strings = 'on';";
|
||||
try {
|
||||
$database_connection->query($query);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Ignore possible errors when the user doesn't have the necessary
|
||||
// privileges to ALTER the database.
|
||||
}
|
||||
|
||||
// Close the database connection so that the configuration parameter
|
||||
// is applied to the current connection.
|
||||
Database::closeConnection();
|
||||
|
||||
// Recheck, if it fails, finally just rely on the end user to do the
|
||||
// right thing.
|
||||
if (!$this->checkStandardConformingStringsSuccess()) {
|
||||
$replacements = array(
|
||||
'%setting' => 'standard_conforming_strings',
|
||||
'%current_value' => 'off',
|
||||
'%needed_value' => 'on',
|
||||
'@query' => $query,
|
||||
);
|
||||
$this->fail(t("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: <code>@query</code>", $replacements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the standard_conforming_strings setting.
|
||||
*/
|
||||
protected function checkStandardConformingStringsSuccess() {
|
||||
$standard_conforming_strings = Database::getConnection()->query("SHOW standard_conforming_strings")->fetchField();
|
||||
return ($standard_conforming_strings == 'on');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make PostgreSQL Drupal friendly.
|
||||
*/
|
||||
|
@ -195,18 +250,23 @@ class Tasks extends InstallTasks {
|
|||
// At the same time checking for the existence of the function fixes
|
||||
// concurrency issues, when both try to update at the same time.
|
||||
try {
|
||||
$connection = Database::getConnection();
|
||||
// Don't use {} around pg_proc table.
|
||||
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
|
||||
db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
|
||||
if (!$connection->query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
|
||||
$connection->query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
|
||||
\'SELECT random();\'
|
||||
LANGUAGE \'sql\''
|
||||
LANGUAGE \'sql\'',
|
||||
[],
|
||||
[ 'allow_delimiter_in_query' => TRUE ]
|
||||
);
|
||||
}
|
||||
|
||||
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'substring_index'")->fetchField()) {
|
||||
db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
|
||||
if (!$connection->query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'substring_index'")->fetchField()) {
|
||||
$connection->query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
|
||||
\'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
|
||||
LANGUAGE \'sql\''
|
||||
LANGUAGE \'sql\'',
|
||||
[],
|
||||
[ 'allow_delimiter_in_query' => TRUE ]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -726,17 +726,23 @@ class Schema extends DatabaseSchema {
|
|||
// Usually, we do this via a simple typecast 'USING fieldname::type'. But
|
||||
// the typecast does not work for conversions to bytea.
|
||||
// @see http://www.postgresql.org/docs/current/static/datatype-binary.html
|
||||
$table_information = $this->queryTableInformation($table);
|
||||
$is_bytea = !empty($table_information->blob_fields[$field]);
|
||||
if ($spec['pgsql_type'] != 'bytea') {
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $field_def . ' USING "' . $field . '"::' . $field_def);
|
||||
if ($is_bytea) {
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $field_def . ' USING convert_from("' . $field . '"' . ", 'UTF8')");
|
||||
}
|
||||
else {
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $field_def . ' USING "' . $field . '"::' . $field_def);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Do not attempt to convert a field that is bytea already.
|
||||
$table_information = $this->queryTableInformation($table);
|
||||
if (!in_array($field, $table_information->blob_fields)) {
|
||||
if (!$is_bytea) {
|
||||
// Convert to a bytea type by using the SQL replace() function to
|
||||
// convert any single backslashes in the field content to double
|
||||
// backslashes ('\' to '\\').
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $field_def . ' USING decode(replace("' . $field . '"' . ", '\\', '\\\\'), 'escape');");
|
||||
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $field_def . ' USING decode(replace("' . $field . '"' . ", E'\\\\', E'\\\\\\\\'), 'escape');");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class Schema extends DatabaseSchema {
|
|||
*/
|
||||
public function createTableSql($name, $table) {
|
||||
$sql = array();
|
||||
$sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumnsSql($name, $table) . "\n);\n";
|
||||
$sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumnsSql($name, $table) . "\n)\n";
|
||||
return array_merge($sql, $this->createIndexSql($name, $table));
|
||||
}
|
||||
|
||||
|
@ -60,12 +60,12 @@ class Schema extends DatabaseSchema {
|
|||
$info = $this->getPrefixInfo($tablename);
|
||||
if (!empty($schema['unique keys'])) {
|
||||
foreach ($schema['unique keys'] as $key => $fields) {
|
||||
$sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
|
||||
$sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . ")\n";
|
||||
}
|
||||
}
|
||||
if (!empty($schema['indexes'])) {
|
||||
foreach ($schema['indexes'] as $key => $fields) {
|
||||
$sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
|
||||
$sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . ")\n";
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
|
|
|
@ -188,6 +188,25 @@ class Condition implements ConditionInterface, \Countable {
|
|||
'operator' => $condition['operator'],
|
||||
'use_value' => TRUE,
|
||||
);
|
||||
// Remove potentially dangerous characters.
|
||||
// If something passed in an invalid character stop early, so we
|
||||
// don't rely on a broken SQL statement when we would just replace
|
||||
// those characters.
|
||||
if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) {
|
||||
$this->changed = TRUE;
|
||||
$this->arguments = [];
|
||||
// Provide a string which will result into an empty query result.
|
||||
$this->stringVersion = '( AND 1 = 0 )';
|
||||
|
||||
// Conceptually throwing an exception caused by user input is bad
|
||||
// as you result into a WSOD, which depending on your webserver
|
||||
// configuration can result into the assumption that your site is
|
||||
// broken.
|
||||
// On top of that the database API relies on __toString() which
|
||||
// does not allow to throw exceptions.
|
||||
trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$operator = $connection->mapConditionOperator($condition['operator']);
|
||||
if (!isset($operator)) {
|
||||
$operator = $this->mapConditionOperator($condition['operator']);
|
||||
|
|
|
@ -45,95 +45,140 @@ class SelectExtender implements SelectInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Database\Query\PlaceholderInterface::uniqueIdentifier().
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function uniqueIdentifier() {
|
||||
return $this->uniqueIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Database\Query\PlaceholderInterface::nextPlaceholder().
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nextPlaceholder() {
|
||||
return $this->placeholder++;
|
||||
}
|
||||
|
||||
/* Implementations of Drupal\Core\Database\Query\AlterableInterface. */
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTag($tag) {
|
||||
$this->query->addTag($tag);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasTag($tag) {
|
||||
return $this->query->hasTag($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasAllTags() {
|
||||
return call_user_func_array(array($this->query, 'hasAllTags'), func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasAnyTag() {
|
||||
return call_user_func_array(array($this->query, 'hasAnyTag'), func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addMetaData($key, $object) {
|
||||
$this->query->addMetaData($key, $object);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetaData($key) {
|
||||
return $this->query->getMetaData($key);
|
||||
}
|
||||
|
||||
/* Implementations of Drupal\Core\Database\Query\ConditionInterface for the WHERE clause. */
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function condition($field, $value = NULL, $operator = '=') {
|
||||
$this->query->condition($field, $value, $operator);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &conditions() {
|
||||
return $this->query->conditions();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function arguments() {
|
||||
return $this->query->arguments();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function where($snippet, $args = array()) {
|
||||
$this->query->where($snippet, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
|
||||
return $this->query->compile($connection, $queryPlaceholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compiled() {
|
||||
return $this->query->compiled();
|
||||
}
|
||||
|
||||
/* Implementations of Drupal\Core\Database\Query\ConditionInterface for the HAVING clause. */
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function havingCondition($field, $value = NULL, $operator = '=') {
|
||||
$this->query->havingCondition($field, $value, $operator);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &havingConditions() {
|
||||
return $this->query->havingConditions();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function havingArguments() {
|
||||
return $this->query->havingArguments();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function having($snippet, $args = array()) {
|
||||
$this->query->having($snippet, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function havingCompile(Connection $connection) {
|
||||
return $this->query->havingCompile($connection);
|
||||
}
|
||||
|
@ -170,8 +215,9 @@ class SelectExtender implements SelectInterface {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/* Implementations of Drupal\Core\Database\Query\ExtendableInterface. */
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extend($extender_name) {
|
||||
$class = $this->connection->getDriverClass($extender_name);
|
||||
return new $class($this, $this->connection);
|
||||
|
@ -179,26 +225,44 @@ class SelectExtender implements SelectInterface {
|
|||
|
||||
/* Alter accessors to expose the query data to alter hooks. */
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &getFields() {
|
||||
return $this->query->getFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &getExpressions() {
|
||||
return $this->query->getExpressions();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &getOrderBy() {
|
||||
return $this->query->getOrderBy();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &getGroupBy() {
|
||||
return $this->query->getGroupBy();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &getTables() {
|
||||
return $this->query->getTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &getUnion() {
|
||||
return $this->query->getUnion();
|
||||
}
|
||||
|
@ -218,14 +282,23 @@ class SelectExtender implements SelectInterface {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArguments(PlaceholderInterface $queryPlaceholder = NULL) {
|
||||
return $this->query->getArguments($queryPlaceholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isPrepared() {
|
||||
return $this->query->isPrepared();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preExecute(SelectInterface $query = NULL) {
|
||||
// If no query object is passed in, use $this.
|
||||
if (!isset($query)) {
|
||||
|
@ -235,6 +308,9 @@ class SelectExtender implements SelectInterface {
|
|||
return $this->query->preExecute($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute() {
|
||||
// By calling preExecute() here, we force it to preprocess the extender
|
||||
// object rather than just the base query object. That means
|
||||
|
@ -246,102 +322,168 @@ class SelectExtender implements SelectInterface {
|
|||
return $this->query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function distinct($distinct = TRUE) {
|
||||
$this->query->distinct($distinct);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addField($table_alias, $field, $alias = NULL) {
|
||||
return $this->query->addField($table_alias, $field, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields($table_alias, array $fields = array()) {
|
||||
$this->query->fields($table_alias, $fields);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addExpression($expression, $alias = NULL, $arguments = array()) {
|
||||
return $this->query->addExpression($expression, $alias, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function join($table, $alias = NULL, $condition = NULL, $arguments = array()) {
|
||||
return $this->query->join($table, $alias, $condition, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
|
||||
return $this->query->innerJoin($table, $alias, $condition, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
|
||||
return $this->query->leftJoin($table, $alias, $condition, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
|
||||
return $this->query->rightJoin($table, $alias, $condition, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) {
|
||||
return $this->query->addJoin($type, $table, $alias, $condition, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function orderBy($field, $direction = 'ASC') {
|
||||
$this->query->orderBy($field, $direction);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function orderRandom() {
|
||||
$this->query->orderRandom();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function range($start = NULL, $length = NULL) {
|
||||
$this->query->range($start, $length);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function union(SelectInterface $query, $type = '') {
|
||||
$this->query->union($query, $type);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function groupBy($field) {
|
||||
$this->query->groupBy($field);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function forUpdate($set = TRUE) {
|
||||
$this->query->forUpdate($set);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countQuery() {
|
||||
return $this->query->countQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function isNull($field) {
|
||||
$this->query->isNull($field);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function isNotNull($field) {
|
||||
$this->query->isNotNull($field);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists(SelectInterface $select) {
|
||||
$this->query->exists($select);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function notExists(SelectInterface $select) {
|
||||
$this->query->notExists($select);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString() {
|
||||
return (string) $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->uniqueIdentifier = uniqid('', TRUE);
|
||||
|
||||
|
|
|
@ -648,4 +648,12 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
|
|||
*/
|
||||
public function forUpdate($set = TRUE);
|
||||
|
||||
/**
|
||||
* Returns a string representation of how the query will be executed in SQL.
|
||||
*
|
||||
* @return string
|
||||
* The Select Query object expressed as a string.
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
}
|
||||
|
|
|
@ -640,6 +640,8 @@ abstract class Schema implements PlaceholderInterface {
|
|||
* The prepared comment.
|
||||
*/
|
||||
public function prepareComment($comment, $length = NULL) {
|
||||
// Remove semicolons to avoid triggering multi-statement check.
|
||||
$comment = strtr($comment, [';' => '.']);
|
||||
return $this->connection->quote($comment);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Datetime;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Datetime\DrupalDateTime;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
|
@ -22,7 +21,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
|
|||
*
|
||||
* @ingroup i18n
|
||||
*/
|
||||
class DateFormatter {
|
||||
class DateFormatter implements DateFormatterInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
|
@ -105,33 +104,7 @@ class DateFormatter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Formats a date, using a date type or a custom date format string.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* A UNIX timestamp to format.
|
||||
* @param string $type
|
||||
* (optional) The format to use, one of:
|
||||
* - One of the built-in formats: 'short', 'medium',
|
||||
* 'long', 'html_datetime', 'html_date', 'html_time',
|
||||
* 'html_yearless_date', 'html_week', 'html_month', 'html_year'.
|
||||
* - The name of a date type defined by a date format config entity.
|
||||
* - The machine name of an administrator-defined date format.
|
||||
* - 'custom', to use $format.
|
||||
* Defaults to 'medium'.
|
||||
* @param string $format
|
||||
* (optional) If $type is 'custom', a PHP date format string suitable for
|
||||
* input to date(). Use a backslash to escape ordinary text, so it does not
|
||||
* get interpreted as date format characters.
|
||||
* @param string|null $timezone
|
||||
* (optional) Time zone identifier, as described at
|
||||
* http://php.net/manual/timezones.php Defaults to the time zone used to
|
||||
* display the page.
|
||||
* @param string|null $langcode
|
||||
* (optional) Language code to translate to. NULL (default) means to use
|
||||
* the user interface language for the page.
|
||||
*
|
||||
* @return string
|
||||
* A translated date string in the requested format.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
|
||||
if (!isset($timezone)) {
|
||||
|
@ -168,32 +141,11 @@ class DateFormatter {
|
|||
$settings = array(
|
||||
'langcode' => $langcode,
|
||||
);
|
||||
return Xss::filter($date->format($format, $settings));
|
||||
return $date->format($format, $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a time interval with the requested granularity.
|
||||
*
|
||||
* Note that for intervals over 30 days, the output is approximate: a "month"
|
||||
* is always exactly 30 days, and a "year" is always 365 days. It is not
|
||||
* possible to make a more exact representation, given that there is only one
|
||||
* input in seconds. If you are formatting an interval between two specific
|
||||
* timestamps, use \Drupal\Core\Datetime\DateFormatter::formatDiff() instead.
|
||||
*
|
||||
* @param int $interval
|
||||
* The length of the interval in seconds.
|
||||
* @param int $granularity
|
||||
* (optional) How many different units to display in the string (2 by
|
||||
* default).
|
||||
* @param string|null $langcode
|
||||
* (optional) langcode: The language code for the language used to format
|
||||
* the date. Defaults to NULL, which results in the user interface language
|
||||
* for the page being used.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the interval.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatDiff()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatInterval($interval, $granularity = 2, $langcode = NULL) {
|
||||
$output = '';
|
||||
|
@ -218,21 +170,7 @@ class DateFormatter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Provides values for all date formatting characters for a given timestamp.
|
||||
*
|
||||
* @param string|null $langcode
|
||||
* (optional) Language code of the date format, if different from the site
|
||||
* default language.
|
||||
* @param int|null $timestamp
|
||||
* (optional) The Unix timestamp to format, defaults to current time.
|
||||
* @param string|null $timezone
|
||||
* (optional) The timezone to use, if different from the site's default
|
||||
* timezone.
|
||||
*
|
||||
* @return array
|
||||
* An array of formatted date values, indexed by the date format character.
|
||||
*
|
||||
* @see date()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSampleDateFormats($langcode = NULL, $timestamp = NULL, $timezone = NULL) {
|
||||
$timestamp = $timestamp ?: time();
|
||||
|
@ -245,30 +183,7 @@ class DateFormatter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Formats the time difference from the current request time to a timestamp.
|
||||
*
|
||||
* @param $timestamp
|
||||
* A UNIX timestamp to compare against the current request time.
|
||||
* @param array $options
|
||||
* (optional) An associative array with additional options. The following
|
||||
* keys can be used:
|
||||
* - granularity: An integer value that signals how many different units to
|
||||
* display in the string. Defaults to 2.
|
||||
* - langcode: The language code for the language used to format the date.
|
||||
* Defaults to NULL, which results in the user interface language for the
|
||||
* page being used.
|
||||
* - strict: A Boolean value indicating whether or not the timestamp can be
|
||||
* before the current request time. If TRUE (default) and $timestamp is
|
||||
* before the current request time, the result string will be "0 seconds".
|
||||
* If FALSE and $timestamp is before the current request time, the result
|
||||
* string will be the formatted time difference.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the difference between the given
|
||||
* timestamp and the current request time. This interval is always positive.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatDiff()
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffSince()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatTimeDiffUntil($timestamp, $options = array()) {
|
||||
$request_time = $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
|
||||
|
@ -276,30 +191,7 @@ class DateFormatter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Formats the time difference from a timestamp to the current request time.
|
||||
*
|
||||
* @param $timestamp
|
||||
* A UNIX timestamp to compare against the current request time.
|
||||
* @param array $options
|
||||
* (optional) An associative array with additional options. The following
|
||||
* keys can be used:
|
||||
* - granularity: An integer value that signals how many different units to
|
||||
* display in the string. Defaults to 2.
|
||||
* - langcode: The language code for the language used to format the date.
|
||||
* Defaults to NULL, which results in the user interface language for the
|
||||
* page being used.
|
||||
* - strict: A Boolean value indicating whether or not the timestamp can be
|
||||
* after the current request time. If TRUE (default) and $timestamp is
|
||||
* after the current request time, the result string will be "0 seconds".
|
||||
* If FALSE and $timestamp is after the current request time, the result
|
||||
* string will be the formatted time difference.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the difference between the given
|
||||
* timestamp and the current request time. This interval is always positive.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatDiff()
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffUntil()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatTimeDiffSince($timestamp, $options = array()) {
|
||||
$request_time = $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
|
||||
|
@ -307,32 +199,7 @@ class DateFormatter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Formats a time interval between two timestamps.
|
||||
*
|
||||
* @param int $from
|
||||
* A UNIX timestamp, defining the from date and time.
|
||||
* @param int $to
|
||||
* A UNIX timestamp, defining the to date and time.
|
||||
* @param array $options
|
||||
* (optional) An associative array with additional options. The following
|
||||
* keys can be used:
|
||||
* - granularity: An integer value that signals how many different units to
|
||||
* display in the string. Defaults to 2.
|
||||
* - langcode: The language code for the language used to format the date.
|
||||
* Defaults to NULL, which results in the user interface language for the
|
||||
* page being used.
|
||||
* - strict: A Boolean value indicating whether or not the $from timestamp
|
||||
* can be after the $to timestamp. If TRUE (default) and $from is after
|
||||
* $to, the result string will be "0 seconds". If FALSE and $from is
|
||||
* after $to, the result string will be the formatted time difference.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the interval. This interval is
|
||||
* always positive.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatInterval()
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffSince()
|
||||
* @see \Drupal\Core\Datetime\DateFormatter::formatTimeDiffUntil()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatDiff($from, $to, $options = array()) {
|
||||
|
||||
|
|
178
core/lib/Drupal/Core/Datetime/DateFormatterInterface.php
Normal file
178
core/lib/Drupal/Core/Datetime/DateFormatterInterface.php
Normal file
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Datetime\DateFormatterInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Datetime;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a date formatter.
|
||||
*/
|
||||
interface DateFormatterInterface {
|
||||
|
||||
/**
|
||||
* Formats a date, using a date type or a custom date format string.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* A UNIX timestamp to format.
|
||||
* @param string $type
|
||||
* (optional) The format to use, one of:
|
||||
* - One of the built-in formats: 'short', 'medium',
|
||||
* 'long', 'html_datetime', 'html_date', 'html_time',
|
||||
* 'html_yearless_date', 'html_week', 'html_month', 'html_year'.
|
||||
* - The name of a date type defined by a date format config entity.
|
||||
* - The machine name of an administrator-defined date format.
|
||||
* - 'custom', to use $format.
|
||||
* Defaults to 'medium'.
|
||||
* @param string $format
|
||||
* (optional) If $type is 'custom', a PHP date format string suitable for
|
||||
* input to date(). Use a backslash to escape ordinary text, so it does not
|
||||
* get interpreted as date format characters.
|
||||
* @param string|null $timezone
|
||||
* (optional) Time zone identifier, as described at
|
||||
* http://php.net/manual/timezones.php Defaults to the time zone used to
|
||||
* display the page.
|
||||
* @param string|null $langcode
|
||||
* (optional) Language code to translate to. NULL (default) means to use
|
||||
* the user interface language for the page.
|
||||
*
|
||||
* @return string
|
||||
* A translated date string in the requested format. Since the format may
|
||||
* contain user input, this value should be escaped when output.
|
||||
*/
|
||||
public function format($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL);
|
||||
|
||||
/**
|
||||
* Formats a time interval with the requested granularity.
|
||||
*
|
||||
* Note that for intervals over 30 days, the output is approximate: a "month"
|
||||
* is always exactly 30 days, and a "year" is always 365 days. It is not
|
||||
* possible to make a more exact representation, given that there is only one
|
||||
* input in seconds. If you are formatting an interval between two specific
|
||||
* timestamps, use \Drupal\Core\Datetime\DateFormatter::formatDiff() instead.
|
||||
*
|
||||
* @param int $interval
|
||||
* The length of the interval in seconds.
|
||||
* @param int $granularity
|
||||
* (optional) How many different units to display in the string (2 by
|
||||
* default).
|
||||
* @param string|null $langcode
|
||||
* (optional) langcode: The language code for the language used to format
|
||||
* the date. Defaults to NULL, which results in the user interface language
|
||||
* for the page being used.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the interval.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatDiff()
|
||||
*/
|
||||
public function formatInterval($interval, $granularity = 2, $langcode = NULL);
|
||||
|
||||
/**
|
||||
* Provides values for all date formatting characters for a given timestamp.
|
||||
*
|
||||
* @param string|null $langcode
|
||||
* (optional) Language code of the date format, if different from the site
|
||||
* default language.
|
||||
* @param int|null $timestamp
|
||||
* (optional) The Unix timestamp to format, defaults to current time.
|
||||
* @param string|null $timezone
|
||||
* (optional) The timezone to use, if different from the site's default
|
||||
* timezone.
|
||||
*
|
||||
* @return array
|
||||
* An array of formatted date values, indexed by the date format character.
|
||||
*
|
||||
* @see date()
|
||||
*/
|
||||
public function getSampleDateFormats($langcode = NULL, $timestamp = NULL, $timezone = NULL);
|
||||
|
||||
/**
|
||||
* Formats the time difference from the current request time to a timestamp.
|
||||
*
|
||||
* @param $timestamp
|
||||
* A UNIX timestamp to compare against the current request time.
|
||||
* @param array $options
|
||||
* (optional) An associative array with additional options. The following
|
||||
* keys can be used:
|
||||
* - granularity: An integer value that signals how many different units to
|
||||
* display in the string. Defaults to 2.
|
||||
* - langcode: The language code for the language used to format the date.
|
||||
* Defaults to NULL, which results in the user interface language for the
|
||||
* page being used.
|
||||
* - strict: A Boolean value indicating whether or not the timestamp can be
|
||||
* before the current request time. If TRUE (default) and $timestamp is
|
||||
* before the current request time, the result string will be "0 seconds".
|
||||
* If FALSE and $timestamp is before the current request time, the result
|
||||
* string will be the formatted time difference.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the difference between the given
|
||||
* timestamp and the current request time. This interval is always positive.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatDiff()
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatTimeDiffSince()
|
||||
*/
|
||||
public function formatTimeDiffUntil($timestamp, $options = array());
|
||||
|
||||
/**
|
||||
* Formats the time difference from a timestamp to the current request time.
|
||||
*
|
||||
* @param $timestamp
|
||||
* A UNIX timestamp to compare against the current request time.
|
||||
* @param array $options
|
||||
* (optional) An associative array with additional options. The following
|
||||
* keys can be used:
|
||||
* - granularity: An integer value that signals how many different units to
|
||||
* display in the string. Defaults to 2.
|
||||
* - langcode: The language code for the language used to format the date.
|
||||
* Defaults to NULL, which results in the user interface language for the
|
||||
* page being used.
|
||||
* - strict: A Boolean value indicating whether or not the timestamp can be
|
||||
* after the current request time. If TRUE (default) and $timestamp is
|
||||
* after the current request time, the result string will be "0 seconds".
|
||||
* If FALSE and $timestamp is after the current request time, the result
|
||||
* string will be the formatted time difference.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the difference between the given
|
||||
* timestamp and the current request time. This interval is always positive.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatDiff()
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatTimeDiffUntil()
|
||||
*/
|
||||
public function formatTimeDiffSince($timestamp, $options = array());
|
||||
|
||||
/**
|
||||
* Formats a time interval between two timestamps.
|
||||
*
|
||||
* @param int $from
|
||||
* A UNIX timestamp, defining the from date and time.
|
||||
* @param int $to
|
||||
* A UNIX timestamp, defining the to date and time.
|
||||
* @param array $options
|
||||
* (optional) An associative array with additional options. The following
|
||||
* keys can be used:
|
||||
* - granularity: An integer value that signals how many different units to
|
||||
* display in the string. Defaults to 2.
|
||||
* - langcode: The language code for the language used to format the date.
|
||||
* Defaults to NULL, which results in the user interface language for the
|
||||
* page being used.
|
||||
* - strict: A Boolean value indicating whether or not the $from timestamp
|
||||
* can be after the $to timestamp. If TRUE (default) and $from is after
|
||||
* $to, the result string will be "0 seconds". If FALSE and $from is
|
||||
* after $to, the result string will be the formatted time difference.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the interval. This interval is
|
||||
* always positive.
|
||||
*
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatInterval()
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatTimeDiffSince()
|
||||
* @see \Drupal\Core\Datetime\DateFormatterInterface::formatTimeDiffUntil()
|
||||
*/
|
||||
public function formatDiff($from, $to, $options = array());
|
||||
|
||||
}
|
|
@ -90,7 +90,8 @@ class DrupalDateTime extends DateTimePlus {
|
|||
* the result of the format() method. Defaults to NULL.
|
||||
*
|
||||
* @return string
|
||||
* The formatted value of the date.
|
||||
* The formatted value of the date. Since the format may contain user input,
|
||||
* this value should be escaped when output.
|
||||
*/
|
||||
public function format($format, $settings = array()) {
|
||||
$langcode = !empty($settings['langcode']) ? $settings['langcode'] : $this->langcode;
|
||||
|
|
|
@ -241,7 +241,7 @@ class Datetime extends DateElementBase {
|
|||
// placeholders are invalid for HTML5 date and datetime, so an example
|
||||
// format is appended to the title to appear in tooltips.
|
||||
$extra_attributes = array(
|
||||
'title' => t('Date (e.g. !format)', array('!format' => static::formatExample($date_format))),
|
||||
'title' => t('Date (e.g. @format)', array('@format' => static::formatExample($date_format))),
|
||||
'type' => $element['#date_date_element'],
|
||||
);
|
||||
|
||||
|
@ -288,7 +288,7 @@ class Datetime extends DateElementBase {
|
|||
|
||||
// Adds the HTML5 attributes.
|
||||
$extra_attributes = array(
|
||||
'title' => t('Time (e.g. !format)', array('!format' => static::formatExample($time_format))),
|
||||
'title' => t('Time (e.g. @format)', array('@format' => static::formatExample($time_format))),
|
||||
'type' => $element['#date_time_element'],
|
||||
'step' => $element['#date_increment'],
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass.
|
||||
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterEventSubscribersPass.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\DependencyInjection\Compiler;
|
||||
|
@ -10,7 +10,10 @@ namespace Drupal\Core\DependencyInjection\Compiler;
|
|||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
class RegisterKernelListenersPass implements CompilerPassInterface {
|
||||
/**
|
||||
* Registers all event subscribers to the event dispatcher.
|
||||
*/
|
||||
class RegisterEventSubscribersPass implements CompilerPassInterface {
|
||||
public function process(ContainerBuilder $container) {
|
||||
if (!$container->hasDefinition('event_dispatcher')) {
|
||||
return;
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Display\ContextAwareVariantInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Display;
|
||||
|
||||
/**
|
||||
* Provides an interface for variant plugins that are context-aware.
|
||||
*/
|
||||
interface ContextAwareVariantInterface extends VariantInterface {
|
||||
|
||||
/**
|
||||
* Gets the values for all defined contexts.
|
||||
*
|
||||
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
|
||||
* An array of set contexts, keyed by context name.
|
||||
*/
|
||||
public function getContexts();
|
||||
|
||||
/**
|
||||
* Sets the context values for this display variant.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
|
||||
* An array of contexts, keyed by context name.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setContexts(array $contexts);
|
||||
|
||||
}
|
|
@ -36,4 +36,15 @@ interface PageVariantInterface extends VariantInterface {
|
|||
*/
|
||||
public function setMainContent(array $main_content);
|
||||
|
||||
/**
|
||||
* Sets the title for the page being rendered.
|
||||
*
|
||||
* @param string|array $title
|
||||
* The page title: either a string for plain titles or a render array for
|
||||
* formatted titles.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTitle($title);
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Display;
|
||||
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\PluginDependencyTrait;
|
||||
|
@ -23,6 +24,7 @@ use Drupal\Core\Session\AccountInterface;
|
|||
abstract class VariantBase extends PluginBase implements VariantInterface {
|
||||
|
||||
use PluginDependencyTrait;
|
||||
use RefinableCacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Drupal\Core\Display;
|
|||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
|
@ -20,7 +21,7 @@ use Drupal\Core\Session\AccountInterface;
|
|||
* @see \Drupal\Core\Display\VariantManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface VariantInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {
|
||||
interface VariantInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface, RefinableCacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* Returns the user-facing display variant label.
|
||||
|
@ -79,6 +80,10 @@ interface VariantInterface extends PluginInspectionInterface, ConfigurablePlugin
|
|||
/**
|
||||
* Builds and returns the renderable array for the display variant.
|
||||
*
|
||||
* The variant can contain cacheability metadata for the configuration that
|
||||
* was passed in setConfiguration(). In the build() method, this should be
|
||||
* added to the render array that is returned.
|
||||
*
|
||||
* @return array
|
||||
* A render array for the display variant.
|
||||
*/
|
||||
|
|
|
@ -992,47 +992,32 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
|
|||
* @todo D8: Eliminate this entirely in favor of Request object.
|
||||
*/
|
||||
protected function initializeRequestGlobals(Request $request) {
|
||||
// Provided by settings.php.
|
||||
global $base_url;
|
||||
// Set and derived from $base_url by this function.
|
||||
global $base_path, $base_root;
|
||||
global $base_secure_url, $base_insecure_url;
|
||||
|
||||
// @todo Refactor with the Symfony Request object.
|
||||
if (isset($base_url)) {
|
||||
// Parse fixed base URL from settings.php.
|
||||
$parts = parse_url($base_url);
|
||||
if (!isset($parts['path'])) {
|
||||
$parts['path'] = '';
|
||||
}
|
||||
$base_path = $parts['path'] . '/';
|
||||
// Build $base_root (everything until first slash after "scheme://").
|
||||
$base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path']));
|
||||
}
|
||||
else {
|
||||
// Create base URL.
|
||||
$base_root = $request->getSchemeAndHttpHost();
|
||||
// Create base URL.
|
||||
$base_root = $request->getSchemeAndHttpHost();
|
||||
$base_url = $base_root;
|
||||
|
||||
$base_url = $base_root;
|
||||
|
||||
// For a request URI of '/index.php/foo', $_SERVER['SCRIPT_NAME'] is
|
||||
// '/index.php', whereas $_SERVER['PHP_SELF'] is '/index.php/foo'.
|
||||
if ($dir = rtrim(dirname($request->server->get('SCRIPT_NAME')), '\/')) {
|
||||
// Remove "core" directory if present, allowing install.php,
|
||||
// authorize.php, and others to auto-detect a base path.
|
||||
$core_position = strrpos($dir, '/core');
|
||||
if ($core_position !== FALSE && strlen($dir) - 5 == $core_position) {
|
||||
$base_path = substr($dir, 0, $core_position);
|
||||
}
|
||||
else {
|
||||
$base_path = $dir;
|
||||
}
|
||||
$base_url .= $base_path;
|
||||
$base_path .= '/';
|
||||
// For a request URI of '/index.php/foo', $_SERVER['SCRIPT_NAME'] is
|
||||
// '/index.php', whereas $_SERVER['PHP_SELF'] is '/index.php/foo'.
|
||||
if ($dir = rtrim(dirname($request->server->get('SCRIPT_NAME')), '\/')) {
|
||||
// Remove "core" directory if present, allowing install.php,
|
||||
// authorize.php, and others to auto-detect a base path.
|
||||
$core_position = strrpos($dir, '/core');
|
||||
if ($core_position !== FALSE && strlen($dir) - 5 == $core_position) {
|
||||
$base_path = substr($dir, 0, $core_position);
|
||||
}
|
||||
else {
|
||||
$base_path = '/';
|
||||
$base_path = $dir;
|
||||
}
|
||||
$base_url .= $base_path;
|
||||
$base_path .= '/';
|
||||
}
|
||||
else {
|
||||
$base_path = '/';
|
||||
}
|
||||
$base_secure_url = str_replace('http://', 'https://', $base_url);
|
||||
$base_insecure_url = str_replace('https://', 'http://', $base_url);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Annotation;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines a config entity type annotation object.
|
||||
|
@ -37,7 +37,7 @@ class ConfigEntityType extends EntityType {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {
|
||||
$this->definition['group_label'] = new TranslationWrapper('Configuration', array(), array('context' => 'Entity type group'));
|
||||
$this->definition['group_label'] = new TranslatableMarkup('Configuration', array(), array('context' => 'Entity type group'));
|
||||
|
||||
return parent::get();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Annotation;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines a content entity type annotation object.
|
||||
|
@ -37,7 +37,7 @@ class ContentEntityType extends EntityType {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {
|
||||
$this->definition['group_label'] = new TranslationWrapper('Content', array(), array('context' => 'Entity type group'));
|
||||
$this->definition['group_label'] = new TranslatableMarkup('Content', array(), array('context' => 'Entity type group'));
|
||||
|
||||
return parent::get();
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ use Drupal\Component\Annotation\Plugin;
|
|||
*
|
||||
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
|
||||
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
|
||||
* @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase
|
||||
* @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
|
|
41
core/lib/Drupal/Core/Entity/BundleEntityFormBase.php
Normal file
41
core/lib/Drupal/Core/Entity/BundleEntityFormBase.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\BundleEntityFormBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
/**
|
||||
* Class BundleEntityFormBase is a base form for bundle config entities.
|
||||
*/
|
||||
class BundleEntityFormBase extends EntityForm {
|
||||
|
||||
/**
|
||||
* Protects the bundle entity's ID property's form element against changes.
|
||||
*
|
||||
* This method is assumed to be called on a completely built entity form,
|
||||
* including a form element for the bundle config entity's ID property.
|
||||
*
|
||||
* @param array $form
|
||||
* The completely built entity bundle form array.
|
||||
*
|
||||
* @return array
|
||||
* The updated entity bundle form array.
|
||||
*/
|
||||
protected function protectBundleIdElement(array $form) {
|
||||
$entity = $this->getEntity();
|
||||
$id_key = $entity->getEntityType()->getKey('id');
|
||||
assert('isset($form[$id_key])');
|
||||
$element = &$form[$id_key];
|
||||
|
||||
// Make sure the element is not accidentally re-enabled if it has already
|
||||
// been disabled.
|
||||
if (empty($element['#disabled'])) {
|
||||
$element['#disabled'] = !$entity->isNew();
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
|
@ -70,7 +70,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
/**
|
||||
* Local cache for the available language objects.
|
||||
*
|
||||
* @var array
|
||||
* @var \Drupal\Core\Language\LanguageInterface[]
|
||||
*/
|
||||
protected $languages;
|
||||
|
||||
|
@ -138,7 +138,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
protected $isDefaultRevision = TRUE;
|
||||
|
||||
/**
|
||||
* Holds translatable entity keys such as the ID, bundle and revision ID.
|
||||
* Holds untranslatable entity keys such as the ID, bundle, and revision ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
@ -593,7 +593,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
}
|
||||
return $this->entityManager()
|
||||
->getAccessControlHandler($this->entityTypeId)
|
||||
->access($this, $operation, $this->activeLangcode, $account, $return_as_object);
|
||||
->access($this, $operation, $account, $return_as_object);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -737,22 +737,11 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
if (isset($this->translations[$langcode]['entity'])) {
|
||||
$translation = $this->translations[$langcode]['entity'];
|
||||
}
|
||||
else {
|
||||
if (isset($this->translations[$langcode])) {
|
||||
$translation = $this->initializeTranslation($langcode);
|
||||
$this->translations[$langcode]['entity'] = $translation;
|
||||
}
|
||||
else {
|
||||
// If we were given a valid language and there is no translation for it,
|
||||
// we return a new one.
|
||||
$this->getLanguages();
|
||||
if (isset($this->languages[$langcode])) {
|
||||
// If the entity or the requested language is not a configured
|
||||
// language, we fall back to the entity itself, since in this case it
|
||||
// cannot have translations.
|
||||
$translation = !$this->languages[$this->defaultLangcode]->isLocked() && !$this->languages[$langcode]->isLocked() ? $this->addTranslation($langcode) : $this;
|
||||
}
|
||||
}
|
||||
// Otherwise if an existing translation language was specified we need to
|
||||
// instantiate the related translation.
|
||||
elseif (isset($this->translations[$langcode])) {
|
||||
$translation = $this->initializeTranslation($langcode);
|
||||
$this->translations[$langcode]['entity'] = $translation;
|
||||
}
|
||||
|
||||
if (empty($translation)) {
|
||||
|
@ -823,10 +812,15 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTranslation($langcode, array $values = array()) {
|
||||
// Make sure we do not attempt to create a translation if an invalid
|
||||
// language is specified or the entity cannot be translated.
|
||||
$this->getLanguages();
|
||||
if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) {
|
||||
if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode) || $this->languages[$langcode]->isLocked()) {
|
||||
throw new \InvalidArgumentException("Invalid translation language ($langcode) specified.");
|
||||
}
|
||||
if ($this->languages[$this->defaultLangcode]->isLocked()) {
|
||||
throw new \InvalidArgumentException("The entity cannot be translated since it is language neutral ({$this->defaultLangcode}).");
|
||||
}
|
||||
|
||||
// Instantiate a new empty entity so default values will be populated in the
|
||||
// specified language.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue