Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023

This commit is contained in:
Pantheon Automation 2015-09-04 13:20:09 -07:00 committed by Greg Anderson
parent 2720a9ec4b
commit f3791f1da3
1898 changed files with 54300 additions and 11481 deletions

View file

@ -1,4 +1,5 @@
{
"extends": "eslint:recommended",
"env": {
"browser": true
},
@ -16,31 +17,72 @@
},
"rules": {
// Errors.
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
"brace-style": [2, "stroustrup", {"allowSingleLine": true}],
"comma-dangle": [2, "never"],
"comma-spacing": 2,
"comma-style": [2, "last"],
"computed-property-spacing": [2, "never"],
"curly": [2, "all"],
"eol-last": 2,
"eqeqeq": [2, "smart"],
"guard-for-in": 2,
"indent": [2, 2, {"indentSwitchCase": true}],
"indent": [2, 2, {"SwitchCase": 1}],
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
"linebreak-style": [2, "unix"],
"lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}],
"new-parens": 2,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 2,
"no-empty-label": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-parens": [2, "functions"],
"no-implied-eval": 2,
"no-mixed-spaces-and-tabs": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-nested-ternary": 2,
"no-reserved-keys": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-return-assign": 2,
"no-script-url": 2,
"no-sequences": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-undefined": 2,
"no-unused-vars": [2, {"vars": "local", "args": "none"}],
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
"no-with": 2,
"object-curly-spacing": [2, "never"],
"one-var": [2, "never"],
"quote-props": [2, "consistent-as-needed"],
"semi": [2, "always"],
"semi-spacing": [2, {"before": false, "after": true}],
"space-after-keywords": [2, "always"],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
"space-in-brackets": [2, "never"],
"space-in-parens": [2, "never"],
"spaced-line-comment": [2, "always"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always"],
"strict": 2,
"yoda": [2, "never"],
// Warnings.
"max-nested-callbacks": [1, 3],
"valid-jsdoc": [1, {
@ -49,17 +91,6 @@
"property": "prop"
},
"requireReturn": false
}],
// Disabled.
"camelcase": 0,
"consistent-return": 0,
"dot-notation": 0,
"new-cap": 0,
"no-alert": 0,
"no-new": 0,
"no-shadow": 0,
"no-underscore-dangle": 0,
"no-use-before-define": 0,
"quotes": 0
}]
}
}

View file

@ -158,30 +158,26 @@ INSTALLATION
b. Missing settings file.
Drupal will try to automatically create settings.php and services.yml
files, which are normally in the directory sites/default (to avoid
problems when upgrading, Drupal is not packaged with this file). If
auto-creation of either file fails, you will need to create the file
yourself. Use the template sites/default/default.settings.php or
sites/default/default.services.yml respectively.
Drupal will try to automatically create a settings.php configuration file,
which is normally in the directory sites/default (to avoid problems when
upgrading, Drupal is not packaged with this file). If auto-creation fails,
you will need to create this file yourself, using the file
sites/default/default.settings.php as a template.
For example, on a Unix/Linux command line, you can make a copy of the
default.settings.php and default.services.yml files with the commands:
default.settings.php file with the command:
cp sites/default/default.settings.php sites/default/settings.php
cp sites/default/default.services.yml sites/default/services.yml
Next, grant write privileges to the file to everyone (including the web
server) with the command:
chmod a+w sites/default/settings.php
chmod a+w sites/default/services.yml
Be sure to set the permissions back after the installation is finished!
Sample command:
chmod go-w sites/default/settings.php
chmod go-w sites/default/services.yml
c. Write permissions after install.
@ -191,7 +187,6 @@ INSTALLATION
from a Unix/Linux command line:
chmod go-w sites/default/settings.php
chmod go-w sites/default/services.yml
chmod go-w sites/default
4. Verify that the site is working.

View file

@ -190,11 +190,10 @@ Routing system
Theme system
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
- Jen Lampton 'jenlampton' https://www.drupal.org/u/jenlampton
- Scott Reeves 'Cottser' https://www.drupal.org/u/cottser
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
- Joël Pittet 'joelpittet' https://www.drupal.org/u/joelpittet
- Lauri Eskola 'lauriii' https://www.drupal.org/u/lauriii
Token system
- Dave Reid 'davereid' https://www.drupal.org/u/davereid
@ -208,6 +207,9 @@ Transliteration system
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon
Typed data system
- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
Topic maintainers
-----------------
@ -509,13 +511,24 @@ Web services
Provisional membership: None at this time.
Core mentoring leads
Core mentoring coordinators
---------------------
The Drupal Core mentoring leads inspire, enable, and encourage new core
contributors. They also work on the core tools, process, and community to make
it easier for new contributors to get involved. The mentoring leads are:
The Drupal Core mentors inspire, enable, and encourage new core contributors.
See https://www.drupal.org/core-mentoring for more information about mentoring.
Mentoring coordinators recruit and coach other mentors. They work on contributor
tools, documentation, and processes to make it easier for new contributors to
get involved. They organize communications and logistics, and actively
participate in mentoring.
- Lucas Hedding 'heddn' https://www.drupal.org/u/heddn
- Valery Lourie 'valthebald' https://www.drupal.org/u/valthebald
- Alina Mackenzie 'alimac' https://www.drupal.org/u/alimac
- Chris McCaferty 'cilefen' https://www.drupal.org/u/cilefen
- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm
- Cathy Theys 'YesCT' https://www.drupal.org/u/yesct
- Andrea Soper 'ZenDoodles' https://www.drupal.org/u/zendoodles
- Cathy Theys 'YesCT' https://www.drupal.org/u/yesct
Provisional membership: None at this time.

View file

@ -120,15 +120,31 @@ if ($is_allowed) {
'#messages' => $results['messages'],
);
$links = array();
if (is_array($results['tasks'])) {
$links += $results['tasks'];
$links = $results['tasks'];
}
else {
$links = array_merge($links, array(
\Drupal::l(t('Administration pages'), new Url('system.admin')),
\Drupal::l(t('Front page'), new Url('<front>')),
));
// Since this is being called outsite of the primary front controller,
// the base_url needs to be set explicitly to ensure that links are
// relative to the site root.
// @todo Simplify with https://www.drupal.org/node/2548095
$default_options = [
'#type' => 'link',
'#options' => [
'absolute' => TRUE,
'base_url' => $GLOBALS['base_url'],
],
];
$links = [
$default_options + [
'#url' => Url::fromRoute('system.admin'),
'#title' => t('Administration pages'),
],
$default_options + [
'#url' => Url::fromRoute('<front>'),
'#title' => t('Front page'),
],
];
}
$content['next_steps'] = array(
@ -139,7 +155,13 @@ if ($is_allowed) {
}
// If a batch is running, let it run.
elseif ($request->query->has('batch')) {
$content = ['#markup' => _batch_page($request)];
$content = _batch_page($request);
// If _batch_page() returns a response object (likely a JsonResponse for
// JavaScript-based batch processing), send it immediately.
if ($content instanceof Response) {
$content->send();
exit;
}
}
else {
if (empty($_SESSION['authorize_operation']) || empty($_SESSION['authorize_filetransfer_info'])) {

View file

@ -18,7 +18,7 @@
"symfony/validator": "2.7.*",
"symfony/process": "2.7.*",
"symfony/yaml": "2.7.*",
"twig/twig": "1.18.*",
"twig/twig": "1.20.*",
"doctrine/common": "~2.4.2",
"doctrine/annotations": "1.2.*",
"guzzlehttp/guzzle": "dev-master#1879fbe853b0c64d109e369c7aeff09849e62d1e",

130
core/composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "3708d8fdb54957e5ce661cda1df88353",
"hash": "6d065bd806544df5f446905bc3d6379f",
"packages": [
{
"name": "behat/mink",
@ -796,7 +796,7 @@
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/1879fbe853b0c64d109e369c7aeff09849e62d1e",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d687700d601f8b5f19b99ffc1c0f6af835a3c7b7",
"reference": "1879fbe853b0c64d109e369c7aeff09849e62d1e",
"shasum": ""
},
@ -850,16 +850,16 @@
},
{
"name": "guzzlehttp/promises",
"version": "1.0.1",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "2ee5bc7f1a92efecc90da7f6711a53a7be26b5b7"
"reference": "97fe7210def29451ec74923b27e552238defd75a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/2ee5bc7f1a92efecc90da7f6711a53a7be26b5b7",
"reference": "2ee5bc7f1a92efecc90da7f6711a53a7be26b5b7",
"url": "https://api.github.com/repos/guzzle/promises/zipball/97fe7210def29451ec74923b27e552238defd75a",
"reference": "97fe7210def29451ec74923b27e552238defd75a",
"shasum": ""
},
"require": {
@ -879,7 +879,7 @@
"GuzzleHttp\\Promise\\": "src/"
},
"files": [
"src/functions.php"
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@ -897,20 +897,20 @@
"keywords": [
"promise"
],
"time": "2015-06-24 16:16:25"
"time": "2015-08-15 19:37:21"
},
{
"name": "guzzlehttp/psr7",
"version": "1.1.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd"
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/af0e1758de355eb113917ad79c3c0e3604bce4bd",
"reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"shasum": ""
},
"require": {
@ -934,7 +934,7 @@
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions.php"
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@ -955,7 +955,7 @@
"stream",
"uri"
],
"time": "2015-06-24 19:55:15"
"time": "2015-08-15 19:32:36"
},
{
"name": "masterminds/html5",
@ -2117,7 +2117,7 @@
},
{
"name": "symfony/browser-kit",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/BrowserKit.git",
@ -2172,7 +2172,7 @@
},
{
"name": "symfony/class-loader",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/ClassLoader.git",
@ -2222,16 +2222,16 @@
},
{
"name": "symfony/console",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "8cf484449130cabfd98dcb4694ca9945802a21ed"
"reference": "d6cf02fe73634c96677e428f840704bfbcaec29e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/8cf484449130cabfd98dcb4694ca9945802a21ed",
"reference": "8cf484449130cabfd98dcb4694ca9945802a21ed",
"url": "https://api.github.com/repos/symfony/Console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e",
"reference": "d6cf02fe73634c96677e428f840704bfbcaec29e",
"shasum": ""
},
"require": {
@ -2275,11 +2275,11 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-07-09 16:07:40"
"time": "2015-07-28 15:18:12"
},
{
"name": "symfony/css-selector",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/CssSelector.git",
@ -2332,7 +2332,7 @@
},
{
"name": "symfony/debug",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Debug.git",
@ -2392,16 +2392,16 @@
},
{
"name": "symfony/dependency-injection",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/DependencyInjection.git",
"reference": "d56b1b89a0c8b34a6eca6211ec76c43256ec4030"
"reference": "851e3ffe8a366b1590bdaf3df2c1395f2d27d8a6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/d56b1b89a0c8b34a6eca6211ec76c43256ec4030",
"reference": "d56b1b89a0c8b34a6eca6211ec76c43256ec4030",
"url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/851e3ffe8a366b1590bdaf3df2c1395f2d27d8a6",
"reference": "851e3ffe8a366b1590bdaf3df2c1395f2d27d8a6",
"shasum": ""
},
"require": {
@ -2448,11 +2448,11 @@
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
"time": "2015-07-09 16:07:40"
"time": "2015-07-28 14:07:07"
},
{
"name": "symfony/dom-crawler",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/DomCrawler.git",
@ -2505,7 +2505,7 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
@ -2563,16 +2563,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/HttpFoundation.git",
"reference": "88903c0531b90d4ecd90282b18f08c0c77bde0b2"
"reference": "863af6898081b34c65d42100c370b9f3c51b70ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/88903c0531b90d4ecd90282b18f08c0c77bde0b2",
"reference": "88903c0531b90d4ecd90282b18f08c0c77bde0b2",
"url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/863af6898081b34c65d42100c370b9f3c51b70ca",
"reference": "863af6898081b34c65d42100c370b9f3c51b70ca",
"shasum": ""
},
"require": {
@ -2612,27 +2612,27 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
"time": "2015-07-09 16:07:40"
"time": "2015-07-22 10:11:00"
},
{
"name": "symfony/http-kernel",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/HttpKernel.git",
"reference": "4a8a6f2a847475b3a38da50363a07f69b5cbf37e"
"reference": "405d3e7a59ff7a28ec469441326a0ac79065ea98"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/HttpKernel/zipball/4a8a6f2a847475b3a38da50363a07f69b5cbf37e",
"reference": "4a8a6f2a847475b3a38da50363a07f69b5cbf37e",
"url": "https://api.github.com/repos/symfony/HttpKernel/zipball/405d3e7a59ff7a28ec469441326a0ac79065ea98",
"reference": "405d3e7a59ff7a28ec469441326a0ac79065ea98",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"psr/log": "~1.0",
"symfony/debug": "~2.6,>=2.6.2",
"symfony/event-dispatcher": "~2.5.9|~2.6,>=2.6.2",
"symfony/event-dispatcher": "~2.6,>=2.6.7",
"symfony/http-foundation": "~2.5,>=2.5.4"
},
"conflict": {
@ -2692,11 +2692,11 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
"time": "2015-07-13 19:27:49"
"time": "2015-07-31 13:24:45"
},
{
"name": "symfony/process",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
@ -2799,7 +2799,7 @@
},
{
"name": "symfony/routing",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Routing.git",
@ -2870,16 +2870,16 @@
},
{
"name": "symfony/serializer",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Serializer.git",
"reference": "4ce2211d3a5d3a3605de7cc040af2808882f3c95"
"reference": "143d318457ecc298a846506acc8e80dea30d2548"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Serializer/zipball/4ce2211d3a5d3a3605de7cc040af2808882f3c95",
"reference": "4ce2211d3a5d3a3605de7cc040af2808882f3c95",
"url": "https://api.github.com/repos/symfony/Serializer/zipball/143d318457ecc298a846506acc8e80dea30d2548",
"reference": "143d318457ecc298a846506acc8e80dea30d2548",
"shasum": ""
},
"require": {
@ -2927,11 +2927,11 @@
],
"description": "Symfony Serializer Component",
"homepage": "https://symfony.com",
"time": "2015-07-08 06:12:51"
"time": "2015-07-22 19:42:44"
},
{
"name": "symfony/translation",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Translation.git",
@ -2992,16 +2992,16 @@
},
{
"name": "symfony/validator",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Validator.git",
"reference": "26a190dbdf7a19fc2251c2d59547a717918b6c77"
"reference": "646df03e635a8a232804274401449ccdf5f03cad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Validator/zipball/26a190dbdf7a19fc2251c2d59547a717918b6c77",
"reference": "26a190dbdf7a19fc2251c2d59547a717918b6c77",
"url": "https://api.github.com/repos/symfony/Validator/zipball/646df03e635a8a232804274401449ccdf5f03cad",
"reference": "646df03e635a8a232804274401449ccdf5f03cad",
"shasum": ""
},
"require": {
@ -3058,20 +3058,20 @@
],
"description": "Symfony Validator Component",
"homepage": "https://symfony.com",
"time": "2015-07-02 06:17:05"
"time": "2015-07-31 06:49:15"
},
{
"name": "symfony/yaml",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "4bfbe0ed3909bfddd75b70c094391ec1f142f860"
"reference": "71340e996171474a53f3d29111d046be4ad8a0ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/4bfbe0ed3909bfddd75b70c094391ec1f142f860",
"reference": "4bfbe0ed3909bfddd75b70c094391ec1f142f860",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff",
"reference": "71340e996171474a53f3d29111d046be4ad8a0ff",
"shasum": ""
},
"require": {
@ -3107,20 +3107,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-07-01 11:25:50"
"time": "2015-07-28 14:07:07"
},
{
"name": "twig/twig",
"version": "v1.18.1",
"version": "v1.20.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "9f70492f44398e276d1b81c1b43adfe6751c7b7f"
"reference": "1ea4e5f81c6d005fe84d0b38e1c4f1955eb86844"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/9f70492f44398e276d1b81c1b43adfe6751c7b7f",
"reference": "9f70492f44398e276d1b81c1b43adfe6751c7b7f",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/1ea4e5f81c6d005fe84d0b38e1c4f1955eb86844",
"reference": "1ea4e5f81c6d005fe84d0b38e1c4f1955eb86844",
"shasum": ""
},
"require": {
@ -3129,7 +3129,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
"dev-master": "1.20-dev"
}
},
"autoload": {
@ -3164,7 +3164,7 @@
"keywords": [
"templating"
],
"time": "2015-04-19 08:30:27"
"time": "2015-08-12 15:56:39"
},
{
"name": "zendframework/zend-diactoros",

View file

@ -491,7 +491,7 @@ field.value.string_long:
# Schema for the configuration of the URI field type.
field.storage_settings.uri:
type: mapping
type: field.storage_settings.string
label: 'URI settings'
mapping:
max_length:
@ -646,10 +646,10 @@ field.field_settings.integer:
type: integer
label: 'Maximum'
prefix:
type: string
type: label
label: 'Prefix'
suffix:
type: string
type: label
label: 'Suffix'
field.value.integer:
@ -684,10 +684,10 @@ field.field_settings.decimal:
type: float
label: 'Maximum'
prefix:
type: string
type: label
label: 'Prefix'
suffix:
type: string
type: label
label: 'Suffix'
field.value.decimal:
@ -715,10 +715,10 @@ field.field_settings.float:
type: float
label: 'Maximum'
prefix:
type: string
type: label
label: 'Prefix'
suffix:
type: string
type: label
label: 'Suffix'
field.value.float:

View file

@ -160,6 +160,17 @@ field.widget.settings.string_textarea:
type: label
label: 'Placeholder'
field.widget.settings.uri:
type: mapping
label: 'URI field'
mapping:
size:
type: integer
label: 'Size of URI field'
placeholder:
type: label
label: 'Placeholder'
field.widget.settings.email_default:
type: mapping
label: 'Email field display format settings'

View file

@ -49,6 +49,7 @@
* - @link user_api User accounts, permissions, and roles @endlink
* - @link theme_render Render API @endlink
* - @link themeable Theme system @endlink
* - @link update_api Update API @endlink
* - @link migration Migration @endlink
*
* @section additional Additional topics
@ -230,11 +231,23 @@
* Whether or not configuration files are being used for the active
* configuration storage on a particular site, configuration files are always
* used for:
* - Defining the default configuration for a module, which is imported to the
* active storage when the module is enabled. Note that changes to this
* default configuration after a module is already enabled have no effect;
* to make a configuration change after a module is enabled, you would need
* to uninstall/reinstall or use a hook_update_N() function.
* - Defining the default configuration for an extension (module, theme, or
* profile), which is imported to the active storage when the extension if
* enabled. These configuration items are located in the config/install
* sub-directory of the extension. Note that changes to this configuration
* after a module or theme is already enabled have no effect; to make a
* configuration change after a module or theme is enabled, you would need to
* uninstall/reinstall or use a hook_update_N() function.
* - Defining optional configuration for a module or theme. Optional
* configuration items are located in the config/optional sub-directory of the
* extension. These configuration items have dependencies that are not
* explicit dependencies of the extension, so they are only installed if all
* dependencies are met. For example, in the scenario that module A defines a
* dependency which requires module B, but module A is installed first and
* module B some time later, then module A's config/optional directory will be
* scanned at that time for newly met dependencies, and the configuration will
* be installed then. If module B is never installed, the configuration item
* will not be installed either.
* - Exporting and importing configuration.
*
* The file storage format for configuration information in Drupal is
@ -318,9 +331,8 @@
* modulename.schema.yml file, with an entry for 'modulename.config_prefix.*'.
* For example, for the Role entity, the file user.schema.yml has an entry
* user.role.*; see @ref sec_yaml above for more information.
* - Your module may also provide a few configuration items to be installed by
* default, by adding configuration files to the module's config/install
* directory; see @ref sec_yaml above for more information.
* - Your module can provide default/optional configuration entities in YAML
* files; see @ref sec_yaml above for more information.
* - Some configuration entities have dependencies on other configuration
* entities, and module developers need to consider this so that configuration
* can be imported, uninstalled, and synchronized in the right order. For
@ -568,6 +580,9 @@
* $settings['cache']['default'] = 'cache.custom';
* @endcode
*
* Finally, you can chain multiple cache backends together, see
* \Drupal\Core\Cache\ChainedFastBackend and \Drupal\Core\Cache\BackendChain.
*
* @see https://www.drupal.org/node/1884796
* @}
*/
@ -830,42 +845,115 @@
* @{
* API for describing data based on a set of available data types.
*
* The Typed Data API was created to provide developers with a consistent
* interface for interacting with data, as well as an API for metadata
* (information about the data, such as the data type, whether it is
* translatable, and who can access it). The Typed Data API is used in several
* Drupal sub-systems, such as the Entity Field API and Configuration API.
* PHP has data types, such as int, string, float, array, etc., and it is an
* object-oriented language that lets you define classes and interfaces.
* However, in some cases, it is useful to be able to define an abstract
* type (as in an interface, free of implementation details), that still has
* properties (which an interface cannot) as well as meta-data. The Typed Data
* API provides this abstraction.
*
* @section sec_overview Overview
* Each data type in the Typed Data API is a plugin class (annotation class
* example: \Drupal\Core\TypedData\Annotation\DataType); these plugins are
* managed by the typed_data_manager service (by default
* \Drupal\Core\TypedData\TypedDataManager). Each data object encapsulates a
* single piece of data, provides access to the metadata, and provides
* validation capability. Also, the typed data plugins have a shorthand
* for easily accessing data values, described in @ref sec_tree.
*
* The metadata of a data object is defined by an object based on a class called
* the definition class (see \Drupal\Core\TypedData\DataDefinitionInterface).
* The class used can vary by data type and can be specified in the data type's
* plugin definition, while the default is set in the $definition_class property
* of the annotation class. The default class is
* \Drupal\Core\TypedData\DataDefinition. For data types provided by a plugin
* deriver, the plugin deriver can set the definition_class property too.
* The metadata object provides information about the data, such as the data
* type, whether it is translatable, the names of its properties (for complex
* types), and who can access it.
*
* See https://www.drupal.org/node/1794140 for more information about the Typed
* Data API.
*
* @section interfaces Interfaces and classes in the Typed Data API
* There are several basic interfaces in the Typed Data API, representing
* different types of data:
* - \Drupal\Core\TypedData\PrimitiveInterface: Used for primitive data, such
* as strings, numeric types, etc. Drupal provides primitive types for
* integers, strings, etc. based on this interface, and you should
* not ever need to create new primitive types.
* - \Drupal\Core\TypedData\TypedDataInterface: Used for single pieces of data,
* with some information about its context. Abstract base class
* \Drupal\Core\TypedData\TypedData is a useful starting point, and contains
* documentation on how to extend it.
* - \Drupal\Core\TypedData\ComplexDataInterface: Used for complex data, which
* contains named and typed properties; extends TypedDataInterface. Examples
* of complex data include content entities and field items. See the
* @link entity_api Entity API topic @endlink for more information about
* entities; for most complex data, developers should use entities.
* - \Drupal\Core\TypedData\ListInterface: Used for a sequential list of other
* typed data. Class \Drupal\Core\TypedData\Plugin\DataType\ItemList is a
* generic implementation of this interface, and it is used by default for
* data declared as a list of some other data type. You can also define a
* custom list class, in which case ItemList is a useful base class.
* @section sec_varieties Varieties of typed data
* There are three kinds of typed data: primitive, complex, and list.
*
* @section defining Defining data types
* @subsection sub_primitive Primitive data types
* Primitive data types wrap PHP data types and also serve as building blocks
* for complex and list typed data. Each primitive data type has an interface
* that extends \Drupal\Core\TypedData\PrimitiveInterface, with getValue()
* and setValue() methods for accessing the data value, and a default plugin
* implementation. Here's a list:
* - \Drupal\Core\TypedData\Type\IntegerInterface: Plugin ID integer,
* corresponds to PHP type int.
* - \Drupal\Core\TypedData\Type\StringInterface: Plugin ID string,
* corresponds to PHP type string.
* - \Drupal\Core\TypedData\Type\FloatInterface: Plugin ID float,
* corresponds to PHP type float.
* - \Drupal\Core\TypedData\Type\BooleanInterface: Plugin ID bool,
* corresponds to PHP type bool.
* - \Drupal\Core\TypedData\Type\BinaryInterface: Plugin ID binary,
* corresponds to a PHP file resource.
* - \Drupal\Core\TypedData\Type\UriInterface: Plugin ID uri.
*
* @subsection sec_complex Complex data
* Complex data types, with interface
* \Drupal\Core\TypedData\ComplexDataInterface, represent data with named
* properties; the properties can be accessed with get() and set() methods.
* The value of each property is itself a typed data object, which can be
* primitive, complex, or list data.
*
* The base type for most complex data is the
* \Drupal\Core\TypedData\Plugin\DataType\Map class, which represents an
* associative array. Map provides its own definition class in the annotation,
* \Drupal\Core\TypedData\MapDataDefinition, and most complex data classes
* extend this class. The getValue() and setValue() methods on the Map class
* enforce the data definition and its property structure.
*
* The Drupal Field API uses complex typed data for its field items, with
* definition class \Drupal\Core\Field\TypedData\FieldItemDataDefinition.
*
* @section sec_list Lists
* List data types, with interface \Drupal\Core\TypedData\ListInterface,
* represent data that is an ordered list of typed data, all of the same type.
* More precisely, the plugins in the list must have the same base plugin ID;
* however, some types (for example field items and entities) are provided by
* plugin derivatives and the sub IDs can be different.
*
* @section sec_tree Tree handling
* Typed data allows you to use shorthand to get data values nested in the
* implicit tree structure of the data. For example, to get the value from
* an entity field item, the Entity Field API allows you to call:
* @code
* $value = $entity->fieldName->propertyName;
* @endcode
* This is really shorthand for:
* @code
* $field_item_list = $entity->get('fieldName');
* $field_item = $field_item_list->get(0);
* $property = $field_item->get('propertyName');
* $value = $property->getValue();
* @endcode
* Some notes:
* - $property, $field_item, and $field_item_list are all typed data objects,
* while $value is a raw PHP value.
* - You can call $property->getParent() to get $field_item,
* $field_item->getParent() to get $field_item_list, or
* $field_item_list->getParent() to get $typed_entity ($entity wrapped in a
* typed data object). $typed_entity->getParent() is NULL.
* - For all of these ->getRoot() returns $typed_entity.
* - The langcode property is on $field_item_list, but you can access it
* on $property as well, so that all items will report the same langcode.
* - When the value of $property is changed by calling $property->setValue(),
* $property->onChange() will fire, which in turn calls the parent object's
* onChange() method and so on. This allows parent objects to react upon
* changes of contained properties or list items.
*
* @section sec_defining Defining data types
* To define a new data type:
* - Create a class that implements one of the Typed Data interfaces.
* Typically, you will want to extend one of the classes listed in the
* section above as a starting point.
* sections above as a starting point.
* - Make your class into a DataType plugin. To do that, put it in namespace
* \Drupal\yourmodule\Plugin\DataType (where "yourmodule" is your module's
* short name), and add annotation of type
@ -873,7 +961,7 @@
* See the @link plugin_api Plugin API topic @endlink and the
* @link annotation Annotations topic @endlink for more information.
*
* @section using Using data types
* @section sec_using Using data types
* The data types of the Typed Data API can be used in several ways, once they
* have been defined:
* - In the Field API, data types can be used as the class in the property
@ -882,6 +970,15 @@
* - In configuration schema files, you can use the unique ID ('id' annotation)
* from any DataType plugin class as the 'type' value for an entry. See the
* @link config_api Confuration API topic @endlink for more information.
* - If you need to create a typed data object in code, first get the
* typed_data_manager service from the container or by calling
* \Drupal::typedDataManager(). Then pass the plugin ID to
* $manager::createDataDefinition() to create an appropriate data definition
* object. Then pass the data definition object and the value of the data to
* $manager::create() to create a typed data object.
*
* @see plugin_api
* @see container
* @}
*/
@ -1000,7 +1097,7 @@
* verified with standard control structures at all times, not just checked in
* development environments with assert() statements on.
*
* When runtime assertions fail in PHP 7 an \AssertionException is thrown.
* When runtime assertions fail in PHP 7 an \AssertionError is thrown.
* Drupal uses an assertion callback to do the same in PHP 5.x so that unit
* tests involving runtime assertions will work uniformly across both versions.
*

