updated core to 8.6.1 via composer

This commit is contained in:
2018-09-12 13:58:26 +02:00
parent a9a219f2ed
commit ea56b9fba3
4443 changed files with 112098 additions and 40708 deletions
@@ -333,7 +333,6 @@ class Inspector {
return FALSE;
}
/**
* Asserts that all members are strings matching a regular expression.
*
@@ -4,6 +4,7 @@ namespace Drupal\Component\Bridge;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Zend\Feed\Reader\ExtensionManagerInterface as ReaderManagerInterface;
use Zend\Feed\Writer\ExtensionManagerInterface as WriterManagerInterface;
@@ -48,6 +49,11 @@ class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterMan
*/
protected $canonicalNames;
/**
* @var \Zend\Feed\Reader\ExtensionManagerInterface|\Zend\Feed\Writer\ExtensionManagerInterface
*/
protected $standalone;
/**
* Constructs a ZfExtensionManagerSfContainer object.
*
@@ -62,14 +68,25 @@ class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterMan
* {@inheritdoc}
*/
public function get($extension) {
return $this->container->get($this->prefix . $this->canonicalizeName($extension));
try {
return $this->container->get($this->prefix . $this->canonicalizeName($extension));
}
catch (ServiceNotFoundException $e) {
if ($this->standalone && $this->standalone->has($extension)) {
return $this->standalone->get($extension);
}
throw $e;
}
}
/**
* {@inheritdoc}
*/
public function has($extension) {
return $this->container->has($this->prefix . $this->canonicalizeName($extension));
if ($this->container->has($this->prefix . $this->canonicalizeName($extension))) {
return TRUE;
}
return $this->standalone && $this->standalone->has($extension);
}
/**
@@ -102,4 +119,14 @@ class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterMan
$this->container = $container;
}
/**
* @param $class
*/
public function setStandalone($class) {
if (!is_subclass_of($class, ReaderManagerInterface::class) && !is_subclass_of($class, WriterManagerInterface::class)) {
throw new \RuntimeException("$class must implement Zend\Feed\Reader\ExtensionManagerInterface or Zend\Feed\Writer\ExtensionManagerInterface");
}
$this->standalone = new $class();
}
}
@@ -41,12 +41,12 @@ class DateTimePlus {
use ToStringTrait;
const FORMAT = 'Y-m-d H:i:s';
const FORMAT = 'Y-m-d H:i:s';
/**
* A RFC7231 Compliant date.
*
* http://tools.ietf.org/html/rfc7231#section-7.1.1.1
* @see http://tools.ietf.org/html/rfc7231#section-7.1.1.1
*
* Example: Sun, 06 Nov 1994 08:49:37 GMT
*/
@@ -477,7 +477,6 @@ class DateTimePlus {
return $format;
}
/**
* Examines getLastErrors() to see what errors to report.
*
@@ -671,7 +670,7 @@ class DateTimePlus {
* Formats the date for display.
*
* @param string $format
* A format string using either PHP's date().
* Format accepted by date().
* @param array $settings
* - timezone: (optional) String timezone name. Defaults to the timezone
* of the date object.
@@ -715,4 +714,14 @@ class DateTimePlus {
$this->dateTimeObject->setTime(12, 0, 0);
}
/**
* Gets a clone of the proxied PHP \DateTime object wrapped by this class.
*
* @return \DateTime
* A clone of the wrapped PHP \DateTime object.
*/
public function getPhpDateTime() {
return clone $this->dateTimeObject;
}
}
@@ -282,7 +282,6 @@ class OptimizedPhpArrayDumper extends Dumper {
return $code;
}
/**
* Dumps a collection to a PHP array.
*
@@ -2,8 +2,6 @@
namespace Drupal\Component\Diff\Engine;
use Drupal\Component\Utility\Unicode;
/**
* Class used internally by Diff to actually compute the diffs.
*
@@ -134,7 +132,7 @@ class DiffEngine {
* Returns the whole line if it's small enough, or the MD5 hash otherwise.
*/
protected function _line_hash($line) {
if (Unicode::strlen($line) > $this::MAX_XREF_LENGTH) {
if (mb_strlen($line) > $this::MAX_XREF_LENGTH) {
return md5($line);
}
else {
@@ -2,8 +2,6 @@
namespace Drupal\Component\Diff\Engine;
use Drupal\Component\Utility\Unicode;
/**
* Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
*/
@@ -64,7 +62,7 @@ class HWLDFWordAccumulator {
}
if ($word[0] == "\n") {
$this->_flushLine($tag);
$word = Unicode::substr($word, 1);
$word = mb_substr($word, 1);
}
assert(!strstr($word, "\n"));
$this->group .= $word;
@@ -3,7 +3,6 @@
namespace Drupal\Component\Diff;
use Drupal\Component\Diff\Engine\HWLDFWordAccumulator;
use Drupal\Component\Utility\Unicode;
/**
* @todo document
@@ -35,7 +34,7 @@ class WordLevelDiff extends MappedDiff {
$words[] = "\n";
$stripped[] = "\n";
}
if (Unicode::strlen($line) > $this::MAX_LINE_LENGTH) {
if (mb_strlen($line) > $this::MAX_LINE_LENGTH) {
$words[] = $line;
$stripped[] = $line;
}
+1 -1
View File
@@ -6,7 +6,7 @@
"license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
"drupal/core-utility": "^8.2"
"symfony/polyfill-mbstring": "~1.0"
},
"autoload": {
"psr-4": {
@@ -36,7 +36,7 @@ class ContainerAwareEventDispatcher implements EventDispatcherInterface {
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface;
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
+22 -22
View File
@@ -27,42 +27,42 @@ class PoHeader {
*
* @var string
*/
private $_langcode;
protected $langcode;
/**
* Formula for the plural form.
*
* @var string
*/
private $_pluralForms;
protected $pluralForms;
/**
* Author(s) of the file.
*
* @var string
*/
private $_authors;
protected $authors;
/**
* Date the po file got created.
*
* @var string
*/
private $_po_date;
protected $poDate;
/**
* Human readable language name.
*
* @var string
*/
private $_languageName;
protected $languageName;
/**
* Name of the project the translation belongs to.
*
* @var string
*/
private $_projectName;
protected $projectName;
/**
* Constructor, creates a PoHeader with default values.
@@ -71,11 +71,11 @@ class PoHeader {
* Language code.
*/
public function __construct($langcode = NULL) {
$this->_langcode = $langcode;
$this->langcode = $langcode;
// Ignore errors when run during site installation before
// date_default_timezone_set() is called.
$this->_po_date = @date("Y-m-d H:iO");
$this->_pluralForms = 'nplurals=2; plural=(n > 1);';
$this->poDate = @date("Y-m-d H:iO");
$this->pluralForms = 'nplurals=2; plural=(n > 1);';
}
/**
@@ -86,7 +86,7 @@ class PoHeader {
* 'nplurals=2; plural=(n > 1);'.
*/
public function getPluralForms() {
return $this->_pluralForms;
return $this->pluralForms;
}
/**
@@ -96,7 +96,7 @@ class PoHeader {
* Human readable language name.
*/
public function setLanguageName($languageName) {
$this->_languageName = $languageName;
$this->languageName = $languageName;
}
/**
@@ -106,7 +106,7 @@ class PoHeader {
* The human readable language name.
*/
public function getLanguageName() {
return $this->_languageName;
return $this->languageName;
}
/**
@@ -116,7 +116,7 @@ class PoHeader {
* Human readable project name.
*/
public function setProjectName($projectName) {
$this->_projectName = $projectName;
$this->projectName = $projectName;
}
/**
@@ -126,7 +126,7 @@ class PoHeader {
* The human readable project name.
*/
public function getProjectName() {
return $this->_projectName;
return $this->projectName;
}
/**
@@ -142,7 +142,7 @@ class PoHeader {
// There is only one value relevant for our header implementation when
// reading, and that is the plural formula.
if (!empty($values['Plural-Forms'])) {
$this->_pluralForms = $values['Plural-Forms'];
$this->pluralForms = $values['Plural-Forms'];
}
}
@@ -152,11 +152,11 @@ class PoHeader {
public function __toString() {
$output = '';
$isTemplate = empty($this->_languageName);
$isTemplate = empty($this->languageName);
$output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->_languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->_projectName) . "\n";
if (!empty($this->_authors)) {
$output .= '# Generated by ' . implode("\n# ", $this->_authors) . "\n";
$output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->projectName) . "\n";
if (!empty($this->authors)) {
$output .= '# Generated by ' . implode("\n# ", $this->authors) . "\n";
}
$output .= "#\n";
@@ -164,14 +164,14 @@ class PoHeader {
$output .= "msgid \"\"\n";
$output .= "msgstr \"\"\n";
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
$output .= "\"POT-Creation-Date: " . $this->_po_date . "\\n\"\n";
$output .= "\"PO-Revision-Date: " . $this->_po_date . "\\n\"\n";
$output .= "\"POT-Creation-Date: " . $this->poDate . "\\n\"\n";
$output .= "\"PO-Revision-Date: " . $this->poDate . "\\n\"\n";
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"MIME-Version: 1.0\\n\"\n";
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
$output .= "\"Plural-Forms: " . $this->_pluralForms . "\\n\"\n";
$output .= "\"Plural-Forms: " . $this->pluralForms . "\\n\"\n";
$output .= "\n";
return $output;
+34 -34
View File
@@ -15,45 +15,45 @@ class PoItem {
*
* @var string
*/
private $_langcode;
protected $langcode;
/**
* The context this translation belongs to.
*
* @var string
*/
private $_context = '';
protected $context = '';
/**
* The source string or array of strings if it has plurals.
*
* @var string|array
*
* @see $_plural
* @see $plural
*/
private $_source;
protected $source;
/**
* Flag indicating if this translation has plurals.
*
* @var bool
*/
private $_plural;
protected $plural;
/**
* The comment of this translation.
*
* @var string
*/
private $_comment;
protected $comment;
/**
* The translation string or array of strings if it has plurals.
*
* @var string|array
* @see $_plural
* @see $plural
*/
private $_translation;
protected $translation;
/**
* Gets the language code of the currently used language.
@@ -61,7 +61,7 @@ class PoItem {
* @return string with langcode
*/
public function getLangcode() {
return $this->_langcode;
return $this->langcode;
}
/**
@@ -70,7 +70,7 @@ class PoItem {
* @param string $langcode
*/
public function setLangcode($langcode) {
$this->_langcode = $langcode;
$this->langcode = $langcode;
}
/**
@@ -79,7 +79,7 @@ class PoItem {
* @return string $context
*/
public function getContext() {
return $this->_context;
return $this->context;
}
/**
@@ -88,7 +88,7 @@ class PoItem {
* @param string $context
*/
public function setContext($context) {
$this->_context = $context;
$this->context = $context;
}
/**
@@ -98,7 +98,7 @@ class PoItem {
* @return string or array $translation
*/
public function getSource() {
return $this->_source;
return $this->source;
}
/**
@@ -108,7 +108,7 @@ class PoItem {
* @param string|array $source
*/
public function setSource($source) {
$this->_source = $source;
$this->source = $source;
}
/**
@@ -118,7 +118,7 @@ class PoItem {
* @return string or array $translation
*/
public function getTranslation() {
return $this->_translation;
return $this->translation;
}
/**
@@ -128,7 +128,7 @@ class PoItem {
* @param string|array $translation
*/
public function setTranslation($translation) {
$this->_translation = $translation;
$this->translation = $translation;
}
/**
@@ -137,7 +137,7 @@ class PoItem {
* @param bool $plural
*/
public function setPlural($plural) {
$this->_plural = $plural;
$this->plural = $plural;
}
/**
@@ -146,7 +146,7 @@ class PoItem {
* @return bool
*/
public function isPlural() {
return $this->_plural;
return $this->plural;
}
/**
@@ -155,7 +155,7 @@ class PoItem {
* @return String $comment
*/
public function getComment() {
return $this->_comment;
return $this->comment;
}
/**
@@ -164,7 +164,7 @@ class PoItem {
* @param string $comment
*/
public function setComment($comment) {
$this->_comment = $comment;
$this->comment = $comment;
}
/**
@@ -185,11 +185,11 @@ class PoItem {
if (isset($values['comment'])) {
$this->setComment($values['comment']);
}
if (isset($this->_source) &&
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->_source) > 1);
if (isset($this->source) &&
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->source) > 1);
}
}
@@ -207,12 +207,12 @@ class PoItem {
$output = '';
// Format string context.
if (!empty($this->_context)) {
$output .= 'msgctxt ' . $this->formatString($this->_context);
if (!empty($this->context)) {
$output .= 'msgctxt ' . $this->formatString($this->context);
}
// Format translation.
if ($this->_plural) {
if ($this->plural) {
$output .= $this->formatPlural();
}
else {
@@ -232,11 +232,11 @@ class PoItem {
$output = '';
// Format source strings.
$output .= 'msgid ' . $this->formatString($this->_source[0]);
$output .= 'msgid_plural ' . $this->formatString($this->_source[1]);
$output .= 'msgid ' . $this->formatString($this->source[0]);
$output .= 'msgid_plural ' . $this->formatString($this->source[1]);
foreach ($this->_translation as $i => $trans) {
if (isset($this->_translation[$i])) {
foreach ($this->translation as $i => $trans) {
if (isset($this->translation[$i])) {
$output .= 'msgstr[' . $i . '] ' . $this->formatString($trans);
}
else {
@@ -252,8 +252,8 @@ class PoItem {
*/
private function formatSingular() {
$output = '';
$output .= 'msgid ' . $this->formatString($this->_source);
$output .= 'msgstr ' . (isset($this->_translation) ? $this->formatString($this->_translation) : '""');
$output .= 'msgid ' . $this->formatString($this->source);
$output .= 'msgstr ' . (isset($this->translation) ? $this->formatString($this->translation) : '""');
return $output;
}
@@ -12,13 +12,13 @@ class PoMemoryWriter implements PoWriterInterface {
*
* @var array
*/
private $_items;
protected $items;
/**
* Constructor, initialize empty items.
*/
public function __construct() {
$this->_items = [];
$this->items = [];
}
/**
@@ -30,7 +30,7 @@ class PoMemoryWriter implements PoWriterInterface {
$item->setTranslation(implode(LOCALE_PLURAL_DELIMITER, $item->getTranslation()));
}
$context = $item->getContext();
$this->_items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
$this->items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
}
/**
@@ -49,7 +49,7 @@ class PoMemoryWriter implements PoWriterInterface {
* @return array PoItem
*/
public function getData() {
return $this->_items;
return $this->items;
}
/**
@@ -2,7 +2,7 @@
namespace Drupal\Component\Gettext;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\FormattableMarkup;
/**
* Implements Gettext PO stream reader.
@@ -17,7 +17,7 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
*
* @var int
*/
private $_line_number = 0;
protected $lineNumber = 0;
/**
* Parser context for the stream reader state machine.
@@ -32,90 +32,90 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
*
* @var string
*/
private $_context = 'COMMENT';
protected $context = 'COMMENT';
/**
* Current entry being read. Incomplete.
*
* @var array
*/
private $_current_item = [];
protected $currentItem = [];
/**
* Current plural index for plural translations.
*
* @var int
*/
private $_current_plural_index = 0;
protected $currentPluralIndex = 0;
/**
* URI of the PO stream that is being read.
*
* @var string
*/
private $_uri = '';
protected $uri = '';
/**
* Language code for the PO stream being read.
*
* @var string
*/
private $_langcode = NULL;
protected $langcode = NULL;
/**
* File handle of the current PO stream.
*
* @var resource
*/
private $_fd;
protected $fd;
/**
* The PO stream header.
*
* @var \Drupal\Component\Gettext\PoHeader
*/
private $_header;
protected $header;
/**
* Object wrapper for the last read source/translation pair.
*
* @var \Drupal\Component\Gettext\PoItem
*/
private $_last_item;
protected $lastItem;
/**
* Indicator of whether the stream reading is finished.
*
* @var bool
*/
private $_finished;
protected $finished;
/**
* Array of translated error strings recorded on reading this stream so far.
*
* @var array
*/
private $_errors;
protected $errors;
/**
* {@inheritdoc}
*/
public function getLangcode() {
return $this->_langcode;
return $this->langcode;
}
/**
* {@inheritdoc}
*/
public function setLangcode($langcode) {
$this->_langcode = $langcode;
$this->langcode = $langcode;
}
/**
* {@inheritdoc}
*/
public function getHeader() {
return $this->_header;
return $this->header;
}
/**
@@ -130,14 +130,14 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
* {@inheritdoc}
*/
public function getURI() {
return $this->_uri;
return $this->uri;
}
/**
* {@inheritdoc}
*/
public function setURI($uri) {
$this->_uri = $uri;
$this->uri = $uri;
}
/**
@@ -150,8 +150,8 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
* If the URI is not yet set.
*/
public function open() {
if (!empty($this->_uri)) {
$this->_fd = fopen($this->_uri, 'rb');
if (!empty($this->uri)) {
$this->fd = fopen($this->uri, 'rb');
$this->readHeader();
}
else {
@@ -166,8 +166,8 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
* If the stream is not open.
*/
public function close() {
if ($this->_fd) {
fclose($this->_fd);
if ($this->fd) {
fclose($this->fd);
}
else {
throw new \Exception('Cannot close stream that is not open.');
@@ -179,14 +179,14 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
*/
public function readItem() {
// Clear out the last item.
$this->_last_item = NULL;
$this->lastItem = NULL;
// Read until finished with the stream or a complete item was identified.
while (!$this->_finished && is_null($this->_last_item)) {
while (!$this->finished && is_null($this->lastItem)) {
$this->readLine();
}
return $this->_last_item;
return $this->lastItem;
}
/**
@@ -196,14 +196,14 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
* The new seek position to set.
*/
public function setSeek($seek) {
fseek($this->_fd, $seek);
fseek($this->fd, $seek);
}
/**
* Gets the pointer position of the current PO stream.
*/
public function getSeek() {
return ftell($this->_fd);
return ftell($this->fd);
}
/**
@@ -221,18 +221,18 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
}
$header = new PoHeader();
$header->setFromString(trim($item->getTranslation()));
$this->_header = $header;
$this->header = $header;
}
/**
* Reads a line from the PO stream and stores data internally.
*
* Expands $this->_current_item based on new data for the current item. If
* Expands $this->current_item based on new data for the current item. If
* this line ends the current item, it is saved with setItemFromArray() with
* data from $this->_current_item.
* data from $this->current_item.
*
* An internal state machine is maintained in this reader using
* $this->_context as the reading state. PO items are in between COMMENT
* $this->context as the reading state. PO items are in between COMMENT
* states (when items have at least one line or comment in between them) or
* indicated by MSGSTR or MSGSTR_ARR followed immediately by an MSGID or
* MSGCTXT (when items closely follow each other).
@@ -245,25 +245,25 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
private function readLine() {
// Read a line and set the stream finished indicator if it was not
// possible anymore.
$line = fgets($this->_fd);
$this->_finished = ($line === FALSE);
$line = fgets($this->fd);
$this->finished = ($line === FALSE);
if (!$this->_finished) {
if (!$this->finished) {
if ($this->_line_number == 0) {
if ($this->lineNumber == 0) {
// The first line might come with a UTF-8 BOM, which should be removed.
$line = str_replace("\xEF\xBB\xBF", '', $line);
// Current plurality for 'msgstr[]'.
$this->_current_plural_index = 0;
$this->currentPluralIndex = 0;
}
// Track the line number for error reporting.
$this->_line_number++;
$this->lineNumber++;
// Initialize common values for error logging.
$log_vars = [
'%uri' => $this->getURI(),
'%line' => $this->_line_number,
'%line' => $this->lineNumber,
];
// Trim away the linefeed. \\n might appear at the end of the string if
@@ -273,24 +273,24 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
if (!strncmp('#', $line, 1)) {
// Lines starting with '#' are comments.
if ($this->_context == 'COMMENT') {
if ($this->context == 'COMMENT') {
// Already in comment context, add to current comment.
$this->_current_item['#'][] = substr($line, 1);
$this->currentItem['#'][] = substr($line, 1);
}
elseif (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
elseif (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
// We are currently in string context, save current item.
$this->setItemFromArray($this->_current_item);
$this->setItemFromArray($this->currentItem);
// Start a new entry for the comment.
$this->_current_item = [];
$this->_current_item['#'][] = substr($line, 1);
$this->currentItem = [];
$this->currentItem['#'][] = substr($line, 1);
$this->_context = 'COMMENT';
$this->context = 'COMMENT';
return;
}
else {
// A comment following any other context is a syntax error.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
return FALSE;
}
return;
@@ -298,9 +298,9 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
elseif (!strncmp('msgid_plural', $line, 12)) {
// A plural form for the current source string.
if ($this->_context != 'MSGID') {
if ($this->context != 'MSGID') {
// A plural form can only be added to an msgid directly.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
return FALSE;
}
@@ -311,34 +311,34 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The plural form must be wrapped in quotes.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains a syntax error on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains a syntax error on line %line.', $log_vars);
return FALSE;
}
// Append the plural source to the current entry.
if (is_string($this->_current_item['msgid'])) {
if (is_string($this->currentItem['msgid'])) {
// The first value was stored as string. Now we know the context is
// plural, it is converted to array.
$this->_current_item['msgid'] = [$this->_current_item['msgid']];
$this->currentItem['msgid'] = [$this->currentItem['msgid']];
}
$this->_current_item['msgid'][] = $quoted;
$this->currentItem['msgid'][] = $quoted;
$this->_context = 'MSGID_PLURAL';
$this->context = 'MSGID_PLURAL';
return;
}
elseif (!strncmp('msgid', $line, 5)) {
// Starting a new message.
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
// We are currently in string context, save current item.
$this->setItemFromArray($this->_current_item);
$this->setItemFromArray($this->currentItem);
// Start a new context for the msgid.
$this->_current_item = [];
$this->currentItem = [];
}
elseif ($this->_context == 'MSGID') {
elseif ($this->context == 'MSGID') {
// We are currently already in the context, meaning we passed an id with no data.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
return FALSE;
}
@@ -349,25 +349,25 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The message id must be wrapped in quotes.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
return FALSE;
}
$this->_current_item['msgid'] = $quoted;
$this->_context = 'MSGID';
$this->currentItem['msgid'] = $quoted;
$this->context = 'MSGID';
return;
}
elseif (!strncmp('msgctxt', $line, 7)) {
// Starting a new context.
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
// We are currently in string context, save current item.
$this->setItemFromArray($this->_current_item);
$this->_current_item = [];
$this->setItemFromArray($this->currentItem);
$this->currentItem = [];
}
elseif (!empty($this->_current_item['msgctxt'])) {
elseif (!empty($this->currentItem['msgctxt'])) {
// A context cannot apply to another context.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
return FALSE;
}
@@ -378,37 +378,37 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The context string must be quoted.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
return FALSE;
}
$this->_current_item['msgctxt'] = $quoted;
$this->currentItem['msgctxt'] = $quoted;
$this->_context = 'MSGCTXT';
$this->context = 'MSGCTXT';
return;
}
elseif (!strncmp('msgstr[', $line, 7)) {
// A message string for a specific plurality.
if (($this->_context != 'MSGID') &&
($this->_context != 'MSGCTXT') &&
($this->_context != 'MSGID_PLURAL') &&
($this->_context != 'MSGSTR_ARR')) {
if (($this->context != 'MSGID') &&
($this->context != 'MSGCTXT') &&
($this->context != 'MSGID_PLURAL') &&
($this->context != 'MSGSTR_ARR')) {
// Plural message strings must come after msgid, msgxtxt,
// msgid_plural, or other msgstr[] entries.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
return FALSE;
}
// Ensure the plurality is terminated.
if (strpos($line, ']') === FALSE) {
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
return FALSE;
}
// Extract the plurality.
$frombracket = strstr($line, '[');
$this->_current_plural_index = substr($frombracket, 1, strpos($frombracket, ']') - 1);
$this->currentPluralIndex = substr($frombracket, 1, strpos($frombracket, ']') - 1);
// Skip to the next whitespace and trim away any further whitespace,
// bringing $line to the message text only.
@@ -417,24 +417,24 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The string must be quoted.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
return FALSE;
}
if (!isset($this->_current_item['msgstr']) || !is_array($this->_current_item['msgstr'])) {
$this->_current_item['msgstr'] = [];
if (!isset($this->currentItem['msgstr']) || !is_array($this->currentItem['msgstr'])) {
$this->currentItem['msgstr'] = [];
}
$this->_current_item['msgstr'][$this->_current_plural_index] = $quoted;
$this->currentItem['msgstr'][$this->currentPluralIndex] = $quoted;
$this->_context = 'MSGSTR_ARR';
$this->context = 'MSGSTR_ARR';
return;
}
elseif (!strncmp("msgstr", $line, 6)) {
// A string pair for an msgid (with optional context).
if (($this->_context != 'MSGID') && ($this->_context != 'MSGCTXT')) {
if (($this->context != 'MSGID') && ($this->context != 'MSGCTXT')) {
// Strings are only valid within an id or context scope.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
return FALSE;
}
@@ -445,13 +445,13 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The string must be quoted.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
return FALSE;
}
$this->_current_item['msgstr'] = $quoted;
$this->currentItem['msgstr'] = $quoted;
$this->_context = 'MSGSTR';
$this->context = 'MSGSTR';
return;
}
elseif ($line != '') {
@@ -460,37 +460,37 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// This string must be quoted.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
return FALSE;
}
// Append the string to the current item.
if (($this->_context == 'MSGID') || ($this->_context == 'MSGID_PLURAL')) {
if (is_array($this->_current_item['msgid'])) {
if (($this->context == 'MSGID') || ($this->context == 'MSGID_PLURAL')) {
if (is_array($this->currentItem['msgid'])) {
// Add string to last array element for plural sources.
$last_index = count($this->_current_item['msgid']) - 1;
$this->_current_item['msgid'][$last_index] .= $quoted;
$last_index = count($this->currentItem['msgid']) - 1;
$this->currentItem['msgid'][$last_index] .= $quoted;
}
else {
// Singular source, just append the string.
$this->_current_item['msgid'] .= $quoted;
$this->currentItem['msgid'] .= $quoted;
}
}
elseif ($this->_context == 'MSGCTXT') {
elseif ($this->context == 'MSGCTXT') {
// Multiline context name.
$this->_current_item['msgctxt'] .= $quoted;
$this->currentItem['msgctxt'] .= $quoted;
}
elseif ($this->_context == 'MSGSTR') {
elseif ($this->context == 'MSGSTR') {
// Multiline translation string.
$this->_current_item['msgstr'] .= $quoted;
$this->currentItem['msgstr'] .= $quoted;
}
elseif ($this->_context == 'MSGSTR_ARR') {
elseif ($this->context == 'MSGSTR_ARR') {
// Multiline plural translation string.
$this->_current_item['msgstr'][$this->_current_plural_index] .= $quoted;
$this->currentItem['msgstr'][$this->currentPluralIndex] .= $quoted;
}
else {
// No valid context to append to.
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
$this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
return FALSE;
}
return;
@@ -498,12 +498,12 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
}
// Empty line read or EOF of PO stream, close out the last entry.
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
$this->setItemFromArray($this->_current_item);
$this->_current_item = [];
if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
$this->setItemFromArray($this->currentItem);
$this->currentItem = [];
}
elseif ($this->_context != 'COMMENT') {
$this->_errors[] = SafeMarkup::format('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
elseif ($this->context != 'COMMENT') {
$this->errors[] = new FormattableMarkup('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
return FALSE;
}
@@ -533,11 +533,11 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$item->setTranslation($value['msgstr']);
$item->setPlural($plural);
$item->setComment($comments);
$item->setLangcode($this->_langcode);
$item->setLangcode($this->langcode);
$this->_last_item = $item;
$this->lastItem = $item;
$this->_context = 'COMMENT';
$this->context = 'COMMENT';
}
/**
@@ -12,21 +12,28 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
*
* @var string
*/
private $_uri;
protected $uri;
/**
* The Gettext PO header.
*
* @var \Drupal\Component\Gettext\PoHeader
*/
private $_header;
protected $header;
/**
* File handle of the current PO stream.
*
* @var resource
*/
private $_fd;
protected $fd;
/**
* The language code of this writer.
*
* @var string
*/
protected $langcode;
/**
* Gets the PO header of the current stream.
@@ -35,7 +42,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The Gettext PO header.
*/
public function getHeader() {
return $this->_header;
return $this->header;
}
/**
@@ -45,7 +52,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The Gettext PO header to set.
*/
public function setHeader(PoHeader $header) {
$this->_header = $header;
$this->header = $header;
}
/**
@@ -55,7 +62,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The language code.
*/
public function getLangcode() {
return $this->_langcode;
return $this->langcode;
}
/**
@@ -65,7 +72,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The language code.
*/
public function setLangcode($langcode) {
$this->_langcode = $langcode;
$this->langcode = $langcode;
}
/**
@@ -73,7 +80,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
*/
public function open() {
// Open in write mode. Will overwrite the stream if it already exists.
$this->_fd = fopen($this->getURI(), 'w');
$this->fd = fopen($this->getURI(), 'w');
// Write the header at the start.
$this->writeHeader();
}
@@ -85,8 +92,8 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* If the stream is not open.
*/
public function close() {
if ($this->_fd) {
fclose($this->_fd);
if ($this->fd) {
fclose($this->fd);
}
else {
throw new \Exception('Cannot close stream that is not open.');
@@ -104,7 +111,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* If writing the data is not possible.
*/
private function write($data) {
$result = fwrite($this->_fd, $data);
$result = fwrite($this->fd, $data);
if ($result === FALSE || $result != strlen($data)) {
throw new \Exception('Unable to write data: ' . substr($data, 0, 20));
}
@@ -114,7 +121,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* Write the PO header to the stream.
*/
private function writeHeader() {
$this->write($this->_header);
$this->write($this->header);
}
/**
@@ -141,17 +148,17 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* If the URI is not set.
*/
public function getURI() {
if (empty($this->_uri)) {
if (empty($this->uri)) {
throw new \Exception('No URI set.');
}
return $this->_uri;
return $this->uri;
}
/**
* {@inheritdoc}
*/
public function setURI($uri) {
$this->_uri = $uri;
$this->uri = $uri;
}
}
@@ -95,6 +95,7 @@ EOF;
<IfModule !mod_authz_core.c>
Deny from all
</IfModule>
$lines
EOF;
}
@@ -0,0 +1,71 @@
<?php
namespace Drupal\Component\Plugin\Definition;
use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
/**
* Provides an interface for plugin definitions which use contexts.
*
* @ingroup Plugin
*/
interface ContextAwarePluginDefinitionInterface extends PluginDefinitionInterface {
/**
* Checks if the plugin defines a particular context.
*
* @param string $name
* The context name.
*
* @return bool
* TRUE if the plugin defines the given context, otherwise FALSE.
*/
public function hasContextDefinition($name);
/**
* Returns all context definitions for this plugin.
*
* @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]
* The context definitions.
*/
public function getContextDefinitions();
/**
* Returns a particular context definition for this plugin.
*
* @param string $name
* The context name.
*
* @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface
* The context definition.
*
* @throws \Drupal\Component\Plugin\Exception\ContextException
* Thrown if the plugin does not define the given context.
*/
public function getContextDefinition($name);
/**
* Adds a context to this plugin definition.
*
* @param string $name
* The context name.
* @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $definition
* The context definition.
*
* @return $this
* The called object.
*/
public function addContextDefinition($name, ContextDefinitionInterface $definition);
/**
* Removes a context definition from this plugin.
*
* @param string $name
* The context name.
*
* @return $this
* The called object.
*/
public function removeContextDefinition($name);
}
@@ -0,0 +1,60 @@
<?php
namespace Drupal\Component\Plugin\Definition;
use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
use Drupal\Component\Plugin\Exception\ContextException;
/**
* Provides a trait for context-aware object-based plugin definitions.
*/
trait ContextAwarePluginDefinitionTrait {
/**
* The context definitions for this plugin definition.
*
* @var \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]
*/
protected $contextDefinitions = [];
/**
* Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::hasContextDefinition().
*/
public function hasContextDefinition($name) {
return array_key_exists($name, $this->contextDefinitions);
}
/**
* Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::getContextDefinitions().
*/
public function getContextDefinitions() {
return $this->contextDefinitions;
}
/**
* Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::getContextDefinition().
*/
public function getContextDefinition($name) {
if ($this->hasContextDefinition($name)) {
return $this->contextDefinitions[$name];
}
throw new ContextException($this->id() . " does not define a '$name' context");
}
/**
* Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::addContextDefinition().
*/
public function addContextDefinition($name, ContextDefinitionInterface $definition) {
$this->contextDefinitions[$name] = $definition;
return $this;
}
/**
* Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::removeContextDefinition().
*/
public function removeContextDefinition($name) {
unset($this->contextDefinitions[$name]);
return $this;
}
}
@@ -33,6 +33,8 @@ interface DiscoveryInterface {
* @return mixed[]
* An array of plugin definitions (empty array if no definitions were
* found). Keys are plugin IDs.
*
* @see \Drupal\Core\Plugin\FilteredPluginManagerInterface::getFilteredDefinitions()
*/
public function getDefinitions();
@@ -0,0 +1,21 @@
<?php
namespace Drupal\Component\Plugin\Exception;
/**
* An exception class thrown when contexts exist but are missing a value.
*/
class MissingValueContextException extends ContextException {
/**
* MissingValueContextException constructor.
*
* @param string[] $contexts_without_value
* List of contexts with missing value.
*/
public function __construct(array $contexts_without_value = []) {
$message = 'Required contexts without a value: ' . implode(', ', $contexts_without_value);
parent::__construct($message);
}
}
@@ -63,7 +63,7 @@ class DefaultFactory implements FactoryInterface {
* @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.
* (optional) The required plugin interface.
*
* @return string
* The appropriate class name.
@@ -41,7 +41,7 @@ abstract class PluginBase implements PluginInspectionInterface, DerivativeInspec
protected $configuration;
/**
* Constructs a Drupal\Component\Plugin\PluginBase object.
* Constructs a \Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
@@ -76,8 +76,7 @@ abstract class PluginManagerBase implements PluginManagerInterface {
return $this->getFactory()->createInstance($plugin_id, $configuration);
}
catch (PluginNotFoundException $e) {
$fallback_id = $this->getFallbackPluginId($plugin_id, $configuration);
return $this->getFactory()->createInstance($fallback_id, $configuration);
return $this->handlePluginNotFound($plugin_id, $configuration);
}
}
else {
@@ -85,6 +84,22 @@ abstract class PluginManagerBase implements PluginManagerInterface {
}
}
/**
* Allows plugin managers to specify custom behavior if a plugin is not found.
*
* @param string $plugin_id
* The ID of the missing requested plugin.
* @param array $configuration
* An array of configuration relevant to the plugin instance.
*
* @return object
* A fallback plugin instance.
*/
protected function handlePluginNotFound($plugin_id, array $configuration) {
$fallback_id = $this->getFallbackPluginId($plugin_id, $configuration);
return $this->getFactory()->createInstance($fallback_id, $configuration);
}
/**
* {@inheritdoc}
*/
@@ -3,7 +3,6 @@
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
/**
@@ -107,7 +106,7 @@ class FormattableMarkup implements MarkupInterface, \Countable {
* The length of the string.
*/
public function count() {
return Unicode::strlen($this->string);
return mb_strlen($this->string);
}
/**
@@ -3,7 +3,6 @@
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.
@@ -43,7 +42,7 @@ class HtmlEscapedText implements MarkupInterface, \Countable {
* {@inheritdoc}
*/
public function count() {
return Unicode::strlen($this->string);
return mb_strlen($this->string);
}
/**
@@ -2,8 +2,6 @@
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Unicode;
/**
* Implements MarkupInterface and Countable for rendered objects.
*
@@ -61,7 +59,7 @@ trait MarkupTrait {
* The length of the string.
*/
public function count() {
return Unicode::strlen($this->string);
return mb_strlen($this->string);
}
/**
@@ -34,7 +34,7 @@ class YamlSymfony implements SerializationInterface {
$yaml = new Parser();
// Make sure we have a single trailing newline. A very simple config like
// 'foo: bar' with no newline will fail to parse otherwise.
return $yaml->parse($raw, SymfonyYaml::PARSE_EXCEPTION_ON_INVALID_TYPE | SymfonyYaml::PARSE_KEYS_AS_STRINGS);
return $yaml->parse($raw, SymfonyYaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
}
catch (\Exception $e) {
throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e);
@@ -14,11 +14,11 @@ $base = [
0x50 => NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
0x60 => '', '', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
0x70 => NULL, NULL, NULL, NULL, '\'', ',', NULL, NULL, NULL, NULL, 'i', NULL, NULL, NULL, '?', NULL,
0x80 => NULL, NULL, NULL, NULL, '', '', 'A', ':', 'E', 'E', 'I', NULL, 'O', NULL, 'Y', 'O',
0x90 => 'i', 'A', 'B', 'G', 'D', 'E', 'Z', 'E', 'TH', 'I', 'K', 'L', 'M', 'N', 'X', 'O',
0xA0 => 'P', 'R', NULL, 'S', 'T', 'Y', 'PH', 'CH', 'PS', 'O', 'I', 'Y', 'a', 'e', 'e', 'i',
0xB0 => 'y', 'a', 'b', 'g', 'd', 'e', 'z', 'e', 'th', 'i', 'k', 'l', 'm', 'n', 'x', 'o',
0xC0 => 'p', 'r', 's', 's', 't', 'y', 'ph', 'ch', 'ps', 'o', 'i', 'y', 'o', 'y', 'o', NULL,
0x80 => NULL, NULL, NULL, NULL, '', '', 'A', ';', 'E', 'I', 'I', NULL, 'O', NULL, 'U', 'O',
0x90 => 'I', 'A', 'B', 'G', 'D', 'E', 'Z', 'I', 'Th', 'I', 'K', 'L', 'M', 'N', 'X', 'O',
0xA0 => 'P', 'R', NULL, 'S', 'T', 'Y', 'F', 'H', 'Ps', 'O', 'I', 'Y', 'a', 'e', 'i', 'i',
0xB0 => 'y', 'a', 'b', 'g', 'd', 'e', 'z', 'i', 'th', 'i', 'k', 'l', 'm', 'n', 'x', 'o',
0xC0 => 'p', 'r', 's', 's', 't', 'y', 'f', 'h', 'ps', 'o', 'i', 'y', 'o', 'y', 'o', NULL,
0xD0 => 'b', 'th', 'Y', 'Y', 'Y', 'ph', 'p', '&', NULL, NULL, 'St', 'st', 'W', 'w', 'Q', 'q',
0xE0 => 'Sp', 'sp', 'Sh', 'sh', 'F', 'f', 'Kh', 'kh', 'H', 'h', 'G', 'g', 'CH', 'ch', 'Ti', 'ti',
0xF0 => 'k', 'r', 's', 'j', 'TH', 'e', NULL, 'S', 's', 'S', 'S', 's', NULL, NULL, NULL, NULL,
@@ -6,12 +6,12 @@
*/
$base = [
0x00 => 'E', 'E', 'D', 'G', 'E', 'Z', 'I', 'I', 'J', 'L', 'N', 'C', 'K', 'I', 'U', 'D',
0x10 => 'A', 'B', 'V', 'G', 'D', 'E', 'Z', 'Z', 'I', 'I', 'K', 'L', 'M', 'N', 'O', 'P',
0x20 => 'R', 'S', 'T', 'U', 'F', 'H', 'C', 'C', 'S', 'S', '', 'Y', '', 'E', 'U', 'A',
0x30 => 'a', 'b', 'v', 'g', 'd', 'e', 'z', 'z', 'i', 'i', 'k', 'l', 'm', 'n', 'o', 'p',
0x40 => 'r', 's', 't', 'u', 'f', 'h', 'c', 'c', 's', 's', '', 'y', '', 'e', 'u', 'a',
0x50 => 'e', 'e', 'd', 'g', 'e', 'z', 'i', 'i', 'j', 'l', 'n', 'c', 'k', 'i', 'u', 'd',
0x00 => 'E', 'YO', 'D', 'G', 'E', 'Z', 'I', 'I', 'J', 'L', 'N', 'C', 'K', 'I', 'U', 'D',
0x10 => 'A', 'B', 'V', 'G', 'D', 'E', 'ZH', 'Z', 'I', 'Y', 'K', 'L', 'M', 'N', 'O', 'P',
0x20 => 'R', 'S', 'T', 'U', 'F', 'KH', 'C', 'CH', 'SH', 'SCH', '', 'Y', '', 'E', 'YU', 'YA',
0x30 => 'a', 'b', 'v', 'g', 'd', 'e', 'zh', 'z', 'i', 'y', 'k', 'l', 'm', 'n', 'o', 'p',
0x40 => 'r', 's', 't', 'u', 'f', 'kh', 'c', 'ch', 'sh', 'sch', '', 'y', '', 'e', 'yu', 'ya',
0x50 => 'e', 'yo', 'd', 'g', 'e', 'z', 'i', 'i', 'j', 'l', 'n', 'c', 'k', 'i', 'u', 'd',
0x60 => 'O', 'o', 'E', 'e', 'Ie', 'ie', 'E', 'e', 'Ie', 'ie', 'O', 'o', 'Io', 'io', 'Ks', 'ks',
0x70 => 'Ps', 'ps', 'F', 'f', 'Y', 'y', 'Y', 'y', 'u', 'u', 'O', 'o', 'O', 'o', 'Ot', 'ot',
0x80 => 'Q', 'q', '*1000*', '', '', '', '', NULL, '*100.000*', '*1.000.000*', NULL, NULL, '"', '"', 'R\'', 'r\'',
+1 -1
View File
@@ -23,7 +23,7 @@ class Color {
// Hash prefix is optional.
$hex = ltrim($hex, '#');
// Must be either RGB or RRGGBB.
$length = Unicode::strlen($hex);
$length = mb_strlen($hex);
$valid = $valid && ($length === 3 || $length === 6);
// Must be a valid hex value.
$valid = $valid && ctype_xdigit($hex);
+7 -6
View File
@@ -71,7 +71,7 @@ class Html {
public static function getClass($class) {
$class = (string) $class;
if (!isset(static::$classes[$class])) {
static::$classes[$class] = static::cleanCssIdentifier(Unicode::strtolower($class));
static::$classes[$class] = static::cleanCssIdentifier(mb_strtolower($class));
}
return static::$classes[$class];
}
@@ -79,9 +79,10 @@ class Html {
/**
* Prepares a string for use as a CSS identifier (element, class, or ID name).
*
* http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for
* valid CSS identifiers (including element names, classes, and IDs in
* selectors.)
* Link below shows the syntax for valid CSS identifiers (including element
* names, classes, and IDs in selectors).
*
* @see http://www.w3.org/TR/CSS21/syndata.html#characters
*
* @param string $identifier
* The identifier to clean.
@@ -124,7 +125,7 @@ class Html {
// Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
$identifier = preg_replace([
'/^[0-9]/',
'/^(-[0-9])|^(--)/'
'/^(-[0-9])|^(--)/',
], ['_', '__'], $identifier);
return $identifier;
}
@@ -215,7 +216,7 @@ class Html {
* @see self::getUniqueId()
*/
public static function getId($id) {
$id = str_replace([' ', '_', '[', ']'], ['-', '-', '-', ''], Unicode::strtolower($id));
$id = str_replace([' ', '_', '[', ']'], ['-', '-', '-', ''], mb_strtolower($id));
// As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
// only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
@@ -259,7 +259,6 @@ class Random {
return $output;
}
/**
* Create a placeholder image.
*
@@ -40,6 +40,7 @@ class SafeMarkup {
* @see https://www.drupal.org/node/2549395
*/
public static function isSafe($string, $strategy = 'html') {
@trigger_error('SafeMarkup::isSafe() is scheduled for removal in Drupal 9.0.0. Instead, you should just check if a variable is an instance of \Drupal\Component\Render\MarkupInterface. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
return $string instanceof MarkupInterface;
}
@@ -66,6 +67,7 @@ class SafeMarkup {
* @see drupal_validate_utf8()
*/
public static function checkPlain($text) {
@trigger_error('SafeMarkup::checkPlain() is scheduled for removal in 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 possible, \Drupal\Component\Utility\Html::escape() can be used in places where explicit escaping is needed. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
return new HtmlEscapedText($text);
}
@@ -93,6 +95,7 @@ class SafeMarkup {
* @see https://www.drupal.org/node/2549395
*/
public static function format($string, array $args) {
@trigger_error('SafeMarkup::format() is scheduled for removal in Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
return new FormattableMarkup($string, $args);
}
+65 -163
View File
@@ -87,13 +87,6 @@ EOD;
*/
const STATUS_ERROR = -1;
/**
* Holds the multibyte capabilities of the current environment.
*
* @var int
*/
protected static $status = 0;
/**
* Gets the current status of unicode/multibyte support on this environment.
*
@@ -107,7 +100,13 @@ EOD;
* An error occurred. No unicode support.
*/
public static function getStatus() {
return static::$status;
switch (static::check()) {
case 'mb_strlen':
return Unicode::STATUS_SINGLEBYTE;
case '':
return Unicode::STATUS_MULTIBYTE;
}
return Unicode::STATUS_ERROR;
}
/**
@@ -123,12 +122,16 @@ EOD;
*
* @param int $status
* The new status of multibyte support.
*
* @deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
* Drupal 9 there will be no way to set the status and in Drupal 8 this
* ability has been removed because mb_*() functions are supplied using
* Symfony's polyfill.
*
* @see https://www.drupal.org/node/2850048
*/
public static function setStatus($status) {
if (!in_array($status, [static::STATUS_SINGLEBYTE, static::STATUS_MULTIBYTE, static::STATUS_ERROR])) {
throw new \InvalidArgumentException('Invalid status value for unicode support.');
}
static::$status = $status;
@trigger_error('\Drupal\Component\Utility\Unicode::setStatus() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In Drupal 9 there will be no way to set the status and in Drupal 8 this ability has been removed because mb_*() functions are supplied using Symfony\'s polyfill. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
}
/**
@@ -143,38 +146,33 @@ EOD;
* Otherwise, an empty string.
*/
public static function check() {
// Set appropriate configuration.
mb_internal_encoding('utf-8');
mb_language('uni');
// Check for mbstring extension.
if (!function_exists('mb_strlen')) {
static::$status = static::STATUS_SINGLEBYTE;
if (!extension_loaded('mbstring')) {
return 'mb_strlen';
}
// Check mbstring configuration.
if (ini_get('mbstring.func_overload') != 0) {
static::$status = static::STATUS_ERROR;
return 'mbstring.func_overload';
}
if (ini_get('mbstring.encoding_translation') != 0) {
static::$status = static::STATUS_ERROR;
return 'mbstring.encoding_translation';
}
// mbstring.http_input and mbstring.http_output are deprecated and empty by
// default in PHP 5.6.
if (version_compare(PHP_VERSION, '5.6.0') == -1) {
if (ini_get('mbstring.http_input') != 'pass') {
static::$status = static::STATUS_ERROR;
return 'mbstring.http_input';
}
if (ini_get('mbstring.http_output') != 'pass') {
static::$status = static::STATUS_ERROR;
return 'mbstring.http_output';
}
}
// Set appropriate configuration.
mb_internal_encoding('utf-8');
mb_language('uni');
static::$status = static::STATUS_MULTIBYTE;
return '';
}
@@ -224,17 +222,7 @@ EOD;
* Converted data or FALSE.
*/
public static function convertToUtf8($data, $encoding) {
if (function_exists('iconv')) {
return @iconv($encoding, 'utf-8', $data);
}
elseif (function_exists('mb_convert_encoding')) {
return @mb_convert_encoding($data, 'utf-8', $encoding);
}
elseif (function_exists('recode_string')) {
return @recode_string($encoding . '..utf-8', $data);
}
// Cannot convert.
return FALSE;
return @iconv($encoding, 'utf-8', $data);
}
/**
@@ -281,15 +269,15 @@ EOD;
*
* @return int
* The length of the string.
*
* @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
* mb_strlen() instead.
*
* @see https://www.drupal.org/node/2850048
*/
public static function strlen($text) {
if (static::getStatus() == static::STATUS_MULTIBYTE) {
return mb_strlen($text);
}
else {
// Do not count UTF-8 continuation bytes.
return strlen(preg_replace("/[\x80-\xBF]/", '', $text));
}
@trigger_error('\Drupal\Component\Utility\Unicode::strlen() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strlen() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
return mb_strlen($text);
}
/**
@@ -300,18 +288,15 @@ EOD;
*
* @return string
* The string in uppercase.
*
* @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
* mb_strtoupper() instead.
*
* @see https://www.drupal.org/node/2850048
*/
public static function strtoupper($text) {
if (static::getStatus() == static::STATUS_MULTIBYTE) {
return mb_strtoupper($text);
}
else {
// Use C-locale for ASCII-only uppercase.
$text = strtoupper($text);
// Case flip Latin-1 accented letters.
$text = preg_replace_callback('/\xC3[\xA0-\xB6\xB8-\xBE]/', '\Drupal\Component\Utility\Unicode::caseFlip', $text);
return $text;
}
@trigger_error('\Drupal\Component\Utility\Unicode::strtoupper() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strtoupper() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
return mb_strtoupper($text);
}
/**
@@ -322,18 +307,15 @@ EOD;
*
* @return string
* The string in lowercase.
*
* @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
* mb_strtolower() instead.
*
* @see https://www.drupal.org/node/2850048
*/
public static function strtolower($text) {
if (static::getStatus() == static::STATUS_MULTIBYTE) {
return mb_strtolower($text);
}
else {
// Use C-locale for ASCII-only lowercase.
$text = strtolower($text);
// Case flip Latin-1 accented letters.
$text = preg_replace_callback('/\xC3[\x80-\x96\x98-\x9E]/', '\Drupal\Component\Utility\Unicode::caseFlip', $text);
return $text;
}
@trigger_error('\Drupal\Component\Utility\Unicode::strtolower() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strtolower() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
return mb_strtolower($text);
}
/**
@@ -346,7 +328,7 @@ EOD;
* The string with the first character as uppercase.
*/
public static function ucfirst($text) {
return static::strtoupper(static::substr($text, 0, 1)) . static::substr($text, 1);
return mb_strtoupper(mb_substr($text, 0, 1)) . mb_substr($text, 1);
}
/**
@@ -362,7 +344,7 @@ EOD;
*/
public static function lcfirst($text) {
// Note: no mbstring equivalent!
return static::strtolower(static::substr($text, 0, 1)) . static::substr($text, 1);
return mb_strtolower(mb_substr($text, 0, 1)) . mb_substr($text, 1);
}
/**
@@ -379,7 +361,7 @@ EOD;
public static function ucwords($text) {
$regex = '/(^|[' . static::PREG_CLASS_WORD_BOUNDARY . '])([^' . static::PREG_CLASS_WORD_BOUNDARY . '])/u';
return preg_replace_callback($regex, function (array $matches) {
return $matches[1] . Unicode::strtoupper($matches[2]);
return $matches[1] . mb_strtoupper($matches[2]);
}, $text);
}
@@ -399,92 +381,15 @@ EOD;
*
* @return string
* The shortened string.
*
* @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
* mb_substr() instead.
*
* @see https://www.drupal.org/node/2850048
*/
public static function substr($text, $start, $length = NULL) {
if (static::getStatus() == static::STATUS_MULTIBYTE) {
return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length);
}
else {
$strlen = strlen($text);
// Find the starting byte offset.
$bytes = 0;
if ($start > 0) {
// 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++;
$c = ord($text[$bytes]);
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
}
}
}
elseif ($start < 0) {
// 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) {
$bytes--;
$c = ord($text[$bytes]);
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
}
}
}
$istart = $bytes;
// Find the ending byte offset.
if ($length === NULL) {
$iend = $strlen;
}
elseif ($length > 0) {
// 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;
while ($iend < $strlen - 1 && $chars < $length) {
$iend++;
$c = ord($text[$iend]);
$last_real = FALSE;
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
$last_real = TRUE;
}
}
// 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 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) {
$iend--;
$c = ord($text[$iend]);
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
}
}
// Backtrace one byte if we are not at the beginning of the string.
if ($iend > 0) {
$iend--;
}
}
else {
// $length == 0, return an empty string.
return '';
}
return substr($text, $istart, max(0, $iend - $istart + 1));
}
@trigger_error('\Drupal\Component\Utility\Unicode::substr() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_substr() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
return mb_substr($text, $start, $length);
}
/**
@@ -526,15 +431,15 @@ EOD;
$max_length = max($max_length, 0);
$min_wordsafe_length = max($min_wordsafe_length, 0);
if (static::strlen($string) <= $max_length) {
if (mb_strlen($string) <= $max_length) {
// No truncation needed, so don't add ellipsis, just return.
return $string;
}
if ($add_ellipsis) {
// Truncate ellipsis in case $max_length is small.
$ellipsis = static::substr('…', 0, $max_length);
$max_length -= static::strlen($ellipsis);
$ellipsis = mb_substr('…', 0, $max_length);
$max_length -= mb_strlen($ellipsis);
$max_length = max($max_length, 0);
}
@@ -553,11 +458,11 @@ EOD;
$string = $matches[1];
}
else {
$string = static::substr($string, 0, $max_length);
$string = mb_substr($string, 0, $max_length);
}
}
else {
$string = static::substr($string, 0, $max_length);
$string = mb_substr($string, 0, $max_length);
}
if ($add_ellipsis) {
@@ -583,7 +488,7 @@ EOD;
* $str2, and 0 if they are equal.
*/
public static function strcasecmp($str1, $str2) {
return strcmp(static::strtoupper($str1), static::strtoupper($str2));
return strcmp(mb_strtoupper($str1), mb_strtoupper($str2));
}
/**
@@ -715,18 +620,15 @@ EOD;
* 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.
*
* @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
* mb_strpos() instead.
*
* @see https://www.drupal.org/node/2850048
*/
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);
}
@trigger_error('\Drupal\Component\Utility\Unicode::strpos() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strpos() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
return mb_strpos($haystack, $needle, $offset);
}
}
@@ -19,7 +19,7 @@ class UrlHelper {
/**
* Parses an array into a valid, rawurlencoded query string.
*
* rawurlencode() is RFC3986 compliant, and as a consequence RFC3987
* Function rawurlencode() is RFC3986 compliant, and as a consequence RFC3987
* compliant. The latter defines the required format of "URLs" in HTML5.
* urlencode() is almost the same as rawurlencode(), except that it encodes
* spaces as "+" instead of "%20". This makes its result non compliant to
@@ -7,7 +7,9 @@
"require": {
"php": ">=5.5.9",
"paragonie/random_compat": "^1.0|^2.0",
"drupal/core-render": "^8.2"
"drupal/core-render": "^8.2",
"symfony/polyfill-iconv": "~1.0",
"symfony/polyfill-mbstring": "~1.0"
},
"autoload": {
"psr-4": {
+1
View File
@@ -8,6 +8,7 @@ namespace Drupal\Component\Uuid;
* @see http://php.net/com_create_guid
*/
class Com implements UuidInterface {
/**
* {@inheritdoc}
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "drupal/core-uuid",
"description": "PHP library for reading PO files.",
"description": "UUID generation and validation.",
"type": "library",
"license": "GPL-2.0-or-later",
"support": {
+11 -12
View File
@@ -336,10 +336,10 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
$merge_other = TRUE;
}
if ($this->isForbidden() && $this instanceof AccessResultReasonInterface) {
if ($this->isForbidden() && $this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
$result->setReason($this->getReason());
}
elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface) {
elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
$result->setReason($other->getReason());
}
}
@@ -353,14 +353,13 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
$result = static::neutral();
if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
$merge_other = TRUE;
if ($other instanceof AccessResultReasonInterface) {
$result->setReason($other->getReason());
}
}
else {
if ($this instanceof AccessResultReasonInterface) {
$result->setReason($this->getReason());
}
if ($this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
$result->setReason($this->getReason());
}
elseif ($other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
$result->setReason($other->getReason());
}
}
$result->inheritCacheability($this);
@@ -427,9 +426,9 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
/**
* Inherits the cacheability of the other access result, if any.
*
* inheritCacheability() differs from addCacheableDependency() in how it
* handles max-age, because it is designed to inherit the cacheability of the
* second operand in the andIf() and orIf() operations. There, the situation
* This method differs from addCacheableDependency() in how it handles
* max-age, because it is designed to inherit the cacheability of the second
* operand in the andIf() and orIf() operations. There, the situation
* "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000
* as the end result.
*
@@ -24,7 +24,6 @@ class AccessResultForbidden extends AccessResult implements AccessResultReasonIn
$this->reason = $reason;
}
/**
* {@inheritdoc}
*/
@@ -142,6 +142,7 @@ class CheckProvider implements CheckProviderInterface, ContainerAwareInterface {
return $checks;
}
/**
* Compiles a mapping of requirement keys to access checker service IDs.
*/
@@ -15,7 +15,6 @@ use Symfony\Component\Routing\RouteCollection;
*/
interface CheckProviderInterface {
/**
* For each route, saves a list of applicable access checks to the route.
*
@@ -61,7 +61,14 @@ class CustomAccessCheck implements RoutingAccessInterface {
* The access result.
*/
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
$callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
try {
$callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
}
catch (\InvalidArgumentException $e) {
// The custom access controller method was not found.
throw new \BadMethodCallException(sprintf('The "%s" method is not callable as a _custom_access callback in route "%s"', $route->getRequirement('_custom_access'), $route->getPath()));
}
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
$arguments = $arguments_resolver->getArguments($callable);
@@ -0,0 +1,99 @@
<?php
namespace Drupal\Core\Action\Plugin\Action;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Redirects to an entity deletion form.
*
* @Action(
* id = "entity:delete_action",
* action_label = @Translation("Delete"),
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver",
* )
*/
class DeleteAction extends EntityActionBase {
/**
* The tempstore object.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new DeleteAction object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
$this->currentUser = $current_user;
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('tempstore.private'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
$selection = [];
foreach ($entities as $entity) {
$langcode = $entity->language()->getId();
$selection[$entity->id()][$langcode] = $langcode;
}
$this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection);
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$this->executeMultiple([$object]);
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
return $object->access('delete', $account, $return_as_object);
}
}
@@ -6,6 +6,8 @@ use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -13,6 +15,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The entity type manager.
*
@@ -25,16 +29,22 @@ abstract class EntityActionDeriverBase extends DeriverBase implements ContainerD
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static($container->get('entity_type.manager'));
return new static(
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}
/**
@@ -0,0 +1,40 @@
<?php
namespace Drupal\Core\Action\Plugin\Action\Derivative;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Provides an action deriver that finds entity types with delete form.
*
* @see \Drupal\Core\Action\Plugin\Action\DeleteAction
*/
class EntityDeleteActionDeriver extends EntityActionDeriverBase {
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
if (empty($this->derivatives)) {
$definitions = [];
foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
$definition = $base_plugin_definition;
$definition['type'] = $entity_type_id;
$definition['label'] = $this->t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]);
$definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.delete_multiple_form';
$definitions[$entity_type_id] = $definition;
}
$this->derivatives = $definitions;
}
return $this->derivatives;
}
/**
* {@inheritdoc}
*/
protected function isApplicable(EntityTypeInterface $entity_type) {
return $entity_type->hasLinkTemplate('delete-multiple-form');
}
}
@@ -0,0 +1,56 @@
<?php
namespace Drupal\Core\Ajax;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a helper to for submitting an AJAX form.
*
* @internal
*/
trait AjaxFormHelperTrait {
use AjaxHelperTrait;
/**
* Submit form dialog #ajax callback.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An AJAX response that display validation error messages or represents a
* successful submission.
*/
public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
if ($form_state->hasAnyErrors()) {
$form['status_messages'] = [
'#type' => 'status_messages',
'#weight' => -1000,
];
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
}
else {
$response = $this->successfulAjaxSubmit($form, $form_state);
}
return $response;
}
/**
* Allows the form to respond to a successful AJAX submission.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An AJAX response.
*/
abstract protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state);
}
@@ -0,0 +1,39 @@
<?php
namespace Drupal\Core\Ajax;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
/**
* Provides a helper to determine if the current request is via AJAX.
*
* @internal
*/
trait AjaxHelperTrait {
/**
* Determines if the current request is via AJAX.
*
* @return bool
* TRUE if the current request is via AJAX, FALSE otherwise.
*/
protected function isAjax() {
foreach (['drupal_ajax', 'drupal_modal', 'drupal_dialog'] as $wrapper) {
if (strpos($this->getRequestWrapperFormat(), $wrapper) !== FALSE) {
return TRUE;
}
}
return FALSE;
}
/**
* Gets the wrapper format of the current request.
*
* @string
* The wrapper format.
*/
protected function getRequestWrapperFormat() {
return \Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT);
}
}
@@ -8,6 +8,7 @@ namespace Drupal\Core\Ajax;
* @ingroup ajax
*/
class OpenModalDialogCommand extends OpenDialogCommand {
/**
* Constructs an OpenModalDialog object.
*
@@ -34,19 +34,22 @@ class OpenOffCanvasDialogCommand extends OpenDialogCommand {
* (optional) Custom settings that will be passed to the Drupal behaviors
* on the content of the dialog. If left empty, the settings will be
* populated automatically from the current request.
* @param string $position
* (optional) The position to render the off-canvas dialog.
*/
public function __construct($title, $content, array $dialog_options = [], $settings = NULL) {
public function __construct($title, $content, array $dialog_options = [], $settings = NULL, $position = 'side') {
parent::__construct('#drupal-off-canvas', $title, $content, $dialog_options, $settings);
$this->dialogOptions['modal'] = FALSE;
$this->dialogOptions['autoResize'] = FALSE;
$this->dialogOptions['resizable'] = 'w';
$this->dialogOptions['draggable'] = FALSE;
$this->dialogOptions['drupalAutoButtons'] = FALSE;
$this->dialogOptions['drupalOffCanvasPosition'] = $position;
// @todo drupal.ajax.js does not respect drupalAutoButtons properly, pass an
// empty set of buttons until https://www.drupal.org/node/2793343 is in.
$this->dialogOptions['buttons'] = [];
if (empty($dialog_options['dialogClass'])) {
$this->dialogOptions['dialogClass'] = 'ui-dialog-off-canvas';
$this->dialogOptions['dialogClass'] = "ui-dialog-off-canvas ui-dialog-position-$position";
}
// If no width option is provided then use the default width to avoid the
// dialog staying at the width of the previous instance when opened
@@ -116,10 +116,36 @@ class ContextDefinition extends Plugin {
if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) {
throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.');
}
$class = isset($values['class']) ? $values['class'] : 'Drupal\Core\Plugin\Context\ContextDefinition';
$class = $this->getDefinitionClass($values);
$this->definition = new $class($values['value'], $values['label'], $values['required'], $values['multiple'], $values['description'], $values['default_value']);
}
/**
* Determines the context definition class to use.
*
* If the annotation specifies a specific context definition class, we use
* that. Otherwise, we use \Drupal\Core\Plugin\Context\EntityContextDefinition
* if the data type starts with 'entity:', since it contains specialized logic
* specific to entities. Otherwise, we fall back to the generic
* \Drupal\Core\Plugin\Context\ContextDefinition class.
*
* @param array $values
* The annotation values.
*
* @return string
* The fully-qualified name of the context definition class.
*/
protected function getDefinitionClass(array $values) {
if (isset($values['class'])) {
return $values['class'];
}
if (strpos($values['value'], 'entity:') === 0) {
return 'Drupal\Core\Plugin\Context\EntityContextDefinition';
}
return 'Drupal\Core\Plugin\Context\ContextDefinition';
}
/**
* Returns the value of an annotation.
*
+1 -1
View File
@@ -577,7 +577,7 @@ class ArchiveTar
* indicated by $p_path. When relevant the memorized path of the
* files/dir can be modified by removing the $p_remove_path path at the
* beginning of the file/dir path.
* While extracting a file, if the directory path does not exists it is
* While extracting a file, if the directory path does not exist it is
* created.
* While extracting a file, if the file already exists it is replaced
* without looking for last modification date.
+2 -2
View File
@@ -119,7 +119,7 @@ class CssOptimizer implements AssetOptimizerInterface {
// If a BOM is found, convert the file to UTF-8, then use substr() to
// remove the BOM from the result.
if ($encoding = (Unicode::encodingFromBOM($contents))) {
$contents = Unicode::substr(Unicode::convertToUtf8($contents, $encoding), 1);
$contents = mb_substr(Unicode::convertToUtf8($contents, $encoding), 1);
}
// If no BOM, check for fallback encoding. Per CSS spec the regex is very strict.
elseif (preg_match('/^@charset "([^"]+)";/', $contents, $matches)) {
@@ -189,7 +189,7 @@ class CssOptimizer implements AssetOptimizerInterface {
if ($optimize) {
// Perform some safe CSS optimizations.
// Regexp to match comment blocks.
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
// Regexp to match double quoted strings.
$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
// Regexp to match single quoted strings.
+1 -1
View File
@@ -24,7 +24,7 @@ class JsOptimizer implements AssetOptimizerInterface {
// remove the BOM from the result.
$data = file_get_contents($js_asset['data']);
if ($encoding = (Unicode::encodingFromBOM($data))) {
$data = Unicode::substr(Unicode::convertToUtf8($data, $encoding), 1);
$data = mb_substr(Unicode::convertToUtf8($data, $encoding), 1);
}
// If no BOM is found, check for the charset attribute.
elseif (isset($js_asset['attributes']['charset'])) {
@@ -65,6 +65,8 @@ class LibraryDependencyResolver implements LibraryDependencyResolverInterface {
* {@inheritdoc}
*/
public function getMinimalRepresentativeSubset(array $libraries) {
assert(count($libraries) === count(array_unique($libraries)), '$libraries can\'t contain duplicate items.');
$minimal = [];
// Determine each library's dependencies.
@@ -19,7 +19,7 @@ class LibraryDiscovery implements LibraryDiscoveryInterface {
/**
* The final library definitions, statically cached.
*
* hook_library_info_alter() and hook_js_settings_alter() allows modules
* Hooks hook_library_info_alter() and hook_js_settings_alter() allow modules
* and themes to dynamically alter a library definition (once per request).
*
* @var array
+340
View File
@@ -0,0 +1,340 @@
<?php
namespace Drupal\Core\Batch;
use Drupal\Core\Queue\QueueInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Builds an array for a batch process.
*
* Example code to create a batch:
* @code
* $batch_builder = (new BatchBuilder())
* ->setTitle(t('Batch Title'))
* ->setFinishCallback('batch_example_finished_callback')
* ->setInitMessage(t('The initialization message (optional)'));
* foreach ($ids as $id) {
* $batch_builder->addOperation('batch_example_callback', [$id]);
* }
* batch_set($batch_builder->toArray());
* @endcode
*/
class BatchBuilder {
/**
* The set of operations to be processed.
*
* Each operation is a tuple of the function / method to use and an array
* containing any parameters to be passed.
*
* @var array
*/
protected $operations = [];
/**
* The title for the batch.
*
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
*/
protected $title;
/**
* The initializing message for the batch.
*
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
*/
protected $initMessage;
/**
* The message to be shown while the batch is in progress.
*
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
*/
protected $progressMessage;
/**
* The message to be shown if a problem occurs.
*
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
*/
protected $errorMessage;
/**
* The name of a function / method to be called when the batch finishes.
*
* @var string
*/
protected $finished;
/**
* The file containing the operation and finished callbacks.
*
* If the callbacks are in the .module file or can be autoloaded, for example,
* static methods on a class, then this does not need to be set.
*
* @var string
*/
protected $file;
/**
* An array of libraries to be included when processing the batch.
*
* @var string[]
*/
protected $libraries = [];
/**
* An array of options to be used with the redirect URL.
*
* @var array
*/
protected $urlOptions = [];
/**
* Specifies if the batch is progressive.
*
* If true, multiple calls are used. Otherwise an attempt is made to process
* the batch in a single run.
*
* @var bool
*/
protected $progressive = TRUE;
/**
* The details of the queue to use.
*
* A tuple containing the name of the queue and the class of the queue to use.
*
* @var array
*/
protected $queue;
/**
* Sets the default values for the batch builder.
*/
public function __construct() {
$this->title = new TranslatableMarkup('Processing');
$this->initMessage = new TranslatableMarkup('Initializing.');
$this->progressMessage = new TranslatableMarkup('Completed @current of @total.');
$this->errorMessage = new TranslatableMarkup('An error has occurred.');
}
/**
* Sets the title.
*
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $title
* The title.
*
* @return $this
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* Sets the finished callback.
*
* This callback will be executed if the batch process is done.
*
* @param callable $callback
* The callback.
*
* @return $this
*/
public function setFinishCallback(callable $callback) {
$this->finished = $callback;
return $this;
}
/**
* Sets the displayed message while processing is initialized.
*
* Defaults to 'Initializing.'.
*
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
* The text to display.
*
* @return $this
*/
public function setInitMessage($message) {
$this->initMessage = $message;
return $this;
}
/**
* Sets the message to display when the batch is being processed.
*
* Defaults to 'Completed @current of @total.'.
*
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
* The text to display. Available placeholders are:
* - '@current'
* - '@remaining'
* - '@total'
* - '@percentage'
* - '@estimate'
* - '@elapsed'.
*
* @return $this
*/
public function setProgressMessage($message) {
$this->progressMessage = $message;
return $this;
}
/**
* Sets the message to display if an error occurs while processing.
*
* Defaults to 'An error has occurred.'.
*
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
* The text to display.
*
* @return $this
*/
public function setErrorMessage($message) {
$this->errorMessage = $message;
return $this;
}
/**
* Sets the file that contains the callback functions.
*
* The path should be relative to base_path(), and thus should be built using
* drupal_get_path(). Defaults to {module_name}.module.
*
* @param string $filename
* The path to the file.
*
* @return $this
*/
public function setFile($filename) {
$this->file = $filename;
return $this;
}
/**
* Sets the libraries to use when processing the batch.
*
* Adds the libraries for use on the progress page. Any previously added
* libraries are removed.
*
* @param string[] $libraries
* The libraries to be used.
*
* @return $this
*/
public function setLibraries(array $libraries) {
$this->libraries = $libraries;
return $this;
}
/**
* Sets the options for redirect URLs.
*
* @param array $options
* The options to use.
*
* @return $this
*
* @see \Drupal\Core\Url
*/
public function setUrlOptions(array $options) {
$this->urlOptions = $options;
return $this;
}
/**
* Sets the batch to run progressively.
*
* @param bool $is_progressive
* (optional) A Boolean that indicates whether or not the batch needs to run
* progressively. TRUE indicates that the batch will run in more than one
* run. FALSE indicates that the batch will finish in a single run. Defaults
* to TRUE.
*
* @return $this
*/
public function setProgressive($is_progressive = TRUE) {
$this->progressive = $is_progressive;
return $this;
}
/**
* Sets an override for the default queue.
*
* The class will typically either be \Drupal\Core\Queue\Batch or
* \Drupal\Core\Queue\BatchMemory. The class defaults to Batch if progressive
* is TRUE, or to BatchMemory if progressive is FALSE.
*
* @param string $name
* The unique identifier for the queue.
* @param string $class
* The fully qualified name of a class that implements
* \Drupal\Core\Queue\QueueInterface.
*
* @return $this
*/
public function setQueue($name, $class) {
if (!class_exists($class)) {
throw new \InvalidArgumentException('Class ' . $class . ' does not exist.');
}
if (!in_array(QueueInterface::class, class_implements($class))) {
throw new \InvalidArgumentException(
'Class ' . $class . ' does not implement \Drupal\Core\Queue\QueueInterface.'
);
}
$this->queue = [
'name' => $name,
'class' => $class,
];
return $this;
}
/**
* Adds a batch operation.
*
* @param callable $callback
* The name of the callback function.
* @param array $arguments
* An array of arguments to pass to the callback function.
*
* @return $this
*/
public function addOperation(callable $callback, array $arguments = []) {
$this->operations[] = [$callback, $arguments];
return $this;
}
/**
* Converts a \Drupal\Core\Batch\Batch object into an array.
*
* @return array
* The array representation of the object.
*/
public function toArray() {
$array = [
'operations' => $this->operations ?: [],
'title' => $this->title ?: '',
'init_message' => $this->initMessage ?: '',
'progress_message' => $this->progressMessage ?: '',
'error_message' => $this->errorMessage ?: '',
'finished' => $this->finished,
'file' => $this->file,
'library' => $this->libraries ?: [],
'url_options' => $this->urlOptions ?: [],
'progressive' => $this->progressive,
];
if ($this->queue) {
$array['queue'] = $this->queue;
}
return $array;
}
}
+3 -2
View File
@@ -4,9 +4,9 @@ namespace Drupal\Core\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerTrait;
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\Plugin\PluginWithFormsInterface;
@@ -26,6 +26,7 @@ use Drupal\Component\Transliteration\TransliterationInterface;
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface {
use ContextAwarePluginAssignmentTrait;
use MessengerTrait;
use PluginWithFormsTrait;
/**
@@ -244,7 +245,7 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
// \Drupal\system\MachineNameController::transliterate(), so it might make
// sense to provide a common service for the two.
$transliterated = $this->transliteration()->transliterate($admin_label, LanguageInterface::LANGCODE_DEFAULT, '_');
$transliterated = Unicode::strtolower($transliterated);
$transliterated = mb_strtolower($transliterated);
$transliterated = preg_replace('@[^a-z0-9_.]+@', '', $transliterated);
+30 -4
View File
@@ -6,8 +6,9 @@ use Drupal\Component\Plugin\FallbackPluginManagerInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Plugin\FilteredPluginManagerTrait;
use Psr\Log\LoggerInterface;
/**
* Manages discovery and instantiation of block plugins.
@@ -21,7 +22,14 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
use CategorizingPluginManagerTrait {
getSortedDefinitions as traitGetSortedDefinitions;
}
use ContextAwarePluginManagerTrait;
use FilteredPluginManagerTrait;
/**
* The logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Constructs a new \Drupal\Core\Block\BlockManager object.
@@ -33,12 +41,22 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
* @param \Psr\Log\LoggerInterface $logger
* The logger.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, LoggerInterface $logger) {
parent::__construct('Plugin/Block', $namespaces, $module_handler, 'Drupal\Core\Block\BlockPluginInterface', 'Drupal\Core\Block\Annotation\Block');
$this->alterInfo('block');
$this->alterInfo($this->getType());
$this->setCacheBackend($cache_backend, 'block_plugins');
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
protected function getType() {
return 'block';
}
/**
@@ -67,4 +85,12 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
return 'broken';
}
/**
* {@inheritdoc}
*/
protected function handlePluginNotFound($plugin_id, array $configuration) {
$this->logger->warning('The "%plugin_id" was not found', ['%plugin_id' => $plugin_id]);
return parent::handlePluginNotFound($plugin_id, $configuration);
}
}
@@ -4,10 +4,11 @@ namespace Drupal\Core\Block;
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface;
use Drupal\Core\Plugin\FilteredPluginManagerInterface;
/**
* Provides an interface for the discovery and instantiation of block plugins.
*/
interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface {
interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface, FilteredPluginManagerInterface {
}
@@ -5,8 +5,7 @@ namespace Drupal\Core\Block;
/**
* The interface for "messages" (#type => status_messages) blocks.
*
* @see drupal_set_message()
* @see drupal_get_message()
* @see \Drupal\Core\Messenger\MessengerInterface
* @see \Drupal\Core\Render\Element\StatusMessages
* @see \Drupal\block\Plugin\DisplayVariant\BlockPageVariant
*
@@ -38,7 +38,7 @@ class Broken extends BlockBase {
*/
protected function brokenMessage() {
$build['message'] = [
'#markup' => $this->t('This block is broken or missing. You may be missing content or you might need to enable the original module.')
'#markup' => $this->t('This block is broken or missing. You may be missing content or you might need to enable the original module.'),
];
return $build;
@@ -7,7 +7,7 @@ use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the SiteCacheContext service, for "per site" caching.
*
* Cache context ID: 'site'.
* Cache context ID: 'url.site'.
*
* A "site" is defined as the combination of URI scheme, domain name, port and
* base path. It allows for varying between the *same* site being accessed via
@@ -74,7 +74,7 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
$max_rows_settings = $this->settings->get('database_cache_max_rows');
// First, look for a cache bin specific setting.
if (isset($max_rows_settings['bins'][$bin])) {
$max_rows = $max_rows_settings['bins'][$bin];
$max_rows = $max_rows_settings['bins'][$bin];
}
// Second, use configured default backend.
elseif (isset($max_rows_settings['default'])) {
@@ -0,0 +1,63 @@
<?php
namespace Drupal\Core\Cache\MemoryCache;
use Drupal\Component\Assertion\Inspector;
use Drupal\Core\Cache\MemoryBackend;
/**
* Defines a memory cache implementation.
*
* Stores cache items in memory using a PHP array.
*
* @ingroup cache
*/
class MemoryCache extends MemoryBackend implements MemoryCacheInterface {
/**
* Prepares a cached item.
*
* Checks that items are either permanent or did not expire, and returns data
* as appropriate.
*
* @param object $cache
* An item loaded from cache_get() or cache_get_multiple().
* @param bool $allow_invalid
* (optional) If TRUE, cache items may be returned even if they have expired
* or been invalidated. Defaults to FALSE.
*
* @return mixed
* The item with data as appropriate or FALSE if there is no
* valid item to load.
*/
protected function prepareItem($cache, $allow_invalid = FALSE) {
if (!isset($cache->data)) {
return FALSE;
}
// Check expire time.
$cache->valid = $cache->expire == static::CACHE_PERMANENT || $cache->expire >= $this->getRequestTime();
if (!$allow_invalid && !$cache->valid) {
return FALSE;
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = MemoryCacheInterface::CACHE_PERMANENT, array $tags = []) {
assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
$tags = array_unique($tags);
$this->cache[$cid] = (object) [
'cid' => $cid,
'data' => $data,
'created' => $this->getRequestTime(),
'expire' => $expire,
'tags' => $tags,
];
}
}
@@ -0,0 +1,18 @@
<?php
namespace Drupal\Core\Cache\MemoryCache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
/**
* Defines an interface for memory cache implementations.
*
* This has additional requirements over CacheBackendInterface and
* CacheTagsInvalidatorInterface. Objects stored must be the same instance when
* retrieved from cache, so that this can be used as a replacement for protected
* properties and similar.
*
* @ingroup cache
*/
interface MemoryCacheInterface extends CacheBackendInterface, CacheTagsInvalidatorInterface {}
@@ -145,7 +145,7 @@ class DbDumpCommand extends DbCommandBase {
$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($connection, $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';
@@ -259,8 +259,12 @@ class DbDumpCommand extends DbCommandBase {
$query = $connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
$data = $query->fetchAssoc();
// Map the collation to a character set. For example, 'utf8mb4_general_ci'
// (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) will be mapped to 'utf8mb4'.
list($charset,) = explode('_', $data['Collation'], 2);
// Set `mysql_character_set`. This will be ignored by other backends.
$definition['mysql_character_set'] = str_replace('_general_ci', '', $data['Collation']);
$definition['mysql_character_set'] = $charset;
}
/**
@@ -0,0 +1,337 @@
<?php
namespace Drupal\Core\Command;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\ConnectionNotDefinedException;
use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Extension\InfoParserDynamic;
use Drupal\Core\Site\Settings;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Installs a Drupal site for local testing/development.
*
* @internal
* This command makes no guarantee of an API for Drupal extensions.
*/
class InstallCommand extends Command {
/**
* The class loader.
*
* @var object
*/
protected $classLoader;
/**
* Constructs a new InstallCommand command.
*
* @param object $class_loader
* The class loader.
*/
public function __construct($class_loader) {
parent::__construct('install');
$this->classLoader = $class_loader;
}
/**
* {@inheritdoc}
*/
protected function configure() {
$this->setName('install')
->setDescription('Installs a Drupal demo site. This is not meant for production and might be too simple for custom development. It is a quick and easy way to get Drupal running.')
->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.')
->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in.', 'en')
->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name.', 'Drupal')
->addUsage('demo_umami --langcode fr')
->addUsage('standard --site-name QuickInstall');
parent::configure();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$io = new SymfonyStyle($input, $output);
if (!extension_loaded('pdo_sqlite')) {
$io->getErrorStyle()->error('You must have the pdo_sqlite PHP extension installed. See core/INSTALL.sqlite.txt for instructions.');
return 1;
}
// Change the directory to the Drupal root.
chdir(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
// Check whether there is already an installation.
if ($this->isDrupalInstalled()) {
// Do not fail if the site is already installed so this command can be
// chained with ServerCommand.
$output->writeln('<info>Drupal is already installed.</info> If you want to reinstall, remove sites/default/files and sites/default/settings.php.');
return 0;
}
$install_profile = $input->getArgument('install-profile');
if ($install_profile && !$this->validateProfile($install_profile, $io)) {
return 1;
}
if (!$install_profile) {
$install_profile = $this->selectProfile($io);
}
return $this->install($this->classLoader, $io, $install_profile, $input->getOption('langcode'), $this->getSitePath(), $input->getOption('site-name'));
}
/**
* Returns whether there is already an existing Drupal installation.
*
* @return bool
*/
protected function isDrupalInstalled() {
try {
$kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
$kernel::bootEnvironment();
$kernel->setSitePath($this->getSitePath());
Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
$kernel->boot();
}
catch (ConnectionNotDefinedException $e) {
return FALSE;
}
return !empty(Database::getConnectionInfo());
}
/**
* Installs Drupal with specified installation profile.
*
* @param object $class_loader
* The class loader.
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* The Symfony output decorator.
* @param string $profile
* The installation profile to use.
* @param string $langcode
* The language to install the site in.
* @param string $site_path
* The path to install the site to, like 'sites/default'.
* @param string $site_name
* The site name.
*
* @throws \Exception
* Thrown when failing to create the $site_path directory or settings.php.
*/
protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, $site_path, $site_name) {
$password = Crypt::randomBytesBase64(12);
$parameters = [
'interactive' => FALSE,
'site_path' => $site_path,
'parameters' => [
'profile' => $profile,
'langcode' => $langcode,
],
'forms' => [
'install_settings_form' => [
'driver' => 'sqlite',
'sqlite' => [
'database' => $site_path . '/files/.sqlite',
],
],
'install_configure_form' => [
'site_name' => $site_name,
'site_mail' => 'drupal@localhost',
'account' => [
'name' => 'admin',
'mail' => 'admin@localhost',
'pass' => [
'pass1' => $password,
'pass2' => $password,
],
],
'enable_update_status_module' => TRUE,
// form_type_checkboxes_value() requires NULL instead of FALSE values
// for programmatic form submissions to disable a checkbox.
'enable_update_status_emails' => NULL,
],
],
];
// Create the directory and settings.php if not there so that the installer
// works.
if (!is_dir($site_path)) {
if ($io->isVerbose()) {
$io->writeln("Creating directory: $site_path");
}
if (!mkdir($site_path, 0775)) {
throw new \RuntimeException("Failed to create directory $site_path");
}
}
if (!file_exists("{$site_path}/settings.php")) {
if ($io->isVerbose()) {
$io->writeln("Creating file: {$site_path}/settings.php");
}
if (!copy('sites/default/default.settings.php', "{$site_path}/settings.php")) {
throw new \RuntimeException("Copying sites/default/default.settings.php to {$site_path}/settings.php failed.");
}
}
require_once 'core/includes/install.core.inc';
$progress_bar = $io->createProgressBar();
install_drupal($class_loader, $parameters, function ($install_state) use ($progress_bar) {
static $started = FALSE;
if (!$started) {
$started = TRUE;
// We've already done 1.
$progress_bar->setFormat("%current%/%max% [%bar%]\n%message%\n");
$progress_bar->setMessage(t('Installing @drupal', ['@drupal' => drupal_install_profile_distribution_name()]));
$tasks = install_tasks($install_state);
$progress_bar->start(count($tasks) + 1);
}
$tasks_to_perform = install_tasks_to_perform($install_state);
$task = current($tasks_to_perform);
if (isset($task['display_name'])) {
$progress_bar->setMessage($task['display_name']);
}
$progress_bar->advance();
});
$success_message = t('Congratulations, you installed @drupal!', [
'@drupal' => drupal_install_profile_distribution_name(),
'@name' => 'admin',
'@pass' => $password,
], ['langcode' => $langcode]);
$progress_bar->setMessage('<info>' . $success_message . '</info>');
$progress_bar->display();
$progress_bar->finish();
$io->writeln('<info>Username:</info> admin');
$io->writeln("<info>Password:</info> $password");
}
/**
* Gets the site path.
*
* Defaults to 'sites/default'. For testing purposes this can be overridden
* using the DRUPAL_DEV_SITE_PATH environment variable.
*
* @return string
* The site path to use.
*/
protected function getSitePath() {
return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
}
/**
* Selects the install profile to use.
*
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* Symfony style output decorator.
*
* @return string
* The selected install profile.
*
* @see _install_select_profile()
* @see \Drupal\Core\Installer\Form\SelectProfileForm
*/
protected function selectProfile(SymfonyStyle $io) {
$profiles = $this->getProfiles();
// If there is a distribution there will be only one profile.
if (count($profiles) == 1) {
return key($profiles);
}
// Display alphabetically by human-readable name, but always put the core
// profiles first (if they are present in the filesystem).
natcasesort($profiles);
if (isset($profiles['minimal'])) {
// If the expert ("Minimal") core profile is present, put it in front of
// any non-core profiles rather than including it with them
// alphabetically, since the other profiles might be intended to group
// together in a particular way.
$profiles = ['minimal' => $profiles['minimal']] + $profiles;
}
if (isset($profiles['standard'])) {
// If the default ("Standard") core profile is present, put it at the very
// top of the list. This profile will have its radio button pre-selected,
// so we want it to always appear at the top.
$profiles = ['standard' => $profiles['standard']] + $profiles;
}
reset($profiles);
return $io->choice('Select an installation profile', $profiles, current($profiles));
}
/**
* Validates a user provided install profile.
*
* @param string $install_profile
* Install profile to validate.
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* Symfony style output decorator.
*
* @return bool
* TRUE if the profile is valid, FALSE if not.
*/
protected function validateProfile($install_profile, SymfonyStyle $io) {
// Allow people to install hidden and non-distribution profiles if they
// supply the argument.
$profiles = $this->getProfiles(TRUE, FALSE);
if (!isset($profiles[$install_profile])) {
$error_msg = sprintf("'%s' is not a valid install profile.", $install_profile);
$alternatives = [];
foreach (array_keys($profiles) as $profile_name) {
$lev = levenshtein($install_profile, $profile_name);
if ($lev <= strlen($profile_name) / 4 || FALSE !== strpos($profile_name, $install_profile)) {
$alternatives[] = $profile_name;
}
}
if (!empty($alternatives)) {
$error_msg .= sprintf(" Did you mean '%s'?", implode("' or '", $alternatives));
}
$io->getErrorStyle()->error($error_msg);
return FALSE;
}
return TRUE;
}
/**
* Gets a list of profiles.
*
* @param bool $include_hidden
* (optional) Whether to include hidden profiles. Defaults to FALSE.
* @param bool $auto_select_distributions
* (optional) Whether to only return the first distribution found.
*
* @return string[]
* An array of profile descriptions keyed by the profile machine name.
*/
protected function getProfiles($include_hidden = FALSE, $auto_select_distributions = TRUE) {
// Build a list of all available profiles.
$listing = new ExtensionDiscovery(getcwd(), FALSE);
$listing->setProfileDirectories([]);
$profiles = [];
$info_parser = new InfoParserDynamic();
foreach ($listing->scan('profile') as $profile) {
$details = $info_parser->parse($profile->getPathname());
// Don't show hidden profiles.
if (!$include_hidden && !empty($details['hidden'])) {
continue;
}
// Determine the name of the profile; default to the internal name if none
// is specified.
$name = isset($details['name']) ? $details['name'] : $profile->getName();
$description = isset($details['description']) ? $details['description'] : $name;
$profiles[$profile->getName()] = $description;
if ($auto_select_distributions && !empty($details['distribution'])) {
return [$profile->getName() => $description];
}
}
return $profiles;
}
}
@@ -0,0 +1,76 @@
<?php
namespace Drupal\Core\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Installs a Drupal site and starts a webserver for local testing/development.
*
* Wraps 'install' and 'server' commands.
*
* @internal
* This command makes no guarantee of an API for Drupal extensions.
*
* @see \Drupal\Core\Command\InstallCommand
* @see \Drupal\Core\Command\ServerCommand
*/
class QuickStartCommand extends Command {
/**
* {@inheritdoc}
*/
protected function configure() {
$this->setName('quick-start')
->setDescription('Installs a Drupal site and runs a web server. This is not meant for production and might be too simple for custom development. It is a quick and easy way to get Drupal running.')
->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.')
->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en.', 'en')
->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name. Defaults to Drupal.', 'Drupal')
->addOption('host', NULL, InputOption::VALUE_OPTIONAL, 'Provide a host for the server to run on. Defaults to 127.0.0.1.', '127.0.0.1')
->addOption('port', NULL, InputOption::VALUE_OPTIONAL, 'Provide a port for the server to run on. Will be determined automatically if none supplied.')
->addOption('suppress-login', 's', InputOption::VALUE_NONE, 'Disable opening a login URL in a browser.')
->addUsage('demo_umami --langcode fr')
->addUsage('standard --site-name QuickInstall --host localhost --port 8080')
->addUsage('minimal --host my-site.com --port 80');
parent::configure();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$command = $this->getApplication()->find('install');
$arguments = [
'command' => 'install',
'install-profile' => $input->getArgument('install-profile'),
'--langcode' => $input->getOption('langcode'),
'--site-name' => $input->getOption('site-name'),
];
$installInput = new ArrayInput($arguments);
$returnCode = $command->run($installInput, $output);
if ($returnCode === 0) {
$command = $this->getApplication()->find('server');
$arguments = [
'command' => 'server',
'--host' => $input->getOption('host'),
'--port' => $input->getOption('port'),
];
if ($input->getOption('suppress-login')) {
$arguments['--suppress-login'] = TRUE;
}
$serverInput = new ArrayInput($arguments);
$returnCode = $command->run($serverInput, $output);
}
return $returnCode;
}
}
@@ -0,0 +1,278 @@
<?php
namespace Drupal\Core\Command;
use Drupal\Core\Database\ConnectionNotDefinedException;
use Drupal\Core\DrupalKernel;
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\Site\Settings;
use Drupal\user\Entity\User;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\PhpProcess;
use Symfony\Component\Process\Process;
/**
* Runs the PHP webserver for a Drupal site for local testing/development.
*
* @internal
* This command makes no guarantee of an API for Drupal extensions.
*/
class ServerCommand extends Command {
/**
* The class loader.
*
* @var object
*/
protected $classLoader;
/**
* Constructs a new ServerCommand command.
*
* @param object $class_loader
* The class loader.
*/
public function __construct($class_loader) {
parent::__construct('server');
$this->classLoader = $class_loader;
}
/**
* {@inheritdoc}
*/
protected function configure() {
$this->setDescription('Starts up a webserver for a site.')
->addOption('host', NULL, InputOption::VALUE_OPTIONAL, 'Provide a host for the server to run on.', '127.0.0.1')
->addOption('port', NULL, InputOption::VALUE_OPTIONAL, 'Provide a port for the server to run on. Will be determined automatically if none supplied.')
->addOption('suppress-login', 's', InputOption::VALUE_NONE, 'Disable opening a login URL in a browser.')
->addUsage('--host localhost --port 8080')
->addUsage('--host my-site.com --port 80');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$io = new SymfonyStyle($input, $output);
$host = $input->getOption('host');
$port = $input->getOption('port');
if (!$port) {
$port = $this->findAvailablePort($host);
}
if (!$port) {
$io->getErrorStyle()->error('Unable to automatically determine a port. Use the --port to hardcode an available port.');
}
try {
$kernel = $this->boot();
}
catch (ConnectionNotDefinedException $e) {
$io->getErrorStyle()->error("No installation found. Use the 'install' command.");
return 1;
}
return $this->start($host, $port, $kernel, $input, $io);
}
/**
* Boots up a Drupal environment.
*
* @return \Drupal\Core\DrupalKernelInterface
* The Drupal kernel.
*
* @throws \Exception
* Exception thrown if kernel does not boot.
*/
protected function boot() {
$kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
$kernel::bootEnvironment();
$kernel->setSitePath($this->getSitePath());
Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
$kernel->boot();
// Some services require a request to work. For example, CommentManager.
// This is needed as generating the URL fires up entity load hooks.
$kernel->getContainer()
->get('request_stack')
->push(Request::createFromGlobals());
return $kernel;
}
/**
* Finds an available port.
*
* @param string $host
* The host to find a port on.
*
* @return int|false
* The available port or FALSE, if no available port found,
*/
protected function findAvailablePort($host) {
$port = 8888;
while ($port >= 8888 && $port <= 9999) {
$connection = @fsockopen($host, $port);
if (is_resource($connection)) {
// Port is being used.
fclose($connection);
}
else {
// Port is available.
return $port;
}
$port++;
}
return FALSE;
}
/**
* Opens a URL in your system default browser.
*
* @param string $url
* The URL to browser to.
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* The IO.
*/
protected function openBrowser($url, SymfonyStyle $io) {
$is_windows = defined('PHP_WINDOWS_VERSION_BUILD');
if ($is_windows) {
// Handle escaping ourselves.
$cmd = 'start "web" "' . $url . '""';
}
else {
$url = escapeshellarg($url);
}
$is_linux = (new Process('which xdg-open'))->run();
$is_osx = (new Process('which open'))->run();
if ($is_linux === 0) {
$cmd = 'xdg-open ' . $url;
}
elseif ($is_osx === 0) {
$cmd = 'open ' . $url;
}
if (empty($cmd)) {
$io->getErrorStyle()
->error('No suitable browser opening command found, open yourself: ' . $url);
return;
}
if ($io->isVerbose()) {
$io->writeln("<info>Browser command:</info> $cmd");
}
// Need to escape double quotes in the command so the PHP will work.
$cmd = str_replace('"', '\"', $cmd);
// Sleep for 2 seconds before opening the browser. This allows the command
// to start up the PHP built-in webserver in the meantime. We use a
// PhpProcess so that Windows powershell users also get a browser opened
// for them.
$php = "<?php sleep(2); passthru(\"$cmd\"); ?>";
$process = new PhpProcess($php);
$process->start();
return;
}
/**
* Gets a one time login URL for user 1.
*
* @return string
* The one time login URL for user 1.
*/
protected function getOneTimeLoginUrl() {
$user = User::load(1);
\Drupal::moduleHandler()->load('user');
return user_pass_reset_url($user);
}
/**
* Starts up a webserver with a running Drupal.
*
* @param string $host
* The hostname of the webserver.
* @param int $port
* The port to start the webserver on.
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The Drupal kernel.
* @param \Symfony\Component\Console\Input\InputInterface $input
* The input.
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
* The IO.
*
* @return int
* The exit status of the PHP in-built webserver command.
*/
protected function start($host, $port, DrupalKernelInterface $kernel, InputInterface $input, SymfonyStyle $io) {
$finder = new PhpExecutableFinder();
$binary = $finder->find();
if ($binary === FALSE) {
throw new \RuntimeException('Unable to find the PHP binary.');
}
$io->writeln("<info>Drupal development server started:</info> <http://{$host}:{$port}>");
$io->writeln('<info>This server is not meant for production use.</info>');
$one_time_login = "http://$host:$port{$this->getOneTimeLoginUrl()}/login";
$io->writeln("<info>One time login url:</info> <$one_time_login>");
$io->writeln('Press Ctrl-C to quit the Drupal development server.');
if (!$input->getOption('suppress-login')) {
if ($this->openBrowser("$one_time_login?destination=" . urlencode("/"), $io) === 1) {
$io->error('Error while opening up a one time login URL');
}
}
// Use the Process object to construct an escaped command line.
$process = new Process([
$binary,
'-S',
$host . ':' . $port,
'.ht.router.php',
], $kernel->getAppRoot(), [], NULL, NULL);
if ($io->isVerbose()) {
$io->writeln("<info>Server command:</info> {$process->getCommandLine()}");
}
// Carefully manage output so we can display output only in verbose mode.
$descriptors = [];
$descriptors[0] = STDIN;
$descriptors[1] = ['pipe', 'w'];
$descriptors[2] = ['pipe', 'w'];
$server = proc_open($process->getCommandLine(), $descriptors, $pipes, $kernel->getAppRoot());
if (is_resource($server)) {
if ($io->isVerbose()) {
// Write a blank line so that server output and the useful information are
// visually separated.
$io->writeln('');
}
$server_status = proc_get_status($server);
while ($server_status['running']) {
if ($io->isVerbose()) {
fpassthru($pipes[2]);
}
sleep(1);
$server_status = proc_get_status($server);
}
}
return proc_close($server);
}
/**
* Gets the site path.
*
* Defaults to 'sites/default'. For testing purposes this can be overridden
* using the DRUPAL_DEV_SITE_PATH environment variable.
*
* @return string
* The site path to use.
*/
protected function getSitePath() {
return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
}
}
+10 -2
View File
@@ -6,6 +6,7 @@ use Drupal\Component\PhpStorage\FileStorage;
use Composer\Script\Event;
use Composer\Installer\PackageEvent;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\ProcessExecutor;
/**
* Provides static functions for composer script events.
@@ -160,7 +161,7 @@ EOT;
return;
}
// If the PHP version is 7.2 or above and PHPUnit is less than version 6
// If the PHP version is 7.0 or above and PHPUnit is less than version 6
// call the drupal-phpunit-upgrade script to upgrade PHPUnit.
if (!static::upgradePHPUnitCheck($phpunit_package->getVersion())) {
$event->getComposer()
@@ -182,7 +183,7 @@ EOT;
* TRUE if the PHPUnit needs to be upgraded, FALSE if not.
*/
public static function upgradePHPUnitCheck($phpunit_version) {
return !(version_compare(PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, '7.2') >= 0 && version_compare($phpunit_version, '6.1') < 0);
return !(version_compare(PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, '7.0') >= 0 && version_compare($phpunit_version, '6.1') < 0);
}
/**
@@ -269,6 +270,13 @@ EOT;
return $package_key;
}
/**
* Removes Composer's timeout so that scripts can run indefinitely.
*/
public static function removeTimeout() {
ProcessExecutor::setTimeout(0);
}
/**
* Helper method to remove directories and the files they contain.
*
@@ -8,8 +8,9 @@ use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Executable\ExecutableInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Plugin\FilteredPluginManagerInterface;
use Drupal\Core\Plugin\FilteredPluginManagerTrait;
/**
* A plugin manager for condition plugins.
@@ -20,10 +21,10 @@ use Drupal\Core\Plugin\DefaultPluginManager;
*
* @ingroup plugin_api
*/
class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface {
class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface, FilteredPluginManagerInterface {
use CategorizingPluginManagerTrait;
use ContextAwarePluginManagerTrait;
use FilteredPluginManagerTrait;
/**
* Constructs a ConditionManager object.
@@ -43,6 +44,13 @@ class ConditionManager extends DefaultPluginManager implements ExecutableManager
parent::__construct('Plugin/Condition', $namespaces, $module_handler, 'Drupal\Core\Condition\ConditionInterface', 'Drupal\Core\Condition\Annotation\Condition');
}
/**
* {@inheritdoc}
*/
protected function getType() {
return 'condition';
}
/**
* {@inheritdoc}
*/
+4 -3
View File
@@ -219,6 +219,10 @@ class Config extends StorableConfigBase {
}
}
// Potentially configuration schema could have changed the underlying data's
// types.
$this->resetOverriddenData();
$this->storage->write($this->name, $this->data);
if (!$this->isNew) {
Cache::invalidateTags($this->getCacheTags());
@@ -226,9 +230,6 @@ class Config extends StorableConfigBase {
$this->isNew = FALSE;
$this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
$this->originalData = $this->data;
// Potentially configuration schema could have changed the underlying data's
// types.
$this->resetOverriddenData();
return $this;
}
+10 -1
View File
@@ -405,6 +405,14 @@ class ConfigImporter {
$module_list = array_reverse($module_list);
$this->extensionChangelist['module']['install'] = array_intersect(array_keys($module_list), $install);
// If we're installing the install profile ensure it comes last. This will
// occur when installing a site from configuration.
$install_profile_key = array_search($new_extensions['profile'], $this->extensionChangelist['module']['install'], TRUE);
if ($install_profile_key !== FALSE) {
unset($this->extensionChangelist['module']['install'][$install_profile_key]);
$this->extensionChangelist['module']['install'][] = $new_extensions['profile'];
}
// Work out what themes to install and to uninstall.
$this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
$this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
@@ -725,7 +733,8 @@ class ConfigImporter {
}
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this));
if (count($this->getErrors())) {
throw new ConfigImporterException('There were errors validating the config synchronization.');
$errors = array_merge(['There were errors validating the config synchronization.'], $this->getErrors());
throw new ConfigImporterException(implode(PHP_EOL, $errors));
}
else {
$this->validated = TRUE;
@@ -3,9 +3,7 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
use Drupal\Core\Config\Entity\ConfigEntityDependency;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ConfigInstaller implements ConfigInstallerInterface {
@@ -205,16 +203,19 @@ class ConfigInstaller implements ConfigInstallerInterface {
$dependency_manager = new ConfigDependencyManager();
$dependency_manager->setData($config_to_create);
$config_to_create = array_merge(array_flip($dependency_manager->sortAll()), $config_to_create);
if (!empty($dependency)) {
// In order to work out dependencies we need the full config graph.
$dependency_manager->setData($this->getActiveStorages()->readMultiple($existing_config) + $config_to_create);
$dependencies = $dependency_manager->getDependentEntities(key($dependency), reset($dependency));
}
foreach ($config_to_create as $config_name => $data) {
// Remove configuration where its dependencies cannot be met.
$remove = !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config);
// If $dependency is defined, remove configuration that does not have a
// matching dependency.
// Remove configuration that is not dependent on $dependency, if it is
// defined.
if (!$remove && !empty($dependency)) {
// Create a light weight dependency object to check dependencies.
$config_entity = new ConfigEntityDependency($config_name, $data);
$remove = !$config_entity->hasDependency(key($dependency), reset($dependency));
$remove = !isset($dependencies[$config_name]);
}
if ($remove) {
@@ -316,10 +317,11 @@ class ConfigInstaller implements ConfigInstallerInterface {
$entity_storage = $this->configManager
->getEntityManager()
->getStorage($entity_type);
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
// It is possible that secondary writes can occur during configuration
// creation. Updates of such configuration are allowed.
if ($this->getActiveStorages($collection)->exists($name)) {
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
$entity = $entity_storage->load($id);
$entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
}
@@ -328,6 +330,9 @@ class ConfigInstaller implements ConfigInstallerInterface {
}
if ($entity->isInstallable()) {
$entity->trustData()->save();
if ($id !== $entity->id()) {
trigger_error(sprintf('The configuration name "%s" does not match the ID "%s"', $name, $entity->id()), E_USER_WARNING);
}
}
}
else {
@@ -344,7 +349,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
// Only install configuration for enabled extensions.
$enabled_extensions = $this->getEnabledExtensions();
$config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
$provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
$provider = mb_substr($config_name, 0, strpos($config_name, '.'));
return in_array($provider, $enabled_extensions);
});
if (!empty($config_to_install)) {
@@ -228,7 +228,6 @@ class DatabaseStorage implements StorageInterface {
->execute();
}
/**
* Implements Drupal\Core\Config\StorageInterface::rename().
*
@@ -3,7 +3,7 @@
namespace Drupal\Core\Config\Development;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Config\Schema\SchemaCheckTrait;
@@ -90,7 +90,7 @@ class ConfigSchemaChecker implements EventSubscriberInterface {
elseif (is_array($errors)) {
$text_errors = [];
foreach ($errors as $key => $error) {
$text_errors[] = SafeMarkup::format('@key @error', ['@key' => $key, '@error' => $error]);
$text_errors[] = new FormattableMarkup('@key @error', ['@key' => $key, '@error' => $error]);
}
throw new SchemaIncompleteException("Schema errors for $name with the following errors: " . implode(', ', $text_errors));
}
@@ -267,18 +267,13 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
$entity_type = $this->getEntityType();
$properties_to_export = $entity_type->getPropertiesToExport();
if (empty($properties_to_export)) {
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
$definition = $this->getTypedConfig()->getDefinition($config_name);
if (!isset($definition['mapping'])) {
throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
}
$properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
}
$id_key = $entity_type->getKey('id');
foreach ($properties_to_export as $property_name => $export_name) {
$property_names = $entity_type->getPropertiesToExport($this->id());
if (empty($property_names)) {
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
}
foreach ($property_names as $property_name => $export_name) {
// Special handling for IDs so that computed compound IDs work.
// @see \Drupal\Core\Entity\EntityDisplayBase::id()
if ($property_name == $id_key) {
@@ -3,6 +3,7 @@
namespace Drupal\Core\Config\Entity;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Entity\EntityInterface;
@@ -104,9 +105,11 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
* The memory cache backend.
*/
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
parent::__construct($entity_type);
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache = NULL) {
parent::__construct($entity_type, $memory_cache);
$this->configFactory = $config_factory;
$this->uuidService = $uuid_service;
@@ -121,7 +124,8 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('language_manager')
$container->get('language_manager'),
$container->get('entity.memory_cache')
);
}
@@ -324,43 +328,10 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
}
/**
* Gets entities from the static cache.
*
* @param array $ids
* If not empty, return entities that match these IDs.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* Array of entities from the entity cache.
* {@inheritdoc}
*/
protected function getFromStaticCache(array $ids) {
$entities = [];
// Load any available entities from the internal cache.
if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
$config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
foreach ($ids as $id) {
if (!empty($this->entities[$id])) {
if (isset($this->entities[$id][$config_overrides_key])) {
$entities[$id] = $this->entities[$id][$config_overrides_key];
}
}
}
}
return $entities;
}
/**
* Stores entities in the static entity cache.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* Entities to store in the cache.
*/
protected function setStaticCache(array $entities) {
if ($this->entityType->isStaticallyCacheable()) {
$config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
foreach ($entities as $id => $entity) {
$this->entities[$id][$config_overrides_key] = $entity;
}
}
protected function buildCacheId($id) {
return parent::buildCacheId($id) . ':' . ($this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys()));
}
/**
@@ -479,9 +450,27 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
$data = $this->mapFromStorageRecords([$values]);
$updated_entity = current($data);
foreach (array_keys($values) as $property) {
$value = $updated_entity->get($property);
$entity->set($property, $value);
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
$entity_type = $this->getEntityType();
$id_key = $entity_type->getKey('id');
$properties = $entity_type->getPropertiesToExport($updated_entity->get($id_key));
if (empty($properties)) {
// Fallback to using the provided values. If the properties cannot be
// determined for the config entity type annotation or configuration
// schema.
$properties = array_keys($values);
}
foreach ($properties as $property) {
if ($property === $this->uuidKey) {
// During an update the UUID field should not be copied. Under regular
// circumstances the values will be equal. If configuration is written
// twice during configuration install the updated entity will not have a
// UUID.
// @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
continue;
}
$entity->set($property, $updated_entity->get($property));
}
return $entity;
@@ -142,30 +142,40 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
/**
* {@inheritdoc}
*/
public function getPropertiesToExport() {
if (!empty($this->config_export)) {
if (empty($this->mergedConfigExport)) {
// Always add default properties to be exported.
$this->mergedConfigExport = [
'uuid' => 'uuid',
'langcode' => 'langcode',
'status' => 'status',
'dependencies' => 'dependencies',
'third_party_settings' => 'third_party_settings',
'_core' => '_core',
];
foreach ($this->config_export as $property => $name) {
if (is_numeric($property)) {
$this->mergedConfigExport[$name] = $name;
}
else {
$this->mergedConfigExport[$property] = $name;
}
}
}
public function getPropertiesToExport($id = NULL) {
if (!empty($this->mergedConfigExport)) {
return $this->mergedConfigExport;
}
return NULL;
if (!empty($this->config_export)) {
// Always add default properties to be exported.
$this->mergedConfigExport = [
'uuid' => 'uuid',
'langcode' => 'langcode',
'status' => 'status',
'dependencies' => 'dependencies',
'third_party_settings' => 'third_party_settings',
'_core' => '_core',
];
foreach ($this->config_export as $property => $name) {
if (is_numeric($property)) {
$this->mergedConfigExport[$name] = $name;
}
else {
$this->mergedConfigExport[$property] = $name;
}
}
}
else {
// @todo https://www.drupal.org/project/drupal/issues/2949021 Deprecate
// fallback to schema.
$config_name = $this->getConfigPrefix() . '.' . $id;
$definition = \Drupal::service('config.typed')->getDefinition($config_name);
if (!isset($definition['mapping'])) {
return NULL;
}
$this->mergedConfigExport = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
}
return $this->mergedConfigExport;
}
/**
@@ -65,11 +65,18 @@ interface ConfigEntityTypeInterface extends EntityTypeInterface {
/**
* Gets the config entity properties to export if declared on the annotation.
*
* Falls back to determining the properties using configuration schema, if the
* config entity properties are not declared.
*
* @param string $id
* The ID of the configuration entity. Used when checking schema instead of
* the annotation.
*
* @return array|null
* The properties to export or NULL if they can not be determine from the
* config entity type annotation.
* config entity type annotation or the schema.
*/
public function getPropertiesToExport();
public function getPropertiesToExport($id = NULL);
/**
* Gets the keys that are available for fast lookup.
@@ -0,0 +1,119 @@
<?php
namespace Drupal\Core\Config\Entity;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A utility class to make updating configuration entities simple.
*
* Use this in a post update function like so:
* @code
* // Update the dependencies of all Vocabulary configuration entities.
* \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'taxonomy_vocabulary');
* @endcode
*
* The number of entities processed in each batch is determined by the
* 'entity_update_batch_size' setting.
*
* @see default.settings.php
*/
class ConfigEntityUpdater implements ContainerInjectionInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The number of entities to process in each batch.
* @var int
*/
protected $batchSize;
/**
* ConfigEntityUpdater constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param int $batch_size
* The number of entities to process in each batch.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, $batch_size) {
$this->entityTypeManager = $entity_type_manager;
$this->batchSize = $batch_size;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('settings')->get('entity_update_batch_size', 50)
);
}
/**
* Updates configuration entities as part of a Drupal update.
*
* @param array $sandbox
* Stores information for batch updates.
* @param string $entity_type_id
* The configuration entity type ID. For example, 'view' or 'vocabulary'.
* @param callable $callback
* (optional) A callback to determine if a configuration entity should be
* saved. The callback will be passed each entity of the provided type that
* exists. The callback should not save an entity itself. Return TRUE to
* save an entity. The callback can make changes to an entity. Note that all
* changes should comply with schema as an entity's data will not be
* validated against schema on save to avoid unexpected errors. If a
* callback is not provided, the default behaviour is to update the
* dependencies if required.
*
* @see hook_post_update_NAME()
*
* @api
*
* @throws \InvalidArgumentException
* Thrown when the provided entity type ID is not a configuration entity
* type.
*/
public function update(array &$sandbox, $entity_type_id, callable $callback = NULL) {
$storage = $this->entityTypeManager->getStorage($entity_type_id);
$sandbox_key = 'config_entity_updater:' . $entity_type_id;
if (!isset($sandbox[$sandbox_key])) {
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
if (!($entity_type instanceof ConfigEntityTypeInterface)) {
throw new \InvalidArgumentException("The provided entity type ID '$entity_type_id' is not a configuration entity type");
}
$sandbox[$sandbox_key]['entities'] = $storage->getQuery()->accessCheck(FALSE)->execute();
$sandbox[$sandbox_key]['count'] = count($sandbox[$sandbox_key]['entities']);
}
// The default behaviour is to fix dependencies.
if ($callback === NULL) {
$callback = function ($entity) {
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
$original_dependencies = $entity->getDependencies();
return $original_dependencies !== $entity->calculateDependencies()->getDependencies();
};
}
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
$entities = $storage->loadMultiple(array_splice($sandbox[$sandbox_key]['entities'], 0, $this->batchSize));
foreach ($entities as $entity) {
if (call_user_func($callback, $entity)) {
$entity->trustData();
$entity->save();
}
}
$sandbox['#finished'] = empty($sandbox[$sandbox_key]['entities']) ? 1 : ($sandbox[$sandbox_key]['count'] - count($sandbox[$sandbox_key]['entities'])) / $sandbox[$sandbox_key]['count'];
}
}
@@ -107,7 +107,7 @@ abstract class DraggableListBuilder extends ConfigEntityListBuilder implements F
$form[$this->entitiesKey] = [
'#type' => 'table',
'#header' => $this->buildHeader(),
'#empty' => t('There is no @label yet.', ['@label' => $this->entityType->getLabel()]),
'#empty' => t('There are no @label yet.', ['@label' => $this->entityType->getPluralLabel()]),
'#tabledrag' => [
[
'action' => 'order',
@@ -2,7 +2,6 @@
namespace Drupal\Core\Config\Entity\Query;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Query\ConditionBase;
use Drupal\Core\Entity\Query\ConditionInterface;
use Drupal\Core\Entity\Query\QueryException;
@@ -32,10 +31,10 @@ class Condition extends ConditionBase {
// Lowercase condition value(s) for case-insensitive matches.
if (is_array($condition['value'])) {
$condition['value'] = array_map('Drupal\Component\Utility\Unicode::strtolower', $condition['value']);
$condition['value'] = array_map('mb_strtolower', $condition['value']);
}
elseif (!is_bool($condition['value'])) {
$condition['value'] = Unicode::strtolower($condition['value']);
$condition['value'] = mb_strtolower($condition['value']);
}
$single_conditions[] = $condition;
@@ -164,7 +163,7 @@ class Condition extends ConditionBase {
if (isset($value)) {
// We always want a case-insensitive match.
if (!is_bool($value)) {
$value = Unicode::strtolower($value);
$value = mb_strtolower($value);
}
switch ($condition['operator']) {
@@ -28,7 +28,7 @@ class QueryFactory implements QueryFactoryInterface, EventSubscriberInterface {
/**
* The config factory used by the config entity query.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface;
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
@@ -41,7 +41,6 @@ interface ThirdPartySettingsInterface {
*/
public function getThirdPartySetting($module, $key, $default = NULL);
/**
* Gets all third-party settings of a given module.
*
@@ -0,0 +1,77 @@
<?php
namespace Drupal\Core\Config\Importer;
use Drupal\Core\Config\ConfigImporter;
/**
* Methods for running the ConfigImporter in a batch.
*
* @see \Drupal\Core\Config\ConfigImporter
*/
class ConfigImporterBatch {
/**
* Processes the config import batch and persists the importer.
*
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The batch config importer object to persist.
* @param string $sync_step
* The synchronization step to do.
* @param array $context
* The batch context.
*/
public static function process(ConfigImporter $config_importer, $sync_step, &$context) {
if (!isset($context['sandbox']['config_importer'])) {
$context['sandbox']['config_importer'] = $config_importer;
}
$config_importer = $context['sandbox']['config_importer'];
$config_importer->doSyncStep($sync_step, $context);
if ($errors = $config_importer->getErrors()) {
if (!isset($context['results']['errors'])) {
$context['results']['errors'] = [];
}
$context['results']['errors'] = array_merge($errors, $context['results']['errors']);
}
}
/**
* Finish batch.
*
* This function is a static function to avoid serializing the ConfigSync
* object unnecessarily.
*
* @param bool $success
* Indicate that the batch API tasks were all completed successfully.
* @param array $results
* An array of all the results that were updated in update_do_one().
* @param array $operations
* A list of the operations that had not been completed by the batch API.
*/
public static function finish($success, $results, $operations) {
$messenger = \Drupal::messenger();
if ($success) {
if (!empty($results['errors'])) {
$logger = \Drupal::logger('config_sync');
foreach ($results['errors'] as $error) {
$messenger->addError($error);
$logger->error($error);
}
$messenger->addWarning(t('The configuration was imported with errors.'));
}
elseif (!drupal_installation_attempted()) {
// Display a success message when not installing Drupal.
$messenger->addStatus(t('The configuration was imported successfully.'));
}
}
else {
// An error occurred.
// $operations contains the operations that remained unprocessed.
$error_operation = reset($operations);
$message = t('An error occurred while processing %error_operation with arguments: @arguments', ['%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)]);
$messenger->addError($message);
}
}
}
@@ -2,7 +2,7 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\FormattableMarkup;
/**
* An exception thrown if configuration with the same name already exists.
@@ -56,10 +56,10 @@ class PreExistingConfigException extends ConfigException {
* @return \Drupal\Core\Config\PreExistingConfigException
*/
public static function create($extension, array $config_objects) {
$message = SafeMarkup::format('Configuration objects (@config_names) provided by @extension already exist in active configuration',
$message = new FormattableMarkup('Configuration objects (@config_names) provided by @extension already exist in active configuration',
[
'@config_names' => implode(', ', static::flattenConfigObjects($config_objects)),
'@extension' => $extension
'@extension' => $extension,
]
);
$e = new static($message);
@@ -92,7 +92,7 @@ class UnmetDependenciesException extends ConfigException {
$message = new FormattableMarkup('Configuration objects provided by %extension have unmet dependencies: %config_names',
[
'%config_names' => static::formatConfigObjectList($config_objects),
'%extension' => $extension
'%extension' => $extension,
]
);
$e = new static($message);
@@ -0,0 +1,47 @@
<?php
namespace Drupal\Core\Controller\ArgumentResolver;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields a PSR7 request object based on the request object passed along.
*/
final class Psr7RequestValueResolver implements ArgumentValueResolverInterface {
/**
* The PSR-7 converter.
*
* @var \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface
*/
protected $httpMessageFactory;
/**
* Constructs a new ControllerResolver.
*
* @param \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface $http_message_factory
* The PSR-7 converter.
*/
public function __construct(HttpMessageFactoryInterface $http_message_factory) {
$this->httpMessageFactory = $http_message_factory;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument) {
return $argument->getType() == ServerRequestInterface::class;
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument) {
yield $this->httpMessageFactory->createRequest($request);
}
}
@@ -0,0 +1,28 @@
<?php
namespace Drupal\Core\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields an argument's value from the request's _raw_variables attribute.
*/
final class RawParameterValueResolver implements ArgumentValueResolverInterface {
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument) {
return !$argument->isVariadic() && $request->attributes->has('_raw_variables') && array_key_exists($argument->getName(), $request->attributes->get('_raw_variables'));
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument) {
yield $request->attributes->get('_raw_variables')[$argument->getName()];
}
}
@@ -0,0 +1,30 @@
<?php
namespace Drupal\Core\Controller\ArgumentResolver;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields a RouteMatch object based on the request object passed along.
*/
final class RouteMatchValueResolver implements ArgumentValueResolverInterface {
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument) {
return $argument->getType() == RouteMatchInterface::class || is_subclass_of($argument->getType(), RouteMatchInterface::class);
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument) {
yield RouteMatch::createFromRequest($request);
}
}
@@ -93,7 +93,7 @@ abstract class ControllerBase implements ContainerInjectionInterface {
/**
* The state service.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
* @var \Drupal\Core\State\StateInterface
*/
protected $stateService;
@@ -222,7 +222,7 @@ abstract class ControllerBase implements ContainerInjectionInterface {
* needs to be the same across development, production, etc. environments
* (for example, the system maintenance message) should use config() instead.
*
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
* @return \Drupal\Core\State\StateInterface
*/
protected function state() {
if (!$this->stateService) {
@@ -80,7 +80,6 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
return $callable;
}
/**
* {@inheritdoc}
*/
@@ -129,6 +128,10 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
* {@inheritdoc}
*/
protected function doGetArguments(Request $request, $controller, array $parameters) {
// Note this duplicates the deprecation message of
// Symfony\Component\HttpKernel\Controller\ControllerResolver::getArguments()
// to ensure it is removed in Drupal 9.
@trigger_error(sprintf('%s is deprecated as of 8.6.0 and will be removed in 9.0. Inject the "http_kernel.controller.argument_resolver" service instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
$attributes = $request->attributes->all();
$raw_parameters = $request->attributes->has('_raw_variables') ? $request->attributes->get('_raw_variables') : [];
$arguments = [];
@@ -7,6 +7,7 @@ use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
/**
* Common base class for form interstitial controllers.
@@ -16,10 +17,24 @@ use Symfony\Component\HttpFoundation\Request;
abstract class FormController {
use DependencySerializationTrait;
/**
* The argument resolver.
*
* @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface
*/
protected $argumentResolver;
/**
* The controller resolver.
*
* @var \Drupal\Core\Controller\ControllerResolverInterface
*
* @deprecated
* Deprecated property that is only assigned when the 'controller_resolver'
* service is used as the first parameter to FormController::__construct().
*
* @see https://www.drupal.org/node/2959408
* @see \Drupal\Core\Controller\FormController::__construct()
*/
protected $controllerResolver;
@@ -33,13 +48,17 @@ abstract class FormController {
/**
* Constructs a new \Drupal\Core\Controller\FormController object.
*
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* The controller resolver.
* @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver
* The argument resolver.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
*/
public function __construct(ControllerResolverInterface $controller_resolver, FormBuilderInterface $form_builder) {
$this->controllerResolver = $controller_resolver;
public function __construct(ArgumentResolverInterface $argument_resolver, FormBuilderInterface $form_builder) {
$this->argumentResolver = $argument_resolver;
if ($argument_resolver instanceof ControllerResolverInterface) {
@trigger_error("Using the 'controller_resolver' service as the first argument is deprecated, use the 'http_kernel.controller.argument_resolver' instead. If your subclass requires the 'controller_resolver' service add it as an additional argument. See https://www.drupal.org/node/2959408.", E_USER_DEPRECATED);
$this->controllerResolver = $argument_resolver;
}
$this->formBuilder = $form_builder;
}
@@ -63,7 +82,7 @@ abstract class FormController {
$form_state = new FormState();
$request->attributes->set('form', []);
$request->attributes->set('form_state', $form_state);
$args = $this->controllerResolver->getArguments($request, [$form_object, 'buildForm']);
$args = $this->argumentResolver->getArguments($request, [$form_object, 'buildForm']);
$request->attributes->remove('form');
$request->attributes->remove('form_state');
@@ -5,6 +5,7 @@ namespace Drupal\Core\Controller;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
/**
* Wrapping controller for forms that serve as the main page body.
@@ -14,22 +15,22 @@ class HtmlFormController extends FormController {
/**
* The class resolver.
*
* @var \Drupal\Core\DependencyInjection\ClassResolverInterface;
* @var \Drupal\Core\DependencyInjection\ClassResolverInterface
*/
protected $classResolver;
/**
* Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
*
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* The controller resolver.
* @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver
* The argument resolver.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
*/
public function __construct(ControllerResolverInterface $controller_resolver, FormBuilderInterface $form_builder, ClassResolverInterface $class_resolver) {
parent::__construct($controller_resolver, $form_builder);
public function __construct(ArgumentResolverInterface $argument_resolver, FormBuilderInterface $form_builder, ClassResolverInterface $class_resolver) {
parent::__construct($argument_resolver, $form_builder);
$this->classResolver = $class_resolver;
}
@@ -5,6 +5,7 @@ namespace Drupal\Core\Controller;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
use Symfony\Component\Routing\Route;
/**
@@ -20,6 +21,13 @@ class TitleResolver implements TitleResolverInterface {
*/
protected $controllerResolver;
/**
* The argument resolver.
*
* @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface
*/
protected $argumentResolver;
/**
* Constructs a TitleResolver instance.
*
@@ -27,10 +35,13 @@ class TitleResolver implements TitleResolverInterface {
* The controller resolver.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
* @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver
* The argument resolver.
*/
public function __construct(ControllerResolverInterface $controller_resolver, TranslationInterface $string_translation) {
public function __construct(ControllerResolverInterface $controller_resolver, TranslationInterface $string_translation, ArgumentResolverInterface $argument_resolver) {
$this->controllerResolver = $controller_resolver;
$this->stringTranslation = $string_translation;
$this->argumentResolver = $argument_resolver;
}
/**
@@ -43,7 +54,7 @@ class TitleResolver implements TitleResolverInterface {
// trying to use empty values.
if ($callback = $route->getDefault('_title_callback')) {
$callable = $this->controllerResolver->getControllerFromDefinition($callback);
$arguments = $this->controllerResolver->getArguments($request, $callable);
$arguments = $this->argumentResolver->getArguments($request, $callable);
$route_title = call_user_func_array($callable, $arguments);
}
elseif ($title = $route->getDefault('_title')) {
+124 -3
View File
@@ -808,12 +808,15 @@ abstract class Connection {
* @param string $table
* The table to use for the insert statement.
* @param array $options
* (optional) An array of options on the query.
* (optional) An associative array of options to control how the query is
* run. The given options will be merged with
* \Drupal\Core\Database\Connection::defaultOptions().
*
* @return \Drupal\Core\Database\Query\Insert
* A new Insert query object.
*
* @see \Drupal\Core\Database\Query\Insert
* @see \Drupal\Core\Database\Connection::defaultOptions()
*/
public function insert($table, array $options = []) {
$class = $this->getDriverClass('Insert');
@@ -862,12 +865,15 @@ abstract class Connection {
* @param string $table
* The table to use for the update statement.
* @param array $options
* (optional) An array of options on the query.
* (optional) An associative array of options to control how the query is
* run. The given options will be merged with
* \Drupal\Core\Database\Connection::defaultOptions().
*
* @return \Drupal\Core\Database\Query\Update
* A new Update query object.
*
* @see \Drupal\Core\Database\Query\Update
* @see \Drupal\Core\Database\Connection::defaultOptions()
*/
public function update($table, array $options = []) {
$class = $this->getDriverClass('Update');
@@ -880,12 +886,15 @@ abstract class Connection {
* @param string $table
* The table to use for the delete statement.
* @param array $options
* (optional) An array of options on the query.
* (optional) An associative array of options to control how the query is
* run. The given options will be merged with
* \Drupal\Core\Database\Connection::defaultOptions().
*
* @return \Drupal\Core\Database\Query\Delete
* A new Delete query object.
*
* @see \Drupal\Core\Database\Query\Delete
* @see \Drupal\Core\Database\Connection::defaultOptions()
*/
public function delete($table, array $options = []) {
$class = $this->getDriverClass('Delete');
@@ -1472,4 +1481,116 @@ abstract class Connection {
throw new \LogicException('The database connection is not serializable. This probably means you are serializing an object that has an indirect reference to the database connection. Adjust your code so that is not necessary. Alternatively, look at DependencySerializationTrait as a temporary solution.');
}
/**
* Creates an array of database connection options from a URL.
*
* @internal
* This method should not be called. Use
* \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo() instead.
*
* @param string $url
* The URL.
* @param string $root
* The root directory of the Drupal installation. Some database drivers,
* like for example SQLite, need this information.
*
* @return array
* The connection options.
*
* @throws \InvalidArgumentException
* Exception thrown when the provided URL does not meet the minimum
* requirements.
*
* @see \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo()
*/
public static function createConnectionOptionsFromUrl($url, $root) {
$url_components = parse_url($url);
if (!isset($url_components['scheme'], $url_components['host'], $url_components['path'])) {
throw new \InvalidArgumentException('Minimum requirement: driver://host/database');
}
$url_components += [
'user' => '',
'pass' => '',
'fragment' => '',
];
// Remove leading slash from the URL path.
if ($url_components['path'][0] === '/') {
$url_components['path'] = substr($url_components['path'], 1);
}
// Use reflection to get the namespace of the class being called.
$reflector = new \ReflectionClass(get_called_class());
$database = [
'driver' => $url_components['scheme'],
'username' => $url_components['user'],
'password' => $url_components['pass'],
'host' => $url_components['host'],
'database' => $url_components['path'],
'namespace' => $reflector->getNamespaceName(),
];
if (isset($url_components['port'])) {
$database['port'] = $url_components['port'];
}
if (!empty($url_components['fragment'])) {
$database['prefix']['default'] = $url_components['fragment'];
}
return $database;
}
/**
* Creates a URL from an array of database connection options.
*
* @internal
* This method should not be called. Use
* \Drupal\Core\Database\Database::getConnectionInfoAsUrl() instead.
*
* @param array $connection_options
* The array of connection options for a database connection.
*
* @return string
* The connection info as a URL.
*
* @throws \InvalidArgumentException
* Exception thrown when the provided array of connection options does not
* meet the minimum requirements.
*
* @see \Drupal\Core\Database\Database::getConnectionInfoAsUrl()
*/
public static function createUrlFromConnectionOptions(array $connection_options) {
if (!isset($connection_options['driver'], $connection_options['database'])) {
throw new \InvalidArgumentException("As a minimum, the connection options array must contain at least the 'driver' and 'database' keys");
}
$user = '';
if (isset($connection_options['username'])) {
$user = $connection_options['username'];
if (isset($connection_options['password'])) {
$user .= ':' . $connection_options['password'];
}
$user .= '@';
}
$host = empty($connection_options['host']) ? 'localhost' : $connection_options['host'];
$db_url = $connection_options['driver'] . '://' . $user . $host;
if (isset($connection_options['port'])) {
$db_url .= ':' . $connection_options['port'];
}
$db_url .= '/' . $connection_options['database'];
if (isset($connection_options['prefix']['default']) && $connection_options['prefix']['default'] !== '') {
$db_url .= '#' . $connection_options['prefix']['default'];
}
return $db_url;
}
}
+41 -53
View File
@@ -365,13 +365,8 @@ abstract class Database {
throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
}
if (!empty(self::$databaseInfo[$key][$target]['namespace'])) {
$driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection';
}
else {
// Fallback for Drupal 7 settings.php.
$driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
}
$namespace = static::getDatabaseDriverNamespace(self::$databaseInfo[$key][$target]);
$driver_class = $namespace . '\\Connection';
$pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
$new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
@@ -455,36 +450,25 @@ abstract class Database {
* requirements.
*/
public static function convertDbUrlToConnectionInfo($url, $root) {
$info = parse_url($url);
if (!isset($info['scheme'], $info['host'], $info['path'])) {
throw new \InvalidArgumentException('Minimum requirement: driver://host/database');
// Check that the URL is well formed, starting with 'scheme://', where
// 'scheme' is a database driver name.
if (preg_match('/^(.*):\/\//', $url, $matches) !== 1) {
throw new \InvalidArgumentException("Missing scheme in URL '$url'");
}
$info += [
'user' => '',
'pass' => '',
'fragment' => '',
];
$driver = $matches[1];
// A SQLite database path with two leading slashes indicates a system path.
// Otherwise the path is relative to the Drupal root.
if ($info['path'][0] === '/') {
$info['path'] = substr($info['path'], 1);
}
if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') {
$info['path'] = $root . '/' . $info['path'];
// Discover if the URL has a valid driver scheme. Try with core drivers
// first.
$connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
if (!class_exists($connection_class)) {
// If the URL is not relative to a core driver, try with custom ones.
$connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
if (!class_exists($connection_class)) {
throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$connection_class' does not exist");
}
}
$database = [
'driver' => $info['scheme'],
'username' => $info['user'],
'password' => $info['pass'],
'host' => $info['host'],
'database' => $info['path'],
];
if (isset($info['port'])) {
$database['port'] = $info['port'];
}
return $database;
return $connection_class::createConnectionOptionsFromUrl($url, $root);
}
/**
@@ -495,32 +479,36 @@ abstract class Database {
*
* @return string
* The connection info as a URL.
*
* @throws \RuntimeException
* When the database connection is not defined.
*/
public static function getConnectionInfoAsUrl($key = 'default') {
$db_info = static::getConnectionInfo($key);
if ($db_info['default']['driver'] == 'sqlite') {
$db_url = 'sqlite://localhost/' . $db_info['default']['database'];
if (empty($db_info) || empty($db_info['default'])) {
throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
}
else {
$user = '';
if ($db_info['default']['username']) {
$user = $db_info['default']['username'];
if ($db_info['default']['password']) {
$user .= ':' . $db_info['default']['password'];
}
$user .= '@';
}
$connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
return $connection_class::createUrlFromConnectionOptions($db_info['default']);
}
$db_url = $db_info['default']['driver'] . '://' . $user . $db_info['default']['host'];
if (isset($db_info['default']['port'])) {
$db_url .= ':' . $db_info['default']['port'];
}
$db_url .= '/' . $db_info['default']['database'];
/**
* Gets the PHP namespace of a database driver from the connection info.
*
* @param array $connection_info
* The database connection information, as defined in settings.php. The
* structure of this array depends on the database driver it is connecting
* to.
*
* @return string
* The PHP namespace of the driver's database.
*/
protected static function getDatabaseDriverNamespace(array $connection_info) {
if (isset($connection_info['namespace'])) {
return $connection_info['namespace'];
}
if ($db_info['default']['prefix']['default']) {
$db_url .= '#' . $db_info['default']['prefix']['default'];
}
return $db_url;
// Fallback for Drupal 7 settings.php.
return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
}
}
@@ -64,6 +64,277 @@ class Connection extends DatabaseConnection {
*/
const MIN_MAX_ALLOWED_PACKET = 1024;
/**
* The list of MySQL reserved key words.
*
* @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html
*/
private $reservedKeyWords = [
'accessible',
'add',
'admin',
'all',
'alter',
'analyze',
'and',
'as',
'asc',
'asensitive',
'before',
'between',
'bigint',
'binary',
'blob',
'both',
'by',
'call',
'cascade',
'case',
'change',
'char',
'character',
'check',
'collate',
'column',
'condition',
'constraint',
'continue',
'convert',
'create',
'cross',
'cube',
'cume_dist',
'current_date',
'current_time',
'current_timestamp',
'current_user',
'cursor',
'database',
'databases',
'day_hour',
'day_microsecond',
'day_minute',
'day_second',
'dec',
'decimal',
'declare',
'default',
'delayed',
'delete',
'dense_rank',
'desc',
'describe',
'deterministic',
'distinct',
'distinctrow',
'div',
'double',
'drop',
'dual',
'each',
'else',
'elseif',
'empty',
'enclosed',
'escaped',
'except',
'exists',
'exit',
'explain',
'false',
'fetch',
'first_value',
'float',
'float4',
'float8',
'for',
'force',
'foreign',
'from',
'fulltext',
'function',
'generated',
'get',
'grant',
'group',
'grouping',
'groups',
'having',
'high_priority',
'hour_microsecond',
'hour_minute',
'hour_second',
'if',
'ignore',
'in',
'index',
'infile',
'inner',
'inout',
'insensitive',
'insert',
'int',
'int1',
'int2',
'int3',
'int4',
'int8',
'integer',
'interval',
'into',
'io_after_gtids',
'io_before_gtids',
'is',
'iterate',
'join',
'json_table',
'key',
'keys',
'kill',
'lag',
'last_value',
'lead',
'leading',
'leave',
'left',
'like',
'limit',
'linear',
'lines',
'load',
'localtime',
'localtimestamp',
'lock',
'long',
'longblob',
'longtext',
'loop',
'low_priority',
'master_bind',
'master_ssl_verify_server_cert',
'match',
'maxvalue',
'mediumblob',
'mediumint',
'mediumtext',
'middleint',
'minute_microsecond',
'minute_second',
'mod',
'modifies',
'natural',
'not',
'no_write_to_binlog',
'nth_value',
'ntile',
'null',
'numeric',
'of',
'on',
'optimize',
'optimizer_costs',
'option',
'optionally',
'or',
'order',
'out',
'outer',
'outfile',
'over',
'partition',
'percent_rank',
'persist',
'persist_only',
'precision',
'primary',
'procedure',
'purge',
'range',
'rank',
'read',
'reads',
'read_write',
'real',
'recursive',
'references',
'regexp',
'release',
'rename',
'repeat',
'replace',
'require',
'resignal',
'restrict',
'return',
'revoke',
'right',
'rlike',
'row',
'rows',
'row_number',
'schema',
'schemas',
'second_microsecond',
'select',
'sensitive',
'separator',
'set',
'show',
'signal',
'smallint',
'spatial',
'specific',
'sql',
'sqlexception',
'sqlstate',
'sqlwarning',
'sql_big_result',
'sql_calc_found_rows',
'sql_small_result',
'ssl',
'starting',
'stored',
'straight_join',
'system',
'table',
'terminated',
'then',
'tinyblob',
'tinyint',
'tinytext',
'to',
'trailing',
'trigger',
'true',
'undo',
'union',
'unique',
'unlock',
'unsigned',
'update',
'usage',
'use',
'using',
'utc_date',
'utc_time',
'utc_timestamp',
'values',
'varbinary',
'varchar',
'varcharacter',
'varying',
'virtual',
'when',
'where',
'while',
'window',
'with',
'write',
'xor',
'year_month',
'zerofill',
];
/**
* Constructs a Connection object.
*/
@@ -160,7 +431,8 @@ class Connection extends DatabaseConnection {
// Force MySQL to use the UTF-8 character set. Also set the collation, if a
// certain one has been set; otherwise, MySQL defaults to
// 'utf8mb4_general_ci' for utf8mb4.
// 'utf8mb4_general_ci' (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) for
// utf8mb4.
if (!empty($connection_options['collation'])) {
$pdo->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
}
@@ -179,9 +451,18 @@ class Connection extends DatabaseConnection {
$connection_options += [
'init_commands' => [],
];
$sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY';
// NO_AUTO_CREATE_USER is removed in MySQL 8.0.11
// https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html#mysqld-8-0-11-deprecation-removal
$version_server = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION);
if (version_compare($version_server, '8.0.11', '<')) {
$sql_mode .= ',NO_AUTO_CREATE_USER';
}
$connection_options['init_commands'] += [
'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,ONLY_FULL_GROUP_BY'",
'sql_mode' => "SET sql_mode = '$sql_mode'",
];
// Execute initial commands.
foreach ($connection_options['init_commands'] as $sql) {
$pdo->exec($sql);
@@ -190,6 +471,49 @@ class Connection extends DatabaseConnection {
return $pdo;
}
/**
* {@inheritdoc}
*/
public function escapeField($field) {
$field = parent::escapeField($field);
return $this->quoteIdentifier($field);
}
/**
* {@inheritdoc}
*/
public function escapeAlias($field) {
// Quote fields so that MySQL reserved words like 'function' can be used
// as aliases.
$field = parent::escapeAlias($field);
return $this->quoteIdentifier($field);
}
/**
* Quotes an identifier if it matches a MySQL reserved keyword.
*
* @param string $identifier
* The field to check.
*
* @return string
* The identifier, quoted if it matches a MySQL reserved keyword.
*/
private function quoteIdentifier($identifier) {
// Quote identifiers so that MySQL reserved words like 'function' can be
// used as column names. Sometimes the 'table.column_name' format is passed
// in. For example,
// \Drupal\Core\Entity\Sql\SqlContentEntityStorage::buildQuery() adds a
// condition on "base.uid" while loading user entities.
if (strpos($identifier, '.') !== FALSE) {
list($table, $identifier) = explode('.', $identifier, 2);
}
if (in_array(strtolower($identifier), $this->reservedKeyWords, TRUE)) {
// Quote the string for MySQL reserved keywords.
$identifier = '"' . $identifier . '"';
}
return isset($table) ? $table . '.' . $identifier : $identifier;
}
/**
* {@inheritdoc}
*/
@@ -44,6 +44,10 @@ class Insert extends QueryInsert {
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$insert_fields = array_map(function ($field) {
return $this->connection->escapeField($field);
}, $insert_fields);
// If we're selecting from a SelectQuery, finish building the query and
// pass it back, as any remaining options are irrelevant.
if (!empty($this->fromQuery)) {

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