Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023
This commit is contained in:
parent
2720a9ec4b
commit
f3791f1da3
69
.eslintrc
69
.eslintrc
|
@ -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
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'])) {
|
||||
|
|
|
@ -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
130
core/composer.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
75
core/lib/Drupal/Component/Assertion/Handle.php
Normal file
75
core/lib/Drupal/Component/Assertion/Handle.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
629
core/lib/Drupal/Component/DependencyInjection/Container.php
Normal file
629
core/lib/Drupal/Component/DependencyInjection/Container.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
18
core/lib/Drupal/Component/DependencyInjection/composer.json
Normal file
18
core/lib/Drupal/Component/DependencyInjection/composer.json
Normal 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\\": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = '';
|
||||
|
|
|
@ -102,4 +102,10 @@ class FileReadOnlyStorage implements PhpStorageInterface {
|
|||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function garbageCollection() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -266,4 +266,10 @@ EOF;
|
|||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function garbageCollection() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -338,14 +338,59 @@ EOD;
|
|||
* "<", not "<"). Be careful when using this function, as it will revert
|
||||
* previous sanitization efforts (<script> will become <script>).
|
||||
*
|
||||
* This method is not the opposite of Html::escape(). For example, this method
|
||||
* will convert "é" to "é", whereas Html::escape() will not convert "é"
|
||||
* to "é".
|
||||
*
|
||||
* @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 &
|
||||
* - " (double quote) becomes "
|
||||
* - ' (single quote) becomes '
|
||||
* - < (less than) becomes <
|
||||
* - > (greater than) becomes >
|
||||
* Special characters that have already been escaped will be double-escaped
|
||||
* (for example, "<" becomes "&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 "é", whereas
|
||||
* Html::decodeEntities() will convert all HTML entities to UTF-8 bytes,
|
||||
* including "é" and "<" 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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
80
core/lib/Drupal/Component/Utility/SafeStringTrait.php
Normal file
80
core/lib/Drupal/Component/Utility/SafeStringTrait.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
namespace Drupal\Component\Uuid;
|
||||
|
||||
/**
|
||||
* Interface that defines a UUID backend.
|
||||
* Interface for generating UUIDs.
|
||||
*/
|
||||
interface UuidInterface {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
71
core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php
Normal file
71
core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
||||
|
|
26
core/lib/Drupal/Core/Cache/CacheableJsonResponse.php
Normal file
26
core/lib/Drupal/Core/Cache/CacheableJsonResponse.php
Normal 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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
46
core/lib/Drupal/Core/Cache/Context/PathCacheContext.php
Normal file
46
core/lib/Drupal/Core/Cache/Context/PathCacheContext.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
45
core/lib/Drupal/Core/Database/Driver/mysql/Upsert.php
Normal file
45
core/lib/Drupal/Core/Database/Driver/mysql/Upsert.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
116
core/lib/Drupal/Core/Database/Driver/pgsql/NativeUpsert.php
Normal file
116
core/lib/Drupal/Core/Database/Driver/pgsql/NativeUpsert.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
88
core/lib/Drupal/Core/Database/Driver/pgsql/Upsert.php
Normal file
88
core/lib/Drupal/Core/Database/Driver/pgsql/Upsert.php
Normal 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.
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
35
core/lib/Drupal/Core/Database/Driver/sqlite/Upsert.php
Normal file
35
core/lib/Drupal/Core/Database/Driver/sqlite/Upsert.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
184
core/lib/Drupal/Core/Database/Query/InsertTrait.php
Normal file
184
core/lib/Drupal/Core/Database/Query/InsertTrait.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {}
|
|
@ -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.
|
||||
*/
|
||||
|
|
119
core/lib/Drupal/Core/Database/Query/Upsert.php
Normal file
119
core/lib/Drupal/Core/Database/Query/Upsert.php
Normal 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
Reference in a new issue