View file

@ -52,6 +52,10 @@ drupal:
drupalSettings:
version: VERSION
js:
# Need to specify a negative weight like drupal.js until
# https://www.drupal.org/node/1945262 is resolved.
misc/drupalSettingsLoader.js: { weight: -18 }
drupalSettings:
# These placeholder values will be set by system_js_settings_alter().
path:
@ -83,6 +87,7 @@ drupal.ajax:
libraries: null
theme: null
theme_token: null
ajaxTrustedUrl: {}
dependencies:
- core/jquery
- core/drupal
@ -328,7 +333,7 @@ html5shiv:
url: http://www.gnu.org/licenses/gpl-2.0.html
gpl-compatible: true
js:
assets/vendor/html5shiv/html5shiv.min.js: { every_page: true, weight: -22, browsers: { IE: 'lte IE 8', '!IE': false }, minified: true }
assets/vendor/html5shiv/html5shiv.min.js: { weight: -22, browsers: { IE: 'lte IE 8', '!IE': false }, minified: true }
jquery:
remote: https://github.com/jquery/jquery
@ -822,7 +827,7 @@ modernizr:
gpl-compatible: true
version: "v2.8.3"
js:
assets/vendor/modernizr/modernizr.min.js: { every_page: true, preprocess: 0, weight: -21, minified: true }
assets/vendor/modernizr/modernizr.min.js: { preprocess: 0, weight: -21, minified: true }
normalize:
remote: https://github.com/necolas/normalize.css
@ -833,7 +838,7 @@ normalize:
gpl-compatible: true
css:
base:
assets/vendor/normalize-css/normalize.css: { every_page: true, weight: -20 }
assets/vendor/normalize-css/normalize.css: { weight: -20 }
picturefill:
remote: https://github.com/scottjehl/picturefill

View file

@ -1,8 +1,19 @@
parameters:
session.storage.options: {}
twig.config: {}
session.storage.options:
gc_probability: 1
gc_divisor: 100
gc_maxlifetime: 200000
cookie_lifetime: 2000000
twig.config:
debug: false
auto_reload: null
cache: true
renderer.config:
required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']
auto_placeholder_conditions:
max-age: 0
contexts: ['session', 'user']
tags: []
factory.keyvalue:
default: keyvalue.database
factory.keyvalue.expirable:
@ -58,6 +69,11 @@ services:
arguments: ['@request_stack']
tags:
- { name: cache.context }
cache_context.url.path:
class: Drupal\Core\Cache\Context\PathCacheContext
arguments: ['@request_stack']
tags:
- { name: cache.context }
cache_context.url.query_args:
class: Drupal\Core\Cache\Context\QueryArgsCacheContext
arguments: ['@request_stack']
@ -159,6 +175,15 @@ services:
cache.backend.php:
class: Drupal\Core\Cache\PhpBackendFactory
arguments: ['@cache_tags.invalidator.checksum']
cache.backend.memory:
class: Drupal\Core\Cache\MemoryBackendFactory
# A special cache bin that does not persist beyond the length of the request.
cache.static:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
- { name: cache.bin, default_backend: cache.backend.memory }
factory: cache_factory:get
arguments: [static]
cache.bootstrap:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
@ -313,7 +338,7 @@ services:
arguments: ['@request_stack', '@url_generator']
form_error_handler:
class: Drupal\Core\Form\FormErrorHandler
arguments: ['@string_translation', '@link_generator']
arguments: ['@string_translation', '@link_generator', '@renderer']
form_cache:
class: Drupal\Core\Form\FormCache
arguments: ['@app.root', '@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@request_stack', '@page_cache_request_policy']
@ -592,7 +617,7 @@ services:
public: false
controller_resolver:
class: Drupal\Core\Controller\ControllerResolver
arguments: ['@class_resolver']
arguments: ['@psr7.http_message_factory', '@class_resolver']
class_resolver:
class: Drupal\Core\DependencyInjection\ClassResolver
calls:
@ -605,12 +630,8 @@ services:
http_kernel.basic:
class: Symfony\Component\HttpKernel\HttpKernel
arguments: ['@event_dispatcher', '@controller_resolver', '@request_stack']
http_negotiation.format_negotiator:
class: Drupal\Core\ContentNegotiation
private: true
http_middleware.negotiation:
class: Drupal\Core\StackMiddleware\NegotiationMiddleware
arguments: ['@http_negotiation.format_negotiator']
tags:
- { name: http_middleware, priority: 400 }
http_middleware.reverse_proxy:
@ -695,13 +716,13 @@ services:
arguments: ['@current_route_match']
router.route_provider:
class: Drupal\Core\Routing\RouteProvider
arguments: ['@database', '@state', '@path.current', '@cache.data', '@path_processor_manager']
arguments: ['@database', '@state', '@path.current', '@cache.data', '@path_processor_manager', '@cache_tags.invalidator']
tags:
- { name: event_subscriber }
- { name: backend_overridable }
router.route_preloader:
class: Drupal\Core\Routing\RoutePreloader
arguments: ['@router.route_provider', '@state']
arguments: ['@router.route_provider', '@state', '@cache.bootstrap']
tags:
- { name: 'event_subscriber' }
router.matcher.final_matcher:
@ -925,7 +946,7 @@ services:
- { name: event_subscriber }
main_content_renderer.html:
class: Drupal\Core\Render\MainContent\HtmlRenderer
arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler', '@renderer', '@render_cache']
arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler', '@renderer', '@render_cache', '%renderer.config%']
tags:
- { name: render.main_content_renderer, format: html }
main_content_renderer.ajax:
@ -1336,7 +1357,7 @@ services:
arguments: ['@current_user', '@session_handler.write_safe']
user_permissions_hash_generator:
class: Drupal\Core\Session\PermissionsHashGenerator
arguments: ['@private_key', '@cache.default']
arguments: ['@private_key', '@cache.default', '@cache.static']
current_user:
class: Drupal\Core\Session\AccountProxy
session_configuration:
@ -1421,7 +1442,7 @@ services:
class: Drupal\Core\Extension\InfoParser
twig:
class: Drupal\Core\Template\TwigEnvironment
arguments: ['@app.root', '@cache.default', '@twig.loader', '%twig.config%']
arguments: ['@app.root', '@cache.default', '%twig_extension_hash%', '@twig.loader', '%twig.config%']
tags:
- { name: service_collector, tag: 'twig.extension', call: addExtension }
twig.extension:

View file

@ -170,7 +170,7 @@ function _batch_progress_page() {
$build = array(
'#theme' => 'progress_bar',
'#percent' => $percentage,
'#message' => $message,
'#message' => array('#markup' => $message),
'#label' => $label,
'#attached' => array(
'html_head' => array(
@ -406,6 +406,7 @@ function _batch_next_set() {
*/
function _batch_finished() {
$batch = &batch_get();
$batch_finished_redirect = NULL;
// Execute the 'finished' callbacks for each batch set, if defined.
foreach ($batch['sets'] as $batch_set) {
@ -417,7 +418,13 @@ function _batch_finished() {
if (is_callable($batch_set['finished'])) {
$queue = _batch_queue($batch_set);
$operations = $queue->getAllItems();
call_user_func_array($batch_set['finished'], array($batch_set['success'], $batch_set['results'], $operations, \Drupal::service('date.formatter')->formatInterval($batch_set['elapsed'] / 1000)));
$batch_set_result = call_user_func_array($batch_set['finished'], array($batch_set['success'], $batch_set['results'], $operations, \Drupal::service('date.formatter')->formatInterval($batch_set['elapsed'] / 1000)));
// If a batch 'finished' callback requested a redirect after the batch
// is complete, save that for later use. If more than one batch set
// returned a redirect, the last one is used.
if ($batch_set_result instanceof RedirectResponse) {
$batch_finished_redirect = $batch_set_result;
}
}
}
}
@ -448,8 +455,13 @@ function _batch_finished() {
\Drupal::request()->query->set('destination', $_batch['destination']);
}
// Determine the target path to redirect to.
if (!isset($_batch['form_state'])) {
// Determine the target path to redirect to. If a batch 'finished' callback
// returned a redirect response object, use that. Otherwise, fall back on
// the form redirection.
if (isset($batch_finished_redirect)) {
return $batch_finished_redirect;
}
elseif (!isset($_batch['form_state'])) {
$_batch['form_state'] = new FormState();
}
if ($_batch['form_state']->getRedirect() === NULL) {

View file

@ -7,6 +7,7 @@
use Drupal\Component\Datetime\DateTimePlus;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Environment;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\DrupalKernel;
@ -278,89 +279,6 @@ function drupal_get_path($type, $name) {
return dirname(drupal_get_filename($type, $name));
}
/**
* Sets an HTTP response header for the current page.
*
* Note: When sending a Content-Type header, always include a 'charset' type,
* too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
*
* @param $name
* The HTTP header name, or the special 'Status' header name.
* @param $value
* The HTTP header value; if equal to FALSE, the specified header is unset.
* If $name is 'Status', this is expected to be a status code followed by a
* reason phrase, e.g. "404 Not Found".
* @param $append
* Whether to append the value to an existing header or to replace it.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Symfony\Component\HttpFoundation\Response->headers->set().
* See https://www.drupal.org/node/2181523.
*/
function _drupal_add_http_header($name, $value, $append = FALSE) {
// The headers as name/value pairs.
$headers = &drupal_static('drupal_http_headers', array());
$name_lower = strtolower($name);
_drupal_set_preferred_header_name($name);
if ($value === FALSE) {
$headers[$name_lower] = FALSE;
}
elseif (isset($headers[$name_lower]) && $append) {
// Multiple headers with identical names may be combined using comma (RFC
// 2616, section 4.2).
$headers[$name_lower] .= ',' . $value;
}
else {
$headers[$name_lower] = $value;
}
}
/**
* Gets the HTTP response headers for the current page.
*
* @param $name
* An HTTP header name. If omitted, all headers are returned as name/value
* pairs. If an array value is FALSE, the header has been unset.
*
* @return
* A string containing the header value, or FALSE if the header has been set,
* or NULL if the header has not been set.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Symfony\Component\HttpFoundation\Response->headers->get().
* See https://www.drupal.org/node/2181523.
*/
function drupal_get_http_header($name = NULL) {
$headers = &drupal_static('drupal_http_headers', array());
if (isset($name)) {
$name = strtolower($name);
return isset($headers[$name]) ? $headers[$name] : NULL;
}
else {
return $headers;
}
}
/**
* Sets the preferred name for the HTTP header.
*
* Header names are case-insensitive, but for maximum compatibility they should
* follow "common form" (see RFC 2616, section 4.2).
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* See https://www.drupal.org/node/2181523.
*/
function _drupal_set_preferred_header_name($name = NULL) {
static $header_names = array();
if (!isset($name)) {
return $header_names;
}
$header_names[strtolower($name)] = $name;
}
/**
* Translates a string to the current language or to a given language.
*
@ -426,8 +344,11 @@ function t($string, array $args = array(), array $options = array()) {
* @see \Drupal\Component\Utility\SafeMarkup::format()
* @see t()
* @ingroup sanitization
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
* Use \Drupal\Component\Utility\SafeMarkup::format().
*/
function format_string($string, array $args = array()) {
function format_string($string, array $args) {
return SafeMarkup::format($string, $args);
}
@ -456,6 +377,9 @@ function format_string($string, array $args = array()) {
* TRUE if the text is valid UTF-8, FALSE if not.
*
* @see \Drupal\Component\Utility\Unicode::validateUtf8()
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
* Use \Drupal\Component\Utility\Unicode::validateUtf8().
*/
function drupal_validate_utf8($text) {
return Unicode::validateUtf8($text);
@ -488,10 +412,7 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia
// Use a default value if $message is not set.
if (empty($message)) {
// The exception message is run through
// \Drupal\Component\Utility\SafeMarkup::checkPlain() by
// \Drupal\Core\Utility\Error:decodeException().
$message = '%type: !message in %function (line %line of %file).';
$message = '%type: @message in %function (line %line of %file).';
}
if ($link) {
@ -514,7 +435,7 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia
* drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
* @endcode
*
* @param string $message
* @param string|\Drupal\Component\Utility\SafeStringInterface $message
* (optional) The translated message to be displayed to the user. For
* consistency with other messages, it should begin with a capital letter and
* end with a period.
@ -561,12 +482,15 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
$_SESSION['messages'][$type] = array();
}
$new = array(
'safe' => SafeMarkup::isSafe($message),
'message' => (string) $message,
);
if ($repeat || !in_array($new, $_SESSION['messages'][$type])) {
$_SESSION['messages'][$type][] = $new;
// Convert strings which are in the safe markup list to SafeString objects.
if (is_string($message) && SafeMarkup::isSafe($message)) {
$message = \Drupal\Core\Render\SafeString::create($message);
}
// Do not use strict type checking so that equivalent string and
// SafeStringInterface objects are detected.
if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
$_SESSION['messages'][$type][] = $message;
}
// Mark this page as being uncacheable.
@ -604,18 +528,6 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
*/
function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
if ($messages = drupal_set_message()) {
foreach ($messages as $message_type => $message_typed_messages) {
foreach ($message_typed_messages as $key => $message) {
// Because the messages are stored in the session, the safe status of
// the messages also needs to be stored in the session. We retrieve the
// safe status here and determine whether to mark the string as safe or
// let autoescape do its thing. See drupal_set_message().
if ($message['safe']) {
$message['message'] = SafeMarkup::set($message['message']);
}
$messages[$message_type][$key] = $message['message'];
}
}
if ($type) {
if ($clear_queue) {
unset($_SESSION['messages'][$type]);
@ -1042,10 +954,16 @@ function drupal_static_reset($name = NULL) {
/**
* Formats text for emphasized display in a placeholder inside a sentence.
*
* @see \Drupal\Component\Utility\SafeMarkup::placeholder()
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use
* \Drupal\Component\Utility\SafeMarkup::format() or Twig's "placeholder"
* filter instead. Note this method should not be used to simply emphasize a
* string and therefore has few valid use-cases. Note also, that this method
* does not mark the string as safe.
*
* @see \Drupal\Component\Utility\SafeMarkup::format()
*/
function drupal_placeholder($text) {
return SafeMarkup::placeholder($text);
return '<em class="placeholder">' . Html::escape($text) . '</em>';
}
/**

View file

@ -22,6 +22,7 @@ use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
@ -270,7 +271,7 @@ function valid_email_address($mail) {
* @param $uri
* A plain-text URI that might contain dangerous protocols.
*
* @return
* @return string
* A URI stripped of dangerous protocols and encoded for output to an HTML
* attribute value. Because it is already encoded, it should not be set as a
* value within a $attributes array passed to Drupal\Core\Template\Attribute,
@ -280,10 +281,20 @@ function valid_email_address($mail) {
* \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() instead.
*
* @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
* @see \Drupal\Component\Utility\SafeMarkup::checkPlain()
* @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
* Use UrlHelper::stripDangerousProtocols() or UrlHelper::filterBadProtocol()
* instead. UrlHelper::stripDangerousProtocols() can be used in conjunction
* with \Drupal\Component\Utility\SafeMarkup::format() and an @variable
* placeholder which will perform the necessary escaping.
* UrlHelper::filterBadProtocol() is functionality equivalent to check_url()
* apart from the fact it is protected from double escaping bugs. Note that
* this method no longer marks its output as safe.
*
*/
function check_url($uri) {
return SafeMarkup::checkPlain(UrlHelper::stripDangerousProtocols($uri));
return Html::escape(UrlHelper::stripDangerousProtocols($uri));
}
/**
@ -366,6 +377,9 @@ function format_size($size, $langcode = NULL) {
* A translated date string in the requested format.
*
* @see \Drupal\Core\Datetime\DateFormatter::format()
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
* Use \Drupal::service('date.formatter')->format().
*/
function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
return \Drupal::service('date.formatter')->format($timestamp, $type, $format, $timezone, $langcode);
@ -487,7 +501,7 @@ function _drupal_add_html_head_link($attributes, $header = FALSE) {
if ($header) {
// Also add a HTTP header "Link:".
$href = '<' . SafeMarkup::checkPlain($attributes['href']) . '>;';
$href = '<' . Html::escape($attributes['href']) . '>;';
unset($attributes['href']);
$element['#attached']['http_header'][] = array('Link', $href . drupal_http_header_attributes($attributes), TRUE);
}
@ -517,7 +531,6 @@ function drupal_js_defaults($data = NULL) {
return array(
'type' => 'file',
'group' => JS_DEFAULT,
'every_page' => FALSE,
'weight' => 0,
'scope' => 'header',
'cache' => TRUE,
@ -642,7 +655,7 @@ function drupal_process_attached(array $elements) {
call_user_func_array('_drupal_add_html_head_link', $args);
break;
case 'http_header':
call_user_func_array('_drupal_add_http_header', $args);
// @todo Remove validation in https://www.drupal.org/node/2477223
break;
default:
throw new \LogicException(sprintf('You are not allowed to use %s in #attached', $callback));
@ -1084,8 +1097,9 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
* can be passed in to save another run of
* \Drupal\Core\Render\Element::children().
*
* @return string
* @return string|\Drupal\Component\Utility\SafeStringInterface
* The rendered HTML of all children of the element.
*
* @see drupal_render()
*/
function drupal_render_children(&$element, $children_keys = NULL) {
@ -1098,7 +1112,7 @@ function drupal_render_children(&$element, $children_keys = NULL) {
$output .= drupal_render($element[$key]);
}
}
return SafeMarkup::set($output);
return SafeString::create($output);
}
/**
@ -1196,7 +1210,7 @@ function show(&$element) {
* Retrieves the default properties for the defined element type.
*
* @param $type
* An element type as defined by hook_element_info().
* An element type as defined by an element plugin.
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
* Use \Drupal::service('element_info')->getInfo() instead.
@ -1209,7 +1223,7 @@ function element_info($type) {
* Retrieves a single property for the defined element type.
*
* @param $type
* An element type as defined by hook_element_info().
* An element type as defined by an element plugin.
* @param $property_name
* The property within the element type that should be returned.
* @param $default
@ -1376,7 +1390,7 @@ function _drupal_flush_css_js() {
*/
function debug($data, $label = NULL, $print_r = TRUE) {
// Print $data contents to string.
$string = SafeMarkup::checkPlain($print_r ? print_r($data, TRUE) : var_export($data, TRUE));
$string = Html::escape($print_r ? print_r($data, TRUE) : var_export($data, TRUE));
// Display values with pre-formatting to increase readability.
$string = '<pre>' . $string . '</pre>';

View file

@ -666,7 +666,6 @@ function db_field_exists($table, $field) {
*
* @param string $table_expression
* An SQL expression, for example "simpletest%" (without the quotes).
* BEWARE: this is not prefixed, the caller should take care of that.
*
* @return array
* Array, both the keys and the values are the matching tables.
@ -905,16 +904,22 @@ function db_drop_unique_key($table, $name) {
* The name of the index.
* @param array $fields
* An array of field names.
* @param array $spec
* The table specification of the table to be altered, as taken from a schema
* definition. See \Drupal\Core\Database\Schema::addIndex() for how to obtain
* this specification.
*
* @deprecated as of Drupal 8.0.x, will be removed in Drupal 9.0.0. Instead, get
* a database connection injected into your service from the container, get
* its schema driver, and call addIndex() on it. E.g.
* $injected_database->schema()->addIndex($table, $name, $fields);
* $injected_database->schema()->addIndex($table, $name, $fields, $spec);
*
* @see hook_schema()
* @see schemaapi
* @see \Drupal\Core\Database\Schema::addIndex()
*/
function db_add_index($table, $name, $fields) {
return Database::getConnection()->schema()->addIndex($table, $name, $fields);
function db_add_index($table, $name, $fields, array $spec) {
return Database::getConnection()->schema()->addIndex($table, $name, $fields, $spec);
}
/**

View file

@ -403,7 +403,7 @@ function entity_view_multiple(array $entities, $view_mode, $langcode = NULL, $re
}
/**
* Returns the entity view display associated to a bundle and view mode.
* Returns the entity view display associated with a bundle and view mode.
*
* Use this function when assigning suggested display options for a component
* in a given view mode. Note that they will only be actually used at render
@ -439,24 +439,24 @@ function entity_view_multiple(array $entities, $view_mode, $langcode = NULL, $re
* this bundle.
*
* @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
* The entity view display associated to the view mode.
* The entity view display associated with the view mode.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0.
* If the display is available in configuration use:
* @code
* @code
* \Drupal::entityManager()->getStorage('entity_view_display')->load($entity_type . '.' . $bundle . '.' . $view_mode);
* @endcode
* @endcode
* When the display is not available in configuration, you can create a new
* EntityViewDisplay object using:
* @code
* $values = ('entity_view_display', array(
* 'targetEntityType' => $entity_type,
* 'bundle' => $bundle,
* 'mode' => $view_mode,
* 'status' => TRUE,
* ));
* \Drupal::entityManager()->getStorage('entity_view_display')->create($values);
* @endcode
* @code
* $values = array(
* 'targetEntityType' => $entity_type,
* 'bundle' => $bundle,
* 'mode' => $view_mode,
* 'status' => TRUE,
* ));
* \Drupal::entityManager()->getStorage('entity_view_display')->create($values);
* @endcode
*
* @see \Drupal\Core\Entity\EntityStorageInterface::create()
* @see \Drupal\Core\Entity\EntityStorageInterface::load()
@ -483,7 +483,7 @@ function entity_get_display($entity_type, $bundle, $view_mode) {
}
/**
* Returns the entity form display associated to a bundle and form mode.
* Returns the entity form display associated with a bundle and form mode.
*
* The function reads the entity form display object from the current
* configuration, or returns a ready-to-use empty one if no configuration entry
@ -515,7 +515,7 @@ function entity_get_display($entity_type, $bundle, $view_mode) {
* The form mode.
*
* @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
* The entity form display associated to the given form mode.
* The entity form display associated with the given form mode.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0.
* If the entity form display is available in configuration use:

View file

@ -8,6 +8,7 @@
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\Response;
@ -68,7 +69,7 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
'%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
// The standard PHP error handler considers that the error messages
// are HTML. We mimick this behavior here.
'!message' => Xss::filterAdmin($message),
'@message' => SafeString::create(Xss::filterAdmin($message)),
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
@ -109,9 +110,9 @@ function error_displayable($error = NULL) {
* Logs a PHP error or exception and displays an error page in fatal cases.
*
* @param $error
* An array with the following keys: %type, !message, %function, %file,
* An array with the following keys: %type, @message, %function, %file,
* %line, severity_level, and backtrace. All the parameters are plain-text,
* with the exception of !message, which needs to be a safe HTML string, and
* with the exception of @message, which needs to be an HTML string, and
* backtrace, which is a standard PHP backtrace.
* @param $fatal
* TRUE if the error is fatal.
@ -130,7 +131,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
// as it uniquely identifies each PHP error.
static $number = 0;
$assertion = array(
$error['!message'],
$error['@message'],
$error['%type'],
array(
'function' => $error['%function'],
@ -154,12 +155,12 @@ function _drupal_log_error($error, $fatal = FALSE) {
// installer.
if (\Drupal::hasService('logger.factory')) {
try {
\Drupal::logger('php')->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
\Drupal::logger('php')->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);
}
catch (\Exception $e) {
// We can't log, for example because the database connection is not
// available. At least try to log to PHP error log.
error_log(sprintf('Failed to log error: %type: !message in %function (line %line of %file).', $error['%type'], $error['%function'], $error['%line'], $error['%file']));
error_log(strtr('Failed to log error: %type: @message in %function (line %line of %file).', $error));
}
}
@ -167,7 +168,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
if ($fatal) {
// When called from CLI, simply output a plain text message.
// Should not translate the string to avoid errors producing more errors.
$response->setContent(html_entity_decode(strip_tags(format_string('%type: !message in %function (line %line of %file).', $error))). "\n");
$response->setContent(html_entity_decode(strip_tags(SafeMarkup::format('%type: @message in %function (line %line of %file).', $error))). "\n");
$response->send();
exit;
}
@ -178,7 +179,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
if (error_displayable($error)) {
// When called from JavaScript, simply output the error message.
// Should not translate the string to avoid errors producing more errors.
$response->setContent(format_string('%type: !message in %function (line %line of %file).', $error));
$response->setContent(SafeMarkup::format('%type: @message in %function (line %line of %file).', $error));
$response->send();
}
exit;
@ -208,20 +209,29 @@ function _drupal_log_error($error, $fatal = FALSE) {
$error['%file'] = substr($error['%file'], $root_length + 1);
}
}
// Should not translate the string to avoid errors producing more errors.
$message = format_string('%type: !message in %function (line %line of %file).', $error);
// Check if verbose error reporting is on.
$error_level = _drupal_get_error_level();
if ($error_level == ERROR_REPORTING_DISPLAY_VERBOSE) {
if ($error_level != ERROR_REPORTING_DISPLAY_VERBOSE) {
// Without verbose logging, use a simple message.
// We call SafeMarkup::format() directly here, rather than use t() since
// we are in the middle of error handling, and we don't want t() to
// cause further errors.
$message = SafeMarkup::format('%type: @message in %function (line %line of %file).', $error);
}
else {
// With verbose logging, we will also include a backtrace.
// First trace is the error itself, already contained in the message.
// While the second trace is the error source and also contained in the
// message, the message doesn't contain argument values, so we output it
// once more in the backtrace.
array_shift($backtrace);
// Generate a backtrace containing only scalar argument values.
$message .= '<pre class="backtrace">' . Error::formatBacktrace($backtrace) . '</pre>';
$error['@backtrace'] = Error::formatBacktrace($backtrace);
$message = SafeMarkup::format('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error);
}
}
@ -248,10 +258,11 @@ function _drupal_log_error($error, $fatal = FALSE) {
// An exception must halt script execution.
exit;
}
else {
if ($message) {
if (\Drupal::hasService('session')) {
// Message display is dependent on sessions being available.
drupal_set_message(SafeMarkup::set($message), $class, TRUE);
drupal_set_message($message, $class, TRUE);
}
else {
print $message;

View file

@ -5,11 +5,11 @@
* API for handling file uploads and server file management.
*/
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\PhpStorage\FileStorage;
use Drupal\Component\Utility\Bytes;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\File\FileSystem;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
@ -371,7 +371,7 @@ function file_save_htaccess($directory, $private = TRUE, $force_overwrite = FALS
return drupal_chmod($htaccess_path, 0444);
}
else {
$variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(SafeMarkup::checkPlain($htaccess_lines)));
$variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(Html::escape($htaccess_lines)));
\Drupal::logger('security')->error("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables);
return FALSE;
}

View file

@ -206,7 +206,10 @@ function template_preprocess_fieldset(&$variables) {
$variables['children'] = $element['#children'];
$variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
$variables['legend']['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
if (isset($element['#title']) && $element['#title'] !== '') {
$variables['legend']['title'] = ['#markup' => $element['#title']];
}
$variables['legend']['attributes'] = new Attribute();
$variables['legend_span']['attributes'] = new Attribute();
@ -373,7 +376,7 @@ function template_preprocess_textarea(&$variables) {
Element\RenderElement::setAttributes($element, array('form-textarea'));
$variables['wrapper_attributes'] = new Attribute();
$variables['attributes'] = new Attribute($element['#attributes']);
$variables['value'] = SafeMarkup::checkPlain($element['#value']);
$variables['value'] = $element['#value'];
$variables['resizable'] = !empty($element['#resizable']) ? $element['#resizable'] : NULL;
$variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
}
@ -504,7 +507,10 @@ function template_preprocess_form_element(&$variables) {
function template_preprocess_form_element_label(&$variables) {
$element = $variables['element'];
// If title and required marker are both empty, output no label.
$variables['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
if (isset($element['#title']) && $element['#title'] !== '') {
$variables['title'] = ['#markup' => $element['#title']];
}
$variables['attributes'] = array();
// Pass elements title_display to template.
@ -770,7 +776,14 @@ function batch_set($batch_definition) {
*
* @param \Drupal\Core\Url|string $redirect
* (optional) Either path or Url object to redirect to when the batch has
* finished processing.
* finished processing. Note that to simply force a batch to (conditionally)
* redirect to a custom location after it is finished processing but to
* otherwise allow the standard form API batch handling to occur, it is not
* necessary to call batch_process() and use this parameter. Instead, make
* the batch 'finished' callback return an instance of
* \Symfony\Component\HttpFoundation\RedirectResponse, which will be used
* automatically by the standard batch processing pipeline (and which takes
* precedence over this parameter).
* @param \Drupal\Core\Url $url
* (optional - should only be used for separate scripts like update.php)
* URL of the batch processing page.

View file

@ -6,7 +6,6 @@ use Drupal\Core\DrupalKernel;
use Drupal\Core\Config\BootstrapConfigStorageFactory;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\Install\TaskException;
use Drupal\Core\Form\FormState;
use Drupal\Core\Installer\Exception\AlreadyInstalledException;
use Drupal\Core\Installer\Exception\InstallerException;
@ -1133,14 +1132,24 @@ function install_database_errors($database, $settings_file) {
// Run tasks associated with the database type. Any errors are caught in the
// calling function.
Database::addConnectionInfo('default', 'default', $database);
try {
db_run_tasks($driver);
}
catch (TaskException $e) {
$errors = db_installer_object($driver)->runTasks();
if (count($errors)) {
$error_message = [
'#type' => 'inline_template',
'#template' => '{% trans %}Resolve all issues below to continue the installation. For help configuring your database server, see the <a href="https://www.drupal.org/getting-started/install">installation handbook</a>, or contact your hosting provider.{% endtrans%}{{ errors }}',
'#context' => [
'errors' => [
'#theme' => 'item_list',
'#items' => $errors,
],
],
];
// These are generic errors, so we do not have any specific key of the
// database connection array to attach them to; therefore, we just put
// them in the error array with standard numeric keys.
$errors[$driver . '][0'] = $e->getMessage();
$errors[$driver . '][0'] = \Drupal::service('renderer')->renderPlain($error_message);
}
}
return $errors;
@ -1590,6 +1599,10 @@ function install_bootstrap_full() {
* The batch definition.
*/
function install_profile_modules(&$install_state) {
// We need to manually trigger the installation of core-provided entity types,
// as those will not be handled by the module installer.
install_core_entity_type_definitions();
$modules = \Drupal::state()->get('install_profile_modules') ?: array();
$files = system_rebuild_module_data();
\Drupal::state()->delete('install_profile_modules');
@ -1629,6 +1642,18 @@ function install_profile_modules(&$install_state) {
return $batch;
}
/**
* Installs entity type definitions provided by core.
*/
function install_core_entity_type_definitions() {
$update_manager = \Drupal::entityDefinitionUpdateManager();
foreach (\Drupal::entityManager()->getDefinitions() as $entity_type) {
if ($entity_type->getProvider() == 'core') {
$update_manager->installEntityType($entity_type);
}
}
}
/**
* Installs themes.
*
@ -1656,12 +1681,6 @@ function install_profile_themes(&$install_state) {
* An array of information about the current installation state.
*/
function install_install_profile(&$install_state) {
// Now that all modules are installed, make sure the entity storage and other
// handlers are up to date with the current entity and field definitions. For
// example, Path module adds a base field to nodes and taxonomy terms after
// those modules are already installed.
\Drupal::service('entity.definition_update_manager')->applyUpdates();
\Drupal::service('module_installer')->install(array(drupal_get_profile()), FALSE);
// Install all available optional config. During installation the module order
// is determined by dependencies. If there are no dependencies between modules
@ -2033,7 +2052,7 @@ function install_check_translations($langcode, $server_pattern) {
'title' => t('Translation'),
'value' => t('The %language translation is not available.', array('%language' => $language)),
'severity' => REQUIREMENT_ERROR,
'description' => t('The %language translation file is not available at the translation server. <a href="!url">Choose a different language</a> or select English and translate your website later.', array('%language' => $language, '!url' => check_url($_SERVER['SCRIPT_NAME']))),
'description' => t('The %language translation file is not available at the translation server. <a href="@url">Choose a different language</a> or select English and translate your website later.', array('%language' => $language, '@url' => UrlHelper::stripDangerousProtocols($_SERVER['SCRIPT_NAME']))),
);
}
else {
@ -2052,7 +2071,7 @@ function install_check_translations($langcode, $server_pattern) {
'title' => t('Translation'),
'value' => t('The %language translation could not be downloaded.', array('%language' => $language)),
'severity' => REQUIREMENT_ERROR,
'description' => t('The %language translation file could not be downloaded. <a href="!url">Choose a different language</a> or select English and translate your website later.', array('%language' => $language, '!url' => check_url($_SERVER['SCRIPT_NAME']))),
'description' => t('The %language translation file could not be downloaded. <a href="@url">Choose a different language</a> or select English and translate your website later.', array('%language' => $language, '@url' => UrlHelper::stripDangerousProtocols($_SERVER['SCRIPT_NAME']))),
);
}
}
@ -2083,13 +2102,6 @@ function install_check_requirements($install_state) {
'description_default' => t('The default settings file does not exist.'),
'title' => t('Settings file'),
);
$default_files['services.yml'] = array(
'file' => 'services.yml',
'file_default' => 'default.services.yml',
'title_default' => t('Default services file'),
'description_default' => t('The default services file does not exist.'),
'title' => t('Services file'),
);
foreach ($default_files as $default_file_info) {
$readable = FALSE;
@ -2269,11 +2281,11 @@ function install_display_requirements($install_state, $requirements) {
$build['report']['#requirements'] = $requirements;
if ($severity == REQUIREMENT_WARNING) {
$build['#title'] = t('Requirements review');
$build['#suffix'] = t('Check the messages and <a href="!retry">retry</a>, or you may choose to <a href="!cont">continue anyway</a>.', array('!retry' => check_url(drupal_requirements_url(REQUIREMENT_ERROR)), '!cont' => check_url(drupal_requirements_url($severity))));
$build['#suffix'] = t('Check the messages and <a href="@retry">retry</a>, or you may choose to <a href="@cont">continue anyway</a>.', array('@retry' => UrlHelper::stripDangerousProtocols(drupal_requirements_url(REQUIREMENT_ERROR)), '@cont' => UrlHelper::stripDangerousProtocols(drupal_requirements_url($severity))));
}
else {
$build['#title'] = t('Requirements problem');
$build['#suffix'] = t('Check the messages and <a href="!url">try again</a>.', array('!url' => check_url(drupal_requirements_url($severity))));
$build['#suffix'] = t('Check the messages and <a href="@url">try again</a>.', array('@url' => UrlHelper::stripDangerousProtocols(drupal_requirements_url($severity))));
}
return $build;
}

View file

@ -210,7 +210,7 @@ function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
}
$variable_names['$'. $setting] = $setting;
}
$contents = file_get_contents(DRUPAL_ROOT . '/' . $settings_file);
$contents = file_get_contents($settings_file);
if ($contents !== FALSE) {
// Initialize the contents for the settings.php file if it is empty.
if (trim($contents) === '') {
@ -315,7 +315,7 @@ function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
}
// Write the new settings file.
if (file_put_contents(DRUPAL_ROOT . '/' . $settings_file, $buffer) === FALSE) {
if (file_put_contents($settings_file, $buffer) === FALSE) {
throw new Exception(t('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file)));
}
else {
@ -864,9 +864,11 @@ function install_goto($path) {
* @return
* The URL of the current script, with query parameters modified by the
* passed-in $query. The URL is not sanitized, so it still needs to be run
* through check_url() if it will be used as an HTML attribute value.
* through \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will be
* used as an HTML attribute value.
*
* @see drupal_requirements_url()
* @see Drupal\Component\Utility\UrlHelper::filterBadProtocol()
*/
function drupal_current_script_url($query = array()) {
$uri = $_SERVER['SCRIPT_NAME'];
@ -890,10 +892,12 @@ function drupal_current_script_url($query = array()) {
*
* @return
* A URL for attempting to proceed to the next step of the script. The URL is
* not sanitized, so it still needs to be run through check_url() if it will
* be used as an HTML attribute value.
* not sanitized, so it still needs to be run through
* \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will be used
* as an HTML attribute value.
*
* @see drupal_current_script_url()
* @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
*/
function drupal_requirements_url($severity) {
$query = array();
@ -1068,19 +1072,6 @@ function install_profile_info($profile, $langcode = 'en') {
return $cache[$profile][$langcode];
}
/**
* Ensures the environment for a Drupal database on a predefined connection.
*
* This will run tasks that check that Drupal can perform all of the functions
* on a database, that Drupal needs. Tasks include simple checks like CREATE
* TABLE to database specific functions like stored procedures and client
* encoding.
*/
function db_run_tasks($driver) {
db_installer_object($driver)->runTasks();
return TRUE;
}
/**
* Returns a database installer object.
*

View file

@ -93,7 +93,7 @@ function menu_list_system_menus() {
}
/**
* Collects the local tasks (tabs), action links, and the root path.
* Collects the local tasks (tabs) for the current route.
*
* @param int $level
* The level of tasks you ask for. Primary tasks are 0, secondary are 1.
@ -101,95 +101,45 @@ function menu_list_system_menus() {
* @return array
* An array containing
* - tabs: Local tasks for the requested level.
* - actions: Action links for the requested level.
* - root_path: The router path for the current page. If the current page is
* a default local task, then this corresponds to the parent tab.
* - route_name: The route name for the current page used to collect the local
* tasks.
*
* @see hook_menu_local_tasks()
* @see hook_menu_local_tasks_alter()
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
function menu_local_tasks($level = 0) {
$data = &drupal_static(__FUNCTION__);
$root_path = &drupal_static(__FUNCTION__ . ':root_path', '');
$empty = array(
'tabs' => array(),
'actions' => array(),
'root_path' => &$root_path,
);
if (!isset($data)) {
// Look for route-based tabs.
$data['tabs'] = array();
$data['actions'] = array();
$route_name = \Drupal::routeMatch()->getRouteName();
if (!\Drupal::request()->attributes->has('exception') && !empty($route_name)) {
$manager = \Drupal::service('plugin.manager.menu.local_task');
$local_tasks = $manager->getTasksBuild($route_name);
foreach ($local_tasks as $level => $items) {
$data['tabs'][$level] = empty($data['tabs'][$level]) ? $items : array_merge($data['tabs'][$level], $items);
}
}
// Allow modules to dynamically add further tasks.
$module_handler = \Drupal::moduleHandler();
foreach ($module_handler->getImplementations('menu_local_tasks') as $module) {
$function = $module . '_menu_local_tasks';
$function($data, $route_name);
}
// Allow modules to alter local tasks.
$module_handler->alter('menu_local_tasks', $data, $route_name);
}
if (isset($data['tabs'][$level])) {
return array(
'tabs' => $data['tabs'][$level],
'actions' => $data['actions'],
'root_path' => $root_path,
);
}
elseif (!empty($data['actions'])) {
return array('actions' => $data['actions']) + $empty;
}
return $empty;
/** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */
$manager = \Drupal::service('plugin.manager.menu.local_task');
return $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), $level);
}
/**
* Returns the rendered local tasks at the top level.
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
function menu_primary_local_tasks() {
$links = menu_local_tasks(0);
/** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */
$manager = \Drupal::service('plugin.manager.menu.local_task');
$links = $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), 0);
// Do not display single tabs.
return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : '';
}
/**
* Returns the rendered local tasks at the second level.
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
function menu_secondary_local_tasks() {
$links = menu_local_tasks(1);
/** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */
$manager = \Drupal::service('plugin.manager.menu.local_task');
$links = $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), 1);
// Do not display single tabs.
return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : '';
}
/**
* Returns the rendered local actions at the current level.
*/
function menu_get_local_actions() {
$links = menu_local_tasks();
$route_name = Drupal::routeMatch()->getRouteName();
$manager = \Drupal::service('plugin.manager.menu.local_action');
return $manager->getActionsForRoute($route_name) + $links['actions'];
}
/**
* Returns the router path, or the path for a default local task's parent.
*/
function menu_tab_root_path() {
$links = menu_local_tasks();
return $links['root_path'];
}
/**
* Returns a renderable element for the primary and secondary tabs.
*/

View file

@ -250,7 +250,7 @@ function template_preprocess_pager(&$variables) {
}
}
// Add an ellipsis if there are further next pages.
if ($i < $pager_max) {
if ($i < $pager_max + 1) {
$variables['ellipses']['next'] = TRUE;
}
}

View file

@ -20,6 +20,7 @@ use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\ThemeSettings;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\SafeString;
/**
* @defgroup content_flags Content markers
@ -115,7 +116,7 @@ function drupal_theme_rebuild() {
*/
function drupal_find_theme_functions($cache, $prefixes) {
$implementations = [];
$grouped_functions = drupal_group_functions_by_prefix();
$grouped_functions = \Drupal::service('theme.registry')->getPrefixGroupedUserFunctions();
foreach ($cache as $hook => $info) {
foreach ($prefixes as $prefix) {
@ -161,25 +162,6 @@ function drupal_find_theme_functions($cache, $prefixes) {
return $implementations;
}
/**
* Group all user functions by word before first underscore.
*
* @return array
* Functions grouped by the first prefix.
*/
function drupal_group_functions_by_prefix() {
$functions = get_defined_functions();
$grouped_functions = [];
// Splitting user defined functions into groups by the first prefix.
foreach ($functions['user'] as $function) {
list($first_prefix,) = explode('_', $function, 2);
$grouped_functions[$first_prefix][] = $function;
}
return $grouped_functions;
}
/**
* Allows themes and/or theme engines to easily discover overridden templates.
*
@ -507,8 +489,8 @@ function template_preprocess_datetime_wrapper(&$variables) {
}
$variables['required'] = FALSE;
// For required datetime fields a 'form-required' class is appended to the
// label attributes.
// For required datetime fields 'form-required' & 'js-form-required' classes
// are appended to the label attributes.
if (!empty($element['#required'])) {
$variables['required'] = TRUE;
}
@ -594,7 +576,6 @@ function template_preprocess_links(&$variables) {
);
// Convert the attributes array into an Attribute object.
$heading['attributes'] = new Attribute($heading['attributes']);
$heading['text'] = SafeMarkup::checkPlain($heading['text']);
}
$variables['links'] = array();
@ -1158,9 +1139,6 @@ function template_preprocess_maintenance_task_list(&$variables) {
* details.
*/
function template_preprocess(&$variables, $hook, $info) {
// Tell all templates where they are located.
$variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath();
// Merge in variables that don't depend on hook and don't change during a
// single page request.
// Use the advanced drupal_static() pattern, since this is called very often.
@ -1203,6 +1181,9 @@ function _template_preprocess_default_variables() {
// Give modules a chance to alter the default template variables.
\Drupal::moduleHandler()->alter('template_preprocess_default_variables', $variables);
// Tell all templates where they are located.
$variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath();
return $variables;
}
@ -1255,9 +1236,14 @@ function template_preprocess_html(&$variables) {
$site_config = \Drupal::config('system.site');
// Construct page title.
if (isset($variables['page']['#title']) && is_array($variables['page']['#title'])) {
// Do an early render if the title is a render array.
$variables['page']['#title'] = (string) \Drupal::service('renderer')->render($variables['page']['#title']);
}
if (!empty($variables['page']['#title'])) {
$head_title = array(
'title' => trim(strip_tags($variables['page']['#title'])),
// Marking the title as safe since it has had the tags stripped.
'title' => SafeString::create(trim(strip_tags($variables['page']['#title']))),
'name' => $site_config->get('name'),
);
}
@ -1295,7 +1281,7 @@ function template_preprocess_html(&$variables) {
'@token' => $token,
]);
$variables[$type]['#markup'] = $placeholder;
$variables[$type]['#attached']['html_response_placeholders'][$type] = $placeholder;
$variables[$type]['#attached']['html_response_attachment_placeholders'][$type] = $placeholder;
}
}
@ -1325,7 +1311,7 @@ function template_preprocess_page(&$variables) {
$variables['front_page'] = \Drupal::url('<front>');
$variables['language'] = $language_interface;
$variables['logo'] = theme_get_setting('logo.url');
$variables['site_name'] = (theme_get_setting('features.name') ? SafeMarkup::checkPlain($site_config->get('name')) : '');
$variables['site_name'] = (theme_get_setting('features.name') ? $site_config->get('name') : '');
$variables['site_slogan']['#markup'] = (theme_get_setting('features.slogan') ? $site_config->get('slogan') : '');
// An exception might be thrown.
@ -1338,14 +1324,6 @@ function template_preprocess_page(&$variables) {
$variables['is_front'] = FALSE;
$variables['db_is_active'] = FALSE;
}
if (!defined('MAINTENANCE_MODE')) {
$variables['action_links'] = menu_get_local_actions();
$variables['tabs'] = menu_local_tabs();
}
else {
$variables['action_links'] = array();
$variables['tabs'] = array();
}
if ($node = \Drupal::routeMatch()->getParameter('node')) {
$variables['node'] = $node;
@ -1490,8 +1468,6 @@ function template_preprocess_region(&$variables) {
* - element: A render element representing the field.
* - attributes: A string containing the attributes for the wrapping div.
* - title_attributes: A string containing the attributes for the title.
* - content_attributes: A string containing the attributes for the content's
* div.
*/
function template_preprocess_field(&$variables, $hook) {
$element = $variables['element'];
@ -1506,13 +1482,24 @@ function template_preprocess_field(&$variables, $hook) {
// Always set the field label - allow themes to decide whether to display it.
// In addition the label should be rendered but hidden to support screen
// readers.
$variables['label'] = SafeMarkup::checkPlain($element['#title']);
$variables['label'] = $element['#title'];
// @todo Check for is_object() required due to pseudo field used in
// quickedit_test_entity_view_alter(). Remove this check after this is fixed
// in https://www.drupal.org/node/2550225.
$variables['multiple'] = is_object($element['#items']) ? $element['#items']->getFieldDefinition()->getFieldStorageDefinition()->isMultiple() : FALSE;
static $default_attributes;
if (!isset($default_attributes)) {
$default_attributes = new Attribute;
}
// Merge attributes when a single-value field has a hidden label.
if ($element['#label_display'] == 'hidden' && !$variables['multiple'] && is_object($element['#items'][0])) {
$variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], (array) $element['#items'][0]->_attributes);
}
// We want other preprocess functions and the theme implementation to have
// fast access to the field item render arrays. The item render array keys
// (deltas) should always be numerically indexed starting from 0, and looping
@ -1556,6 +1543,7 @@ function template_preprocess_field_multiple_value_form(&$variables) {
$order_class = $element['#field_name'] . '-delta-order';
$header_attributes = new Attribute(array('class' => array('label')));
if (!empty($element['#required'])) {
$header_attributes['class'][] = 'js-form-required';
$header_attributes['class'][] = 'form-required';
}
$header = array(
@ -1746,17 +1734,10 @@ function drupal_common_theme() {
'maintenance_task_list' => array(
'variables' => array('items' => NULL, 'active' => NULL, 'variant' => NULL),
),
'authorize_message' => array(
'variables' => array('message' => NULL, 'success' => TRUE),
'function' => 'theme_authorize_message',
'path' => 'core/includes',
'file' => 'theme.maintenance.inc',
),
'authorize_report' => array(
'variables' => array('messages' => array()),
'function' => 'theme_authorize_report',
'path' => 'core/includes',
'file' => 'theme.maintenance.inc',
'variables' => ['messages' => [], 'attributes' => []],
'includes' => ['core/includes/theme.maintenance.inc'],
'template' => 'authorize-report',
),
// From pager.inc.
'pager' => array(

View file

@ -100,64 +100,37 @@ function _drupal_maintenance_theme() {
}
/**
* Returns HTML for a results report of an operation run by authorize.php.
* Prepares variables for authorize.php operation report templates.
*
* @param $variables
* This report displays the results of an operation run via authorize.php.
*
* Default template: authorize-report.html.twig.
*
* @param array $variables
* An associative array containing:
* - messages: An array of result messages.
*
* @ingroup themeable
*/
function theme_authorize_report($variables) {
$messages = $variables['messages'];
$output = '';
if (!empty($messages)) {
$output .= '<div class="authorize-results">';
foreach ($messages as $heading => $logs) {
$items = array();
function template_preprocess_authorize_report(&$variables) {
$messages = [];
if (!empty($variables['messages'])) {
foreach ($variables['messages'] as $heading => $logs) {
$items = [];
foreach ($logs as $number => $log_message) {
if ($number === '#abort') {
continue;
}
$authorize_message = array(
'#theme' => 'authorize_message',
'#message' => $log_message['message'],
'#success' => $log_message['success'],
);
$items[] = drupal_render($authorize_message);
$class = 'authorize-results__' . ($log_message['success'] ? 'success' : 'failure');
$items[] = [
'#wrapper_attributes' => ['class' => [$class]],
'#markup' => $log_message['message'],
];
}
$item_list = array(
$messages[] = [
'#theme' => 'item_list',
'#items' => $items,
'#title' => $heading,
);
$output .= drupal_render($item_list);
];
}
$output .= '</div>';
}
return $output;
}
/**
* Returns HTML for a single log message from the authorize.php batch operation.
*
* @param $variables
* An associative array containing:
* - message: The log message.
* It's the caller's responsibility to ensure this string contains no
* dangerous HTML such as SCRIPT tags.
* - success: A boolean indicating failure or success.
*
* @ingroup themeable
*/
function theme_authorize_message($variables) {
$message = $variables['message'];
$success = $variables['success'];
if ($success) {
$item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__success'));
}
else {
$item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__failure'));
}
return $item;
$variables['messages'] = $messages;
}

View file

@ -9,7 +9,7 @@
*/
use Drupal\Component\Graph\Graph;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Utility\Error;
@ -188,10 +188,7 @@ function update_do_one($module, $number, $dependency_map, &$context) {
$variables = Error::decodeException($e);
unset($variables['backtrace']);
// The exception message is run through
// \Drupal\Component\Utility\SafeMarkup::checkPlain() by
// \Drupal\Core\Utility\Error::decodeException().
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: @message in %function (line %line of %file).', $variables));
}
}
@ -218,30 +215,7 @@ function update_do_one($module, $number, $dependency_map, &$context) {
drupal_set_installed_schema_version($module, $number);
}
$context['message'] = 'Updating ' . SafeMarkup::checkPlain($module) . ' module';
}
/**
* Performs entity definition updates, which can trigger schema updates.
*
* @param $context
* The batch context array.
*/
function update_entity_definitions(&$context) {
try {
\Drupal::service('entity.definition_update_manager')->applyUpdates();
}
catch (EntityStorageException $e) {
watchdog_exception('update', $e);
$variables = Error::decodeException($e);
unset($variables['backtrace']);
// The exception message is run through
// \Drupal\Component\Utility\SafeMarkup::checkPlain() by
// \Drupal\Core\Utility\Error::decodeException().
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
$context['results']['core']['update_entity_definitions'] = $ret;
$context['results']['#abort'][] = 'update_entity_definitions';
}
$context['message'] = 'Updating ' . Html::escape($module) . ' module';
}
/**

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.0.0-beta14';
const VERSION = '8.0.0-beta15';
/**
* Core API compatibility.
@ -687,4 +687,14 @@ class Drupal {
return static::getContainer()->get('redirect.destination');
}
/**
* Returns the entity definition update manager.
*
* @return \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
* The entity definition update manager.
*/
public static function entityDefinitionUpdateManager() {
return static::getContainer()->get('entity.definition_update_manager');
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\Component\Assertion\Handle.
*
* For PHP 5 this contains \AssertionError as well.
*/
namespace {
if (!class_exists('AssertionError', FALSE)) {
/**
* Emulates PHP 7 AssertionError as closely as possible.
*
* We force this class to exist at the root namespace for PHP 5.
* This class exists natively in PHP 7. Note that in PHP 7 it extends from
* Error, not Exception, but that isn't possible for PHP 5 - all exceptions
* must extend from exception.
*/
class AssertionError extends Exception {
/**
* {@inheritdoc}
*/
public function __construct($message = '', $code = 0, Exception $previous = NULL, $file = '', $line = 0) {
parent::__construct($message, $code, $previous);
// Preserve the filename and line number of the assertion failure.
$this->file = $file;
$this->line = $line;
}
}
}
}
namespace Drupal\Component\Assertion {
/**
* Handler for runtime assertion failures.
*
* This class allows PHP 5.x to throw exceptions on runtime assertion fails
* in the same manner as PHP 7, and sets the ASSERT_EXCEPTION flag to TRUE
* for the PHP 7 runtime.
*
* @ingroup php_assert
*/
class Handle {
/**
* Registers uniform assertion handling.
*/
public static function register() {
// Since we're using exceptions, turn error warnings off.
assert_options(ASSERT_WARNING, FALSE);
if (version_compare(PHP_VERSION, '7.0.0-dev') < 0) {
// PHP 5 - create a handler to throw the exception directly.
assert_options(ASSERT_CALLBACK, function($file, $line, $code, $message) {
if (empty($message)) {
$message = $code;
}
throw new \AssertionError($message, 0, NULL, $file, $line);
});
}
else {
// PHP 7 - just turn exception throwing on.
assert_options(ASSERT_EXCEPTION, TRUE);
}
}
}
}

View file

@ -0,0 +1,629 @@
<?php
/**
* @file
* Contains \Drupal\Component\DependencyInjection\Container.
*/
namespace Drupal\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
use Symfony\Component\DependencyInjection\ScopeInterface;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
/**
* Provides a container optimized for Drupal's needs.
*
* This container implementation is compatible with the default Symfony
* dependency injection container and similar to the Symfony ContainerBuilder
* class, but optimized for speed.
*
* It is based on a PHP array container definition dumped as a
* performance-optimized machine-readable format.
*
* The best way to initialize this container is to use a Container Builder,
* compile it and then retrieve the definition via
* \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper::getArray().
*
* The retrieved array can be cached safely and then passed to this container
* via the constructor.
*
* As the container is unfrozen by default, a second parameter can be passed to
* the container to "freeze" the parameter bag.
*
* This container is different in behavior from the default Symfony container in
* the following ways:
*
* - It only allows lowercase service and parameter names, though it does only
* enforce it via assertions for performance reasons.
* - The following functions, that are not part of the interface, are explicitly
* not supported: getParameterBag(), isFrozen(), compile(),
* getAServiceWithAnIdByCamelCase().
* - The function getServiceIds() was added as it has a use-case in core and
* contrib.
* - Scopes are explicitly not allowed, because Symfony 2.8 has deprecated
* them and they will be removed in Symfony 3.0.
* - Synchronized services are explicitly not supported, because Symfony 2.8 has
* deprecated them and they will be removed in Symfony 3.0.
*
* @ingroup container
*/
class Container implements IntrospectableContainerInterface {
/**
* The parameters of the container.
*
* @var array
*/
protected $parameters = array();
/**
* The aliases of the container.
*
* @var array
*/
protected $aliases = array();
/**
* The service definitions of the container.
*
* @var array
*/
protected $serviceDefinitions = array();
/**
* The instantiated services.
*
* @var array
*/
protected $services = array();
/**
* The instantiated private services.
*
* @var array
*/
protected $privateServices = array();
/**
* The currently loading services.
*
* @var array
*/
protected $loading = array();
/**
* Whether the container parameters can still be changed.
*
* For testing purposes the container needs to be changed.
*
* @var bool
*/
protected $frozen = TRUE;
/**
* Constructs a new Container instance.
*
* @param array $container_definition
* An array containing the following keys:
* - aliases: The aliases of the container.
* - parameters: The parameters of the container.
* - services: The service definitions of the container.
* - frozen: Whether the container definition came from a frozen
* container builder or not.
* - machine_format: Whether this container definition uses the optimized
* machine-readable container format.
*/
public function __construct(array $container_definition = array()) {
if (!empty($container_definition) && (!isset($container_definition['machine_format']) || $container_definition['machine_format'] !== TRUE)) {
throw new InvalidArgumentException('The non-optimized format is not supported by this class. Use an optimized machine-readable format instead, e.g. as produced by \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.');
}
$this->aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : array();
$this->parameters = isset($container_definition['parameters']) ? $container_definition['parameters'] : array();
$this->serviceDefinitions = isset($container_definition['services']) ? $container_definition['services'] : array();
$this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE;
// Register the service_container with itself.
$this->services['service_container'] = $this;
}
/**
* {@inheritdoc}
*/
public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
if (isset($this->aliases[$id])) {
$id = $this->aliases[$id];
}
// Re-use shared service instance if it exists.
if (isset($this->services[$id]) || ($invalid_behavior === ContainerInterface::NULL_ON_INVALID_REFERENCE && array_key_exists($id, $this->services))) {
return $this->services[$id];
}
if (isset($this->loading[$id])) {
throw new ServiceCircularReferenceException($id, array_keys($this->loading));
}
$definition = isset($this->serviceDefinitions[$id]) ? $this->serviceDefinitions[$id] : NULL;
if (!$definition && $invalid_behavior === ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
if (!$id) {
throw new ServiceNotFoundException($id);
}
throw new ServiceNotFoundException($id, NULL, NULL, $this->getServiceAlternatives($id));
}
// In case something else than ContainerInterface::NULL_ON_INVALID_REFERENCE
// is used, the actual wanted behavior is to re-try getting the service at a
// later point.
if (!$definition) {
return;
}
// Definition is a keyed array, so [0] is only defined when it is a
// serialized string.
if (isset($definition[0])) {
$definition = unserialize($definition);
}
// Now create the service.
$this->loading[$id] = TRUE;
try {
$service = $this->createService($definition, $id);
}
catch (\Exception $e) {
unset($this->loading[$id]);
// Remove a potentially shared service that was constructed incompletely.
if (array_key_exists($id, $this->services)) {
unset($this->services[$id]);
}
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalid_behavior) {
return;
}
throw $e;
}
unset($this->loading[$id]);
return $service;
}
/**
* Creates a service from a service definition.
*
* @param array $definition
* The service definition to create a service from.
* @param string $id
* The service identifier, necessary so it can be shared if its public.
*
* @return object
* The service described by the service definition.
*
* @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException
* Thrown when the service is a synthetic service.
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* Thrown when the configurator callable in $definition['configurator'] is
* not actually a callable.
* @throws \ReflectionException
* Thrown when the service class takes more than 10 parameters to construct,
* and cannot be instantiated.
*/
protected function createService(array $definition, $id) {
if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) {
throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The service container does not know how to construct this service. The service will need to be set before it is first used.', $id));
}
$arguments = array();
if (isset($definition['arguments'])) {
$arguments = $definition['arguments'];
if ($arguments instanceof \stdClass) {
$arguments = $this->resolveServicesAndParameters($arguments);
}
}
if (isset($definition['file'])) {
$file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters(array($definition['file'])));
require_once $file;
}
if (isset($definition['factory'])) {
$factory = $definition['factory'];
if (is_array($factory)) {
$factory = $this->resolveServicesAndParameters(array($factory[0], $factory[1]));
}
elseif (!is_string($factory)) {
throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
}
$service = call_user_func_array($factory, $arguments);
}
else {
$class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters(array($definition['class'])));
$length = isset($definition['arguments_count']) ? $definition['arguments_count'] : count($arguments);
// Optimize class instantiation for services with up to 10 parameters as
// ReflectionClass is noticeably slow.
switch ($length) {
case 0:
$service = new $class();
break;
case 1:
$service = new $class($arguments[0]);
break;
case 2:
$service = new $class($arguments[0], $arguments[1]);
break;
case 3:
$service = new $class($arguments[0], $arguments[1], $arguments[2]);
break;
case 4:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
break;
case 5:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
break;
case 6:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
break;
case 7:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
break;
case 8:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]);
break;
case 9:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]);
break;
case 10:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]);
break;
default:
$r = new \ReflectionClass($class);
$service = $r->newInstanceArgs($arguments);
break;
}
}
// Share the service if it is public.
if (!isset($definition['public']) || $definition['public'] !== FALSE) {
// Forward compatibility fix for Symfony 2.8 update.
if (!isset($definition['shared']) || $definition['shared'] !== FALSE) {
$this->services[$id] = $service;
}
}
if (isset($definition['calls'])) {
foreach ($definition['calls'] as $call) {
$method = $call[0];
$arguments = array();
if (!empty($call[1])) {
$arguments = $call[1];
if ($arguments instanceof \stdClass) {
$arguments = $this->resolveServicesAndParameters($arguments);
}
}
call_user_func_array(array($service, $method), $arguments);
}
}
if (isset($definition['properties'])) {
if ($definition['properties'] instanceof \stdClass) {
$definition['properties'] = $this->resolveServicesAndParameters($definition['properties']);
}
foreach ($definition['properties'] as $key => $value) {
$service->{$key} = $value;
}
}
if (isset($definition['configurator'])) {
$callable = $definition['configurator'];
if (is_array($callable)) {
$callable = $this->resolveServicesAndParameters($callable);
}
if (!is_callable($callable)) {
throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service)));
}
call_user_func($callable, $service);
}
return $service;
}
/**
* {@inheritdoc}
*/
public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) {
$this->services[$id] = $service;
}
/**
* {@inheritdoc}
*/
public function has($id) {
return isset($this->services[$id]) || isset($this->serviceDefinitions[$id]);
}
/**
* {@inheritdoc}
*/
public function getParameter($name) {
if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) {
if (!$name) {
throw new ParameterNotFoundException($name);
}
throw new ParameterNotFoundException($name, NULL, NULL, NULL, $this->getParameterAlternatives($name));
}
return $this->parameters[$name];
}
/**
* {@inheritdoc}
*/
public function hasParameter($name) {
return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters);
}
/**
* {@inheritdoc}
*/
public function setParameter($name, $value) {
if ($this->frozen) {
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
}
$this->parameters[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function initialized($id) {
if (isset($this->aliases[$id])) {
$id = $this->aliases[$id];
}
return isset($this->services[$id]) || array_key_exists($id, $this->services);
}
/**
* Resolves arguments that represent services or variables to the real values.
*
* @param array|\stdClass $arguments
* The arguments to resolve.
*
* @return array
* The resolved arguments.
*
* @throws \Symfony\Component\DependencyInjection\Exception\RuntimeException
* If a parameter/service could not be resolved.
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* If an unknown type is met while resolving parameters and services.
*/
protected function resolveServicesAndParameters($arguments) {
// Check if this collection needs to be resolved.
if ($arguments instanceof \stdClass) {
if ($arguments->type !== 'collection') {
throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $arguments->type));
}
// In case there is nothing to resolve, we are done here.
if (!$arguments->resolve) {
return $arguments->value;
}
$arguments = $arguments->value;
}
// Process the arguments.
foreach ($arguments as $key => $argument) {
// For this machine-optimized format, only \stdClass arguments are
// processed and resolved. All other values are kept as is.
if ($argument instanceof \stdClass) {
$type = $argument->type;
// Check for parameter.
if ($type == 'parameter') {
$name = $argument->name;
if (!isset($this->parameters[$name])) {
$arguments[$key] = $this->getParameter($name);
// This can never be reached as getParameter() throws an Exception,
// because we already checked that the parameter is not set above.
}
// Update argument.
$argument = $arguments[$key] = $this->parameters[$name];
// In case there is not a machine readable value (e.g. a service)
// behind this resolved parameter, continue.
if (!($argument instanceof \stdClass)) {
continue;
}
// Fall through.
$type = $argument->type;
}
// Create a service.
if ($type == 'service') {
$id = $argument->id;
// Does the service already exist?
if (isset($this->aliases[$id])) {
$id = $this->aliases[$id];
}
if (isset($this->services[$id])) {
$arguments[$key] = $this->services[$id];
continue;
}
// Return the service.
$arguments[$key] = $this->get($id, $argument->invalidBehavior);
continue;
}
// Create private service.
elseif ($type == 'private_service') {
$id = $argument->id;
// Does the private service already exist.
if (isset($this->privateServices[$id])) {
$arguments[$key] = $this->privateServices[$id];
continue;
}
// Create the private service.
$arguments[$key] = $this->createService($argument->value, $id);
if ($argument->shared) {
$this->privateServices[$id] = $arguments[$key];
}
continue;
}
// Check for collection.
elseif ($type == 'collection') {
$value = $argument->value;
// Does this collection need resolving?
if ($argument->resolve) {
$arguments[$key] = $this->resolveServicesAndParameters($value);
}
else {
$arguments[$key] = $value;
}
continue;
}
if ($type !== NULL) {
throw new InvalidArgumentException(sprintf('Undefined type "%s" while resolving parameters and services.', $type));
}
}
}
return $arguments;
}
/**
* Provides alternatives for a given array and key.
*
* @param string $search_key
* The search key to get alternatives for.
* @param array $keys
* The search space to search for alternatives in.
*
* @return string[]
* An array of strings with suitable alternatives.
*/
protected function getAlternatives($search_key, array $keys) {
$alternatives = array();
foreach ($keys as $key) {
$lev = levenshtein($search_key, $key);
if ($lev <= strlen($search_key) / 3 || strpos($key, $search_key) !== FALSE) {
$alternatives[] = $key;
}
}
return $alternatives;
}
/**
* Provides alternatives in case a service was not found.
*
* @param string $id
* The service to get alternatives for.
*
* @return string[]
* An array of strings with suitable alternatives.
*/
protected function getServiceAlternatives($id) {
$all_service_keys = array_unique(array_merge(array_keys($this->services), array_keys($this->serviceDefinitions)));
return $this->getAlternatives($id, $all_service_keys);
}
/**
* Provides alternatives in case a parameter was not found.
*
* @param string $name
* The parameter to get alternatives for.
*
* @return string[]
* An array of strings with suitable alternatives.
*/
protected function getParameterAlternatives($name) {
return $this->getAlternatives($name, array_keys($this->parameters));
}
/**
* {@inheritdoc}
*/
public function enterScope($name) {
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
}
/**
* {@inheritdoc}
*/
public function leaveScope($name) {
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
}
/**
* {@inheritdoc}
*/
public function addScope(ScopeInterface $scope) {
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
}
/**
* {@inheritdoc}
*/
public function hasScope($name) {
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
}
/**
* {@inheritdoc}
*/
public function isScopeActive($name) {
throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__));
}
/**
* Gets all defined service IDs.
*
* @return array
* An array of all defined service IDs.
*/
public function getServiceIds() {
return array_keys($this->serviceDefinitions + $this->services);
}
}

View file

@ -0,0 +1,513 @@
<?php
/**
* @file
* Contains \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.
*/
namespace Drupal\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Dumper\Dumper;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* OptimizedPhpArrayDumper dumps a service container as a serialized PHP array.
*
* The format of this dumper is very similar to the internal structure of the
* ContainerBuilder, but based on PHP arrays and \stdClass objects instead of
* rich value objects for performance reasons.
*
* By removing the abstraction and optimizing some cases like deep collections,
* fewer classes need to be loaded, fewer function calls need to be executed and
* fewer run time checks need to be made.
*
* In addition to that, this container dumper treats private services as
* strictly private with their own private services storage, whereas in the
* Symfony service container builder and PHP dumper, shared private services can
* still be retrieved via get() from the container.
*
* It is machine-optimized, for a human-readable version based on this one see
* \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper.
*
* @see \Drupal\Component\DependencyInjection\Container
*/
class OptimizedPhpArrayDumper extends Dumper {
/**
* Whether to serialize service definitions or not.
*
* Service definitions are serialized by default to avoid having to
* unserialize the whole container on loading time, which improves early
* bootstrap performance for e.g. the page cache.
*
* @var bool
*/
protected $serialize = TRUE;
/**
* {@inheritdoc}
*/
public function dump(array $options = array()) {
return serialize($this->getArray());
}
/**
* Gets the service container definition as a PHP array.
*
* @return array
* A PHP array representation of the service container.
*/
public function getArray() {
$definition = array();
$definition['aliases'] = $this->getAliases();
$definition['parameters'] = $this->getParameters();
$definition['services'] = $this->getServiceDefinitions();
$definition['frozen'] = $this->container->isFrozen();
$definition['machine_format'] = $this->supportsMachineFormat();
return $definition;
}
/**
* Gets the aliases as a PHP array.
*
* @return array
* The aliases.
*/
protected function getAliases() {
$alias_definitions = array();
$aliases = $this->container->getAliases();
foreach ($aliases as $alias => $id) {
$id = (string) $id;
while (isset($aliases[$id])) {
$id = (string) $aliases[$id];
}
$alias_definitions[$alias] = $id;
}
return $alias_definitions;
}
/**
* Gets parameters of the container as a PHP array.
*
* @return array
* The escaped and prepared parameters of the container.
*/
protected function getParameters() {
if (!$this->container->getParameterBag()->all()) {
return array();
}
$parameters = $this->container->getParameterBag()->all();
$is_frozen = $this->container->isFrozen();
return $this->prepareParameters($parameters, $is_frozen);
}
/**
* Gets services of the container as a PHP array.
*
* @return array
* The service definitions.
*/
protected function getServiceDefinitions() {
if (!$this->container->getDefinitions()) {
return array();
}
$services = array();
foreach ($this->container->getDefinitions() as $id => $definition) {
// Only store public service definitions, references to shared private
// services are handled in ::getReferenceCall().
if ($definition->isPublic()) {
$service_definition = $this->getServiceDefinition($definition);
$services[$id] = $this->serialize ? serialize($service_definition) : $service_definition;
}
}
return $services;
}
/**
* Prepares parameters for the PHP array dumping.
*
* @param array $parameters
* An array of parameters.
* @param bool $escape
* Whether keys with '%' should be escaped or not.
*
* @return array
* An array of prepared parameters.
*/
protected function prepareParameters(array $parameters, $escape = TRUE) {
$filtered = array();
foreach ($parameters as $key => $value) {
if (is_array($value)) {
$value = $this->prepareParameters($value, $escape);
}
elseif ($value instanceof Reference) {
$value = $this->dumpValue($value);
}
$filtered[$key] = $value;
}
return $escape ? $this->escape($filtered) : $filtered;
}
/**
* Escapes parameters.
*
* @param array $parameters
* The parameters to escape for '%' characters.
*
* @return array
* The escaped parameters.
*/
protected function escape(array $parameters) {
$args = array();
foreach ($parameters as $key => $value) {
if (is_array($value)) {
$args[$key] = $this->escape($value);
}
elseif (is_string($value)) {
$args[$key] = str_replace('%', '%%', $value);
}
else {
$args[$key] = $value;
}
}
return $args;
}
/**
* Gets a service definition as PHP array.
*
* @param \Symfony\Component\DependencyInjection\Definition $definition
* The definition to process.
*
* @return array
* The service definition as PHP array.
*
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* Thrown when the definition is marked as decorated, or with an explicit
* scope different from SCOPE_CONTAINER and SCOPE_PROTOTYPE.
*/
protected function getServiceDefinition(Definition $definition) {
$service = array();
if ($definition->getClass()) {
$service['class'] = $definition->getClass();
}
if (!$definition->isPublic()) {
$service['public'] = FALSE;
}
if ($definition->getFile()) {
$service['file'] = $definition->getFile();
}
if ($definition->isSynthetic()) {
$service['synthetic'] = TRUE;
}
if ($definition->isLazy()) {
$service['lazy'] = TRUE;
}
if ($definition->getArguments()) {
$arguments = $definition->getArguments();
$service['arguments'] = $this->dumpCollection($arguments);
$service['arguments_count'] = count($arguments);
}
else {
$service['arguments_count'] = 0;
}
if ($definition->getProperties()) {
$service['properties'] = $this->dumpCollection($definition->getProperties());
}
if ($definition->getMethodCalls()) {
$service['calls'] = $this->dumpMethodCalls($definition->getMethodCalls());
}
if (($scope = $definition->getScope()) !== ContainerInterface::SCOPE_CONTAINER) {
if ($scope === ContainerInterface::SCOPE_PROTOTYPE) {
// Scope prototype has been replaced with 'shared' => FALSE.
// This is a Symfony 2.8 forward compatibility fix.
// Reference: https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.8.md#dependencyinjection
$service['shared'] = FALSE;
}
else {
throw new InvalidArgumentException("The 'scope' definition is deprecated in Symfony 3.0 and not supported by Drupal 8.");
}
}
if (($decorated = $definition->getDecoratedService()) !== NULL) {
throw new InvalidArgumentException("The 'decorated' definition is not supported by the Drupal 8 run-time container. The Container Builder should have resolved that during the DecoratorServicePass compiler pass.");
}
if ($callable = $definition->getFactory()) {
$service['factory'] = $this->dumpCallable($callable);
}
if ($callable = $definition->getConfigurator()) {
$service['configurator'] = $this->dumpCallable($callable);
}
return $service;
}
/**
* Dumps method calls to a PHP array.
*
* @param array $calls
* An array of method calls.
*
* @return array
* The PHP array representation of the method calls.
*/
protected function dumpMethodCalls(array $calls) {
$code = array();
foreach ($calls as $key => $call) {
$method = $call[0];
$arguments = array();
if (!empty($call[1])) {
$arguments = $this->dumpCollection($call[1]);
}
$code[$key] = [$method, $arguments];
}
return $code;
}
/**
* Dumps a collection to a PHP array.
*
* @param mixed $collection
* A collection to process.
* @param bool &$resolve
* Used for passing the information to the caller whether the given
* collection needed to be resolved or not. This is used for optimizing
* deep arrays that don't need to be traversed.
*
* @return \stdClass|array
* The collection in a suitable format.
*/
protected function dumpCollection($collection, &$resolve = FALSE) {
$code = array();
foreach ($collection as $key => $value) {
if (is_array($value)) {
$resolve_collection = FALSE;
$code[$key] = $this->dumpCollection($value, $resolve_collection);
if ($resolve_collection) {
$resolve = TRUE;
}
}
else {
if (is_object($value)) {
$resolve = TRUE;
}
$code[$key] = $this->dumpValue($value);
}
}
if (!$resolve) {
return $collection;
}
return (object) array(
'type' => 'collection',
'value' => $code,
'resolve' => $resolve,
);
}
/**
* Dumps callable to a PHP array.
*
* @param array|callable $callable
* The callable to process.
*
* @return callable
* The processed callable.
*/
protected function dumpCallable($callable) {
if (is_array($callable)) {
$callable[0] = $this->dumpValue($callable[0]);
$callable = array($callable[0], $callable[1]);
}
return $callable;
}
/**
* Gets a private service definition in a suitable format.
*
* @param string $id
* The ID of the service to get a private definition for.
* @param \Symfony\Component\DependencyInjection\Definition $definition
* The definition to process.
* @param bool $shared
* (optional) Whether the service will be shared with others.
* By default this parameter is FALSE.
*
* @return \stdClass
* A very lightweight private service value object.
*/
protected function getPrivateServiceCall($id, Definition $definition, $shared = FALSE) {
$service_definition = $this->getServiceDefinition($definition);
if (!$id) {
$hash = hash('sha1', serialize($service_definition));
$id = 'private__' . $hash;
}
return (object) array(
'type' => 'private_service',
'id' => $id,
'value' => $service_definition,
'shared' => $shared,
);
}
/**
* Dumps the value to PHP array format.
*
* @param mixed $value
* The value to dump.
*
* @return mixed
* The dumped value in a suitable format.
*
* @throws RuntimeException
* When trying to dump object or resource.
*/
protected function dumpValue($value) {
if (is_array($value)) {
$code = array();
foreach ($value as $k => $v) {
$code[$k] = $this->dumpValue($v);
}
return $code;
}
elseif ($value instanceof Reference) {
return $this->getReferenceCall((string) $value, $value);
}
elseif ($value instanceof Definition) {
return $this->getPrivateServiceCall(NULL, $value);
}
elseif ($value instanceof Parameter) {
return $this->getParameterCall((string) $value);
}
elseif ($value instanceof Expression) {
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
elseif (is_object($value)) {
// Drupal specific: Instantiated objects have a _serviceId parameter.
if (isset($value->_serviceId)) {
return $this->getReferenceCall($value->_serviceId);
}
throw new RuntimeException('Unable to dump a service container if a parameter is an object without _serviceId.');
}
elseif (is_resource($value)) {
throw new RuntimeException('Unable to dump a service container if a parameter is a resource.');
}
return $value;
}
/**
* Gets a service reference for a reference in a suitable PHP array format.
*
* The main difference is that this function treats references to private
* services differently and returns a private service reference instead of
* a normal reference.
*
* @param string $id
* The ID of the service to get a reference for.
* @param \Symfony\Component\DependencyInjection\Reference|NULL $reference
* (optional) The reference object to process; needed to get the invalid
* behavior value.
*
* @return string|\stdClass
* A suitable representation of the service reference.
*/
protected function getReferenceCall($id, Reference $reference = NULL) {
$invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
if ($reference !== NULL) {
$invalid_behavior = $reference->getInvalidBehavior();
}
// Private shared service.
$definition = $this->container->getDefinition($id);
if (!$definition->isPublic()) {
// The ContainerBuilder does not share a private service, but this means a
// new service is instantiated every time. Use a private shared service to
// circumvent the problem.
return $this->getPrivateServiceCall($id, $definition, TRUE);
}
return $this->getServiceCall($id, $invalid_behavior);
}
/**
* Gets a service reference for an ID in a suitable PHP array format.
*
* @param string $id
* The ID of the service to get a reference for.
* @param int $invalid_behavior
* (optional) The invalid behavior of the service.
*
* @return string|\stdClass
* A suitable representation of the service reference.
*/
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
return (object) array(
'type' => 'service',
'id' => $id,
'invalidBehavior' => $invalid_behavior,
);
}
/**
* Gets a parameter reference in a suitable PHP array format.
*
* @param string $name
* The name of the parameter to get a reference for.
*
* @return string|\stdClass
* A suitable representation of the parameter reference.
*/
protected function getParameterCall($name) {
return (object) array(
'type' => 'parameter',
'name' => $name,
);
}
/**
* Whether this supports the machine-optimized format or not.
*
* @return bool
* TRUE if this supports machine-optimized format, FALSE otherwise.
*/
protected function supportsMachineFormat() {
return TRUE;
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper.
*/
namespace Drupal\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* PhpArrayDumper dumps a service container as a PHP array.
*
* The format of this dumper is a human-readable serialized PHP array, which is
* very similar to the YAML based format, but based on PHP arrays instead of
* YAML strings.
*
* It is human-readable, for a machine-optimized version based on this one see
* \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper.
*
* @see \Drupal\Component\DependencyInjection\PhpArrayContainer
*/
class PhpArrayDumper extends OptimizedPhpArrayDumper {
/**
* {@inheritdoc}
*/
public function getArray() {
$this->serialize = FALSE;
return parent::getArray();
}
/**
* {@inheritdoc}
*/
protected function dumpCollection($collection, &$resolve = FALSE) {
$code = array();
foreach ($collection as $key => $value) {
if (is_array($value)) {
$code[$key] = $this->dumpCollection($value);
}
else {
$code[$key] = $this->dumpValue($value);
}
}
return $code;
}
/**
* {@inheritdoc}
*/
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
return '@?' . $id;
}
return '@' . $id;
}
/**
* {@inheritdoc}
*/
protected function getParameterCall($name) {
return '%' . $name . '%';
}
/**
* {@inheritdoc}
*/
protected function supportsMachineFormat() {
return FALSE;
}
}

View file

@ -0,0 +1,276 @@
<?php
/**
* @file
* Contains \Drupal\Component\DependencyInjection\PhpArrayContainer.
*/
namespace Drupal\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Provides a container optimized for Drupal's needs.
*
* This container implementation is compatible with the default Symfony
* dependency injection container and similar to the Symfony ContainerBuilder
* class, but optimized for speed.
*
* It is based on a human-readable PHP array container definition with a
* structure very similar to the YAML container definition.
*
* @see \Drupal\Component\DependencyInjection\Container
* @see \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper
* @see \Drupal\Component\DependencyInjection\DependencySerializationTrait
*
* @ingroup container
*/
class PhpArrayContainer extends Container {
/**
* {@inheritdoc}
*/
public function __construct(array $container_definition = array()) {
if (isset($container_definition['machine_format']) && $container_definition['machine_format'] === TRUE) {
throw new InvalidArgumentException('The machine-optimized format is not supported by this class. Use a human-readable format instead, e.g. as produced by \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper.');
}
// Do not call the parent's constructor as it would bail on the
// machine-optimized format.
$this->aliases = isset($container_definition['aliases']) ? $container_definition['aliases'] : array();
$this->parameters = isset($container_definition['parameters']) ? $container_definition['parameters'] : array();
$this->serviceDefinitions = isset($container_definition['services']) ? $container_definition['services'] : array();
$this->frozen = isset($container_definition['frozen']) ? $container_definition['frozen'] : FALSE;
// Register the service_container with itself.
$this->services['service_container'] = $this;
}
/**
* {@inheritdoc}
*/
protected function createService(array $definition, $id) {
// This method is a verbatim copy of
// \Drupal\Component\DependencyInjection\Container::createService
// except for the following difference:
// - There are no instanceof checks on \stdClass, which are used in the
// parent class to avoid resolving services and parameters when it is
// known from dumping that there is nothing to resolve.
if (isset($definition['synthetic']) && $definition['synthetic'] === TRUE) {
throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The service container does not know how to construct this service. The service will need to be set before it is first used.', $id));
}
$arguments = array();
if (isset($definition['arguments'])) {
$arguments = $this->resolveServicesAndParameters($definition['arguments']);
}
if (isset($definition['file'])) {
$file = $this->frozen ? $definition['file'] : current($this->resolveServicesAndParameters(array($definition['file'])));
require_once $file;
}
if (isset($definition['factory'])) {
$factory = $definition['factory'];
if (is_array($factory)) {
$factory = $this->resolveServicesAndParameters(array($factory[0], $factory[1]));
}
elseif (!is_string($factory)) {
throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
}
$service = call_user_func_array($factory, $arguments);
}
else {
$class = $this->frozen ? $definition['class'] : current($this->resolveServicesAndParameters(array($definition['class'])));
$length = isset($definition['arguments_count']) ? $definition['arguments_count'] : count($arguments);
// Optimize class instantiation for services with up to 10 parameters as
// reflection is noticeably slow.
switch ($length) {
case 0:
$service = new $class();
break;
case 1:
$service = new $class($arguments[0]);
break;
case 2:
$service = new $class($arguments[0], $arguments[1]);
break;
case 3:
$service = new $class($arguments[0], $arguments[1], $arguments[2]);
break;
case 4:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
break;
case 5:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
break;
case 6:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
break;
case 7:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
break;
case 8:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7]);
break;
case 9:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8]);
break;
case 10:
$service = new $class($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6], $arguments[7], $arguments[8], $arguments[9]);
break;
default:
$r = new \ReflectionClass($class);
$service = $r->newInstanceArgs($arguments);
break;
}
}
// Share the service if it is public.
if (!isset($definition['public']) || $definition['public'] !== FALSE) {
// Forward compatibility fix for Symfony 2.8 update.
if (!isset($definition['shared']) || $definition['shared'] !== FALSE) {
$this->services[$id] = $service;
}
}
if (isset($definition['calls'])) {
foreach ($definition['calls'] as $call) {
$method = $call[0];
$arguments = array();
if (!empty($call[1])) {
$arguments = $call[1];
$arguments = $this->resolveServicesAndParameters($arguments);
}
call_user_func_array(array($service, $method), $arguments);
}
}
if (isset($definition['properties'])) {
$definition['properties'] = $this->resolveServicesAndParameters($definition['properties']);
foreach ($definition['properties'] as $key => $value) {
$service->{$key} = $value;
}
}
if (isset($definition['configurator'])) {
$callable = $definition['configurator'];
if (is_array($callable)) {
$callable = $this->resolveServicesAndParameters($callable);
}
if (!is_callable($callable)) {
throw new InvalidArgumentException(sprintf('The configurator for class "%s" is not a callable.', get_class($service)));
}
call_user_func($callable, $service);
}
return $service;
}
/**
* {@inheritdoc}
*/
protected function resolveServicesAndParameters($arguments) {
// This method is different from the parent method only for the following
// cases:
// - A service is denoted by '@service' and not by a \stdClass object.
// - A parameter is denoted by '%parameter%' and not by a \stdClass object.
// - The depth of the tree representing the arguments is not known in
// advance, so it needs to be fully traversed recursively.
foreach ($arguments as $key => $argument) {
if ($argument instanceof \stdClass) {
$type = $argument->type;
// Private services are a special flavor: In case a private service is
// only used by one other service, the ContainerBuilder uses a
// Definition object as an argument, which does not have an ID set.
// Therefore the format uses a \stdClass object to store the definition
// and to be able to create the service on the fly.
//
// Note: When constructing a private service by hand, 'id' must be set.
//
// The PhpArrayDumper just uses the hash of the private service
// definition to generate a unique ID.
//
// @see \Drupal\Component\DependecyInjection\Dumper\OptimizedPhpArrayDumper::getPrivateServiceCall
if ($type == 'private_service') {
$id = $argument->id;
// Check if the private service already exists - in case it is shared.
if (!empty($argument->shared) && isset($this->privateServices[$id])) {
$arguments[$key] = $this->privateServices[$id];
continue;
}
// Create a private service from a service definition.
$arguments[$key] = $this->createService($argument->value, $id);
if (!empty($argument->shared)) {
$this->privateServices[$id] = $arguments[$key];
}
continue;
}
if ($type !== NULL) {
throw new InvalidArgumentException("Undefined type '$type' while resolving parameters and services.");
}
}
if (is_array($argument)) {
$arguments[$key] = $this->resolveServicesAndParameters($argument);
continue;
}
if (!is_string($argument)) {
continue;
}
// Resolve parameters.
if ($argument[0] === '%') {
$name = substr($argument, 1, -1);
if (!isset($this->parameters[$name])) {
$arguments[$key] = $this->getParameter($name);
// This can never be reached as getParameter() throws an Exception,
// because we already checked that the parameter is not set above.
}
$argument = $this->parameters[$name];
$arguments[$key] = $argument;
}
// Resolve services.
if ($argument[0] === '@') {
$id = substr($argument, 1);
$invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
if ($id[0] === '?') {
$id = substr($id, 1);
$invalid_behavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
}
if (isset($this->services[$id])) {
$arguments[$key] = $this->services[$id];
}
else {
$arguments[$key] = $this->get($id, $invalid_behavior);
}
}
}
return $arguments;
}
}

View file

@ -0,0 +1,18 @@
{
"name": "drupal/core-dependency-injection",
"description": "Dependency Injection container optimized for Drupal's needs.",
"keywords": ["drupal", "dependency injection"],
"type": "library",
"homepage": "https://www.drupal.org/project/drupal",
"license": "GPL-2.0+",
"support": {
"issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute",
"source": "https://www.drupal.org/project/drupal/git-instructions"
},
"autoload": {
"psr-4": {
"Drupal\\Component\\DependencyInjection\\": ""
}
}
}

View file

@ -8,7 +8,6 @@
namespace Drupal\Component\Diff\Engine;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\SafeMarkup;
/**
* Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
@ -38,10 +37,10 @@ class HWLDFWordAccumulator {
protected function _flushGroup($new_tag) {
if ($this->group !== '') {
if ($this->tag == 'mark') {
$this->line = SafeMarkup::format('@original_line<span class="diffchange">@group</span>', ['@original_line' => $this->line, '@group' => $this->group]);
$this->line = $this->line . '<span class="diffchange">' . $this->group . '</span>';
}
else {
$this->line = SafeMarkup::format('@original_line@group', ['@original_line' => $this->line, '@group' => $this->group]);
$this->line = $this->line . $this->group;
}
}
$this->group = '';

View file

@ -102,4 +102,10 @@ class FileReadOnlyStorage implements PhpStorageInterface {
return $names;
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
}
}

View file

@ -266,4 +266,10 @@ EOF;
return $names;
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
}
}

View file

@ -63,7 +63,7 @@ class MTimeProtectedFastFileStorage extends FileStorage {
}
/**
* Implements Drupal\Component\PhpStorage\PhpStorageInterface::save().
* {@inheritdoc}
*/
public function save($name, $data) {
$this->ensureDirectory($this->directory);
@ -78,44 +78,32 @@ class MTimeProtectedFastFileStorage extends FileStorage {
// permission.
chmod($temporary_path, 0444);
// Prepare a directory dedicated for just this file. Ensure it has a current
// mtime so that when the file (hashed on that mtime) is moved into it, the
// mtime remains the same (unless the clock ticks to the next second during
// the rename, in which case we'll try again).
$directory = $this->getContainingDirectoryFullPath($name);
if (file_exists($directory)) {
$this->unlink($directory);
}
$this->ensureDirectory($directory);
// Determine the exact modification time of the file.
$mtime = $this->getUncachedMTime($temporary_path);
// Move the file to its final place. The mtime of a directory is the time of
// the last file create or delete in the directory. So the moving will
// update the directory mtime. However, this update will very likely not
// show up, because it has a coarse, one second granularity and typical
// moves takes significantly less than that. In the unlucky case the clock
// ticks during the move, we need to keep trying until the mtime we hashed
// on and the updated mtime match.
$previous_mtime = 0;
$i = 0;
while (($mtime = $this->getUncachedMTime($directory)) && ($mtime != $previous_mtime)) {
$previous_mtime = $mtime;
// Reset the file back in the temporary location if this is not the first
// iteration.
if ($i > 0) {
$this->unlink($temporary_path);
$temporary_path = $this->tempnam($this->directory, '.');
rename($full_path, $temporary_path);
// Make sure to not loop infinitely on a hopelessly slow filesystem.
if ($i > 10) {
$this->unlink($temporary_path);
return FALSE;
}
}
$full_path = $this->getFullPath($name, $directory, $mtime);
rename($temporary_path, $full_path);
$i++;
// Move the temporary file into the proper directory. Note that POSIX
// compliant systems as well as modern Windows perform the rename operation
// atomically, i.e. there is no point at which another process attempting to
// access the new path will find it missing.
$directory = $this->getContainingDirectoryFullPath($name);
$this->ensureDirectory($directory);
$full_path = $this->getFullPath($name, $directory, $mtime);
$result = rename($temporary_path, $full_path);
// Finally reset the modification time of the directory to match the one of
// the newly created file. In order to prevent the creation of a file if the
// directory does not exist, ensure that the path terminates with a
// directory separator.
//
// Recall that when subsequently loading the file, the hash is calculated
// based on the file name, the containing mtime, and a the secret string.
// Hence updating the mtime here is comparable to pointing a symbolic link
// at a new target, i.e., the newly created file.
if ($result) {
$result &= touch($directory . '/', $mtime);
}
return TRUE;
return (bool) $result;
}
/**
@ -161,6 +149,44 @@ class MTimeProtectedFastFileStorage extends FileStorage {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
$flags = \FilesystemIterator::CURRENT_AS_FILEINFO;
$flags += \FilesystemIterator::SKIP_DOTS;
foreach ($this->listAll() as $name) {
$directory = $this->getContainingDirectoryFullPath($name);
try {
$dir_iterator = new \FilesystemIterator($directory, $flags);
}
catch (\UnexpectedValueException $e) {
// FilesystemIterator throws an UnexpectedValueException if the
// specified path is not a directory, or if it is not accessible.
continue;
}
$directory_unlink = TRUE;
$directory_mtime = filemtime($directory);
foreach ($dir_iterator as $fileinfo) {
if ($directory_mtime > $fileinfo->getMTime()) {
// Ensure the folder is writable.
@chmod($directory, 0777);
@unlink($fileinfo->getPathName());
}
else {
// The directory still contains valid files.
$directory_unlink = FALSE;
}
}
if ($directory_unlink) {
$this->unlink($name);
}
}
}
/**
* Gets the full path of the containing directory where the file is or should
* be stored.
@ -208,4 +234,5 @@ class MTimeProtectedFastFileStorage extends FileStorage {
} while (file_exists($path));
return $path;
}
}

View file

@ -99,4 +99,11 @@ interface PhpStorageInterface {
*/
public function listAll();
/**
* Performs garbage collection on the storage.
*
* The storage may choose to delete expired or invalidated items.
*/
public function garbageCollection();
}

View file

@ -46,7 +46,7 @@ class ReflectionFactory extends DefaultFactory {
* @param string $plugin_id
* The identifier of the plugin implementation.
* @param mixed $plugin_definition
* The definition associated to the plugin_id.
* The definition associated with the plugin_id.
* @param array $configuration
* An array of configuration that may be passed to the instance.
*

View file

@ -338,14 +338,59 @@ EOD;
* "&lt;", not "<"). Be careful when using this function, as it will revert
* previous sanitization efforts (&lt;script&gt; will become <script>).
*
* This method is not the opposite of Html::escape(). For example, this method
* will convert "&eacute;" to "é", whereas Html::escape() will not convert "é"
* to "&eacute;".
*
* @param string $text
* The text to decode entities in.
*
* @return string
* The input $text, with all HTML entities decoded once.
*
* @see html_entity_decode()
* @see \Drupal\Component\Utility\Html::escape()
*/
public static function decodeEntities($text) {
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
}
/**
* Escapes text by converting special characters to HTML entities.
*
* This method escapes HTML for sanitization purposes by replacing the
* following special characters with their HTML entity equivalents:
* - & (ampersand) becomes &amp;
* - " (double quote) becomes &quot;
* - ' (single quote) becomes &#039;
* - < (less than) becomes &lt;
* - > (greater than) becomes &gt;
* Special characters that have already been escaped will be double-escaped
* (for example, "&lt;" becomes "&amp;lt;"), and invalid UTF-8 encoding
* will be converted to the Unicode replacement character ("<EFBFBD>").
*
* This method is not the opposite of Html::decodeEntities(). For example,
* this method will not encode "é" to "&eacute;", whereas
* Html::decodeEntities() will convert all HTML entities to UTF-8 bytes,
* including "&eacute;" and "&lt;" to "é" and "<".
*
* When constructing @link theme_render render arrays @endlink passing the output of Html::escape() to
* '#markup' is not recommended. Use the '#plain_text' key instead and the
* renderer will autoescape the text.
*
* @param string $text
* The input text.
*
* @return string
* The text with all HTML special characters converted.
*
* @see htmlspecialchars()
* @see \Drupal\Component\Utility\Html::decodeEntities()
*
* @ingroup sanitization
*/
public static function escape($text) {
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}

View file

@ -15,9 +15,9 @@ namespace Drupal\Component\Utility;
* provides a store for known safe strings and methods to manage them
* throughout the page request.
*
* Strings sanitized by self::checkPlain() and self::escape() or
* self::xssFilter() are automatically marked safe, as are markup strings
* created from @link theme_render render arrays @endlink via drupal_render().
* Strings sanitized by self::checkPlain() and self::escape() are automatically
* marked safe, as are markup strings created from @link theme_render render
* arrays @endlink via drupal_render().
*
* This class should be limited to internal use only. Module developers should
* instead use the appropriate
@ -35,57 +35,28 @@ class SafeMarkup {
/**
* The list of safe strings.
*
* Strings in this list are marked as secure for the entire page render, not
* just the code or element that set it. Therefore, only valid HTML should be
* marked as safe (never partial markup). For example, you should never mark
* string such as '<' or '<script>' safe.
*
* @var array
*/
protected static $safeStrings = array();
/**
* Adds a string to a list of strings marked as secure.
*
* This method is for internal use. Do not use it to prevent escaping of
* markup; instead, use the appropriate
* @link sanitization sanitization functions @endlink or the
* @link theme_render theme and render systems @endlink so that the output
* can be themed, escaped, and altered properly.
*
* This marks strings as secure for the entire page render, not just the code
* or element that set it. Therefore, only valid HTML should be
* marked as safe (never partial markup). For example, you should never do:
* @code
* SafeMarkup::set('<');
* @endcode
* or:
* @code
* SafeMarkup::set('<script>');
* @endcode
*
* @param string $string
* The content to be marked as secure.
* @param string $strategy
* The escaping strategy used for this string. Two values are supported
* by default:
* - 'html': (default) The string is safe for use in HTML code.
* - 'all': The string is safe for all use cases.
* See the
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
* for more information on escaping strategies in Twig.
*
* @return string
* The input string that was marked as safe.
*/
public static function set($string, $strategy = 'html') {
$string = (string) $string;
static::$safeStrings[$string][$strategy] = TRUE;
return $string;
}
/**
* Checks if a string is safe to output.
*
* @param string|\Drupal\Component\Utility\SafeStringInterface $string
* The content to be checked.
* @param string $strategy
* The escaping strategy. See self::set(). Defaults to 'html'.
* The escaping strategy. Defaults to 'html'. Two escaping strategies are
* supported by default:
* - 'html': (default) The string is safe for use in HTML code.
* - 'all': The string is safe for all use cases.
* See the
* @link http://twig.sensiolabs.org/doc/filters/escape.html Twig escape documentation @endlink
* for more information on escaping strategies in Twig.
*
* @return bool
* TRUE if the string has been marked secure, FALSE otherwise.
@ -100,14 +71,34 @@ class SafeMarkup {
/**
* Adds previously retrieved known safe strings to the safe string list.
*
* This is useful for the batch and form APIs, where it is important to
* preserve the safe markup state across page requests. The strings will be
* added to any safe strings already marked for the current request.
* This method is for internal use. Do not use it to prevent escaping of
* markup; instead, use the appropriate
* @link sanitization sanitization functions @endlink or the
* @link theme_render theme and render systems @endlink so that the output
* can be themed, escaped, and altered properly.
*
* This marks strings as secure for the entire page render, not just the code
* or element that set it. Therefore, only valid HTML should be
* marked as safe (never partial markup). For example, you should never do:
* @code
* SafeMarkup::setMultiple(['<' => ['html' => TRUE]]);
* @endcode
* or:
* @code
* SafeMarkup::setMultiple(['<script>' => ['all' => TRUE]]);
* @endcode
* @param array $safe_strings
* A list of safe strings as previously retrieved by self::getAll().
* Every string in this list will be represented by a multidimensional
* array in which the keys are the string and the escaping strategy used for
* this string, and in which the value is the boolean TRUE.
* See self::isSafe() for the list of supported escaping strategies.
*
* @throws \UnexpectedValueException
*
* @internal This is called by FormCache, StringTranslation and the Batch API.
* It should not be used anywhere else.
*/
public static function setMultiple(array $safe_strings) {
foreach ($safe_strings as $string => $strategies) {
@ -124,98 +115,6 @@ class SafeMarkup {
}
}
/**
* Encodes special characters in a plain-text string for display as HTML.
*
* @param string $string
* A string.
*
* @return string
* The escaped string. If $string was already set as safe with
* self::set(), it won't be escaped again.
*/
public static function escape($string) {
return static::isSafe($string) ? $string : static::checkPlain($string);
}
/**
* Applies a very permissive XSS/HTML filter for admin-only use.
*
* Note: This method only filters if $string is not marked safe already.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 8.0.0. If the
* string used as part of a @link theme_render render array @endlink use
* #markup to allow the render system to filter automatically. If the result
* is not being used directly in the rendering system (for example, when its
* result is being combined with other strings before rendering), use
* Xss::filterAdmin(). Otherwise, use SafeMarkup::xssFilter() and the tag
* list provided by Xss::getAdminTagList() instead. In the rare instance
* that the caller does not want to filter strings that are marked safe
* already, it needs to check SafeMarkup::isSafe() itself.
*
* @see \Drupal\Component\Utility\SafeMarkup::xssFilter()
* @see \Drupal\Component\Utility\SafeMarkup::isSafe()
* @see \Drupal\Component\Utility\Xss::filterAdmin()
* @see \Drupal\Component\Utility\Xss::getAdminTagList()
*/
public static function checkAdminXss($string) {
return static::isSafe($string) ? $string : static::xssFilter($string, Xss::getAdminTagList());
}
/**
* Filters HTML for XSS vulnerabilities and marks the result as safe.
*
* Calling this method unnecessarily will result in bloating the safe string
* list and increases the chance of unintended side effects.
*
* If Twig receives a value that is not marked as safe then it will
* automatically encode special characters in a plain-text string for display
* as HTML. Therefore, SafeMarkup::xssFilter() should only be used when the
* string might contain HTML that needs to be rendered properly by the
* browser.
*
* If you need to filter for admin use, like Xss::filterAdmin(), then:
* - If the string is used as part of a @link theme_render render array @endlink,
* use #markup to allow the render system to filter by the admin tag list
* automatically.
* - Otherwise, use the SafeMarkup::xssFilter() with tag list provided by
* Xss::getAdminTagList() instead.
*
* This method should only be used instead of Xss::filter() when the result is
* being added to a render array that is constructed before rendering begins.
*
* In the rare instance that the caller does not want to filter strings that
* are marked safe already, it needs to check SafeMarkup::isSafe() itself.
*
* @param $string
* The string with raw HTML in it. It will be stripped of everything that
* can cause an XSS attack. The string provided will always be escaped
* regardless of whether the string is already marked as safe.
* @param array $html_tags
* (optional) An array of HTML tags. If omitted, it uses the default tag
* list defined by \Drupal\Component\Utility\Xss::filter().
*
* @return string
* An XSS-safe version of $string, or an empty string if $string is not
* valid UTF-8. The string is marked as safe.
*
* @ingroup sanitization
*
* @see \Drupal\Component\Utility\Xss::filter()
* @see \Drupal\Component\Utility\Xss::filterAdmin()
* @see \Drupal\Component\Utility\Xss::getAdminTagList()
* @see \Drupal\Component\Utility\SafeMarkup::isSafe()
*/
public static function xssFilter($string, $html_tags = NULL) {
if (is_null($html_tags)) {
$string = Xss::filter($string);
}
else {
$string = Xss::filter($string, $html_tags);
}
return static::set($string);
}
/**
* Gets all strings currently marked as safe.
*
@ -244,10 +143,17 @@ class SafeMarkup {
*
* @ingroup sanitization
*
* @deprecated Will be removed before Drupal 8.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 drupal_validate_utf8()
*/
public static function checkPlain($text) {
$string = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
$string = Html::escape($text);
static::$safeStrings[$string]['html'] = TRUE;
return $string;
}
@ -275,8 +181,8 @@ class SafeMarkup {
* formatting depends on the first character of the key:
* - @variable: Escaped to HTML using self::escape(). Use this as the
* default choice for anything displayed on a page on the site.
* - %variable: Escaped to HTML and formatted using self::placeholder(),
* which makes the following HTML code:
* - %variable: Escaped to HTML wrapped in <em> tags, which makes the
* following HTML code:
* @code
* <em class="placeholder">text output here.</em>
* @endcode
@ -296,7 +202,7 @@ class SafeMarkup {
*
* @see t()
*/
public static function format($string, array $args = array()) {
public static function format($string, array $args) {
$safe = TRUE;
// Transform arguments before inserting them.
@ -304,13 +210,18 @@ class SafeMarkup {
switch ($key[0]) {
case '@':
// Escaped only.
$args[$key] = static::escape($value);
if (!SafeMarkup::isSafe($value)) {
$args[$key] = Html::escape($value);
}
break;
case '%':
default:
// Escaped and placeholder.
$args[$key] = static::placeholder($value);
if (!SafeMarkup::isSafe($value)) {
$value = Html::escape($value);
}
$args[$key] = '<em class="placeholder">' . $value . '</em>';
break;
case '!':
@ -329,68 +240,4 @@ class SafeMarkup {
return $output;
}
/**
* Formats text for emphasized display in a placeholder inside a sentence.
*
* Used automatically by self::format().
*
* @param string $text
* The text to format (plain-text).
*
* @return string
* The formatted text (html).
*/
public static function placeholder($text) {
$string = '<em class="placeholder">' . static::escape($text) . '</em>';
static::$safeStrings[$string]['html'] = TRUE;
return $string;
}
/**
* Replaces all occurrences of the search string with the replacement string.
*
* Functions identically to str_replace(), but marks the returned output as
* safe if all the inputs and the subject have also been marked as safe.
*
* @param string|array $search
* The value being searched for. An array may be used to designate multiple
* values to search for.
* @param string|array $replace
* The replacement value that replaces found search values. An array may be
* used to designate multiple replacements.
* @param string $subject
* The string or array being searched and replaced on.
*
* @return string
* The passed subject with replaced values.
*/
public static function replace($search, $replace, $subject) {
$output = str_replace($search, $replace, $subject);
// If any replacement is unsafe, then the output is also unsafe, so just
// return the output.
if (!is_array($replace)) {
if (!SafeMarkup::isSafe($replace)) {
return $output;
}
}
else {
foreach ($replace as $replacement) {
if (!SafeMarkup::isSafe($replacement)) {
return $output;
}
}
}
// If the subject is unsafe, then the output is as well, so return it.
if (!SafeMarkup::isSafe($subject)) {
return $output;
}
else {
// If we have reached this point, then all replacements were safe. If the
// subject was also safe, then mark the entire output as safe.
return SafeMarkup::set($output);
}
}
}

View file

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

View file

@ -0,0 +1,80 @@
<?php
/**
* @file
* Contains \Drupal\Component\Utility\SafeStringTrait.
*/
namespace Drupal\Component\Utility;
/**
* Implements SafeStringInterface and Countable for rendered objects.
*
* @see \Drupal\Component\Utility\SafeStringInterface
*/
trait SafeStringTrait {
/**
* The safe string.
*
* @var string
*/
protected $string;
/**
* Creates a SafeString object if necessary.
*
* If $string is equal to a blank string then it is not necessary to create a
* SafeString object. If $string is an object that implements
* SafeStringInterface it is returned unchanged.
*
* @param mixed $string
* The string to mark as safe. This value will be cast to a string.
*
* @return string|\Drupal\Component\Utility\SafeStringInterface
* A safe string.
*/
public static function create($string) {
if ($string instanceof SafeStringInterface) {
return $string;
}
$string = (string) $string;
if ($string === '') {
return '';
}
$safe_string = new static();
$safe_string->string = $string;
return $safe_string;
}
/**
* Returns the string version of the SafeString object.
*
* @return string
* The safe string content.
*/
public function __toString() {
return $this->string;
}
/**
* Returns the string length.
*
* @return int
* The length of the string.
*/
public function count() {
return Unicode::strlen($this->string);
}
/**
* Returns a representation of the object for use in JSON serialization.
*
* @return string
* The safe string content.
*/
public function jsonSerialize() {
return $this->__toString();
}
}

View file

@ -508,7 +508,7 @@ EOD;
* @param bool $add_ellipsis
* If TRUE, add '...' to the end of the truncated string (defaults to
* FALSE). The string length will still fall within $max_length.
* @param bool $min_wordsafe_length
* @param int $min_wordsafe_length
* If $wordsafe is TRUE, the minimum acceptable length for truncation (before
* adding an ellipsis, if $add_ellipsis is TRUE). Has no effect if $wordsafe
* is FALSE. This can be used to prevent having a very short resulting string

View file

@ -272,7 +272,7 @@ class UrlHelper {
// Get the plain text representation of the attribute value (i.e. its
// meaning).
$string = Html::decodeEntities($string);
return SafeMarkup::checkPlain(static::stripDangerousProtocols($string));
return Html::escape(static::stripDangerousProtocols($string));
}
/**
@ -300,10 +300,11 @@ class UrlHelper {
*
* This function must be called for all URIs within user-entered input prior
* to being output to an HTML attribute value. It is often called as part of
* check_url() or Drupal\Component\Utility\Xss::filter(), but those functions
* return an HTML-encoded string, so this function can be called independently
* when the output needs to be a plain-text string for passing to functions
* that will call \Drupal\Component\Utility\SafeMarkup::checkPlain() separately.
* \Drupal\Component\Utility\UrlHelper::filterBadProtocol() or
* \Drupal\Component\Utility\Xss::filter(), but those functions return an
* HTML-encoded string, so this function can be called independently when the
* output needs to be a plain-text string for passing to functions that will
* call \Drupal\Component\Utility\SafeMarkup::checkPlain() separately.
*
* @param string $uri
* A plain-text URI that might contain dangerous protocols.

View file

@ -46,9 +46,9 @@ class Variable {
elseif (is_string($var)) {
if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) {
// If the string contains a line break or a single quote, use the
// double quote export mode. Encode backslash and double quotes and
// transform some common control characters.
$var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\n', '\r', '\t'), $var);
// double quote export mode. Encode backslash, dollar symbols, and
// double quotes and transform some common control characters.
$var = str_replace(array('\\', '$', '"', "\n", "\r", "\t"), array('\\\\', '\$', '\"', '\n', '\r', '\t'), $var);
$output = '"' . $var . '"';
}
else {

View file

@ -15,7 +15,7 @@ namespace Drupal\Component\Utility;
class Xss {
/**
* The list of html tags allowed by filterAdmin().
* The list of HTML tags allowed by filterAdmin().
*
* @var array
*
@ -23,19 +23,21 @@ class Xss {
*/
protected static $adminTags = array('a', 'abbr', 'acronym', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'mark', 'menu', 'meter', 'nav', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr');
/**
* The default list of HTML tags allowed by filter().
*
* @var array
*
* @see \Drupal\Component\Utility\Xss::filter()
*/
protected static $htmlTags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd');
/**
* Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
*
* Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
* For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
*
* This method is preferred to
* \Drupal\Component\Utility\SafeMarkup::xssFilter() when the result is not
* being used directly in the rendering system (for example, when its result
* is being combined with other strings before rendering). This avoids
* bloating the safe string list with partial strings if the whole result will
* be marked safe.
*
* This code does four things:
* - Removes characters and constructs that can trick browsers.
* - Makes sure all HTML entities are well-formed.
@ -54,11 +56,13 @@ class Xss {
* valid UTF-8.
*
* @see \Drupal\Component\Utility\Unicode::validateUtf8()
* @see \Drupal\Component\Utility\SafeMarkup::xssFilter()
*
* @ingroup sanitization
*/
public static function filter($string, $html_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
public static function filter($string, array $html_tags = NULL) {
if (is_null($html_tags)) {
$html_tags = static::$htmlTags;
}
// Only operate on valid UTF-8 strings. This is necessary to prevent cross
// site scripting issues on Internet Explorer 6.
if (!Unicode::validateUtf8($string)) {
@ -84,10 +88,7 @@ class Xss {
$splitter = function ($matches) use ($html_tags, $class) {
return $class::split($matches[1], $html_tags, $class);
};
// Strip any tags that are not in the whitelist, then mark the text as safe
// for output. All other known XSS vectors have been filtered out by this
// point and any HTML tags remaining will have been deliberately allowed, so
// it is acceptable to call SafeMarkup::set() on the resultant string.
// Strip any tags that are not in the whitelist.
return preg_replace_callback('%
(
<(?=[^a-zA-Z!/]) # a lone <
@ -108,13 +109,6 @@ class Xss {
* is desired (so \Drupal\Component\Utility\SafeMarkup::checkPlain() is
* not acceptable).
*
* This method is preferred to
* \Drupal\Component\Utility\SafeMarkup::xssFilter() when the result is
* not being used directly in the rendering system (for example, when its
* result is being combined with other strings before rendering). This avoids
* bloating the safe string list with partial strings if the whole result will
* be marked safe.
*
* Allows all tags that can be used inside an HTML body, save
* for scripts and styles.
*
@ -126,7 +120,6 @@ class Xss {
*
* @ingroup sanitization
*
* @see \Drupal\Component\Utility\SafeMarkup::xssFilter()
* @see \Drupal\Component\Utility\Xss::getAdminTagList()
*
*/
@ -338,13 +331,22 @@ class Xss {
}
/**
* Gets the list of html tags allowed by Xss::filterAdmin().
* Gets the list of HTML tags allowed by Xss::filterAdmin().
*
* @return array
* The list of html tags allowed by filterAdmin().
* The list of HTML tags allowed by filterAdmin().
*/
public static function getAdminTagList() {
return static::$adminTags;
}
/**
* Gets the standard list of HTML tags allowed by Xss::filter().
*
* @return array
* The list of HTML tags allowed by Xss::filter().
*/
public static function getHtmlTagList() {
return static::$htmlTags;
}
}

View file

@ -8,7 +8,7 @@
namespace Drupal\Component\Uuid;
/**
* Interface that defines a UUID backend.
* Interface for generating UUIDs.
*/
interface UuidInterface {

View file

@ -8,6 +8,8 @@ namespace Drupal\Core\Access;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use Drupal\Core\Config\ConfigBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
@ -25,47 +27,10 @@ use Drupal\Core\Session\AccountInterface;
*
* When using ::orIf() and ::andIf(), cacheability metadata will be merged
* accordingly as well.
*
* @todo Use RefinableCacheableDependencyInterface and the corresponding trait in
* https://www.drupal.org/node/2526326.
*/
abstract class AccessResult implements AccessResultInterface, CacheableDependencyInterface {
abstract class AccessResult implements AccessResultInterface, RefinableCacheableDependencyInterface {
/**
* The cache context IDs (to vary a cache item ID based on active contexts).
*
* @see \Drupal\Core\Cache\Context\CacheContextInterface
* @see \Drupal\Core\Cache\Context\CacheContextsManager::convertTokensToKeys()
*
* @var string[]
*/
protected $contexts;
/**
* The cache tags.
*
* @var array
*/
protected $tags;
/**
* The maximum caching time in seconds.
*
* @var int
*/
protected $maxAge;
/**
* Constructs a new AccessResult object.
*/
public function __construct() {
$this->resetCacheContexts()
->resetCacheTags()
// Max-age must be non-zero for an access result to be cacheable.
// Typically, cache items are invalidated via associated cache tags, not
// via a maximum age.
->setCacheMaxAge(Cache::PERMANENT);
}
use RefinableCacheableDependencyTrait;
/**
* Creates an AccessResultInterface object with isNeutral() === TRUE.
@ -215,35 +180,21 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* {@inheritdoc}
*/
public function getCacheContexts() {
sort($this->contexts);
return $this->contexts;
return $this->cacheContexts;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return $this->tags;
return $this->cacheTags;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return $this->maxAge;
}
/**
* Adds cache contexts associated with the access result.
*
* @param string[] $contexts
* An array of cache context IDs, used to generate a cache ID.
*
* @return $this
*/
public function addCacheContexts(array $contexts) {
$this->contexts = array_unique(array_merge($this->contexts, $contexts));
return $this;
return $this->cacheMaxAge;
}
/**
@ -252,20 +203,7 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* @return $this
*/
public function resetCacheContexts() {
$this->contexts = array();
return $this;
}
/**
* Adds cache tags associated with the access result.
*
* @param array $tags
* An array of cache tags.
*
* @return $this
*/
public function addCacheTags(array $tags) {
$this->tags = Cache::mergeTags($this->tags, $tags);
$this->cacheContexts = [];
return $this;
}
@ -275,7 +213,7 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* @return $this
*/
public function resetCacheTags() {
$this->tags = array();
$this->cacheTags = [];
return $this;
}
@ -288,7 +226,7 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* @return $this
*/
public function setCacheMaxAge($max_age) {
$this->maxAge = $max_age;
$this->cacheMaxAge = $max_age;
return $this;
}
@ -342,28 +280,6 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
return $this->addCacheableDependency($configuration);
}
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|object $other_object
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*/
public function addCacheableDependency($other_object) {
if ($other_object instanceof CacheableDependencyInterface) {
$this->contexts = Cache::mergeContexts($this->contexts, $other_object->getCacheContexts());
$this->tags = Cache::mergeTags($this->tags, $other_object->getCacheTags());
$this->maxAge = Cache::mergeMaxAges($this->maxAge, $other_object->getCacheMaxAge());
}
else {
$this->maxAge = 0;
}
return $this;
}
/**
* {@inheritdoc}
*/
@ -452,12 +368,19 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
/**
* 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
* "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000
* as the end result.
*
* @param \Drupal\Core\Access\AccessResultInterface $other
* The other access result, whose cacheability (if any) to inherit.
*
* @return $this
*/
public function inheritCacheability(AccessResultInterface $other) {
$this->addCacheableDependency($other);
if ($other instanceof CacheableDependencyInterface) {
if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) {
$this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge()));
@ -465,14 +388,6 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
else {
$this->setCacheMaxAge($other->getCacheMaxAge());
}
$this->addCacheContexts($other->getCacheContexts());
$this->addCacheTags($other->getCacheTags());
}
// If any of the access results don't provide cacheability metadata, then
// we cannot cache the combined access result, for we may not make
// assumptions.
else {
$this->setCacheMaxAge(0);
}
return $this;
}

View file

@ -164,15 +164,15 @@ class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
$resource_commands = array();
if ($css_assets) {
$css_render_array = $this->cssCollectionRenderer->render($css_assets);
$resource_commands[] = new AddCssCommand((string) $this->renderer->renderPlain($css_render_array));
$resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
}
if ($js_assets_header) {
$js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
$resource_commands[] = new PrependCommand('head', (string) $this->renderer->renderPlain($js_header_render_array));
$resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
}
if ($js_assets_footer) {
$js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
$resource_commands[] = new AppendCommand('body', (string) $this->renderer->renderPlain($js_footer_render_array));
$resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
}
foreach (array_reverse($resource_commands) as $resource_command) {
$response->addCommand($resource_command, TRUE);

View file

@ -29,7 +29,7 @@ trait CommandWithAttachedAssetsTrait {
* If content is a render array, it may contain attached assets to be
* processed.
*
* @return string
* @return string|\Drupal\Component\Utility\SafeStringInterface
* HTML rendered content.
*/
protected function getRenderedContent() {
@ -37,10 +37,10 @@ trait CommandWithAttachedAssetsTrait {
if (is_array($this->content)) {
$html = \Drupal::service('renderer')->renderRoot($this->content);
$this->attachedAssets = AttachedAssets::createFromRenderArray($this->content);
return (string) $html;
return $html;
}
else {
return (string) $this->content;
return $this->content;
}
}

View file

@ -127,7 +127,6 @@ class AssetResolver implements AssetResolverInterface {
'type' => 'file',
'group' => CSS_AGGREGATE_DEFAULT,
'weight' => 0,
'every_page' => FALSE,
'media' => 'all',
'preprocess' => TRUE,
'browsers' => [],
@ -221,7 +220,7 @@ class AssetResolver implements AssetResolverInterface {
// Add the theme name to the cache key since themes may implement
// hook_js_alter(). Additionally add the current language to support
// translation of JavaScript files.
$cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($assets));
$cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($assets)) . (int) $optimize;
if ($cached = $this->cache->get($cid)) {
list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;
@ -231,7 +230,6 @@ class AssetResolver implements AssetResolverInterface {
$default_options = [
'type' => 'file',
'group' => JS_DEFAULT,
'every_page' => FALSE,
'weight' => 0,
'cache' => TRUE,
'preprocess' => TRUE,
@ -338,7 +336,6 @@ class AssetResolver implements AssetResolverInterface {
$settings_as_inline_javascript = [
'type' => 'setting',
'group' => JS_SETTING,
'every_page' => TRUE,
'weight' => 0,
'browsers' => [],
'data' => $settings,
@ -384,16 +381,6 @@ class AssetResolver implements AssetResolverInterface {
elseif ($a['group'] > $b['group']) {
return 1;
}
// Within a group, order all infrequently needed, page-specific files after
// common files needed throughout the website. Separating this way allows
// for the aggregate file generated for all of the common files to be reused
// across a site visit without being cut by a page using a less common file.
elseif ($a['every_page'] && !$b['every_page']) {
return -1;
}
elseif (!$a['every_page'] && $b['every_page']) {
return 1;
}
// Finally, order by weight.
elseif ($a['weight'] < $b['weight']) {
return -1;

View file

@ -58,9 +58,8 @@ class CssCollectionGrouper implements AssetCollectionGrouperInterface {
case 'file':
// Group file items if their 'preprocess' flag is TRUE.
// Help ensure maximum reuse of aggregate files by only grouping
// together items that share the same 'group' value and 'every_page'
// flag.
$group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE;
// together items that share the same 'group' value.
$group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['media'], $item['browsers']) : FALSE;
break;
case 'inline':

View file

@ -7,7 +7,7 @@
namespace Drupal\Core\Asset;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\State\StateInterface;
/**
@ -103,7 +103,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
// For filthy IE hack.
$current_ie_group_keys = NULL;
$get_ie_group_key = function ($css_asset) {
return array($css_asset['type'], $css_asset['preprocess'], $css_asset['group'], $css_asset['every_page'], $css_asset['media'], $css_asset['browsers']);
return array($css_asset['type'], $css_asset['preprocess'], $css_asset['group'], $css_asset['media'], $css_asset['browsers']);
};
// Loop through all CSS assets, by key, to allow for the special IE
@ -123,9 +123,9 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
// LINK tag.
// - file CSS assets that can be aggregated (and possibly have been):
// in this case, figure out which subsequent file CSS assets share
// the same key properties ('group', 'every_page', 'media' and
// 'browsers') and output this group into as few STYLE tags as
// possible (a STYLE tag may contain only 31 @import statements).
// the same key properties ('group', 'media' and 'browsers') and
// output this group into as few STYLE tags as possible (a STYLE
// tag may contain only 31 @import statements).
case 'file':
// The dummy query string needs to be added to the URL to control
// browser-caching.
@ -159,7 +159,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
$import = array();
// Start with the current CSS asset, iterate over subsequent CSS
// assets and find which ones have the same 'type', 'group',
// 'every_page', 'preprocess', 'media' and 'browsers' properties.
// 'preprocess', 'media' and 'browsers' properties.
$j = $i;
$next_css_asset = $css_asset;
$current_ie_group_key = $get_ie_group_key($css_asset);
@ -168,7 +168,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface {
// control browser-caching. IE7 does not support a media type on
// the @import statement, so we instead specify the media for
// the group on the STYLE tag.
$import[] = '@import url("' . SafeMarkup::checkPlain(file_create_url($next_css_asset['data']) . '?' . $query_string) . '");';
$import[] = '@import url("' . Html::escape(file_create_url($next_css_asset['data']) . '?' . $query_string) . '");';
// Move the outer for loop skip the next item, since we
// processed it here.
$i = $j;

View file

@ -45,9 +45,8 @@ class JsCollectionGrouper implements AssetCollectionGrouperInterface {
case 'file':
// Group file items if their 'preprocess' flag is TRUE.
// Help ensure maximum reuse of aggregate files by only grouping
// together items that share the same 'group' value and 'every_page'
// flag.
$group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['browsers']) : FALSE;
// together items that share the same 'group' value.
$group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['browsers']) : FALSE;
break;
case 'external':

View file

@ -51,12 +51,6 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface {
// query-string instead, to enforce reload on every page request.
$default_query_string = $this->state->get('system.css_js_query_string') ?: '0';
// For inline JavaScript to validate as XHTML, all JavaScript containing
// XHTML needs to be wrapped in CDATA. To make that backwards compatible
// with HTML 4, we need to comment out the CDATA-tag.
$embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
$embed_suffix = "\n//--><!]]>\n";
// Defaults for each SCRIPT element.
$element_defaults = array(
'#type' => 'html_tag',
@ -73,9 +67,13 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface {
// Element properties that depend on item type.
switch ($js_asset['type']) {
case 'setting':
$element['#value_prefix'] = $embed_prefix;
$element['#value'] = 'var drupalSettings = ' . Json::encode($js_asset['data']) . ";";
$element['#value_suffix'] = $embed_suffix;
$element['#attributes'] = array(
// This type attribute prevents this from being parsed as an
// inline script.
'type' => 'application/json',
'data-drupal-selector' => 'drupal-settings-json',
);
$element['#value'] = Json::encode($js_asset['data']);
break;
case 'file':

View file

@ -8,7 +8,6 @@
namespace Drupal\Core\Block;
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Form\FormStateInterface;
@ -166,7 +165,7 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
$form['admin_label'] = array(
'#type' => 'item',
'#title' => $this->t('Block description'),
'#markup' => SafeMarkup::checkPlain($definition['admin_label']),
'#plain_text' => $definition['admin_label'],
);
$form['label'] = array(
'#type' => 'textfield',

View file

@ -63,6 +63,11 @@ interface BlockPluginInterface extends ConfigurablePluginInterface, PluginFormIn
/**
* Builds and returns the renderable array for this block plugin.
*
* If a block should not be rendered because it has no content, then this
* method must also ensure to return no content: it must then only return an
* empty array, or an empty array with #cache set (with cacheability metadata
* indicating the circumstances for it being empty).
*
* @return array
* A renderable array representing the content of the block.
*

View file

@ -0,0 +1,71 @@
<?php
/**
* @file
* Contains \Drupal\Core\Breadcrumb\Breadcrumb.
*/
namespace Drupal\Core\Breadcrumb;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Link;
/**
* Used to return generated breadcrumbs with associated cacheability metadata.
*
* @todo implement RenderableInterface once https://www.drupal.org/node/2529560 lands.
*/
class Breadcrumb extends CacheableMetadata {
/**
* An ordered list of links for the breadcrumb.
*
* @var \Drupal\Core\Link[]
*/
protected $links = [];
/**
* Gets the breadcrumb links.
*
* @return \Drupal\Core\Link[]
*/
public function getLinks() {
return $this->links;
}
/**
* Sets the breadcrumb links.
*
* @param \Drupal\Core\Link[] $links
* The breadcrumb links.
*
* @return $this
*
* @throws \LogicException
* Thrown when setting breadcrumb links after they've already been set.
*/
public function setLinks(array $links) {
if (!empty($this->links)) {
throw new \LogicException('Once breadcrumb links are set, only additional breadcrumb links can be added.');
}
$this->links = $links;
return $this;
}
/**
* Appends a link to the end of the ordered list of breadcrumb links.
*
* @param \Drupal\Core\Link $link
* The link appended to the breadcrumb.
*
* @return $this
*/
public function addLink(Link $link) {
$this->links[] = $link;
return $this;
}
}

View file

@ -32,9 +32,8 @@ interface BreadcrumbBuilderInterface {
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*
* @return \Drupal\Core\Link[]
* An array of links for the breadcrumb. Returning an empty array will
* suppress all breadcrumbs.
* @return \Drupal\Core\Breadcrumb\Breadcrumb
* A breadcrumb.
*/
public function build(RouteMatchInterface $route_match);

View file

@ -75,7 +75,7 @@ class BreadcrumbManager implements ChainBreadcrumbBuilderInterface {
* {@inheritdoc}
*/
public function build(RouteMatchInterface $route_match) {
$breadcrumb = array();
$breadcrumb = new Breadcrumb();
$context = array('builder' => NULL);
// Call the build method of registered breadcrumb builders,
// until one of them returns an array.
@ -85,11 +85,9 @@ class BreadcrumbManager implements ChainBreadcrumbBuilderInterface {
continue;
}
$build = $builder->build($route_match);
$breadcrumb = $builder->build($route_match);
if (is_array($build)) {
// The builder returned an array of breadcrumb links.
$breadcrumb = $build;
if ($breadcrumb instanceof Breadcrumb) {
$context['builder'] = $builder;
break;
}
@ -99,7 +97,7 @@ class BreadcrumbManager implements ChainBreadcrumbBuilderInterface {
}
// Allow modules to alter the breadcrumb.
$this->moduleHandler->alter('system_breadcrumb', $breadcrumb, $route_match, $context);
// Fall back to an empty breadcrumb.
return $breadcrumb;
}

View file

@ -20,6 +20,8 @@ namespace Drupal\Core\Cache;
* to ensure fast retrieval on the next request. On cache sets and deletes, both
* backends will be invoked to ensure consistency.
*
* @see \Drupal\Core\Cache\ChainedFastBackend
*
* @ingroup cache
*/

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheableResponse.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* A JsonResponse that contains and can expose cacheability metadata.
*
* Supports Drupal's caching concepts: cache tags for invalidation and cache
* contexts for variations.
*
* @see \Drupal\Core\Cache\Cache
* @see \Drupal\Core\Cache\CacheableMetadata
* @see \Drupal\Core\Cache\CacheableResponseTrait
*/
class CacheableJsonResponse extends JsonResponse implements CacheableResponseInterface {
use CacheableResponseTrait;
}

View file

@ -11,50 +11,16 @@ namespace Drupal\Core\Cache;
*
* @ingroup cache
*
* @todo Use RefinableCacheableDependencyInterface and the corresponding trait in
* https://www.drupal.org/node/2526326.
*/
class CacheableMetadata implements CacheableDependencyInterface {
class CacheableMetadata implements RefinableCacheableDependencyInterface {
/**
* Cache contexts.
*
* @var string[]
*/
protected $contexts = [];
/**
* Cache tags.
*
* @var string[]
*/
protected $tags = [];
/**
* Cache max-age.
*
* @var int
*/
protected $maxAge = Cache::PERMANENT;
use RefinableCacheableDependencyTrait;
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return $this->tags;
}
/**
* Adds cache tags.
*
* @param string[] $cache_tags
* The cache tags to be added.
*
* @return $this
*/
public function addCacheTags(array $cache_tags) {
$this->tags = Cache::mergeTags($this->tags, $cache_tags);
return $this;
return $this->cacheTags;
}
/**
@ -66,7 +32,7 @@ class CacheableMetadata implements CacheableDependencyInterface {
* @return $this
*/
public function setCacheTags(array $cache_tags) {
$this->tags = $cache_tags;
$this->cacheTags = $cache_tags;
return $this;
}
@ -74,20 +40,7 @@ class CacheableMetadata implements CacheableDependencyInterface {
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->contexts;
}
/**
* Adds cache contexts.
*
* @param string[] $cache_contexts
* The cache contexts to be added.
*
* @return $this
*/
public function addCacheContexts(array $cache_contexts) {
$this->contexts = Cache::mergeContexts($this->contexts, $cache_contexts);
return $this;
return $this->cacheContexts;
}
/**
@ -99,7 +52,7 @@ class CacheableMetadata implements CacheableDependencyInterface {
* @return $this
*/
public function setCacheContexts(array $cache_contexts) {
$this->contexts = $cache_contexts;
$this->cacheContexts = $cache_contexts;
return $this;
}
@ -107,7 +60,7 @@ class CacheableMetadata implements CacheableDependencyInterface {
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return $this->maxAge;
return $this->cacheMaxAge;
}
/**
@ -128,36 +81,7 @@ class CacheableMetadata implements CacheableDependencyInterface {
throw new \InvalidArgumentException('$max_age must be an integer');
}
$this->maxAge = $max_age;
return $this;
}
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $other_object
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*/
public function addCacheableDependency($other_object) {
if ($other_object instanceof CacheableDependencyInterface) {
$this->addCacheTags($other_object->getCacheTags());
$this->addCacheContexts($other_object->getCacheContexts());
if ($this->maxAge === Cache::PERMANENT) {
$this->maxAge = $other_object->getCacheMaxAge();
}
elseif (($max_age = $other_object->getCacheMaxAge()) && $max_age !== Cache::PERMANENT) {
$this->maxAge = Cache::mergeMaxAges($this->maxAge, $max_age);
}
}
else {
// Not a cacheable dependency, this can not be cached.
$this->maxAge = 0;
}
$this->cacheMaxAge = $max_age;
return $this;
}
@ -175,34 +99,34 @@ class CacheableMetadata implements CacheableDependencyInterface {
// This is called many times per request, so avoid merging unless absolutely
// necessary.
if (empty($this->contexts)) {
$result->contexts = $other->contexts;
if (empty($this->cacheContexts)) {
$result->cacheContexts = $other->cacheContexts;
}
elseif (empty($other->contexts)) {
$result->contexts = $this->contexts;
elseif (empty($other->cacheContexts)) {
$result->cacheContexts = $this->cacheContexts;
}
else {
$result->contexts = Cache::mergeContexts($this->contexts, $other->contexts);
$result->cacheContexts = Cache::mergeContexts($this->cacheContexts, $other->cacheContexts);
}
if (empty($this->tags)) {
$result->tags = $other->tags;
if (empty($this->cacheTags)) {
$result->cacheTags = $other->cacheTags;
}
elseif (empty($other->tags)) {
$result->tags = $this->tags;
elseif (empty($other->cacheTags)) {
$result->cacheTags = $this->cacheTags;
}
else {
$result->tags = Cache::mergeTags($this->tags, $other->tags);
$result->cacheTags = Cache::mergeTags($this->cacheTags, $other->cacheTags);
}
if ($this->maxAge === Cache::PERMANENT) {
$result->maxAge = $other->maxAge;
if ($this->cacheMaxAge === Cache::PERMANENT) {
$result->cacheMaxAge = $other->cacheMaxAge;
}
elseif ($other->maxAge === Cache::PERMANENT) {
$result->maxAge = $this->maxAge;
elseif ($other->cacheMaxAge === Cache::PERMANENT) {
$result->cacheMaxAge = $this->cacheMaxAge;
}
else {
$result->maxAge = Cache::mergeMaxAges($this->maxAge, $other->maxAge);
$result->cacheMaxAge = Cache::mergeMaxAges($this->cacheMaxAge, $other->cacheMaxAge);
}
return $result;
}
@ -214,9 +138,9 @@ class CacheableMetadata implements CacheableDependencyInterface {
* A render array.
*/
public function applyTo(array &$build) {
$build['#cache']['contexts'] = $this->contexts;
$build['#cache']['tags'] = $this->tags;
$build['#cache']['max-age'] = $this->maxAge;
$build['#cache']['contexts'] = $this->cacheContexts;
$build['#cache']['tags'] = $this->cacheTags;
$build['#cache']['max-age'] = $this->cacheMaxAge;
}
/**
@ -229,9 +153,9 @@ class CacheableMetadata implements CacheableDependencyInterface {
*/
public static function createFromRenderArray(array $build) {
$meta = new static();
$meta->contexts = (isset($build['#cache']['contexts'])) ? $build['#cache']['contexts'] : [];
$meta->tags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : [];
$meta->maxAge = (isset($build['#cache']['max-age'])) ? $build['#cache']['max-age'] : Cache::PERMANENT;
$meta->cacheContexts = (isset($build['#cache']['contexts'])) ? $build['#cache']['contexts'] : [];
$meta->cacheTags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : [];
$meta->cacheMaxAge = (isset($build['#cache']['max-age'])) ? $build['#cache']['max-age'] : Cache::PERMANENT;
return $meta;
}
@ -249,16 +173,16 @@ class CacheableMetadata implements CacheableDependencyInterface {
public static function createFromObject($object) {
if ($object instanceof CacheableDependencyInterface) {
$meta = new static();
$meta->contexts = $object->getCacheContexts();
$meta->tags = $object->getCacheTags();
$meta->maxAge = $object->getCacheMaxAge();
$meta->cacheContexts = $object->getCacheContexts();
$meta->cacheTags = $object->getCacheTags();
$meta->cacheMaxAge = $object->getCacheMaxAge();
return $meta;
}
// Objects that don't implement CacheableDependencyInterface must be assumed
// to be uncacheable, so set max-age 0.
$meta = new static();
$meta->maxAge = 0;
$meta->cacheMaxAge = 0;
return $meta;
}

View file

@ -39,6 +39,15 @@ namespace Drupal\Core\Cache;
* Because this backend will mark all the cache entries in a bin as out-dated
* for each write to a bin, it is best suited to bins with fewer changes.
*
* Note that this is designed specifically for combining a fast inconsistent
* cache backend with a slower consistent cache back-end. To still function
* correctly, it needs to do a consistency check (see the "last write timestamp"
* logic). This contrasts with \Drupal\Core\Cache\BackendChain, which assumes
* both chained cache backends are consistent, thus a consistency check being
* pointless.
*
* @see \Drupal\Core\Cache\BackendChain
*
* @ingroup cache
*/
class ChainedFastBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface {

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\PathCacheContext.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the PathCacheContext service, for "per URL path" caching.
*
* Cache context ID: 'url.path'.
*
* (This allows for caching relative URLs.)
*
* @see \Symfony\Component\HttpFoundation\Request::getBasePath()
* @see \Symfony\Component\HttpFoundation\Request::getPathInfo()
*/
class PathCacheContext extends RequestStackCacheContextBase implements CacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Path');
}
/**
* {@inheritdoc}
*/
public function getContext() {
$request = $this->requestStack->getCurrentRequest();
return $request->getBasePath() . $request->getPathInfo();
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View file

@ -147,17 +147,26 @@ class DatabaseBackend implements CacheBackendInterface {
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
Cache::validateTags($tags);
$tags = array_unique($tags);
// Sort the cache tags so that they are stored consistently in the database.
sort($tags);
$this->setMultiple([
$cid => [
'data' => $data,
'expire' => $expire,
'tags' => $tags,
],
]);
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
$try_again = FALSE;
try {
// The bin might not yet exist.
$this->doSet($cid, $data, $expire, $tags);
$this->doSetMultiple($items);
}
catch (\Exception $e) {
// If there was an exception, try to create the bins.
@ -169,39 +178,19 @@ class DatabaseBackend implements CacheBackendInterface {
}
// Now that the bin has been created, try again if necessary.
if ($try_again) {
$this->doSet($cid, $data, $expire, $tags);
$this->doSetMultiple($items);
}
}
/**
* Actually set the cache.
* Stores multiple items in the persistent cache.
*
* @param array $items
* An array of cache items, keyed by cid.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::setMultiple()
*/
protected function doSet($cid, $data, $expire, $tags) {
$fields = array(
'created' => round(microtime(TRUE), 3),
'expire' => $expire,
'tags' => implode(' ', $tags),
'checksum' => $this->checksumProvider->getCurrentChecksum($tags),
);
if (!is_string($data)) {
$fields['data'] = serialize($data);
$fields['serialized'] = 1;
}
else {
$fields['data'] = $data;
$fields['serialized'] = 0;
}
$this->connection->merge($this->bin)
->key('cid', $this->normalizeCid($cid))
->fields($fields)
->execute();
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
protected function doSetMultiple(array $items) {
$values = array();
foreach ($items as $cid => $item) {
@ -216,7 +205,7 @@ class DatabaseBackend implements CacheBackendInterface {
sort($item['tags']);
$fields = array(
'cid' => $cid,
'cid' => $this->normalizeCid($cid),
'expire' => $item['expire'],
'created' => round(microtime(TRUE), 3),
'tags' => implode(' ', $item['tags']),
@ -234,34 +223,20 @@ class DatabaseBackend implements CacheBackendInterface {
$values[] = $fields;
}
// Use a transaction so that the database can write the changes in a single
// commit. The transaction is started after calculating the tag checksums
// since that can create a table and this causes an exception when using
// PostgreSQL.
$transaction = $this->connection->startTransaction();
try {
// Delete all items first so we can do one insert. Rather than multiple
// merge queries.
$this->deleteMultiple(array_keys($items));
$query = $this->connection
->insert($this->bin)
->fields(array('cid', 'expire', 'created', 'tags', 'checksum', 'data', 'serialized'));
foreach ($values as $fields) {
// Only pass the values since the order of $fields matches the order of
// the insert fields. This is a performance optimization to avoid
// unnecessary loops within the method.
$query->values(array_values($fields));
}
$query->execute();
}
catch (\Exception $e) {
$transaction->rollback();
// @todo Log something here or just re throw?
throw $e;
// Use an upsert query which is atomic and optimized for multiple-row
// merges.
$query = $this->connection
->upsert($this->bin)
->key('cid')
->fields(array('cid', 'expire', 'created', 'tags', 'checksum', 'data', 'serialized'));
foreach ($values as $fields) {
// Only pass the values since the order of $fields matches the order of
// the insert fields. This is a performance optimization to avoid
// unnecessary loops within the method.
$query->values(array_values($fields));
}
$query->execute();
}
/**

View file

@ -217,4 +217,13 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf
return [];
}
/**
* Reset statically cached variables.
*
* This is only used by tests.
*/
public function reset() {
$this->cache = [];
}
}

View file

@ -44,7 +44,7 @@ trait RefinableCacheableDependencyTrait {
}
else {
// Not a cacheable dependency, this can not be cached.
$this->maxAge = 0;
$this->cacheMaxAge = 0;
}
return $this;
}
@ -53,7 +53,9 @@ trait RefinableCacheableDependencyTrait {
* {@inheritdoc}
*/
public function addCacheContexts(array $cache_contexts) {
$this->cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts);
if ($cache_contexts) {
$this->cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts);
}
return $this;
}
@ -61,7 +63,9 @@ trait RefinableCacheableDependencyTrait {
* {@inheritdoc}
*/
public function addCacheTags(array $cache_tags) {
$this->cacheTags = Cache::mergeTags($this->cacheTags, $cache_tags);
if ($cache_tags) {
$this->cacheTags = Cache::mergeTags($this->cacheTags, $cache_tags);
}
return $this;
}

View file

@ -129,12 +129,9 @@ class DbDumpCommand extends Command {
* An array of table names.
*/
protected function getTables() {
$pattern = $this->connection->tablePrefix() . '%';
$tables = array_values($this->connection->schema()->findTables($pattern));
foreach ($tables as $key => $table) {
// The prefix is removed for the resultant script.
$table = $tables[$key] = str_replace($this->connection->tablePrefix(), '', $table);
$tables = array_values($this->connection->schema()->findTables('%'));
foreach ($tables as $key => $table) {
// Remove any explicitly excluded tables.
foreach ($this->excludeTables as $pattern) {
if (preg_match('/^' . $pattern . '$/', $table)) {
@ -142,6 +139,7 @@ class DbDumpCommand extends Command {
}
}
}
return $tables;
}

View file

@ -53,11 +53,13 @@ class Condition extends Plugin {
public $module;
/**
* An array of contextual data.
* An array of context definitions describing the context used by the plugin.
*
* @var array
* The array is keyed by context names.
*
* @var \Drupal\Core\Annotation\ContextDefinition[]
*/
public $condition = array();
public $context = array();
/**
* The category under which the condition should listed in the UI.

View file

@ -158,6 +158,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
*/
public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = []) {
$profile = $this->drupalGetProfile();
$optional_profile_config = [];
if (!$storage) {
// Search the install profile's optional configuration too.
$storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE);
@ -168,6 +169,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
// Creates a profile storage to search for overrides.
$profile_install_path = $this->drupalGetPath('module', $profile) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
$profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
$optional_profile_config = $profile_storage->listAll();
}
else {
// Profile has not been set yet. For example during the first steps of the
@ -178,7 +180,8 @@ class ConfigInstaller implements ConfigInstallerInterface {
$enabled_extensions = $this->getEnabledExtensions();
$existing_config = $this->getActiveStorages()->listAll();
$list = array_filter($storage->listAll(), function($config_name) use ($existing_config) {
$list = array_unique(array_merge($storage->listAll(), $optional_profile_config));
$list = array_filter($list, function($config_name) use ($existing_config) {
// Only list configuration that:
// - does not already exist
// - is a configuration entity (this also excludes config that has an
@ -188,7 +191,8 @@ class ConfigInstaller implements ConfigInstallerInterface {
$all_config = array_merge($existing_config, $list);
$config_to_create = $storage->readMultiple($list);
// Check to see if the corresponding override storage has any overrides.
// Check to see if the corresponding override storage has any overrides or
// new configuration that can be installed.
if ($profile_storage) {
$config_to_create = $profile_storage->readMultiple($list) + $config_to_create;
}

View file

@ -193,19 +193,23 @@ class FileStorage implements StorageInterface {
* Implements Drupal\Core\Config\StorageInterface::listAll().
*/
public function listAll($prefix = '') {
// glob() silently ignores the error of a non-existing search directory,
// even with the GLOB_ERR flag.
$dir = $this->getCollectionDirectory();
if (!file_exists($dir)) {
if (!is_dir($dir)) {
return array();
}
$extension = '.' . static::getFileExtension();
// \GlobIterator on Windows requires an absolute path.
$files = new \GlobIterator(realpath($dir) . '/' . $prefix . '*' . $extension);
// glob() directly calls into libc glob(), which is not aware of PHP stream
// wrappers. Same for \GlobIterator (which additionally requires an absolute
// realpath() on Windows).
// @see https://github.com/mikey179/vfsStream/issues/2
$files = scandir($dir);
$names = array();
foreach ($files as $file) {
$names[] = $file->getBasename($extension);
if ($file[0] !== '.' && fnmatch($prefix . '*' . $extension, $file)) {
$names[] = basename($file, $extension);
}
}
return $names;
@ -299,13 +303,15 @@ class FileStorage implements StorageInterface {
$collections[] = $collection . '.' . $sub_collection;
}
}
// Check that the collection is valid by searching if for configuration
// Check that the collection is valid by searching it for configuration
// objects. A directory without any configuration objects is not a valid
// collection.
// \GlobIterator on Windows requires an absolute path.
$files = new \GlobIterator(realpath($directory . '/' . $collection) . '/*.' . $this->getFileExtension());
if (count($files)) {
$collections[] = $collection;
// @see \Drupal\Core\Config\FileStorage::listAll()
foreach (scandir($directory . '/' . $collection) as $file) {
if ($file[0] !== '.' && fnmatch('*.' . $this->getFileExtension(), $file)) {
$collections[] = $collection;
break;
}
}
}
}

View file

@ -195,10 +195,17 @@ class InstallStorage extends FileStorage {
// We don't have to use ExtensionDiscovery here because our list of
// extensions was already obtained through an ExtensionDiscovery scan.
$directory = $this->getComponentFolder($extension_object);
if (file_exists($directory)) {
$files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
if (is_dir($directory)) {
// glob() directly calls into libc glob(), which is not aware of PHP
// stream wrappers. Same for \GlobIterator (which additionally requires
// an absolute realpath() on Windows).
// @see https://github.com/mikey179/vfsStream/issues/2
$files = scandir($directory);
foreach ($files as $file) {
$folders[$file->getBasename($extension)] = $directory;
if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
$folders[basename($file, $extension)] = $directory;
}
}
}
}
@ -215,10 +222,17 @@ class InstallStorage extends FileStorage {
$extension = '.' . $this->getFileExtension();
$folders = array();
$directory = $this->getCoreFolder();
if (file_exists($directory)) {
$files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
if (is_dir($directory)) {
// glob() directly calls into libc glob(), which is not aware of PHP
// stream wrappers. Same for \GlobIterator (which additionally requires an
// absolute realpath() on Windows).
// @see https://github.com/mikey179/vfsStream/issues/2
$files = scandir($directory);
foreach ($files as $file) {
$folders[$file->getBasename($extension)] = $directory;
if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
$folders[basename($file, $extension)] = $directory;
}
}
}
return $folders;

View file

@ -42,13 +42,20 @@ trait SchemaCheckTrait {
* @param string $config_name
* The configuration name.
* @param array $config_data
* The configuration data.
* The configuration data, assumed to be data for a top-level config object.
*
* @return array|bool
* FALSE if no schema found. List of errors if any found. TRUE if fully
* valid.
*/
public function checkConfigSchema(TypedConfigManagerInterface $typed_config, $config_name, $config_data) {
// We'd like to verify that the top-level type is either config_base,
// config_entity, or a derivative. The only thing we can really test though
// is that the schema supports having langcode in it. So add 'langcode' to
// the data if it doesn't already exist.
if (!isset($config_data['langcode'])) {
$config_data['langcode'] = 'en';
}
$this->configName = $config_name;
if (!$typed_config->hasConfigSchema($config_name)) {
return FALSE;

View file

@ -73,7 +73,7 @@ class ConfigSchemaChecker implements EventSubscriberInterface {
$name = $saved_config->getName();
$data = $saved_config->get();
$checksum = crc32(serialize($data));
$checksum = hash('crc32b', serialize($data));
$exceptions = array(
// Following are used to test lack of or partial schema. Where partial
// schema is provided, that is explicitly tested in specific tests.

View file

@ -7,11 +7,13 @@
namespace Drupal\Core\Controller;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Routing\RouteMatch;
use Psr\Log\LoggerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
/**
* ControllerResolver to enhance controllers beyond Symfony's basic handling.
@ -37,13 +39,24 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
*/
protected $classResolver;
/**
* 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.
*
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
*/
public function __construct(ClassResolverInterface $class_resolver) {
public function __construct(HttpMessageFactoryInterface $http_message_factory, ClassResolverInterface $class_resolver) {
$this->httpMessageFactory = $http_message_factory;
$this->classResolver = $class_resolver;
}
@ -94,10 +107,10 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
* A PHP callable.
*
* @throws \LogicException
* If the controller cannot be parsed
* If the controller cannot be parsed.
*
* @throws \InvalidArgumentException
* If the controller class does not exist
* If the controller class does not exist.
*/
protected function createController($controller) {
// Controller in the service:method notation.
@ -135,7 +148,10 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
elseif ($param->getClass() && $param->getClass()->isInstance($request)) {
$arguments[] = $request;
}
elseif ($param->getClass() && ($param->getClass()->name == 'Drupal\Core\Routing\RouteMatchInterface' || is_subclass_of($param->getClass()->name, 'Drupal\Core\Routing\RouteMatchInterface'))) {
elseif ($param->getClass() && $param->getClass()->name === ServerRequestInterface::class) {
$arguments[] = $this->httpMessageFactory->createRequest($request);
}
elseif ($param->getClass() && ($param->getClass()->name == RouteMatchInterface::class || is_subclass_of($param->getClass()->name, RouteMatchInterface::class))) {
$arguments[] = RouteMatch::createFromRequest($request);
}
elseif ($param->isDefaultValueAvailable()) {

View file

@ -19,6 +19,7 @@ use Drupal\Core\DependencyInjection\Compiler\DependencySerializationTraitPass;
use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
use Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass;
use Drupal\Core\DependencyInjection\Compiler\TwigExtensionPass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
@ -78,6 +79,8 @@ class CoreServiceProvider implements ServiceProviderInterface {
$container->addCompilerPass(new RegisterStreamWrappersPass());
$container->addCompilerPass(new GuzzleMiddlewarePass());
$container->addCompilerPass(new TwigExtensionPass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);

View file

@ -138,6 +138,13 @@ abstract class Connection {
*/
protected $prefixReplace = array();
/**
* List of un-prefixed table names, keyed by prefixed table names.
*
* @var array
*/
protected $unprefixedTablesMap = [];
/**
* Constructs a Connection object.
*
@ -185,7 +192,9 @@ abstract class Connection {
// Destroy all references to this connection by setting them to NULL.
// The Statement class attribute only accepts a new value that presents a
// proper callable, so we reset it to PDOStatement.
$this->connection->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array()));
if (!empty($this->statementClass)) {
$this->connection->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array()));
}
$this->schema = NULL;
}
@ -289,6 +298,13 @@ abstract class Connection {
$this->prefixReplace[] = $this->prefixes['default'];
$this->prefixSearch[] = '}';
$this->prefixReplace[] = '';
// Set up a map of prefixed => un-prefixed tables.
foreach ($this->prefixes as $table_name => $prefix) {
if ($table_name !== 'default') {
$this->unprefixedTablesMap[$prefix . $table_name] = $table_name;
}
}
}
/**
@ -327,6 +343,17 @@ abstract class Connection {
}
}
/**
* Gets a list of individually prefixed table names.
*
* @return array
* An array of un-prefixed table names, keyed by their fully qualified table
* names (i.e. prefix + table_name).
*/
public function getUnprefixedTablesMap() {
return $this->unprefixedTablesMap;
}
/**
* Get a fully qualified table name.
*
@ -502,7 +529,7 @@ abstract class Connection {
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
return strtr($comment, ['*' => ' * ']);
}
/**
@ -786,6 +813,23 @@ abstract class Connection {
return new $class($this, $table, $options);
}
/**
* Prepares and returns an UPSERT query object.
*
* @param string $table
* The table to use for the upsert query.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\Query\Upsert
* A new Upsert query object.
*
* @see \Drupal\Core\Database\Query\Upsert
*/
public function upsert($table, array $options = array()) {
$class = $this->getDriverClass('Upsert');
return new $class($this, $table, $options);
}
/**
* Prepares and returns an UPDATE query object.
@ -1216,6 +1260,13 @@ abstract class Connection {
return $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION);
}
/**
* Returns the version of the database client.
*/
public function clientVersion() {
return $this->connection->getAttribute(\PDO::ATTR_CLIENT_VERSION);
}
/**
* Determines if this driver supports transactions.
*

View file

@ -28,6 +28,11 @@ class Connection extends DatabaseConnection {
*/
const DATABASE_NOT_FOUND = 1049;
/**
* Error code for "Can't initialize character set" error.
*/
const UNSUPPORTED_CHARSET = 2019;
/**
* Flag to indicate if the cleanup function in __destruct() should run.
*
@ -82,6 +87,13 @@ class Connection extends DatabaseConnection {
* {@inheritdoc}
*/
public static function open(array &$connection_options = array()) {
if (isset($connection_options['_dsn_utf8_fallback']) && $connection_options['_dsn_utf8_fallback'] === TRUE) {
// Only used during the installer version check, as a fallback from utf8mb4.
$charset = 'utf8';
}
else {
$charset = 'utf8mb4';
}
// The DSN should use either a socket or a host/port.
if (isset($connection_options['unix_socket'])) {
$dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
@ -93,7 +105,7 @@ class Connection extends DatabaseConnection {
// Character set is added to dsn to ensure PDO uses the proper character
// set when escaping. This has security implications. See
// https://www.drupal.org/node/1201452 for further discussion.
$dsn .= ';charset=utf8mb4';
$dsn .= ';charset=' . $charset;
if (!empty($connection_options['database'])) {
$dsn .= ';dbname=' . $connection_options['database'];
}
@ -124,10 +136,10 @@ class Connection extends DatabaseConnection {
// certain one has been set; otherwise, MySQL defaults to
// 'utf8mb4_general_ci' for utf8mb4.
if (!empty($connection_options['collation'])) {
$pdo->exec('SET NAMES utf8mb4 COLLATE ' . $connection_options['collation']);
$pdo->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
}
else {
$pdo->exec('SET NAMES utf8mb4');
$pdo->exec('SET NAMES ' . $charset);
}
// Set MySQL init_commands if not already defined. Default Drupal's MySQL

View file

@ -55,30 +55,7 @@ class Insert extends QueryInsert {
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$max_placeholder = 0;
$values = array();
if (count($this->insertValues)) {
foreach ($this->insertValues as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
return $query;

View file

@ -16,6 +16,17 @@ use Drupal\Core\Database\DatabaseNotFoundException;
* Specifies installation tasks for MySQL and equivalent databases.
*/
class Tasks extends InstallTasks {
/**
* Minimum required MySQLnd version.
*/
const MYSQLND_MINIMUM_VERSION = '5.0.9';
/**
* Minimum required libmysqlclient version.
*/
const LIBMYSQLCLIENT_MINIMUM_VERSION = '5.5.3';
/**
* The PDO driver name for MySQL and equivalent databases.
*
@ -27,13 +38,6 @@ class Tasks extends InstallTasks {
* Constructs a \Drupal\Core\Database\Driver\mysql\Install\Tasks object.
*/
public function __construct() {
$this->tasks[] = array(
'arguments' => array(
'SET NAMES utf8mb4',
'The %name database server supports utf8mb4 character encoding.',
'The %name database server must support utf8mb4 character encoding to work with Drupal. Make sure to use a database server that supports utf8mb4 character encoding, such as MySQL/MariaDB/Percona versions 5.5.3 and up.',
),
);
$this->tasks[] = array(
'arguments' => array(),
'function' => 'ensureInnoDbAvailable',
@ -62,7 +66,34 @@ class Tasks extends InstallTasks {
// This doesn't actually test the connection.
db_set_active();
// Now actually do a check.
Database::getConnection();
try {
Database::getConnection();
}
catch (\Exception $e) {
// Detect utf8mb4 incompability.
if ($e->getCode() == Connection::UNSUPPORTED_CHARSET) {
$this->fail(t('Your MySQL server and PHP MySQL driver must support utf8mb4 character encoding. Make sure to use a database system that supports this (such as MySQL/MariaDB/Percona 5.5.3 and up), and that the utf8mb4 character set is compiled in. See the <a href="@documentation" target="_blank">MySQL documentation</a> for more information.', array('@documentation' => 'https://dev.mysql.com/doc/refman/5.0/en/cannot-initialize-character-set.html')));
$info = Database::getConnectionInfo();
$info_copy = $info;
// Set a flag to fall back to utf8. Note: this flag should only be
// used here and is for internal use only.
$info_copy['default']['_dsn_utf8_fallback'] = TRUE;
// In order to change the Database::$databaseInfo array, we need to
// remove the active connection, then re-add it with the new info.
Database::removeConnection('default');
Database::addConnectionInfo('default', 'default', $info_copy['default']);
// Connect with the new database info, using the utf8 character set so
// that we can run the checkEngineVersion test.
Database::getConnection();
// Revert to the old settings.
Database::removeConnection('default');
Database::addConnectionInfo('default', 'default', $info['default']);
}
else {
// Rethrow the exception.
throw $e;
}
}
$this->pass('Drupal can CONNECT to the database ok.');
}
catch (\Exception $e) {
@ -121,4 +152,27 @@ class Tasks extends InstallTasks {
}
}
/**
* {@inheritdoc}
*/
protected function checkEngineVersion() {
parent::checkEngineVersion();
// Ensure that the MySQL driver supports utf8mb4 encoding.
$version = Database::getConnection()->clientVersion();
if (FALSE !== strpos($version, 'mysqlnd')) {
// The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
$version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
if (version_compare($version, self::MYSQLND_MINIMUM_VERSION, '<')) {
$this->fail(t("The MySQLnd driver version %version is less than the minimum required version. Upgrade to MySQLnd version %mysqlnd_minimum_version or up, or alternatively switch mysql drivers to libmysqlclient version %libmysqlclient_minimum_version or up.", array('%version' => Database::getConnection()->version(), '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION, '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION)));
}
}
else {
// The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
if (version_compare($version, self::LIBMYSQLCLIENT_MINIMUM_VERSION, '<')) {
$this->fail(t("The libmysqlclient driver version %version is less than the minimum required version. Upgrade to libmysqlclient version %libmysqlclient_minimum_version or up, or alternatively switch mysql drivers to MySQLnd version %mysqlnd_minimum_version or up.", array('%version' => Database::getConnection()->version(), '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION, '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION)));
}
}
}
}

View file

@ -9,6 +9,7 @@ namespace Drupal\Core\Database\Driver\mysql;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\SchemaException;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Core\Database\Schema as DatabaseSchema;
@ -60,8 +61,7 @@ class Schema extends DatabaseSchema {
$info['table'] = substr($table, ++$pos);
}
else {
$db_info = Database::getConnectionInfo();
$info['database'] = $db_info[$this->connection->getTarget()]['database'];
$info['database'] = $this->connection->getConnectionOptions()['database'];
$info['table'] = $table;
}
return $info;
@ -299,14 +299,17 @@ class Schema extends DatabaseSchema {
* Shortens indexes to 191 characters if they apply to utf8mb4-encoded
* fields, in order to comply with the InnoDB index limitation of 756 bytes.
*
* @param $spec
* @param array $spec
* The table specification.
*
* @return array
* List of shortened indexes.
*
* @throws \Drupal\Core\Database\SchemaException
* Thrown if field specification is missing.
*/
protected function getNormalizedIndexes($spec) {
$indexes = $spec['indexes'];
protected function getNormalizedIndexes(array $spec) {
$indexes = isset($spec['indexes']) ? $spec['indexes'] : [];
foreach ($indexes as $index_name => $index_fields) {
foreach ($index_fields as $index_key => $index_field) {
// Get the name of the field from the index specification.
@ -323,6 +326,9 @@ class Schema extends DatabaseSchema {
}
}
}
else {
throw new SchemaException("MySQL needs the '$field_name' field specification in order to normalize the '$index_name' index");
}
}
}
return $indexes;
@ -486,7 +492,10 @@ class Schema extends DatabaseSchema {
return TRUE;
}
public function addIndex($table, $name, $fields) {
/**
* {@inheritdoc}
*/
public function addIndex($table, $name, $fields, array $spec) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
}
@ -494,7 +503,10 @@ class Schema extends DatabaseSchema {
throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')');
$spec['indexes'][$name] = $fields;
$indexes = $this->getNormalizedIndexes($spec);
$this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($indexes[$name]) . ')');
}
public function dropIndex($table, $name) {

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\mysql\Upsert.
*/
namespace Drupal\Core\Database\Driver\mysql;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the Upsert query for the MySQL database driver.
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
// Updating the unique / primary key is not necessary.
unset($insert_fields[$this->key]);
$update = [];
foreach ($insert_fields as $field) {
$update[] = "$field = VALUES($field)";
}
$query .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $update);
return $query;
}
}

View file

@ -383,6 +383,22 @@ class Connection extends DatabaseConnection {
$this->rollback($savepoint_name);
}
}
/**
* {@inheritdoc}
*/
public function upsert($table, array $options = array()) {
// Use the (faster) native Upsert implementation for PostgreSQL >= 9.5.
if (version_compare($this->version(), '9.5', '>=')) {
$class = $this->getDriverClass('NativeUpsert');
}
else {
$class = $this->getDriverClass('Upsert');
}
return new $class($this, $table, $options);
}
}
/**

View file

@ -128,30 +128,7 @@ class Insert extends QueryInsert {
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$max_placeholder = 0;
$values = array();
if (count($this->insertValues)) {
foreach ($this->insertValues as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
return $query;

View file

@ -180,8 +180,8 @@ class Tasks extends InstallTasks {
* Verify that a binary data roundtrip returns the original string.
*/
protected function checkBinaryOutputSuccess() {
$bytea_output = db_query("SELECT 'encoding'::bytea AS output")->fetchField();
return ($bytea_output == 'encoding');
$bytea_output = db_query("SHOW bytea_output")->fetchField();
return ($bytea_output == 'escape');
}
/**
@ -192,17 +192,9 @@ class Tasks extends InstallTasks {
// like we do with table names. This is so that we don't double up if more
// than one instance of Drupal is running on a single database. We therefore
// avoid trying to create them again in that case.
// At the same time checking for the existence of the function fixes
// concurrency issues, when both try to update at the same time.
try {
// Create functions.
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
\'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
LANGUAGE \'sql\''
);
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
\'SELECT greatest($1, greatest($2, $3));\'
LANGUAGE \'sql\''
);
// Don't use {} around pg_proc table.
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
@ -211,37 +203,17 @@ class Tasks extends InstallTasks {
);
}
db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
\'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
LANGUAGE \'sql\''
);
// Using || to concatenate in Drupal is not recommended because there are
// database drivers for Drupal that do not support the syntax, however
// they do support CONCAT(item1, item2) which we can replicate in
// PostgreSQL. PostgreSQL requires the function to be defined for each
// different argument variation the function can handle.
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS
\'SELECT CAST($1 AS text) || CAST($2 AS text);\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS
\'SELECT $1 || CAST($2 AS text);\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS
\'SELECT CAST($1 AS text) || $2;\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
\'SELECT $1 || $2;\'
LANGUAGE \'sql\'
');
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'substring_index'")->fetchField()) {
db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
\'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
LANGUAGE \'sql\''
);
}
$this->pass(t('PostgreSQL has initialized itself.'));
}
catch (\Exception $e) {
$this->fail(t('Drupal could not be correctly setup with the existing database. Revise any errors.'));
$this->fail(t('Drupal could not be correctly setup with the existing database due to the following error: @error.', ['@error' => $e->getMessage()]));
}
}

View file

@ -0,0 +1,116 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\pgsql\NativeUpsert.
*/
namespace Drupal\Core\Database\Driver\pgsql;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the native Upsert query for the PostgreSQL database driver.
*
* @see http://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT
*/
class NativeUpsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
$stmt = $this->connection->prepareQuery((string) $this);
// Fetch the list of blobs and sequences used on that table.
$table_information = $this->connection->schema()->queryTableInformation($this->table);
$max_placeholder = 0;
$blobs = [];
$blob_count = 0;
foreach ($this->insertValues as $insert_values) {
foreach ($this->insertFields as $idx => $field) {
if (isset($table_information->blob_fields[$field])) {
$blobs[$blob_count] = fopen('php://memory', 'a');
fwrite($blobs[$blob_count], $insert_values[$idx]);
rewind($blobs[$blob_count]);
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], \PDO::PARAM_LOB);
// Pre-increment is faster in PHP than increment.
++$blob_count;
}
else {
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
}
}
// Check if values for a serial field has been passed.
if (!empty($table_information->serial_fields)) {
foreach ($table_information->serial_fields as $index => $serial_field) {
$serial_key = array_search($serial_field, $this->insertFields);
if ($serial_key !== FALSE) {
$serial_value = $insert_values[$serial_key];
// Sequences must be greater than or equal to 1.
if ($serial_value === NULL || !$serial_value) {
$serial_value = 1;
}
// Set the sequence to the bigger value of either the passed
// value or the max value of the column. It can happen that another
// thread calls nextval() which could lead to a serial number being
// used twice. However, trying to insert a value into a serial
// column should only be done in very rare cases and is not thread
// safe by definition.
$this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", array(':serial_value' => (int)$serial_value));
}
}
}
}
$options = $this->queryOptions;
if (!empty($table_information->sequences)) {
$options['sequence_name'] = $table_information->sequences[0];
}
$this->connection->query($stmt, [], $options);
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = [];
return TRUE;
}
/**
* {@inheritdoc}
*/
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$insert_fields = array_map(function($f) { return $this->connection->escapeField($f); }, $insert_fields);
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
// Updating the unique / primary key is not necessary.
unset($insert_fields[$this->key]);
$update = [];
foreach ($insert_fields as $field) {
$update[] = "$field = EXCLUDED.$field";
}
$query .= ' ON CONFLICT (' . $this->connection->escapeField($this->key) . ') DO UPDATE SET ' . implode(', ', $update);
return $query;
}
}

View file

@ -93,9 +93,17 @@ class Schema extends DatabaseSchema {
public function queryTableInformation($table) {
// Generate a key to reference this table's information on.
$key = $this->connection->prefixTables('{' . $table . '}');
if (strpos($key, '.') === FALSE) {
// Take into account that temporary tables are stored in a different schema.
// \Drupal\Core\Database\Connection::generateTemporaryTableName() sets the
// 'db_temporary_' prefix to all temporary tables.
if (strpos($key, '.') === FALSE && strpos($table, 'db_temporary_') === FALSE) {
$key = 'public.' . $key;
}
else {
$schema = $this->connection->query('SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema()')->fetchField();
$key = $schema . '.' . $key;
}
if (!isset($this->tableInformation[$key])) {
// Split the key into schema and table for querying.
@ -580,13 +588,28 @@ class Schema extends DatabaseSchema {
/**
* Helper function: check if a constraint (PK, FK, UK) exists.
*
* @param $table
* @param string $table
* The name of the table.
* @param $name
* The name of the constraint (typically 'pkey' or '[constraint]_key').
* @param string $name
* The name of the constraint (typically 'pkey' or '[constraint]__key').
*
* @return bool
* TRUE if the constraint exists, FALSE otherwise.
*/
public function constraintExists($table, $name) {
$constraint_name = $this->ensureIdentifiersLength($table, $name);
// ::ensureIdentifiersLength() expects three parameters, although not
// explicitly stated in its signature, thus we split our constraint name in
// a proper name and a suffix.
if ($name == 'pkey') {
$suffix = $name;
$name = '';
}
else {
$pos = strrpos($name, '__');
$suffix = substr($name, $pos + 2);
$name = substr($name, 0, $pos);
}
$constraint_name = $this->ensureIdentifiersLength($table, $name, $suffix);
// Remove leading and trailing quotes because the index name is in a WHERE
// clause and not used as an identifier.
$constraint_name = str_replace('"', '', $constraint_name);
@ -637,7 +660,10 @@ class Schema extends DatabaseSchema {
return TRUE;
}
public function addIndex($table, $name, $fields) {
/**
* {@inheritdoc}
*/
public function addIndex($table, $name, $fields, array $spec) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
}
@ -779,7 +805,10 @@ class Schema extends DatabaseSchema {
}
if (isset($new_keys['indexes'])) {
foreach ($new_keys['indexes'] as $name => $fields) {
$this->addIndex($table, $name, $fields);
// Even though $new_keys is not a full schema it still has 'indexes' and
// so is a partial schema. Technically addIndex() doesn't do anything
// with it so passing an empty array would work as well.
$this->addIndex($table, $name, $fields, $new_keys);
}
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\pgsql\Upsert.
*/
namespace Drupal\Core\Database\Driver\pgsql;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the Upsert query for the PostgreSQL database driver.
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
// Default options for upsert queries.
$this->queryOptions += array(
'throw_exception' => TRUE,
);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$table = $this->connection->escapeTable($this->table);
// We have to execute multiple queries, therefore we wrap everything in a
// transaction so that it is atomic where possible.
$transaction = $this->connection->startTransaction();
try {
// First, lock the table we're upserting into.
$this->connection->query('LOCK TABLE {' . $table . '} IN SHARE ROW EXCLUSIVE MODE', [], $this->queryOptions);
// Second, delete all items first so we can do one insert.
$unique_key_position = array_search($this->key, $insert_fields);
$delete_ids = [];
foreach ($this->insertValues as $insert_values) {
$delete_ids[] = $insert_values[$unique_key_position];
}
// Delete in chunks when a large array is passed.
foreach (array_chunk($delete_ids, 1000) as $delete_ids_chunk) {
$this->connection->delete($this->table, $this->queryOptions)
->condition($this->key, $delete_ids_chunk, 'IN')
->execute();
}
// Third, insert all the values.
$insert = $this->connection->insert($this->table, $this->queryOptions)
->fields($insert_fields);
foreach ($this->insertValues as $insert_values) {
$insert->values($insert_values);
}
$insert->execute();
}
catch (\Exception $e) {
// One of the queries failed, rollback the whole batch.
$transaction->rollback();
// Rethrow the exception for the calling code.
throw $e;
}
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = array();
// Transaction commits here where $transaction looses scope.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function __toString() {
// Nothing to do.
}
}

View file

@ -154,9 +154,9 @@ class Connection extends DatabaseConnection {
// We can prune the database file if it doesn't have any tables.
if ($count == 0) {
// Detach the database.
$this->query('DETACH DATABASE :schema', array(':schema' => $prefix));
// Destroy the database file.
// Detaching the database fails at this point, but no other queries
// are executed after the connection is destructed so we can simply
// remove the database file.
unlink($this->connectionOptions['database'] . '-' . $prefix);
}
}
@ -168,6 +168,18 @@ class Connection extends DatabaseConnection {
}
}
/**
* Gets all the attached databases.
*
* @return array
* An array of attached database names.
*
* @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct()
*/
public function getAttachedDatabases() {
return $this->attachedDatabases;
}
/**
* SQLite compatibility implementation for the IF() SQL function.
*/

View file

@ -582,7 +582,10 @@ class Schema extends DatabaseSchema {
return $key_definition;
}
public function addIndex($table, $name, $fields) {
/**
* {@inheritdoc}
*/
public function addIndex($table, $name, $fields, array $spec) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
}
@ -693,16 +696,31 @@ class Schema extends DatabaseSchema {
$this->alterTable($table, $old_schema, $new_schema);
}
/**
* {@inheritdoc}
*/
public function findTables($table_expression) {
// Don't add the prefix, $table_expression already includes the prefix.
$info = $this->getPrefixInfo($table_expression, FALSE);
$tables = [];
// Can't use query placeholders for the schema because the query would have
// to be :prefixsqlite_master, which does not work.
$result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array(
':type' => 'table',
':table_name' => $info['table'],
));
return $result->fetchAllKeyed(0, 0);
// The SQLite implementation doesn't need to use the same filtering strategy
// as the parent one because individually prefixed tables live in their own
// schema (database), which means that neither the main database nor any
// attached one will contain a prefixed table name, so we just need to loop
// over all known schemas and filter by the user-supplied table expression.
$attached_dbs = $this->connection->getAttachedDatabases();
foreach ($attached_dbs as $schema) {
// Can't use query placeholders for the schema because the query would
// have to be :prefixsqlite_master, which does not work. We also need to
// ignore the internal SQLite tables.
$result = db_query("SELECT name FROM " . $schema . ".sqlite_master WHERE type = :type AND name LIKE :table_name AND name NOT LIKE :pattern", array(
':type' => 'table',
':table_name' => $table_expression,
':pattern' => 'sqlite_%',
));
$tables += $result->fetchAllKeyed(0, 0);
}
return $tables;
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\sqlite\Upsert.
*/
namespace Drupal\Core\Database\Driver\sqlite;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the Upsert query for the SQLite database driver.
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$query = $comments . 'INSERT OR REPLACE INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
return $query;
}
}

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Database\Install;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Database\Database;
/**
@ -80,7 +79,10 @@ abstract class Tasks {
*
* @var array
*/
protected $results = array();
protected $results = array(
'fail' => array(),
'pass' => array(),
);
/**
* Ensure the PDO driver is supported by the version of PHP in use.
@ -93,14 +95,14 @@ abstract class Tasks {
* Assert test as failed.
*/
protected function fail($message) {
$this->results[$message] = FALSE;
$this->results['fail'][] = $message;
}
/**
* Assert test as a pass.
*/
protected function pass($message) {
$this->results[$message] = TRUE;
$this->results['pass'][] = $message;
}
/**
@ -128,6 +130,9 @@ abstract class Tasks {
/**
* Run database tasks and tests to see if Drupal can run on the database.
*
* @return array
* A list of error messages.
*/
public function runTasks() {
// We need to establish a connection before we can run tests.
@ -143,21 +148,11 @@ abstract class Tasks {
}
}
else {
throw new TaskException(t("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function'])));
$this->fail(t("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function'])));
}
}
}
// Check for failed results and compile message
$message = '';
foreach ($this->results as $result => $success) {
if (!$success) {
$message = SafeMarkup::isSafe($result) ? $result : SafeMarkup::checkPlain($result);
}
}
if (!empty($message)) {
$message = SafeMarkup::set('Resolve all issues below to continue the installation. For help configuring your database server, see the <a href="https://www.drupal.org/getting-started/install">installation handbook</a>, or contact your hosting provider.' . $message);
throw new TaskException($message);
}
return $this->results['fail'];
}
/**
@ -196,8 +191,9 @@ abstract class Tasks {
* Check the engine version.
*/
protected function checkEngineVersion() {
// Ensure that the database server has the right version.
if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) {
$this->fail(t("The database version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion())));
$this->fail(t("The database server version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion())));
}
}

View file

@ -16,43 +16,7 @@ use Drupal\Core\Database\Database;
*/
class Insert extends Query {
/**
* The table on which to insert.
*
* @var string
*/
protected $table;
/**
* An array of fields on which to insert.
*
* @var array
*/
protected $insertFields = array();
/**
* An array of fields that should be set to their database-defined defaults.
*
* @var array
*/
protected $defaultFields = array();
/**
* A nested array of values to insert.
*
* $insertValues is an array of arrays. Each sub-array is either an
* associative array whose keys are field names and whose values are field
* values to insert, or a non-associative array of values in the same order
* as $insertFields.
*
* Whether multiple insert sets will be run in a single query or multiple
* queries is left to individual drivers to implement in whatever manner is
* most appropriate. The order of values in each sub-array must match the
* order of fields in $insertFields.
*
* @var array
*/
protected $insertValues = array();
use InsertTrait;
/**
* A SelectQuery object to fetch the rows that should be inserted.
@ -79,96 +43,6 @@ class Insert extends Query {
$this->table = $table;
}
/**
* Adds a set of field->value pairs to be inserted.
*
* This method may only be called once. Calling it a second time will be
* ignored. To queue up multiple sets of values to be inserted at once,
* use the values() method.
*
* @param $fields
* An array of fields on which to insert. This array may be indexed or
* associative. If indexed, the array is taken to be the list of fields.
* If associative, the keys of the array are taken to be the fields and
* the values are taken to be corresponding values to insert. If a
* $values argument is provided, $fields must be indexed.
* @param $values
* An array of fields to insert into the database. The values must be
* specified in the same order as the $fields array.
*
* @return \Drupal\Core\Database\Query\Insert
* The called object.
*/
public function fields(array $fields, array $values = array()) {
if (empty($this->insertFields)) {
if (empty($values)) {
if (!is_numeric(key($fields))) {
$values = array_values($fields);
$fields = array_keys($fields);
}
}
$this->insertFields = $fields;
if (!empty($values)) {
$this->insertValues[] = $values;
}
}
return $this;
}
/**
* Adds another set of values to the query to be inserted.
*
* If $values is a numeric-keyed array, it will be assumed to be in the same
* order as the original fields() call. If it is associative, it may be
* in any order as long as the keys of the array match the names of the
* fields.
*
* @param $values
* An array of values to add to the query.
*
* @return \Drupal\Core\Database\Query\Insert
* The called object.
*/
public function values(array $values) {
if (is_numeric(key($values))) {
$this->insertValues[] = $values;
}
else {
// Reorder the submitted values to match the fields array.
foreach ($this->insertFields as $key) {
$insert_values[$key] = $values[$key];
}
// For consistency, the values array is always numerically indexed.
$this->insertValues[] = array_values($insert_values);
}
return $this;
}
/**
* Specifies fields for which the database defaults should be used.
*
* If you want to force a given field to use the database-defined default,
* not NULL or undefined, use this method to instruct the database to use
* default values explicitly. In most cases this will not be necessary
* unless you are inserting a row that is all default values, as you cannot
* specify no values in an INSERT query.
*
* Specifying a field both in fields() and in useDefaults() is an error
* and will not execute.
*
* @param $fields
* An array of values for which to use the default values
* specified in the table definition.
*
* @return \Drupal\Core\Database\Query\Insert
* The called object.
*/
public function useDefaults(array $fields) {
$this->defaultFields = $fields;
return $this;
}
/**
* Sets the fromQuery on this InsertQuery object.
*
@ -265,13 +139,13 @@ class Insert extends Query {
/**
* Preprocesses and validates the query.
*
* @return
* @return bool
* TRUE if the validation was successful, FALSE if not.
*
* @throws \Drupal\Core\Database\Query\FieldsOverlapException
* @throws \Drupal\Core\Database\Query\NoFieldsException
*/
public function preExecute() {
protected function preExecute() {
// Confirm that the user did not try to specify an identical
// field and default field.
if (array_intersect($this->insertFields, $this->defaultFields)) {

View file

@ -0,0 +1,184 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Query\InsertTrait.
*/
namespace Drupal\Core\Database\Query;
/**
* Provides common functionality for INSERT and UPSERT queries.
*
* @ingroup database
*/
trait InsertTrait {
/**
* The table on which to insert.
*
* @var string
*/
protected $table;
/**
* An array of fields on which to insert.
*
* @var array
*/
protected $insertFields = array();
/**
* An array of fields that should be set to their database-defined defaults.
*
* @var array
*/
protected $defaultFields = array();
/**
* A nested array of values to insert.
*
* $insertValues is an array of arrays. Each sub-array is either an
* associative array whose keys are field names and whose values are field
* values to insert, or a non-associative array of values in the same order
* as $insertFields.
*
* Whether multiple insert sets will be run in a single query or multiple
* queries is left to individual drivers to implement in whatever manner is
* most appropriate. The order of values in each sub-array must match the
* order of fields in $insertFields.
*
* @var array
*/
protected $insertValues = array();
/**
* Adds a set of field->value pairs to be inserted.
*
* This method may only be called once. Calling it a second time will be
* ignored. To queue up multiple sets of values to be inserted at once,
* use the values() method.
*
* @param array $fields
* An array of fields on which to insert. This array may be indexed or
* associative. If indexed, the array is taken to be the list of fields.
* If associative, the keys of the array are taken to be the fields and
* the values are taken to be corresponding values to insert. If a
* $values argument is provided, $fields must be indexed.
* @param array $values
* (optional) An array of fields to insert into the database. The values
* must be specified in the same order as the $fields array.
*
* @return $this
* The called object.
*/
public function fields(array $fields, array $values = array()) {
if (empty($this->insertFields)) {
if (empty($values)) {
if (!is_numeric(key($fields))) {
$values = array_values($fields);
$fields = array_keys($fields);
}
}
$this->insertFields = $fields;
if (!empty($values)) {
$this->insertValues[] = $values;
}
}
return $this;
}
/**
* Adds another set of values to the query to be inserted.
*
* If $values is a numeric-keyed array, it will be assumed to be in the same
* order as the original fields() call. If it is associative, it may be
* in any order as long as the keys of the array match the names of the
* fields.
*
* @param array $values
* An array of values to add to the query.
*
* @return $this
* The called object.
*/
public function values(array $values) {
if (is_numeric(key($values))) {
$this->insertValues[] = $values;
}
elseif ($this->insertFields) {
// Reorder the submitted values to match the fields array.
foreach ($this->insertFields as $key) {
$insert_values[$key] = $values[$key];
}
// For consistency, the values array is always numerically indexed.
$this->insertValues[] = array_values($insert_values);
}
return $this;
}
/**
* Specifies fields for which the database defaults should be used.
*
* If you want to force a given field to use the database-defined default,
* not NULL or undefined, use this method to instruct the database to use
* default values explicitly. In most cases this will not be necessary
* unless you are inserting a row that is all default values, as you cannot
* specify no values in an INSERT query.
*
* Specifying a field both in fields() and in useDefaults() is an error
* and will not execute.
*
* @param array $fields
* An array of values for which to use the default values
* specified in the table definition.
*
* @return $this
* The called object.
*/
public function useDefaults(array $fields) {
$this->defaultFields = $fields;
return $this;
}
/**
* Returns the query placeholders for values that will be inserted.
*
* @param array $nested_insert_values
* A nested array of values to insert.
* @param array $default_fields
* An array of fields that should be set to their database-defined defaults.
*
* @return array
* An array of insert placeholders.
*/
protected function getInsertPlaceholderFragment(array $nested_insert_values, array $default_fields) {
$max_placeholder = 0;
$values = array();
if ($nested_insert_values) {
foreach ($nested_insert_values as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($default_fields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($default_fields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
return $values;
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Query\NoUniqueFieldException.
*/
namespace Drupal\Core\Database\Query;
use Drupal\Core\Database\DatabaseException;
/**
* Exception thrown if an upsert query doesn't specify a unique field.
*/
class NoUniqueFieldException extends \InvalidArgumentException implements DatabaseException {}

View file

@ -147,6 +147,9 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
* For some database drivers, it may also wrap the field name in
* database-specific escape characters.
*
* @param string $string
* An unsanitized field name.
*
* @return
* The sanitized field name string.
*/

View file

@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Query\Upsert.
*/
namespace Drupal\Core\Database\Query;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
/**
* General class for an abstracted "Upsert" (UPDATE or INSERT) query operation.
*
* This class can only be used with a table with a single unique index.
* Often, this will be the primary key. On such a table this class works like
* Insert except the rows will be set to the desired values even if the key
* existed before.
*/
abstract class Upsert extends Query {
use InsertTrait;
/**
* The unique or primary key of the table.
*
* @var string
*/
protected $key;
/**
* Constructs an Upsert object.
*
* @param \Drupal\Core\Database\Connection $connection
* A Connection object.
* @param string $table
* Name of the table to associate with this query.
* @param array $options
* (optional) An array of database options.
*/
public function __construct(Connection $connection, $table, array $options = []) {
$options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options);
$this->table = $table;
}
/**
* Sets the unique / primary key field to be used as condition for this query.
*
* @param string $field
* The name of the field to set.
*
* @return $this
*/
public function key($field) {
$this->key = $field;
return $this;
}
/**
* Preprocesses and validates the query.
*
* @return bool
* TRUE if the validation was successful, FALSE otherwise.
*
* @throws \Drupal\Core\Database\Query\NoUniqueFieldException
* @throws \Drupal\Core\Database\Query\FieldsOverlapException
* @throws \Drupal\Core\Database\Query\NoFieldsException
*/
protected function preExecute() {
// Confirm that the user set the unique/primary key of the table.
if (!$this->key) {
throw new NoUniqueFieldException('There is no unique field specified.');
}
// Confirm that the user did not try to specify an identical
// field and default field.
if (array_intersect($this->insertFields, $this->defaultFields)) {
throw new FieldsOverlapException('You may not specify the same field to have a value and a schema-default value.');
}
// Don't execute query without fields.
if (count($this->insertFields) + count($this->defaultFields) == 0) {
throw new NoFieldsException('There are no fields available to insert with.');
}
// If no values have been added, silently ignore this query. This can happen
// if values are added conditionally, so we don't want to throw an
// exception.
return isset($this->insertValues[0]) || $this->insertFields;
}
/**
* {@inheritdoc}
*/
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
$max_placeholder = 0;
$values = array();
foreach ($this->insertValues as $insert_values) {
foreach ($insert_values as $value) {
$values[':db_insert_placeholder_' . $max_placeholder++] = $value;
}
}
$last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions);
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = array();
return $last_insert_id;
}
}

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