diff --git a/.htaccess b/.htaccess index 83cb71a04..9a73a3d3a 100644 --- a/.htaccess +++ b/.htaccess @@ -3,7 +3,7 @@ # # Protect files and directories from prying eyes. - + Require all denied diff --git a/autoload.php b/autoload.php index 1e5ad34e3..2ca88c2f2 100644 --- a/autoload.php +++ b/autoload.php @@ -4,6 +4,8 @@ * @file * Includes the autoloader created by Composer. * + * This file was generated by drupal-scaffold. + *. * @see composer.json * @see index.php * @see core/install.php diff --git a/composer.json b/composer.json index 9ce735843..96b46aafa 100644 --- a/composer.json +++ b/composer.json @@ -1,66 +1,70 @@ { - "name": "drupal/drupal", - "description": "Drupal is an open source content management platform powering millions of websites and applications.", + "name": "drupal/legacy-project", + "description": "Project template for Drupal 8 projects with composer following drupal/drupal layout", "type": "project", "license": "GPL-2.0-or-later", + "homepage": "https://www.drupal.org/project/drupal", + "support": { + "docs": "https://www.drupal.org/docs/user_guide/en/index.html", + "chat": "https://www.drupal.org/node/314178" + }, + "repositories": [ + { + "type": "composer", + "url": "https://packages.drupal.org/8" + } + ], "require": { - "composer/installers": "^1.0.24", - "wikimedia/composer-merge-plugin": "^1.4" + "composer/installers": "^1.2", + "drupal/core-composer-scaffold": "^8.8", + "drupal/core-project-message": "^8.8", + "drupal/core-recommended": "^8.8", + "drupal/core-vendor-hardening": "^8.8" + }, + "require-dev": { }, - "replace": { - "drupal/core": "^8.7" + "conflict": { + "drupal/drupal": "*" }, "minimum-stability": "dev", "prefer-stable": true, "config": { - "preferred-install": "dist", - "autoloader-suffix": "Drupal8" + "sort-packages": true }, "extra": { - "_readme": [ - "By default Drupal loads the autoloader from ./vendor/autoload.php.", - "To change the autoloader you can edit ./autoload.php.", - "This file specifies the packages.drupal.org repository.", - "You can read more about this composer repository at:", - "https://www.drupal.org/node/2718229" - ], - "merge-plugin": { - "include": [ - "core/composer.json" - ], - "recurse": true, - "replace": false, - "merge-extra": false + "drupal-scaffold": { + "locations": { + "web-root": "./" + } }, "installer-paths": { "core": ["type:drupal-core"], + "libraries/{$name}": ["type:drupal-library"], "modules/contrib/{$name}": ["type:drupal-module"], "profiles/contrib/{$name}": ["type:drupal-profile"], "themes/contrib/{$name}": ["type:drupal-theme"], - "drush/contrib/{$name}": ["type:drupal-drush"], + "drush/Commands/contrib/{$name}": ["type:drupal-drush"], "modules/custom/{$name}": ["type:drupal-custom-module"], "themes/custom/{$name}": ["type:drupal-custom-theme"] + }, + "drupal-core-project-message": { + "include-keys": ["homepage", "support"], + "post-create-project-cmd-message": [ + " ", + " Congratulations, you’ve installed the Drupal codebase ", + " from the drupal/legacy-project template! ", + " ", + "", + "Next steps:", + + " * Install the site: https://www.drupal.org/docs/8/install", + " * Read the user guide: https://www.drupal.org/docs/user_guide/en/index.html", + " * Get support: https://www.drupal.org/support", + " * Get involved with the Drupal community:", + " https://www.drupal.org/getting-involved", + " * Remove the plugin that prints this message:", + " composer remove drupal/core-project-message" + ] } - }, - "autoload": { - "psr-4": { - "Drupal\\Core\\Composer\\": "core/lib/Drupal/Core/Composer" - } - }, - "scripts": { - "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump", - "post-autoload-dump": "Drupal\\Core\\Composer\\Composer::ensureHtaccess", - "post-package-install": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup", - "post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup", - "drupal-phpunit-upgrade-check": "Drupal\\Core\\Composer\\Composer::upgradePHPUnit", - "drupal-phpunit-upgrade": "@composer update phpunit/phpunit phpspec/prophecy symfony/yaml --with-dependencies --no-progress", - "phpcs": "phpcs --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --", - "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --" - }, - "repositories": [ - { - "type": "composer", - "url": "https://packages.drupal.org/8" - } - ] + } } diff --git a/composer.lock b/composer.lock index 9890d4860..55568f888 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d764df12b2cff2be49d45e95ecbeb5d7", + "content-hash": "3b34da3979293256b863926cdd0cfac4", "packages": [ { "name": "asm89/stack-cors", @@ -58,54 +58,18 @@ ], "time": "2017-12-20T14:37:45+00:00" }, - { - "name": "brumann/polyfill-unserialize", - "version": "v1.0.3", - "source": { - "type": "git", - "url": "https://github.com/dbrumann/polyfill-unserialize.git", - "reference": "844d7e44b62a1a3d5c68cfb7ebbd59c17ea0fd7b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dbrumann/polyfill-unserialize/zipball/844d7e44b62a1a3d5c68cfb7ebbd59c17ea0fd7b", - "reference": "844d7e44b62a1a3d5c68cfb7ebbd59c17ea0fd7b", - "shasum": "" - }, - "require": { - "php": "^5.3|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Brumann\\Polyfill\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Denis Brumann", - "email": "denis.brumann@sensiolabs.de" - } - ], - "description": "Backports unserialize options introduced in PHP 7.0 to older PHP versions.", - "time": "2017-02-03T09:55:47+00:00" - }, { "name": "composer/installers", - "version": "v1.6.0", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b" + "reference": "141b272484481432cda342727a427dc1e206bfa0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/cfcca6b1b60bc4974324efb5783c13dca6932b5b", - "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b", + "url": "https://api.github.com/repos/composer/installers/zipball/141b272484481432cda342727a427dc1e206bfa0", + "reference": "141b272484481432cda342727a427dc1e206bfa0", "shasum": "" }, "require": { @@ -161,6 +125,7 @@ "RadPHP", "SMF", "Thelia", + "Whmcs", "WolfCMS", "agl", "aimeos", @@ -183,6 +148,7 @@ "installer", "itop", "joomla", + "known", "kohana", "laravel", "lavalite", @@ -212,7 +178,7 @@ "zend", "zikula" ], - "time": "2018-08-27T06:10:37+00:00" + "time": "2019-08-12T15:00:31+00:00" }, { "name": "composer/semver", @@ -278,35 +244,35 @@ }, { "name": "doctrine/annotations", - "version": "v1.2.7", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", - "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", "shasum": "" }, "require": { "doctrine/lexer": "1.*", - "php": ">=5.3.2" + "php": "^5.6 || ^7.0" }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "^5.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Annotations\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } }, "notification-url": "https://packagist.org/downloads/", @@ -342,7 +308,7 @@ "docblock", "parser" ], - "time": "2015-08-31T12:32:49+00:00" + "time": "2017-02-24T16:22:25+00:00" }, { "name": "doctrine/cache", @@ -416,28 +382,29 @@ }, { "name": "doctrine/collections", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a" + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/6c1e4eef75f310ea1b3e30945e9f06e652128b8a", - "reference": "6c1e4eef75f310ea1b3e30945e9f06e652128b8a", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { @@ -478,20 +445,20 @@ "collections", "iterator" ], - "time": "2015-04-14T22:21:58+00:00" + "time": "2017-01-03T10:49:41+00:00" }, { "name": "doctrine/common", - "version": "v2.6.2", + "version": "v2.7.3", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "7bce00698899aa2c06fe7365c76e4d78ddb15fa3" + "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/7bce00698899aa2c06fe7365c76e4d78ddb15fa3", - "reference": "7bce00698899aa2c06fe7365c76e4d78ddb15fa3", + "url": "https://api.github.com/repos/doctrine/common/zipball/4acb8f89626baafede6ee5475bc5844096eba8a9", + "reference": "4acb8f89626baafede6ee5475bc5844096eba8a9", "shasum": "" }, "require": { @@ -500,10 +467,10 @@ "doctrine/collections": "1.*", "doctrine/inflector": "1.*", "doctrine/lexer": "1.*", - "php": "~5.5|~7.0" + "php": "~5.6|~7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.0" + "phpunit/phpunit": "^5.4.6" }, "type": "library", "extra": { @@ -551,37 +518,37 @@ "persistence", "spl" ], - "time": "2016-11-30T16:50:46+00:00" + "time": "2017-07-22T08:35:12+00:00" }, { "name": "doctrine/inflector", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/e11d84c6e018beedd929cff5220969a3c6d1d462", + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Inflector\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -618,25 +585,28 @@ "singularize", "string" ], - "time": "2015-11-06T14:35:42+00:00" + "time": "2017-07-22T12:18:28+00:00" }, { "name": "doctrine/lexer", - "version": "v1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", "shasum": "" }, "require": { "php": ">=5.3.2" }, + "require-dev": { + "phpunit/phpunit": "^4.5" + }, "type": "library", "extra": { "branch-alias": { @@ -644,8 +614,8 @@ } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } }, "notification-url": "https://packagist.org/downloads/", @@ -666,13 +636,449 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", "keywords": [ + "annotations", + "docblock", "lexer", - "parser" + "parser", + "php" + ], + "time": "2019-06-08T11:03:04+00:00" + }, + { + "name": "drupal/core", + "version": "8.8.0", + "source": { + "type": "git", + "url": "https://github.com/drupal/core.git", + "reference": "c4890669449ccbab770818de9c9cb7a9f0ffc32e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core/zipball/c4890669449ccbab770818de9c9cb7a9f0ffc32e", + "reference": "c4890669449ccbab770818de9c9cb7a9f0ffc32e", + "shasum": "" + }, + "require": { + "asm89/stack-cors": "^1.1", + "composer/semver": "^1.0", + "doctrine/annotations": "^1.4", + "doctrine/common": "^2.7", + "easyrdf/easyrdf": "^0.9", + "egulias/email-validator": "^2.0", + "ext-date": "*", + "ext-dom": "*", + "ext-filter": "*", + "ext-gd": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-pdo": "*", + "ext-session": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "guzzlehttp/guzzle": "^6.3", + "masterminds/html5": "^2.1", + "pear/archive_tar": "^1.4.8", + "php": ">=7.0.8", + "stack/builder": "^1.0", + "symfony-cmf/routing": "^1.4", + "symfony/class-loader": "~3.4.0", + "symfony/console": "~3.4.0", + "symfony/dependency-injection": "~3.4.26", + "symfony/event-dispatcher": "~3.4.0", + "symfony/http-foundation": "~3.4.27", + "symfony/http-kernel": "~3.4.14", + "symfony/polyfill-iconv": "^1.0", + "symfony/process": "~3.4.0", + "symfony/psr-http-message-bridge": "^1.1.2", + "symfony/routing": "~3.4.0", + "symfony/serializer": "~3.4.0", + "symfony/translation": "~3.4.0", + "symfony/validator": "~3.4.0", + "symfony/yaml": "~3.4.5", + "twig/twig": "^1.38.2", + "typo3/phar-stream-wrapper": "^3.1.3", + "zendframework/zend-diactoros": "^1.8", + "zendframework/zend-feed": "^2.12" + }, + "conflict": { + "drupal/pathauto": "<1.6", + "drush/drush": "<8.1.10" + }, + "replace": { + "drupal/action": "self.version", + "drupal/aggregator": "self.version", + "drupal/automated_cron": "self.version", + "drupal/ban": "self.version", + "drupal/bartik": "self.version", + "drupal/basic_auth": "self.version", + "drupal/big_pipe": "self.version", + "drupal/block": "self.version", + "drupal/block_content": "self.version", + "drupal/block_place": "self.version", + "drupal/book": "self.version", + "drupal/breakpoint": "self.version", + "drupal/ckeditor": "self.version", + "drupal/claro": "self.version", + "drupal/classy": "self.version", + "drupal/color": "self.version", + "drupal/comment": "self.version", + "drupal/config": "self.version", + "drupal/config_translation": "self.version", + "drupal/contact": "self.version", + "drupal/content_moderation": "self.version", + "drupal/content_translation": "self.version", + "drupal/contextual": "self.version", + "drupal/core-annotation": "self.version", + "drupal/core-assertion": "self.version", + "drupal/core-bridge": "self.version", + "drupal/core-class-finder": "self.version", + "drupal/core-datetime": "self.version", + "drupal/core-dependency-injection": "self.version", + "drupal/core-diff": "self.version", + "drupal/core-discovery": "self.version", + "drupal/core-event-dispatcher": "self.version", + "drupal/core-file-cache": "self.version", + "drupal/core-file-security": "self.version", + "drupal/core-filesystem": "self.version", + "drupal/core-gettext": "self.version", + "drupal/core-graph": "self.version", + "drupal/core-http-foundation": "self.version", + "drupal/core-php-storage": "self.version", + "drupal/core-plugin": "self.version", + "drupal/core-proxy-builder": "self.version", + "drupal/core-render": "self.version", + "drupal/core-serialization": "self.version", + "drupal/core-transliteration": "self.version", + "drupal/core-utility": "self.version", + "drupal/core-uuid": "self.version", + "drupal/core-version": "self.version", + "drupal/datetime": "self.version", + "drupal/datetime_range": "self.version", + "drupal/dblog": "self.version", + "drupal/dynamic_page_cache": "self.version", + "drupal/editor": "self.version", + "drupal/entity_reference": "self.version", + "drupal/field": "self.version", + "drupal/field_layout": "self.version", + "drupal/field_ui": "self.version", + "drupal/file": "self.version", + "drupal/filter": "self.version", + "drupal/forum": "self.version", + "drupal/hal": "self.version", + "drupal/help": "self.version", + "drupal/help_topics": "self.version", + "drupal/history": "self.version", + "drupal/image": "self.version", + "drupal/inline_form_errors": "self.version", + "drupal/jsonapi": "self.version", + "drupal/language": "self.version", + "drupal/layout_builder": "self.version", + "drupal/layout_discovery": "self.version", + "drupal/link": "self.version", + "drupal/locale": "self.version", + "drupal/media": "self.version", + "drupal/media_library": "self.version", + "drupal/menu_link_content": "self.version", + "drupal/menu_ui": "self.version", + "drupal/migrate": "self.version", + "drupal/migrate_drupal": "self.version", + "drupal/migrate_drupal_multilingual": "self.version", + "drupal/migrate_drupal_ui": "self.version", + "drupal/minimal": "self.version", + "drupal/node": "self.version", + "drupal/options": "self.version", + "drupal/page_cache": "self.version", + "drupal/path": "self.version", + "drupal/path_alias": "self.version", + "drupal/quickedit": "self.version", + "drupal/rdf": "self.version", + "drupal/responsive_image": "self.version", + "drupal/rest": "self.version", + "drupal/search": "self.version", + "drupal/serialization": "self.version", + "drupal/settings_tray": "self.version", + "drupal/seven": "self.version", + "drupal/shortcut": "self.version", + "drupal/simpletest": "self.version", + "drupal/standard": "self.version", + "drupal/stark": "self.version", + "drupal/statistics": "self.version", + "drupal/syslog": "self.version", + "drupal/system": "self.version", + "drupal/taxonomy": "self.version", + "drupal/telephone": "self.version", + "drupal/text": "self.version", + "drupal/toolbar": "self.version", + "drupal/tour": "self.version", + "drupal/tracker": "self.version", + "drupal/update": "self.version", + "drupal/user": "self.version", + "drupal/views": "self.version", + "drupal/views_ui": "self.version", + "drupal/workflows": "self.version", + "drupal/workspaces": "self.version" + }, + "type": "drupal-core", + "extra": { + "drupal-scaffold": { + "file-mapping": { + "[project-root]/.editorconfig": "assets/scaffold/files/editorconfig", + "[project-root]/.gitattributes": "assets/scaffold/files/gitattributes", + "[web-root]/.csslintrc": "assets/scaffold/files/csslintrc", + "[web-root]/.eslintignore": "assets/scaffold/files/eslintignore", + "[web-root]/.eslintrc.json": "assets/scaffold/files/eslintrc.json", + "[web-root]/.ht.router.php": "assets/scaffold/files/ht.router.php", + "[web-root]/.htaccess": "assets/scaffold/files/htaccess", + "[web-root]/example.gitignore": "assets/scaffold/files/example.gitignore", + "[web-root]/index.php": "assets/scaffold/files/index.php", + "[web-root]/INSTALL.txt": "assets/scaffold/files/drupal.INSTALL.txt", + "[web-root]/README.txt": "assets/scaffold/files/drupal.README.txt", + "[web-root]/robots.txt": "assets/scaffold/files/robots.txt", + "[web-root]/update.php": "assets/scaffold/files/update.php", + "[web-root]/web.config": "assets/scaffold/files/web.config", + "[web-root]/sites/README.txt": "assets/scaffold/files/sites.README.txt", + "[web-root]/sites/development.services.yml": "assets/scaffold/files/development.services.yml", + "[web-root]/sites/example.settings.local.php": "assets/scaffold/files/example.settings.local.php", + "[web-root]/sites/example.sites.php": "assets/scaffold/files/example.sites.php", + "[web-root]/sites/default/default.services.yml": "assets/scaffold/files/default.services.yml", + "[web-root]/sites/default/default.settings.php": "assets/scaffold/files/default.settings.php", + "[web-root]/modules/README.txt": "assets/scaffold/files/modules.README.txt", + "[web-root]/profiles/README.txt": "assets/scaffold/files/profiles.README.txt", + "[web-root]/themes/README.txt": "assets/scaffold/files/themes.README.txt" + } + } + }, + "autoload": { + "psr-4": { + "Drupal\\Core\\": "lib/Drupal/Core", + "Drupal\\Component\\": "lib/Drupal/Component", + "Drupal\\Driver\\": "../drivers/lib/Drupal/Driver" + }, + "classmap": [ + "lib/Drupal.php", + "lib/Drupal/Component/Utility/Timer.php", + "lib/Drupal/Component/Utility/Unicode.php", + "lib/Drupal/Core/Database/Database.php", + "lib/Drupal/Core/DrupalKernel.php", + "lib/Drupal/Core/DrupalKernelInterface.php", + "lib/Drupal/Core/Site/Settings.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Drupal is an open source content management platform powering millions of websites and applications.", + "time": "2019-12-04T08:44:18+00:00" + }, + { + "name": "drupal/core-composer-scaffold", + "version": "8.8.0", + "source": { + "type": "git", + "url": "https://github.com/drupal/core-composer-scaffold.git", + "reference": "dca4b123a638d78bf77719632993e920de6cc426" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core-composer-scaffold/zipball/dca4b123a638d78bf77719632993e920de6cc426", + "reference": "dca4b123a638d78bf77719632993e920de6cc426", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0", + "php": ">=7.0.8" + }, + "conflict": { + "drupal-composer/drupal-scaffold": "*" + }, + "require-dev": { + "composer/composer": "^1.8@stable" + }, + "type": "composer-plugin", + "extra": { + "class": "Drupal\\Composer\\Plugin\\Scaffold\\Plugin", + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Drupal\\Composer\\Plugin\\Scaffold\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "A flexible Composer project scaffold builder.", + "homepage": "https://www.drupal.org/project/drupal", + "keywords": [ + "drupal" + ], + "time": "2019-10-09T02:55:24+00:00" + }, + { + "name": "drupal/core-project-message", + "version": "8.8.0", + "source": { + "type": "git", + "url": "https://github.com/drupal/core-project-message.git", + "reference": "418988513bafe533d6731650b0067b839eb61fc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core-project-message/zipball/418988513bafe533d6731650b0067b839eb61fc2", + "reference": "418988513bafe533d6731650b0067b839eb61fc2", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1", + "php": ">=7.0.8" + }, + "type": "composer-plugin", + "extra": { + "class": "Drupal\\Composer\\Plugin\\ProjectMessage\\MessagePlugin" + }, + "autoload": { + "psr-4": { + "Drupal\\Composer\\Plugin\\ProjectMessage\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Adds a message after Composer installation.", + "homepage": "https://www.drupal.org/project/drupal", + "keywords": [ + "drupal" + ], + "time": "2019-11-04T11:12:32+00:00" + }, + { + "name": "drupal/core-recommended", + "version": "8.8.0", + "source": { + "type": "git", + "url": "https://github.com/drupal/core-recommended.git", + "reference": "8d7b0ddac7c3a39e55f8cbbf71b9f4f6ecf765e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core-recommended/zipball/8d7b0ddac7c3a39e55f8cbbf71b9f4f6ecf765e6", + "reference": "8d7b0ddac7c3a39e55f8cbbf71b9f4f6ecf765e6", + "shasum": "" + }, + "require": { + "asm89/stack-cors": "1.2.0", + "composer/installers": "v1.7.0", + "composer/semver": "1.5.0", + "doctrine/annotations": "v1.4.0", + "doctrine/cache": "v1.6.2", + "doctrine/collections": "v1.4.0", + "doctrine/common": "v2.7.3", + "doctrine/inflector": "v1.2.0", + "doctrine/lexer": "1.0.2", + "drupal/core": "8.8.0", + "easyrdf/easyrdf": "0.9.1", + "egulias/email-validator": "2.1.11", + "guzzlehttp/guzzle": "6.3.3", + "guzzlehttp/promises": "v1.3.1", + "guzzlehttp/psr7": "1.6.1", + "masterminds/html5": "2.3.0", + "paragonie/random_compat": "v9.99.99", + "pear/archive_tar": "1.4.8", + "pear/console_getopt": "v1.4.2", + "pear/pear-core-minimal": "v1.10.9", + "pear/pear_exception": "v1.0.0", + "psr/container": "1.0.0", + "psr/http-message": "1.0.1", + "psr/log": "1.1.0", + "ralouphie/getallheaders": "3.0.3", + "stack/builder": "v1.0.5", + "symfony-cmf/routing": "1.4.1", + "symfony/class-loader": "v3.4.35", + "symfony/console": "v3.4.35", + "symfony/debug": "v3.4.35", + "symfony/dependency-injection": "v3.4.35", + "symfony/event-dispatcher": "v3.4.35", + "symfony/http-foundation": "v3.4.35", + "symfony/http-kernel": "v3.4.35", + "symfony/polyfill-ctype": "v1.12.0", + "symfony/polyfill-iconv": "v1.12.0", + "symfony/polyfill-mbstring": "v1.12.0", + "symfony/polyfill-php56": "v1.12.0", + "symfony/polyfill-php70": "v1.12.0", + "symfony/polyfill-util": "v1.12.0", + "symfony/process": "v3.4.35", + "symfony/psr-http-message-bridge": "v1.1.2", + "symfony/routing": "v3.4.35", + "symfony/serializer": "v3.4.35", + "symfony/translation": "v3.4.35", + "symfony/validator": "v3.4.35", + "symfony/yaml": "v3.4.35", + "twig/twig": "v1.42.3", + "typo3/phar-stream-wrapper": "v3.1.3", + "zendframework/zend-diactoros": "1.8.7", + "zendframework/zend-escaper": "2.6.1", + "zendframework/zend-feed": "2.12.0", + "zendframework/zend-stdlib": "3.2.1" + }, + "conflict": { + "webflo/drupal-core-strict": "*" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Locked core dependencies; require this project INSTEAD OF drupal/core.", + "time": "2019-12-04T08:44:18+00:00" + }, + { + "name": "drupal/core-vendor-hardening", + "version": "8.8.0", + "source": { + "type": "git", + "url": "https://github.com/drupal/core-vendor-hardening.git", + "reference": "f08bdad7de04c369b7cf18e642f6d344f4b2dc07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/drupal/core-vendor-hardening/zipball/f08bdad7de04c369b7cf18e642f6d344f4b2dc07", + "reference": "f08bdad7de04c369b7cf18e642f6d344f4b2dc07", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1", + "php": ">=7.0.8" + }, + "type": "composer-plugin", + "extra": { + "class": "Drupal\\Composer\\Plugin\\VendorHardening\\VendorHardeningPlugin" + }, + "autoload": { + "psr-4": { + "Drupal\\Composer\\Plugin\\VendorHardening\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Hardens the vendor directory for when it's in the docroot.", + "homepage": "https://www.drupal.org/project/drupal", + "keywords": [ + "drupal" ], - "time": "2014-09-09T13:34:57+00:00" + "time": "2019-11-04T10:55:17+00:00" }, { "name": "easyrdf/easyrdf", @@ -738,16 +1144,16 @@ }, { "name": "egulias/email-validator", - "version": "2.1.7", + "version": "2.1.11", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e" + "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/709f21f92707308cdf8f9bcfa1af4cb26586521e", - "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23", + "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23", "shasum": "" }, "require": { @@ -757,7 +1163,8 @@ "require-dev": { "dominicsayers/isemail": "dev-master", "phpunit/phpunit": "^4.8.35||^5.7||^6.0", - "satooshi/php-coveralls": "^1.0.1" + "satooshi/php-coveralls": "^1.0.1", + "symfony/phpunit-bridge": "^4.4@dev" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -765,7 +1172,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -791,7 +1198,7 @@ "validation", "validator" ], - "time": "2018-12-04T22:38:24+00:00" + "time": "2019-08-13T17:33:27+00:00" }, { "name": "guzzlehttp/guzzle", @@ -911,32 +1318,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -966,13 +1378,14 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "masterminds/html5", @@ -1041,33 +1454,29 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.18", + "version": "v9.99.99", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": "^7" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -1086,20 +1495,20 @@ "pseudorandom", "random" ], - "time": "2019-01-03T20:59:08+00:00" + "time": "2018-07-02T15:55:56+00:00" }, { "name": "pear/archive_tar", - "version": "1.4.6", + "version": "1.4.8", "source": { "type": "git", "url": "https://github.com/pear/Archive_Tar.git", - "reference": "b8e33f9063a7cd1d20f079014f8382b3a7aee47e" + "reference": "442bdffb7edb84c898cfd94f7ac8500e49d5bbb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/b8e33f9063a7cd1d20f079014f8382b3a7aee47e", - "reference": "b8e33f9063a7cd1d20f079014f8382b3a7aee47e", + "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/442bdffb7edb84c898cfd94f7ac8500e49d5bbb5", + "reference": "442bdffb7edb84c898cfd94f7ac8500e49d5bbb5", "shasum": "" }, "require": { @@ -1152,20 +1561,20 @@ "archive", "tar" ], - "time": "2019-02-01T11:10:38+00:00" + "time": "2019-10-21T13:31:24+00:00" }, { "name": "pear/console_getopt", - "version": "v1.4.1", + "version": "v1.4.2", "source": { "type": "git", "url": "https://github.com/pear/Console_Getopt.git", - "reference": "82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f" + "reference": "6c77aeb625b32bd752e89ee17972d103588b90c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/Console_Getopt/zipball/82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f", - "reference": "82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f", + "url": "https://api.github.com/repos/pear/Console_Getopt/zipball/6c77aeb625b32bd752e89ee17972d103588b90c0", + "reference": "6c77aeb625b32bd752e89ee17972d103588b90c0", "shasum": "" }, "type": "library", @@ -1199,20 +1608,20 @@ } ], "description": "More info available on: http://pear.php.net/package/Console_Getopt", - "time": "2015-07-20T20:28:12+00:00" + "time": "2019-02-06T16:52:33+00:00" }, { "name": "pear/pear-core-minimal", - "version": "v1.10.7", + "version": "v1.10.9", "source": { "type": "git", "url": "https://github.com/pear/pear-core-minimal.git", - "reference": "19a3e0fcd50492c4357372f623f55f1b144346da" + "reference": "742be8dd68c746a01e4b0a422258e9c9cae1c37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/19a3e0fcd50492c4357372f623f55f1b144346da", - "reference": "19a3e0fcd50492c4357372f623f55f1b144346da", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/742be8dd68c746a01e4b0a422258e9c9cae1c37f", + "reference": "742be8dd68c746a01e4b0a422258e9c9cae1c37f", "shasum": "" }, "require": { @@ -1243,7 +1652,7 @@ } ], "description": "Minimal set of PEAR core files to be used as composer dependency", - "time": "2018-12-05T20:03:52+00:00" + "time": "2019-03-13T18:15:44+00:00" }, { "name": "pear/pear_exception", @@ -1401,16 +1810,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -1444,7 +1853,47 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" }, { "name": "stack/builder", @@ -1556,16 +2005,16 @@ }, { "name": "symfony/class-loader", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "4459eef5298dedfb69f771186a580062b8516497" + "reference": "e212b06996819a2bce026a63da03b7182d05a690" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/4459eef5298dedfb69f771186a580062b8516497", - "reference": "4459eef5298dedfb69f771186a580062b8516497", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/e212b06996819a2bce026a63da03b7182d05a690", + "reference": "e212b06996819a2bce026a63da03b7182d05a690", "shasum": "" }, "require": { @@ -1608,20 +2057,20 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2019-01-16T09:39:14+00:00" + "time": "2019-08-20T13:31:17+00:00" }, { "name": "symfony/console", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "15a9104356436cb26e08adab97706654799d31d8" + "reference": "17b154f932c5874cdbda6d05796b6490eec9f9f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/15a9104356436cb26e08adab97706654799d31d8", - "reference": "15a9104356436cb26e08adab97706654799d31d8", + "url": "https://api.github.com/repos/symfony/console/zipball/17b154f932c5874cdbda6d05796b6490eec9f9f7", + "reference": "17b154f932c5874cdbda6d05796b6490eec9f9f7", "shasum": "" }, "require": { @@ -1680,20 +2129,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-04-08T09:29:13+00:00" + "time": "2019-11-13T07:12:39+00:00" }, { "name": "symfony/debug", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "681afbb26488903c5ac15e63734f1d8ac430c9b9" + "reference": "f72e33fdb1170b326e72c3157f0cd456351dd086" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/681afbb26488903c5ac15e63734f1d8ac430c9b9", - "reference": "681afbb26488903c5ac15e63734f1d8ac430c9b9", + "url": "https://api.github.com/repos/symfony/debug/zipball/f72e33fdb1170b326e72c3157f0cd456351dd086", + "reference": "f72e33fdb1170b326e72c3157f0cd456351dd086", "shasum": "" }, "require": { @@ -1736,20 +2185,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-04-11T09:48:14+00:00" + "time": "2019-10-24T15:33:53+00:00" }, { "name": "symfony/dependency-injection", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "dee85a9148399cdb2731603802842bcfd8afe5ab" + "reference": "0ea4d39ca82409a25a43b61ce828048a90000920" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/dee85a9148399cdb2731603802842bcfd8afe5ab", - "reference": "dee85a9148399cdb2731603802842bcfd8afe5ab", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0ea4d39ca82409a25a43b61ce828048a90000920", + "reference": "0ea4d39ca82409a25a43b61ce828048a90000920", "shasum": "" }, "require": { @@ -1807,20 +2256,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-04-16T11:13:42+00:00" + "time": "2019-11-08T16:18:30+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a088aafcefb4eef2520a290ed82e4374092a6dff" + "reference": "f9031c22ec127d4a2450760f81a8677fe8a10177" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a088aafcefb4eef2520a290ed82e4374092a6dff", - "reference": "a088aafcefb4eef2520a290ed82e4374092a6dff", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f9031c22ec127d4a2450760f81a8677fe8a10177", + "reference": "f9031c22ec127d4a2450760f81a8677fe8a10177", "shasum": "" }, "require": { @@ -1870,20 +2319,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-04-02T08:51:52+00:00" + "time": "2019-10-24T15:33:53+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.4.27", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "fa02215233be8de1c2b44617088192f9e8db3512" + "reference": "9e4b3ac8fa3348b4811674d23de32d201de225ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fa02215233be8de1c2b44617088192f9e8db3512", - "reference": "fa02215233be8de1c2b44617088192f9e8db3512", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9e4b3ac8fa3348b4811674d23de32d201de225ce", + "reference": "9e4b3ac8fa3348b4811674d23de32d201de225ce", "shasum": "" }, "require": { @@ -1924,20 +2373,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-05-01T08:04:33+00:00" + "time": "2019-11-11T12:53:10+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "14fa41ccd38570b5e3120a3754bbaa144a15f311" + "reference": "e1764b3de00ec5636dd03d02fd44bcb1147d70d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/14fa41ccd38570b5e3120a3754bbaa144a15f311", - "reference": "14fa41ccd38570b5e3120a3754bbaa144a15f311", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e1764b3de00ec5636dd03d02fd44bcb1147d70d9", + "reference": "e1764b3de00ec5636dd03d02fd44bcb1147d70d9", "shasum": "" }, "require": { @@ -1946,7 +2395,8 @@ "symfony/debug": "^3.3.3|~4.0", "symfony/event-dispatcher": "~2.8|~3.0|~4.0", "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php56": "~1.8" }, "conflict": { "symfony/config": "<2.8", @@ -2013,20 +2463,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-04-17T15:57:07+00:00" + "time": "2019-11-13T08:44:50+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -2038,7 +2488,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2054,13 +2504,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -2071,20 +2521,20 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7" + "reference": "685968b11e61a347c18bf25db32effa478be610f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f037ea22acfaee983e271dd9c3b8bb4150bd8ad7", - "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f", + "reference": "685968b11e61a347c18bf25db32effa478be610f", "shasum": "" }, "require": { @@ -2096,7 +2546,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2130,20 +2580,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { @@ -2155,7 +2605,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2189,41 +2639,38 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.11.0", + "name": "symfony/polyfill-php56", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "bc4858fb611bda58719124ca079baff854149c89" + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", - "reference": "bc4858fb611bda58719124ca079baff854149c89", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" + "Symfony\\Polyfill\\Php56\\": "" }, "files": [ "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2240,7 +2687,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2248,37 +2695,41 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/process", - "version": "v3.4.26", + "name": "symfony/polyfill-php70", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "a9c4dfbf653023b668c282e4e02609d131f4057a" + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "54b4c428a0054e254223797d2713c31e08610831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/a9c4dfbf653023b668c282e4e02609d131f4057a", - "reference": "a9c4dfbf653023b668c282e4e02609d131f4057a", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", + "reference": "54b4c428a0054e254223797d2713c31e08610831", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Polyfill\\Php70\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2287,36 +2738,143 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", "homepage": "https://symfony.com", - "time": "2019-04-08T16:15:54+00:00" + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/psr-http-message-bridge", - "version": "v1.1.2", + "name": "symfony/polyfill-util", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "a33352af16f78a5ff4f9d90811536abf210df12b" + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "4317de1386717b4c22caed7725350a8887ab205c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/a33352af16f78a5ff4f9d90811536abf210df12b", - "reference": "a33352af16f78a5ff4f9d90811536abf210df12b", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", + "reference": "4317de1386717b4c22caed7725350a8887ab205c", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0", - "psr/http-message": "^1.0", - "symfony/http-foundation": "^2.3.42 || ^3.4 || ^4.0" + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/process", + "version": "v3.4.35", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c19da50bc3e8fa7d60628fdb4ab5d67de534cf3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c19da50bc3e8fa7d60628fdb4ab5d67de534cf3e", + "reference": "c19da50bc3e8fa7d60628fdb4ab5d67de534cf3e", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2019-10-24T15:33:53+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "a33352af16f78a5ff4f9d90811536abf210df12b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/a33352af16f78a5ff4f9d90811536abf210df12b", + "reference": "a33352af16f78a5ff4f9d90811536abf210df12b", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^2.3.42 || ^3.4 || ^4.0" }, "require-dev": { "symfony/phpunit-bridge": "^3.4 || ^4.0" @@ -2364,16 +2922,16 @@ }, { "name": "symfony/routing", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ff11aac46d6cb8a65f2855687bb9a1ac9d860eec" + "reference": "afc10b9c6b5196e0fecbc3bd373c7b4482e5b6b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ff11aac46d6cb8a65f2855687bb9a1ac9d860eec", - "reference": "ff11aac46d6cb8a65f2855687bb9a1ac9d860eec", + "url": "https://api.github.com/repos/symfony/routing/zipball/afc10b9c6b5196e0fecbc3bd373c7b4482e5b6b5", + "reference": "afc10b9c6b5196e0fecbc3bd373c7b4482e5b6b5", "shasum": "" }, "require": { @@ -2436,20 +2994,20 @@ "uri", "url" ], - "time": "2019-03-29T21:58:42+00:00" + "time": "2019-11-08T17:25:00+00:00" }, { "name": "symfony/serializer", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "14b3221cc41dcfef404205f0060cda873f43a534" + "reference": "9d14f7ff2c585a8a9f6f980253066285ddc2f675" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/14b3221cc41dcfef404205f0060cda873f43a534", - "reference": "14b3221cc41dcfef404205f0060cda873f43a534", + "url": "https://api.github.com/repos/symfony/serializer/zipball/9d14f7ff2c585a8a9f6f980253066285ddc2f675", + "reference": "9d14f7ff2c585a8a9f6f980253066285ddc2f675", "shasum": "" }, "require": { @@ -2472,7 +3030,7 @@ "symfony/dependency-injection": "~3.2|~4.0", "symfony/http-foundation": "~2.8|~3.0|~4.0", "symfony/property-access": "~2.8|~3.0|~4.0", - "symfony/property-info": "~3.1|~4.0", + "symfony/property-info": "^3.4.13|~4.0", "symfony/yaml": "~3.4|~4.0" }, "suggest": { @@ -2480,7 +3038,7 @@ "doctrine/cache": "For using the default cached annotation reader and metadata cache.", "psr/cache-implementation": "For using the metadata cache.", "symfony/config": "For using the XML mapping loader.", - "symfony/http-foundation": "To use the DataUriNormalizer.", + "symfony/http-foundation": "For using a MIME type guesser within the DataUriNormalizer.", "symfony/property-access": "For using the ObjectNormalizer.", "symfony/property-info": "To deserialize relations.", "symfony/yaml": "For using the default YAML mapping loader." @@ -2515,20 +3073,20 @@ ], "description": "Symfony Serializer Component", "homepage": "https://symfony.com", - "time": "2019-04-11T05:44:34+00:00" + "time": "2019-11-12T17:51:12+00:00" }, { "name": "symfony/translation", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "aae26f143da71adc8707eb489f1dc86aef7d376b" + "reference": "2031c895bc97ac1787d418d90bd1ed7d299f2772" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/aae26f143da71adc8707eb489f1dc86aef7d376b", - "reference": "aae26f143da71adc8707eb489f1dc86aef7d376b", + "url": "https://api.github.com/repos/symfony/translation/zipball/2031c895bc97ac1787d418d90bd1ed7d299f2772", + "reference": "2031c895bc97ac1787d418d90bd1ed7d299f2772", "shasum": "" }, "require": { @@ -2585,20 +3143,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2019-04-10T16:00:48+00:00" + "time": "2019-10-30T12:43:22+00:00" }, { "name": "symfony/validator", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "83da5259779aaf9dde220130e62b785f74e2ac49" + "reference": "b11f45742c5c9a228cedc46b70c6317780a1ac80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/83da5259779aaf9dde220130e62b785f74e2ac49", - "reference": "83da5259779aaf9dde220130e62b785f74e2ac49", + "url": "https://api.github.com/repos/symfony/validator/zipball/b11f45742c5c9a228cedc46b70c6317780a1ac80", + "reference": "b11f45742c5c9a228cedc46b70c6317780a1ac80", "shasum": "" }, "require": { @@ -2608,15 +3166,16 @@ "symfony/translation": "~2.8|~3.0|~4.0" }, "conflict": { + "doctrine/lexer": "<1.0.2", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/dependency-injection": "<3.3", "symfony/http-kernel": "<3.3.5", "symfony/yaml": "<3.4" }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "egulias/email-validator": "^1.2.8|~2.0", + "egulias/email-validator": "^2.1.10", "symfony/cache": "~3.1|~4.0", "symfony/config": "~2.8|~3.0|~4.0", "symfony/dependency-injection": "~3.3|~4.0", @@ -2670,20 +3229,20 @@ ], "description": "Symfony Validator Component", "homepage": "https://symfony.com", - "time": "2019-04-16T11:21:44+00:00" + "time": "2019-11-05T22:03:38+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.26", + "version": "v3.4.35", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996" + "reference": "dab657db15207879217fc81df4f875947bf68804" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/212a27b731e5bfb735679d1ffaac82bd6a1dc996", - "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996", + "url": "https://api.github.com/repos/symfony/yaml/zipball/dab657db15207879217fc81df4f875947bf68804", + "reference": "dab657db15207879217fc81df4f875947bf68804", "shasum": "" }, "require": { @@ -2729,35 +3288,35 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-03-25T07:48:46+00:00" + "time": "2019-10-24T15:33:53+00:00" }, { "name": "twig/twig", - "version": "v1.38.4", + "version": "v1.42.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "7732e9e7017d751313811bd118de61302e9c8b35" + "reference": "201baee843e0ffe8b0b956f336dd42b2a92fae4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/7732e9e7017d751313811bd118de61302e9c8b35", - "reference": "7732e9e7017d751313811bd118de61302e9c8b35", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/201baee843e0ffe8b0b956f336dd42b2a92fae4e", + "reference": "201baee843e0ffe8b0b956f336dd42b2a92fae4e", "shasum": "" }, "require": { - "php": ">=5.4.0", + "php": ">=5.5.0", "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "psr/container": "^1.0", - "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8" + "symfony/debug": "^3.4|^4.2", + "symfony/phpunit-bridge": "^4.4@dev|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.38-dev" + "dev-master": "1.42-dev" } }, "autoload": { @@ -2779,15 +3338,15 @@ "homepage": "http://fabien.potencier.org", "role": "Lead Developer" }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - }, { "name": "Twig Team", "homepage": "https://twig.symfony.com/contributors", "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" } ], "description": "Twig, the flexible, fast, and secure template language for PHP", @@ -2795,35 +3354,39 @@ "keywords": [ "templating" ], - "time": "2019-03-23T14:27:19+00:00" + "time": "2019-08-24T12:51:03+00:00" }, { "name": "typo3/phar-stream-wrapper", - "version": "v2.1.2", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/TYPO3/phar-stream-wrapper.git", - "reference": "057622f5a3b92a5ffbea0fbaadce573500a62870" + "reference": "586ff60beea128e069a5dc271d3d8133a9bc460a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/TYPO3/phar-stream-wrapper/zipball/057622f5a3b92a5ffbea0fbaadce573500a62870", - "reference": "057622f5a3b92a5ffbea0fbaadce573500a62870", + "url": "https://api.github.com/repos/TYPO3/phar-stream-wrapper/zipball/586ff60beea128e069a5dc271d3d8133a9bc460a", + "reference": "586ff60beea128e069a5dc271d3d8133a9bc460a", "shasum": "" }, "require": { - "brumann/polyfill-unserialize": "^1.0", "ext-json": "*", - "php": "^5.3.3|^7.0" + "php": "^7.0" }, "require-dev": { "ext-xdebug": "*", - "phpunit/phpunit": "^4.8.36" + "phpunit/phpunit": "^6.5" }, "suggest": { "ext-fileinfo": "For PHP builtin file type guessing, otherwise uses internal processing" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "v3.x-dev" + } + }, "autoload": { "psr-4": { "TYPO3\\PharStreamWrapper\\": "src/" @@ -2841,74 +3404,25 @@ "security", "stream-wrapper" ], - "time": "2019-05-14T13:14:31+00:00" - }, - { - "name": "wikimedia/composer-merge-plugin", - "version": "v1.4.1", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/composer-merge-plugin.git", - "reference": "81c6ac72a24a67383419c7eb9aa2b3437f2ab100" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/81c6ac72a24a67383419c7eb9aa2b3437f2ab100", - "reference": "81c6ac72a24a67383419c7eb9aa2b3437f2ab100", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0", - "php": ">=5.3.2" - }, - "require-dev": { - "composer/composer": "~1.0.0", - "jakub-onderka/php-parallel-lint": "~0.8", - "phpunit/phpunit": "~4.8|~5.0", - "squizlabs/php_codesniffer": "~2.1.0" - }, - "type": "composer-plugin", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - }, - "class": "Wikimedia\\Composer\\MergePlugin" - }, - "autoload": { - "psr-4": { - "Wikimedia\\Composer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bryan Davis", - "email": "bd808@wikimedia.org" - } - ], - "description": "Composer plugin to merge multiple composer.json files", - "time": "2017-04-25T02:31:25+00:00" + "time": "2019-10-18T11:57:16+00:00" }, { "name": "zendframework/zend-diactoros", - "version": "1.4.1", + "version": "1.8.7", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "424a840dc3bedcdeea510b42e056c77c2d6c4bef" + "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/424a840dc3bedcdeea510b42e056c77c2d6c4bef", - "reference": "424a840dc3bedcdeea510b42e056c77c2d6c4bef", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/a85e67b86e9b8520d07e6415fcbcb8391b44a75b", + "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0", - "psr/http-message": "~1.0" + "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0" }, "provide": { "psr/http-message-implementation": "1.0" @@ -2916,17 +3430,27 @@ "require-dev": { "ext-dom": "*", "ext-libxml": "*", - "phpunit/phpunit": "^4.6 || ^5.5", - "zendframework/zend-coding-standard": "~1.0.0" + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", + "zendframework/zend-coding-standard": "~1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev", - "dev-develop": "1.5-dev" + "dev-release-1.8": "1.8.x-dev" } }, "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], "psr-4": { "Zend\\Diactoros\\": "src/" } @@ -2942,34 +3466,34 @@ "psr", "psr-7" ], - "time": "2017-08-17T21:21:00+00:00" + "time": "2019-08-06T17:53:53+00:00" }, { "name": "zendframework/zend-escaper", - "version": "2.5.2", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-escaper.git", - "reference": "2dcd14b61a72d8b8e27d579c6344e12c26141d4e" + "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/2dcd14b61a72d8b8e27d579c6344e12c26141d4e", - "reference": "2dcd14b61a72d8b8e27d579c6344e12c26141d4e", + "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", + "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^5.6 || ^7.0" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" } }, "autoload": { @@ -2981,55 +3505,58 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-escaper", + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", "keywords": [ + "ZendFramework", "escaper", - "zf2" + "zf" ], - "time": "2016-06-30T19:48:38+00:00" + "time": "2019-09-05T20:03:20+00:00" }, { "name": "zendframework/zend-feed", - "version": "2.7.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-feed.git", - "reference": "12b328d382aa5200f1de53d4147033b885776b67" + "reference": "d926c5af34b93a0121d5e2641af34ddb1533d733" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/12b328d382aa5200f1de53d4147033b885776b67", - "reference": "12b328d382aa5200f1de53d4147033b885776b67", + "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/d926c5af34b93a0121d5e2641af34ddb1533d733", + "reference": "d926c5af34b93a0121d5e2641af34ddb1533d733", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.6 || ^7.0", + "zendframework/zend-escaper": "^2.5.2", + "zendframework/zend-stdlib": "^3.2.1" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "psr/http-message": "^1.0", - "zendframework/zend-cache": "^2.5", - "zendframework/zend-db": "^2.5", - "zendframework/zend-http": "^2.5", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.5" + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-message": "^1.0.1", + "zendframework/zend-cache": "^2.7.2", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-db": "^2.8.2", + "zendframework/zend-http": "^2.7", + "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", + "zendframework/zend-validator": "^2.10.1" }, "suggest": { - "psr/http-message": "PSR-7 ^1.0, if you wish to use Zend\\Feed\\Reader\\Http\\Psr7ResponseDecorator", + "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Zend\\Feed\\Reader\\Http\\Psr7ResponseDecorator", "zendframework/zend-cache": "Zend\\Cache component, for optionally caching feeds between requests", "zendframework/zend-db": "Zend\\Db component, for use with PubSubHubbub", "zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader", "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for easily extending ExtensionManager implementations", - "zendframework/zend-validator": "Zend\\Validator component, for validating email addresses used in Atom feeds and entries ehen using the Writer subcomponent" + "zendframework/zend-validator": "Zend\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "2.8-dev" + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" } }, "autoload": { @@ -3042,40 +3569,40 @@ "BSD-3-Clause" ], "description": "provides functionality for consuming RSS and Atom feeds", - "homepage": "https://github.com/zendframework/zend-feed", "keywords": [ + "ZendFramework", "feed", - "zf2" + "zf" ], - "time": "2016-02-11T18:54:29+00:00" + "time": "2019-03-05T20:08:49+00:00" }, { "name": "zendframework/zend-stdlib", - "version": "3.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "8bafa58574204bdff03c275d1d618aaa601588ae" + "reference": "66536006722aff9e62d1b331025089b7ec71c065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/8bafa58574204bdff03c275d1d618aaa601588ae", - "reference": "8bafa58574204bdff03c275d1d618aaa601588ae", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/66536006722aff9e62d1b331025089b7ec71c065", + "reference": "66536006722aff9e62d1b331025089b7ec71c065", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "athletic/athletic": "~0.1", - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev", - "dev-develop": "3.1-dev" + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" } }, "autoload": { @@ -3087,1887 +3614,24 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-stdlib", + "description": "SPL extensions, array utilities, error handlers, and more", "keywords": [ + "ZendFramework", "stdlib", - "zf2" - ], - "time": "2016-04-12T21:19:36+00:00" - } - ], - "packages-dev": [ - { - "name": "behat/mink", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/minkphp/Mink.git", - "reference": "9ea1cebe3dc529ba3861d87c818f045362c40484" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/minkphp/Mink/zipball/9ea1cebe3dc529ba3861d87c818f045362c40484", - "reference": "9ea1cebe3dc529ba3861d87c818f045362c40484", - "shasum": "" - }, - "require": { - "php": ">=5.3.1", - "symfony/css-selector": "~2.1|~3.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7|~3.0" - }, - "suggest": { - "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", - "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", - "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", - "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Browser controller/emulator abstraction for PHP", - "homepage": "http://mink.behat.org/", - "keywords": [ - "browser", - "testing", - "web" - ], - "time": "2017-02-06T09:59:54+00:00" - }, - { - "name": "behat/mink-browserkit-driver", - "version": "1.3.3", - "source": { - "type": "git", - "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", - "reference": "1b9a7ce903cfdaaec5fb32bfdbb26118343662eb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/1b9a7ce903cfdaaec5fb32bfdbb26118343662eb", - "reference": "1b9a7ce903cfdaaec5fb32bfdbb26118343662eb", - "shasum": "" - }, - "require": { - "behat/mink": "^1.7.1@dev", - "php": ">=5.3.6", - "symfony/browser-kit": "~2.3|~3.0|~4.0", - "symfony/dom-crawler": "~2.3|~3.0|~4.0" - }, - "require-dev": { - "mink/driver-testsuite": "dev-master", - "symfony/http-kernel": "~2.3|~3.0|~4.0" - }, - "type": "mink-driver", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\Driver\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Symfony2 BrowserKit driver for Mink framework", - "homepage": "http://mink.behat.org/", - "keywords": [ - "Mink", - "Symfony2", - "browser", - "testing" - ], - "time": "2018-05-02T09:25:31+00:00" - }, - { - "name": "behat/mink-goutte-driver", - "version": "v1.2.1", - "source": { - "type": "git", - "url": "https://github.com/minkphp/MinkGoutteDriver.git", - "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", - "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", - "shasum": "" - }, - "require": { - "behat/mink": "~1.6@dev", - "behat/mink-browserkit-driver": "~1.2@dev", - "fabpot/goutte": "~1.0.4|~2.0|~3.1", - "php": ">=5.3.1" - }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7|~3.0" - }, - "type": "mink-driver", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\Driver\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Goutte driver for Mink framework", - "homepage": "http://mink.behat.org/", - "keywords": [ - "browser", - "goutte", - "headless", - "testing" + "zf" ], - "time": "2016-03-05T09:04:22+00:00" - }, - { - "name": "behat/mink-selenium2-driver", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/minkphp/MinkSelenium2Driver.git", - "reference": "93474c65a2a7bf959200ab5f7a14cc450645c185" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/93474c65a2a7bf959200ab5f7a14cc450645c185", - "reference": "93474c65a2a7bf959200ab5f7a14cc450645c185", - "shasum": "" - }, - "require": { - "behat/mink": "~1.7@dev", - "instaclick/php-webdriver": "~1.1", - "php": ">=5.3.1" - }, - "require-dev": { - "mink/driver-testsuite": "dev-master" - }, - "type": "mink-driver", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\Driver\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Pete Otaqui", - "email": "pete@otaqui.com", - "homepage": "https://github.com/pete-otaqui" - } - ], - "description": "Selenium2 (WebDriver) driver for Mink framework", - "homepage": "http://mink.behat.org/", - "keywords": [ - "ajax", - "browser", - "javascript", - "selenium", - "testing", - "webdriver" - ], - "time": "2018-01-07T19:17:08+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14T21:17:01+00:00" - }, - { - "name": "drupal/coder", - "version": "8.3.1", - "source": { - "type": "git", - "url": "https://git.drupal.org/project/coder.git", - "reference": "29a25627e7148b3119c84f18e087fc3b8c85b959" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.4.0", - "squizlabs/php_codesniffer": "^3.0.1", - "symfony/yaml": ">=2.0.0" - }, - "require-dev": { - "phpunit/phpunit": ">=3.7 <6" - }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-0": { - "Drupal\\": "coder_sniffer/Drupal/", - "DrupalPractice\\": "coder_sniffer/Drupal/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0+" - ], - "description": "Coder is a library to review Drupal code.", - "homepage": "https://www.drupal.org/project/coder", - "keywords": [ - "code review", - "phpcs", - "standards" - ], - "time": "2018-09-21T14:22:49+00:00" - }, - { - "name": "fabpot/goutte", - "version": "v3.2.3", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfPHP/Goutte.git", - "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/3f0eaf0a40181359470651f1565b3e07e3dd31b8", - "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "^6.0", - "php": ">=5.5.0", - "symfony/browser-kit": "~2.1|~3.0|~4.0", - "symfony/css-selector": "~2.1|~3.0|~4.0", - "symfony/dom-crawler": "~2.1|~3.0|~4.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "^3.3 || ^4" - }, - "type": "application", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "psr-4": { - "Goutte\\": "Goutte" - }, - "exclude-from-classmap": [ - "Goutte/Tests" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "A simple PHP Web Scraper", - "homepage": "https://github.com/FriendsOfPHP/Goutte", - "keywords": [ - "scraper" - ], - "time": "2018-06-29T15:13:57+00:00" - }, - { - "name": "instaclick/php-webdriver", - "version": "1.4.5", - "source": { - "type": "git", - "url": "https://github.com/instaclick/php-webdriver.git", - "reference": "6fa959452e774dcaed543faad3a9d1a37d803327" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/6fa959452e774dcaed543faad3a9d1a37d803327", - "reference": "6fa959452e774dcaed543faad3a9d1a37d803327", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "^4.8", - "satooshi/php-coveralls": "^1.0||^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-0": { - "WebDriver": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Justin Bishop", - "email": "jubishop@gmail.com", - "role": "Developer" - }, - { - "name": "Anthon Pang", - "email": "apang@softwaredevelopment.ca", - "role": "Fork Maintainer" - } - ], - "description": "PHP WebDriver for Selenium 2", - "homepage": "http://instaclick.com/", - "keywords": [ - "browser", - "selenium", - "webdriver", - "webtest" - ], - "time": "2017-06-30T04:02:48+00:00" - }, - { - "name": "ircmaxell/password-compat", - "version": "v1.0.4", - "source": { - "type": "git", - "url": "https://github.com/ircmaxell/password_compat.git", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "4.*" - }, - "type": "library", - "autoload": { - "files": [ - "lib/password.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anthony Ferrara", - "email": "ircmaxell@php.net", - "homepage": "http://blog.ircmaxell.com" - } - ], - "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", - "homepage": "https://github.com/ircmaxell/password_compat", - "keywords": [ - "hashing", - "password" - ], - "time": "2014-11-20T16:49:30+00:00" - }, - { - "name": "jcalderonzumba/gastonjs", - "version": "v1.0.2", - "source": { - "type": "git", - "url": "https://github.com/jcalderonzumba/gastonjs.git", - "reference": "21bebb8ca03eb0f93ec2f3fad61192fb079e2622" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jcalderonzumba/gastonjs/zipball/21bebb8ca03eb0f93ec2f3fad61192fb079e2622", - "reference": "21bebb8ca03eb0f93ec2f3fad61192fb079e2622", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "~5.0|~6.0", - "php": ">=5.4" - }, - "require-dev": { - "phpunit/phpunit": "~4.6", - "silex/silex": "~1.2", - "symfony/phpunit-bridge": "~2.7", - "symfony/process": "~2.1" - }, - "type": "phantomjs-api", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Zumba\\GastonJS\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Juan Francisco Calderón Zumba", - "email": "juanfcz@gmail.com", - "homepage": "http://github.com/jcalderonzumba" - } - ], - "description": "PhantomJS API based server for webpage automation", - "homepage": "https://github.com/jcalderonzumba/gastonjs", - "keywords": [ - "api", - "automation", - "browser", - "headless", - "phantomjs" - ], - "time": "2016-01-18T09:21:03+00:00" - }, - { - "name": "jcalderonzumba/mink-phantomjs-driver", - "version": "v0.3.2", - "source": { - "type": "git", - "url": "https://github.com/jcalderonzumba/MinkPhantomJSDriver.git", - "reference": "194942e14557b86467bf31e313f1370645d6c828" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jcalderonzumba/MinkPhantomJSDriver/zipball/194942e14557b86467bf31e313f1370645d6c828", - "reference": "194942e14557b86467bf31e313f1370645d6c828", - "shasum": "" - }, - "require": { - "behat/mink": "~1.7", - "jcalderonzumba/gastonjs": "~1.0", - "php": ">=5.4", - "twig/twig": "~1.20|~2.0" - }, - "require-dev": { - "mink/driver-testsuite": "dev-master", - "phpunit/phpunit": "~4.6" - }, - "type": "mink-driver", - "extra": { - "branch-alias": { - "dev-master": "0.4.x-dev" - } - }, - "autoload": { - "psr-4": { - "Zumba\\Mink\\Driver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Juan Francisco Calderón Zumba", - "email": "juanfcz@gmail.com", - "homepage": "http://github.com/jcalderonzumba" - } - ], - "description": "PhantomJS driver for Mink framework", - "homepage": "http://mink.behat.org/", - "keywords": [ - "ajax", - "browser", - "headless", - "javascript", - "phantomjs", - "testing" - ], - "time": "2016-10-04T09:27:04+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "5.2.8", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4", - "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "time": "2019-01-14T23:55:14+00:00" - }, - { - "name": "mikey179/vfsStream", - "version": "v1.6.5", - "source": { - "type": "git", - "url": "https://github.com/bovigo/vfsStream.git", - "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", - "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "org\\bovigo\\vfs\\": "src/main/php" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Frank Kleine", - "homepage": "http://frankkleine.de/", - "role": "Developer" - } - ], - "description": "Virtual file system to mock the real file system in unit tests.", - "homepage": "http://vfs.bovigo.org/", - "time": "2017-08-01T08:02:14+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" - } - ], - "time": "2015-02-03T12:10:50+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1|^2.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2017-03-02T20:05:34+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "2.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2015-10-06T15:47:00+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2017-11-27T13:52:08+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.9", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2017-02-26T11:10:40+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.12", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2017-12-04T08:55:13+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "4.8.36", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", - "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "~2.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.2.2", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.1|~3.0" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.8.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2017-06-21T08:07:12+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "2.3.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "abandoned": true, - "time": "2015-10-02T06:51:40+00:00" - }, - { - "name": "sebastian/comparator", - "version": "1.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2017-01-29T09:50:25+00:00" - }, - { - "name": "sebastian/diff", - "version": "1.4.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2017-05-22T07:24:03+00:00" - }, - { - "name": "sebastian/environment", - "version": "1.3.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2016-08-18T05:49:44+00:00" - }, - { - "name": "sebastian/exporter", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2016-06-17T09:04:28+00:00" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12T03:26:01+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-10-03T07:41:43+00:00" - }, - { - "name": "sebastian/version", - "version": "1.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "shasum": "" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21T13:59:46+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.4.1", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5b4333b4010625d29580eb4a41f1e53251be6baa", - "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2019-03-19T03:22:27+00:00" - }, - { - "name": "symfony/browser-kit", - "version": "v3.4.26", - "source": { - "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "7f2b0843d5045468225f9a9b27a0cb171ae81828" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/7f2b0843d5045468225f9a9b27a0cb171ae81828", - "reference": "7f2b0843d5045468225f9a9b27a0cb171ae81828", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/dom-crawler": "~2.8|~3.0|~4.0" - }, - "require-dev": { - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony BrowserKit Component", - "homepage": "https://symfony.com", - "time": "2019-04-06T19:33:58+00:00" - }, - { - "name": "symfony/css-selector", - "version": "v3.4.26", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/8ca29297c29b64fb3a1a135e71cb25f67f9fdccf", - "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2019-01-16T09:39:14+00:00" - }, - { - "name": "symfony/dom-crawler", - "version": "v3.4.26", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "d40023c057393fb25f7ca80af2a56ed948c45a09" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d40023c057393fb25f7ca80af2a56ed948c45a09", - "reference": "d40023c057393fb25f7ca80af2a56ed948c45a09", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "symfony/css-selector": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com", - "time": "2019-02-23T15:06:07+00:00" - }, - { - "name": "symfony/phpunit-bridge", - "version": "v3.4.26", - "source": { - "type": "git", - "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "a43a2f6c465a2d99635fea0addbebddc3864ad97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/a43a2f6c465a2d99635fea0addbebddc3864ad97", - "reference": "a43a2f6c465a2d99635fea0addbebddc3864ad97", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" - }, - "suggest": { - "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" - }, - "bin": [ - "bin/simple-phpunit" - ], - "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - }, - "thanks": { - "name": "phpunit/phpunit", - "url": "https://github.com/sebastianbergmann/phpunit" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Bridge\\PhpUnit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony PHPUnit Bridge", - "homepage": "https://symfony.com", - "time": "2019-04-16T09:03:16+00:00" + "time": "2018-08-28T21:34:05+00:00" } ], + "packages-dev": [], "aliases": [], "minimum-stability": "dev", - "stability-flags": { - "behat/mink": 20, - "behat/mink-selenium2-driver": 20 - }, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, - "platform": { - "ext-date": "*", - "ext-dom": "*", - "ext-filter": "*", - "ext-gd": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-pdo": "*", - "ext-session": "*", - "ext-simplexml": "*", - "ext-spl": "*", - "ext-tokenizer": "*", - "ext-xml": "*", - "php": "^5.5.9|>=7.0.8" - }, - "platform-dev": [] + "platform": [], + "platform-dev": [], + "platform-overrides": { + "php": "7.0.8" + } } diff --git a/core/.eslintrc.json b/core/.eslintrc.json index e3d505cad..a140e5bcf 100644 --- a/core/.eslintrc.json +++ b/core/.eslintrc.json @@ -19,6 +19,8 @@ "matchMedia": true, "Backbone": true, "Modernizr": true, + "Popper": true, + "Sortable": true, "CKEDITOR": true }, "rules": { diff --git a/core/.stylelintignore b/core/.stylelintignore new file mode 100644 index 000000000..d70b0031c --- /dev/null +++ b/core/.stylelintignore @@ -0,0 +1,2 @@ +themes/claro/**/*.css +!themes/claro/**/*.pcss.css diff --git a/core/.stylelintrc.json b/core/.stylelintrc.json index 6da50f08a..2d003dcc9 100644 --- a/core/.stylelintrc.json +++ b/core/.stylelintrc.json @@ -1,7 +1,8 @@ { "extends": "stylelint-config-standard", "plugins": [ - "stylelint-no-browser-hacks/lib" + "stylelint-no-browser-hacks/lib", + "stylelint-order" ], "rules": { "comment-empty-line-before": null, @@ -17,6 +18,409 @@ ] }], "number-leading-zero": "always", + "order/order": [ + "custom-properties", + "dollar-variables", + { + "type": "at-rule", + "hasBlock": false + }, + "declarations", + "rules", + { + "type": "at-rule", + "hasBlock": true + } + ], + "order/properties-order": [ + "position", + "z-index", + "top", + "right", + "bottom", + "left", + + "display", + "visibility", + "float", + "clear", + "overflow", + "overflow-x", + "overflow-y", + "-ms-overflow-x", + "-ms-overflow-y", + "-webkit-overflow-scrolling", + "clip", + "zoom", + + "flex", + "flex-flow", + "flex-direction", + "flex-wrap", + "flex-basis", + "flex-grow", + "flex-shrink", + "flex-order", + "flex-pack", + + "-ms-grid", + "grid", + "grid-area", + "grid-template", + "grid-template-areas", + "-ms-grid-rows", + "grid-template-rows", + "-ms-grid-columns", + "grid-template-columns", + "grid-row", + "-ms-grid-row", + "grid-row-start", + "grid-row-end", + "grid-column", + "-ms-grid-column", + "grid-column-start", + "grid-column-end", + "grid-auto-rows", + "grid-auto-columns", + "grid-auto-flow", + "grid-gap", + "grid-row-gap", + "grid-column-gap", + "-ms-grid-row-align", + "-ms-grid-column-align", + + "place-content", + "place-items", + "align-content", + "align-items", + "align-self", + "justify-content", + "justify-items", + "justify-self", + + "order", + + "-webkit-box-sizing", + "-moz-box-sizing", + "box-sizing", + "width", + "min-width", + "max-width", + "height", + "min-height", + "max-height", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + + "table-layout", + "-webkit-columns", + "-moz-columns", + "columns", + "-webkit-column-span", + "-moz-column-span", + "column-span", + "-webkit-column-width", + "-moz-column-width", + "column-width", + "-webkit-column-count", + "-moz-column-count", + "column-count", + "-webkit-column-fill", + "-moz-column-fill", + "column-fill", + "-webkit-column-gap", + "-moz-column-gap", + "column-gap", + "-webkit-column-rule", + "-moz-column-rule", + "column-rule", + "-webkit-column-rule-width", + "-moz-column-rule-width", + "column-rule-width", + "-webkit-column-rule-style", + "-moz-column-rule-style", + "column-rule-style", + "-webkit-column-rule-color", + "-moz-column-rule-color", + "column-rule-color", + "empty-cells", + "caption-side", + "border-spacing", + "border-collapse", + "$counter-style", + "list-style", + "list-style-position", + "list-style-type", + "list-style-image", + + "content", + "quotes", + "counter-reset", + "counter-increment", + "resize", + "cursor", + "-webkit-user-select", + "-moz-user-select", + "-ms-user-select", + "user-select", + "nav-index", + "nav-up", + "nav-right", + "nav-down", + "nav-left", + "-webkit-transition", + "-moz-transition", + "-ms-transition", + "-o-transition", + "transition", + "-webkit-transition-delay", + "-moz-transition-delay", + "-ms-transition-delay", + "-o-transition-delay", + "transition-delay", + "-webkit-transition-timing-function", + "-moz-transition-timing-function", + "-ms-transition-timing-function", + "-o-transition-timing-function", + "transition-timing-function", + "-webkit-transition-duration", + "-moz-transition-duration", + "-ms-transition-duration", + "-o-transition-duration", + "transition-duration", + "-webkit-transition-property", + "-moz-transition-property", + "-ms-transition-property", + "-o-transition-property", + "transition-property", + "-webkit-transform", + "-moz-transform", + "-ms-transform", + "-o-transform", + "transform", + "-webkit-transform-origin", + "-moz-transform-origin", + "-ms-transform-origin", + "-o-transform-origin", + "transform-origin", + "$keyframes", + "-webkit-animation", + "-moz-animation", + "-ms-animation", + "-o-animation", + "animation", + "-webkit-animation-name", + "-moz-animation-name", + "-ms-animation-name", + "-o-animation-name", + "animation-name", + "-webkit-animation-duration", + "-moz-animation-duration", + "-ms-animation-duration", + "-o-animation-duration", + "animation-duration", + "-webkit-animation-play-state", + "-moz-animation-play-state", + "-ms-animation-play-state", + "-o-animation-play-state", + "animation-play-state", + "-webkit-animation-timing-function", + "-moz-animation-timing-function", + "-ms-animation-timing-function", + "-o-animation-timing-function", + "animation-timing-function", + "-webkit-animation-delay", + "-moz-animation-delay", + "-ms-animation-delay", + "-o-animation-delay", + "animation-delay", + "-webkit-animation-iteration-count", + "-moz-animation-iteration-count", + "-ms-animation-iteration-count", + "-o-animation-iteration-count", + "animation-iteration-count", + "-webkit-animation-direction", + "-moz-animation-direction", + "-ms-animation-direction", + "-o-animation-direction", + "animation-direction", + "text-align", + "-webkit-text-align-last", + "-moz-text-align-last", + "-ms-text-align-last", + "text-align-last", + "vertical-align", + "white-space", + "text-decoration", + "text-emphasis", + "text-emphasis-color", + "text-emphasis-style", + "text-emphasis-position", + "text-indent", + "-ms-text-justify", + "text-justify", + "text-transform", + "letter-spacing", + "word-spacing", + "-ms-writing-mode", + "text-outline", + "text-transform", + "text-wrap", + "text-overflow", + "-ms-text-overflow", + "text-overflow-ellipsis", + "text-overflow-mode", + "-ms-word-wrap", + "word-wrap", + "word-break", + "-ms-word-break", + "-moz-tab-size", + "-o-tab-size", + "tab-size", + "-webkit-hyphens", + "-moz-hyphens", + "hyphens", + "pointer-events", + "direction", + "unicode-bidi", + "orphans", + "widows", + "object-fit", + "object-position", + + "opacity", + "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", + "-webkit-filter", + "-ms-filter", + "filter", + "-ms-interpolation-mode", + "color", + "border", + "border-collapse", + "border-width", + "border-style", + "border-color", + "border-top", + "border-top-width", + "border-top-style", + "border-top-color", + "border-right", + "border-right-width", + "border-right-style", + "border-right-color", + "border-bottom", + "border-bottom-width", + "border-bottom-style", + "border-bottom-color", + "border-left", + "border-left-width", + "border-left-style", + "border-left-color", + "-webkit-border-radius", + "-moz-border-radius", + "border-radius", + "-webkit-border-top-left-radius", + "-moz-border-radius-topleft", + "border-top-left-radius", + "-webkit-border-top-right-radius", + "-moz-border-radius-topright", + "border-top-right-radius", + "-webkit-border-bottom-right-radius", + "-moz-border-radius-bottomright", + "border-bottom-right-radius", + "-webkit-border-bottom-left-radius", + "-moz-border-radius-bottomleft", + "border-bottom-left-radius", + "-webkit-border-image", + "-moz-border-image", + "-o-border-image", + "border-image", + "-webkit-border-image-source", + "-moz-border-image-source", + "-o-border-image-source", + "border-image-source", + "-webkit-border-image-slice", + "-moz-border-image-slice", + "-o-border-image-slice", + "border-image-slice", + "-webkit-border-image-width", + "-moz-border-image-width", + "-o-border-image-width", + "border-image-width", + "-webkit-border-image-outset", + "-moz-border-image-outset", + "-o-border-image-outset", + "border-image-outset", + "-webkit-border-image-repeat", + "-moz-border-image-repeat", + "-o-border-image-repeat", + "border-image-repeat", + "outline", + "outline-width", + "outline-style", + "outline-color", + "outline-offset", + "background", + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", + "background-color", + "background-image", + "background-repeat", + "background-attachment", + "background-position", + "background-position-x", + "-ms-background-position-x", + "background-position-y", + "-ms-background-position-y", + "-webkit-background-clip", + "-moz-background-clip", + "background-clip", + "background-origin", + "-webkit-background-size", + "-moz-background-size", + "-o-background-size", + "background-size", + "box-decoration-break", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "filter:progid:DXImageTransform.Microsoft.gradient", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", + "text-shadow", + + "$font-face", + "font", + "font-family", + "src", + "$font-feature-values", + "$swash", + "$annotation", + "$ornaments", + "$stylistic", + "$styleset", + "$character-variant", + "font-variant-alternates", + "font-size", + "font-weight", + "font-style", + "font-variant", + "font-size-adjust", + "font-stretch", + "font-effect", + "font-emphasize", + "font-emphasize-position", + "font-emphasize-style", + "font-smooth", + "line-height" + ], "plugin/no-browser-hacks": [true, { "browsers": [ "ie >= 9", @@ -36,6 +440,7 @@ }, "ignoreFiles": [ "assets/vendor/**/*.css", - "tests/Drupal/Tests/Core/Asset/css_test_files/**/*.css" + "tests/Drupal/Tests/Core/Asset/css_test_files/**/*.css", + "modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css" ] } diff --git a/core/INSTALL.sqlite.txt b/core/INSTALL.sqlite.txt index adff4682e..e22aaa89c 100644 --- a/core/INSTALL.sqlite.txt +++ b/core/INSTALL.sqlite.txt @@ -2,11 +2,8 @@ SQLITE REQUIREMENTS ------------------- -To use SQLite with your Drupal installation, the following requirements must be -met: Server has PHP 5.3.10 or later with PDO, and the PDO SQLite driver must be -enabled. - -If you have not pdo_sqlite available depending on your system there are different ways to install it. +PHP's PDO SQLite driver must be enabled. If you do not have pdo_sqlite +available, depending on your system there are different ways to install it. Windows ------- diff --git a/core/INSTALL.txt b/core/INSTALL.txt index 22cd8ceca..5dbc9e023 100644 --- a/core/INSTALL.txt +++ b/core/INSTALL.txt @@ -15,7 +15,7 @@ QUICKSTART ---------------------- Prerequisites: -- PHP 5.5.9 (or greater) (https://php.net). +- PHP 7.0.8 (or greater) (https://php.net). In the instructions below, replace the version x.y.z with the specific version you wish to download. Example: 8.6.0.zip. You can find the latest stable version @@ -48,8 +48,8 @@ Drupal requires: - A web server with PHP support, for example: - Apache 2.0 (or greater) (http://httpd.apache.org/). - Nginx 1.1 (or greater) (http://nginx.com/). -- PHP 5.5.9 (or greater) (http://php.net/). For better security support it is - recommended to update to at least 5.5.21 or 5.6.5. +- PHP 7.0.8 (or greater) (http://php.net/). For better security support it is + recommended to update to at least 7.2.17. - One of the following databases: - MySQL 5.5.3 (or greater) (http://www.mysql.com/). - MariaDB 5.5.20 (or greater) (https://mariadb.org/). MariaDB is a fully @@ -93,10 +93,6 @@ OPTIONAL SERVER REQUIREMENTS configuration allows the web server to initiate outbound connections. Most web hosting setups allow this. -- PHP 5.5.21 provides features for improved security when used with MySQL. While - this is not required, it is highly encouraged to use PHP 5.5.21 or 5.6.5 and - above. - INSTALLATION ------------ diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 8a6c98b37..93a78aa38 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -27,9 +27,9 @@ Framework managers Backend - Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia - Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch + - Francesco Placella 'plach' https://www.drupal.org/u/plach - Alex Pott 'alexpott' https://www.drupal.org/u/alexpott - Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan - - (provisional) Francesco Placella 'plach' https://www.drupal.org/u/plach Frontend - Lauri Eskola 'lauriii' https://www.drupal.org/u/lauriii @@ -39,6 +39,8 @@ Release managers - Chris McCafferty 'cilefen' https://www.drupal.org/u/cilefen - Jess Myrbo 'xjm' https://www.drupal.org/u/xjm +Committer team facilitators +- (provisional) Pamela Barone 'pameeela' https://www.drupal.org/u/pameeela Subsystem maintainers --------------------- @@ -210,7 +212,7 @@ Field UI - Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu File -- ? +- Kim Pepper 'kim.pepper' https://www.drupal.org/u/kimpepper Filter - ? @@ -231,6 +233,10 @@ Hypertext Application Language (HAL) Help - ? +Help Topics +- Amber Matz 'Amber Himes Matz' https://www.drupal.org/u/amber-himes-matz +- Andrey Postnikov 'andypost' https://www.drupal.org/u/andypost + Image - Claudiu Cristea 'claudiu.cristea' https://www.drupal.org/u/claudiu.cristea @@ -245,7 +251,6 @@ Interface Translation (locale) JavaScript - Théodore Biadala 'nod_' https://www.drupal.org/u/nod_ -- Matthew Grill 'alwaysworking' https://www.drupal.org/u/alwaysworking - Sally Young 'justafish' https://www.drupal.org/u/justafish JSON:API @@ -280,6 +285,10 @@ Media - Christian Fritsch 'chr.fritsch' https://www.drupal.org/u/chr.fritsch - Adam Globus-Hoenich 'phenaproxima' https://www.drupal.org/u/phenaproxima + Media Library + - Sean Blommaert 'seanB' https://www.drupal.org/u/seanb + - Adam Globus-Hoenich 'phenaproxima' https://www.drupal.org/u/phenaproxima + Menu - Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner - Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin @@ -315,6 +324,9 @@ Page Cache Path - Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch +Path Alias +- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch + Plugin - Kris Vanderwater 'EclipseGc' https://www.drupal.org/u/eclipseGc - Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia @@ -445,6 +457,7 @@ Topic maintainers ----------------- Accessibility +- Rain Breaw Michaels 'rainbreaw' https://www.drupal.org/u/rainbreaw - Mike Gifford 'mgifford' https://www.drupal.org/u/mgifford - Andrew Macpherson 'andrewmacpherson' https://www.drupal.org/u/andrewmacpherson @@ -495,7 +508,6 @@ API-first Initiative Admin UI & JavaScript Modernisation Initiative - Angela Byron 'webchick' https://www.drupal.org/u/webchick - Cristina Chumillas 'ckrina' https://www.drupal.org/u/ckrina -- Matthew Grill 'alwaysworking' https://www.drupal.org/u/alwaysworking - Sally Young 'justafish' https://www.drupal.org/u/justafish Layout Initiative diff --git a/core/assets/scaffold/README.txt b/core/assets/scaffold/README.txt new file mode 100644 index 000000000..767b508da --- /dev/null +++ b/core/assets/scaffold/README.txt @@ -0,0 +1,11 @@ +Drupal Scaffold Files are files that are contained inside drupal/core, but are +installed outside of the core directory (e.g. at the Drupal root). + +Scaffold files were added to drupal/core in Drupal 8.8.x. During the Drupal 8 +development cycle, the scaffold files are also being maintained in their original +locations. This is done so that Drupal sites based on the template project +drupal-composer/drupal-project may continue to download these files from the same +URLs they have historically been found at. + +The scaffold files will be deleted from their original location in Drupal 9. +See https://www.drupal.org/project/drupal/issues/3075954 for follow-on work. diff --git a/core/assets/scaffold/TESTING.txt b/core/assets/scaffold/TESTING.txt new file mode 100644 index 000000000..d845c31da --- /dev/null +++ b/core/assets/scaffold/TESTING.txt @@ -0,0 +1,16 @@ +HOW-TO: Test these Drupal scaffold files + +In order to test these scaffold files, you'll need to get the entire Drupal repo and +run the tests there. + +You'll find the tests in core/tests/Drupal/Tests/ComposerIntegrationTest.php. + +You can get the full Drupal repo here: +https://www.drupal.org/project/drupal/git-instructions + +You can find more information about running PHPUnit tests with Drupal here: +https://www.drupal.org/node/2116263 + +You can run a single phpunit test file like so: + +$ ./vendor/bin/phpunit -c core core/tests/Drupal/Tests/ComposerIntegrationTest.php diff --git a/core/assets/scaffold/files/csslintrc b/core/assets/scaffold/files/csslintrc new file mode 100644 index 000000000..177e4fcc7 --- /dev/null +++ b/core/assets/scaffold/files/csslintrc @@ -0,0 +1,40 @@ +--errors=box-model, + display-property-grouping, + duplicate-background-images, + duplicate-properties, + empty-rules, + ids, + import, + important, + known-properties, + outline-none, + overqualified-elements, + qualified-headings, + shorthand, + star-property-hack, + text-indent, + underscore-property-hack, + unique-headings, + unqualified-attributes, + vendor-prefix, + zero-units +--ignore=adjoining-classes, + box-sizing, + bulletproof-font-face, + compatible-vendor-prefixes, + errors, + fallback-colors, + floats, + font-faces, + font-sizes, + gradients, + import-ie-limit, + order-alphabetical, + regex-selectors, + rules-count, + selector-max, + selector-max-approaching, + selector-newline, + universal-selector +--exclude-list=core/assets, + vendor diff --git a/core/assets/scaffold/files/default.services.yml b/core/assets/scaffold/files/default.services.yml new file mode 100644 index 000000000..e1bbbc7e2 --- /dev/null +++ b/core/assets/scaffold/files/default.services.yml @@ -0,0 +1,174 @@ +parameters: + session.storage.options: + # Default ini options for sessions. + # + # Some distributions of Linux (most notably Debian) ship their PHP + # installations with garbage collection (gc) disabled. Since Drupal depends + # on PHP's garbage collection for clearing sessions, ensure that garbage + # collection occurs by using the most common settings. + # @default 1 + gc_probability: 1 + # @default 100 + gc_divisor: 100 + # + # Set session lifetime (in seconds), i.e. the time from the user's last + # visit to the active session may be deleted by the session garbage + # collector. When a session is deleted, authenticated users are logged out, + # and the contents of the user's $_SESSION variable is discarded. + # @default 200000 + gc_maxlifetime: 200000 + # + # Set session cookie lifetime (in seconds), i.e. the time from the session + # is created to the cookie expires, i.e. when the browser is expected to + # discard the cookie. The value 0 means "until the browser is closed". + # @default 2000000 + cookie_lifetime: 2000000 + # + # Drupal automatically generates a unique session cookie name based on the + # full domain name used to access the site. This mechanism is sufficient + # for most use-cases, including multi-site deployments. However, if it is + # desired that a session can be reused across different subdomains, the + # cookie domain needs to be set to the shared base domain. Doing so assures + # that users remain logged in as they cross between various subdomains. + # To maximize compatibility and normalize the behavior across user agents, + # the cookie domain should start with a dot. + # + # @default none + # cookie_domain: '.example.com' + # + twig.config: + # Twig debugging: + # + # When debugging is enabled: + # - The markup of each Twig template is surrounded by HTML comments that + # contain theming information, such as template file name suggestions. + # - Note that this debugging markup will cause automated tests that directly + # check rendered HTML to fail. When running automated tests, 'debug' + # should be set to FALSE. + # - The dump() function can be used in Twig templates to output information + # about template variables. + # - Twig templates are automatically recompiled whenever the source code + # changes (see auto_reload below). + # + # For more information about debugging Twig templates, see + # https://www.drupal.org/node/1906392. + # + # Not recommended in production environments + # @default false + debug: false + # Twig auto-reload: + # + # Automatically recompile Twig templates whenever the source code changes. + # If you don't provide a value for auto_reload, it will be determined + # based on the value of debug. + # + # Not recommended in production environments + # @default null + auto_reload: null + # Twig cache: + # + # By default, Twig templates will be compiled and stored in the filesystem + # to increase performance. Disabling the Twig cache will recompile the + # templates from source each time they are used. In most cases the + # auto_reload setting above should be enabled rather than disabling the + # Twig cache. + # + # Not recommended in production environments + # @default true + cache: true + renderer.config: + # Renderer required cache contexts: + # + # The Renderer will automatically associate these cache contexts with every + # render array, hence varying every render array by these cache contexts. + # + # @default ['languages:language_interface', 'theme', 'user.permissions'] + required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions'] + # Renderer automatic placeholdering conditions: + # + # Drupal allows portions of the page to be automatically deferred when + # rendering to improve cache performance. That is especially helpful for + # cache contexts that vary widely, such as the active user. On some sites + # those may be different, however, such as sites with only a handful of + # users. If you know what the high-cardinality cache contexts are for your + # site, specify those here. If you're not sure, the defaults are fairly safe + # in general. + # + # For more information about rendering optimizations see + # https://www.drupal.org/developing/api/8/render/arrays/cacheability#optimizing + auto_placeholder_conditions: + # Max-age at or below which caching is not considered worthwhile. + # + # Disable by setting to -1. + # + # @default 0 + max-age: 0 + # Cache contexts with a high cardinality. + # + # Disable by setting to []. + # + # @default ['session', 'user'] + contexts: ['session', 'user'] + # Tags with a high invalidation frequency. + # + # Disable by setting to []. + # + # @default [] + tags: [] + # Cacheability debugging: + # + # Responses with cacheability metadata (CacheableResponseInterface instances) + # get X-Drupal-Cache-Tags and X-Drupal-Cache-Contexts headers. + # + # For more information about debugging cacheable responses, see + # https://www.drupal.org/developing/api/8/response/cacheable-response-interface + # + # Not recommended in production environments + # @default false + http.response.debug_cacheability_headers: false + factory.keyvalue: + {} + # Default key/value storage service to use. + # @default keyvalue.database + # default: keyvalue.database + # Collection-specific overrides. + # state: keyvalue.database + factory.keyvalue.expirable: + {} + # Default key/value expirable storage service to use. + # @default keyvalue.database.expirable + # default: keyvalue.database.expirable + # Allowed protocols for URL generation. + filter_protocols: + - http + - https + - ftp + - news + - nntp + - tel + - telnet + - mailto + - irc + - ssh + - sftp + - webcal + - rtsp + + # Configure Cross-Site HTTP requests (CORS). + # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + # for more information about the topic in general. + # Note: By default the configuration is disabled. + cors.config: + enabled: false + # Specify allowed headers, like 'x-allowed-header'. + allowedHeaders: [] + # Specify allowed request methods, specify ['*'] to allow all possible ones. + allowedMethods: [] + # Configure requests allowed from specific origins. + allowedOrigins: ['*'] + # Sets the Access-Control-Expose-Headers header. + exposedHeaders: false + # Sets the Access-Control-Max-Age header. + maxAge: false + # Sets the Access-Control-Allow-Credentials header. + supportsCredentials: false diff --git a/core/assets/scaffold/files/default.settings.php b/core/assets/scaffold/files/default.settings.php new file mode 100644 index 000000000..1c8dbee9e --- /dev/null +++ b/core/assets/scaffold/files/default.settings.php @@ -0,0 +1,757 @@ + 'databasename', + * 'username' => 'sqlusername', + * 'password' => 'sqlpassword', + * 'host' => 'localhost', + * 'port' => '3306', + * 'driver' => 'mysql', + * 'prefix' => '', + * 'collation' => 'utf8mb4_general_ci', + * ]; + * @endcode + */ +$databases = []; + +/** + * Customizing database settings. + * + * Many of the values of the $databases array can be customized for your + * particular database system. Refer to the sample in the section above as a + * starting point. + * + * The "driver" property indicates what Drupal database driver the + * connection should use. This is usually the same as the name of the + * database type, such as mysql or sqlite, but not always. The other + * properties will vary depending on the driver. For SQLite, you must + * specify a database file name in a directory that is writable by the + * webserver. For most other drivers, you must specify a + * username, password, host, and database name. + * + * Transaction support is enabled by default for all drivers that support it, + * including MySQL. To explicitly disable it, set the 'transactions' key to + * FALSE. + * Note that some configurations of MySQL, such as the MyISAM engine, don't + * support it and will proceed silently even if enabled. If you experience + * transaction related crashes with such configuration, set the 'transactions' + * key to FALSE. + * + * For each database, you may optionally specify multiple "target" databases. + * A target database allows Drupal to try to send certain queries to a + * different database if it can but fall back to the default connection if not. + * That is useful for primary/replica replication, as Drupal may try to connect + * to a replica server when appropriate and if one is not available will simply + * fall back to the single primary server (The terms primary/replica are + * traditionally referred to as master/slave in database server documentation). + * + * The general format for the $databases array is as follows: + * @code + * $databases['default']['default'] = $info_array; + * $databases['default']['replica'][] = $info_array; + * $databases['default']['replica'][] = $info_array; + * $databases['extra']['default'] = $info_array; + * @endcode + * + * In the above example, $info_array is an array of settings described above. + * The first line sets a "default" database that has one primary database + * (the second level default). The second and third lines create an array + * of potential replica databases. Drupal will select one at random for a given + * request as needed. The fourth line creates a new database with a name of + * "extra". + * + * You can optionally set prefixes for some or all database table names + * by using the 'prefix' setting. If a prefix is specified, the table + * name will be prepended with its value. Be sure to use valid database + * characters only, usually alphanumeric and underscore. If no prefixes + * are desired, leave it as an empty string ''. + * + * To have all database names prefixed, set 'prefix' as a string: + * @code + * 'prefix' => 'main_', + * @endcode + * + * Per-table prefixes are deprecated as of Drupal 8.2, and will be removed in + * Drupal 9.0. After that, only a single prefix for all tables will be + * supported. + * + * To provide prefixes for specific tables, set 'prefix' as an array. + * The array's keys are the table names and the values are the prefixes. + * The 'default' element is mandatory and holds the prefix for any tables + * not specified elsewhere in the array. Example: + * @code + * 'prefix' => [ + * 'default' => 'main_', + * 'users' => 'shared_', + * 'sessions' => 'shared_', + * 'role' => 'shared_', + * 'authmap' => 'shared_', + * ], + * @endcode + * You can also use a reference to a schema/database as a prefix. This may be + * useful if your Drupal installation exists in a schema that is not the default + * or you want to access several databases from the same code base at the same + * time. + * Example: + * @code + * 'prefix' => [ + * 'default' => 'main.', + * 'users' => 'shared.', + * 'sessions' => 'shared.', + * 'role' => 'shared.', + * 'authmap' => 'shared.', + * ]; + * @endcode + * NOTE: MySQL and SQLite's definition of a schema is a database. + * + * Advanced users can add or override initial commands to execute when + * connecting to the database server, as well as PDO connection settings. For + * example, to enable MySQL SELECT queries to exceed the max_join_size system + * variable, and to reduce the database connection timeout to 5 seconds: + * @code + * $databases['default']['default'] = [ + * 'init_commands' => [ + * 'big_selects' => 'SET SQL_BIG_SELECTS=1', + * ], + * 'pdo' => [ + * PDO::ATTR_TIMEOUT => 5, + * ], + * ]; + * @endcode + * + * WARNING: The above defaults are designed for database portability. Changing + * them may cause unexpected behavior, including potential data loss. See + * https://www.drupal.org/developing/api/database/configuration for more + * information on these defaults and the potential issues. + * + * More details can be found in the constructor methods for each driver: + * - \Drupal\Core\Database\Driver\mysql\Connection::__construct() + * - \Drupal\Core\Database\Driver\pgsql\Connection::__construct() + * - \Drupal\Core\Database\Driver\sqlite\Connection::__construct() + * + * Sample Database configuration format for PostgreSQL (pgsql): + * @code + * $databases['default']['default'] = [ + * 'driver' => 'pgsql', + * 'database' => 'databasename', + * 'username' => 'sqlusername', + * 'password' => 'sqlpassword', + * 'host' => 'localhost', + * 'prefix' => '', + * ]; + * @endcode + * + * Sample Database configuration format for SQLite (sqlite): + * @code + * $databases['default']['default'] = [ + * 'driver' => 'sqlite', + * 'database' => '/path/to/databasefilename', + * ]; + * @endcode + */ + +/** + * Location of the site configuration files. + * + * The $settings['config_sync_directory'] specifies the location of file system + * directory used for syncing configuration data. On install, the directory is + * created. This is used for configuration imports. + * + * The default location for this directory is inside a randomly-named + * directory in the public files path. The setting below allows you to set + * its location. + */ +# $settings['config_sync_directory'] = '/directory/outside/webroot'; + +/** + * Settings: + * + * $settings contains environment-specific configuration, such as the files + * directory and reverse proxy address, and temporary configuration, such as + * security overrides. + * + * @see \Drupal\Core\Site\Settings::get() + */ + +/** + * Salt for one-time login links, cancel links, form tokens, etc. + * + * This variable will be set to a random value by the installer. All one-time + * login links will be invalidated if the value is changed. Note that if your + * site is deployed on a cluster of web servers, you must ensure that this + * variable has the same value on each server. + * + * For enhanced security, you may set this variable to the contents of a file + * outside your document root; you should also ensure that this file is not + * stored with backups of your database. + * + * Example: + * @code + * $settings['hash_salt'] = file_get_contents('/home/example/salt.txt'); + * @endcode + */ +$settings['hash_salt'] = ''; + +/** + * Deployment identifier. + * + * Drupal's dependency injection container will be automatically invalidated and + * rebuilt when the Drupal core version changes. When updating contributed or + * custom code that changes the container, changing this identifier will also + * allow the container to be invalidated as soon as code is deployed. + */ +# $settings['deployment_identifier'] = \Drupal::VERSION; + +/** + * Access control for update.php script. + * + * If you are updating your Drupal installation using the update.php script but + * are not logged in using either an account with the "Administer software + * updates" permission or the site maintenance account (the account that was + * created during installation), you will need to modify the access check + * statement below. Change the FALSE to a TRUE to disable the access check. + * After finishing the upgrade, be sure to open this file again and change the + * TRUE back to a FALSE! + */ +$settings['update_free_access'] = FALSE; + +/** + * External access proxy settings: + * + * If your site must access the Internet via a web proxy then you can enter the + * proxy settings here. Set the full URL of the proxy, including the port, in + * variables: + * - $settings['http_client_config']['proxy']['http']: The proxy URL for HTTP + * requests. + * - $settings['http_client_config']['proxy']['https']: The proxy URL for HTTPS + * requests. + * You can pass in the user name and password for basic authentication in the + * URLs in these settings. + * + * You can also define an array of host names that can be accessed directly, + * bypassing the proxy, in $settings['http_client_config']['proxy']['no']. + */ +# $settings['http_client_config']['proxy']['http'] = 'http://proxy_user:proxy_pass@example.com:8080'; +# $settings['http_client_config']['proxy']['https'] = 'http://proxy_user:proxy_pass@example.com:8080'; +# $settings['http_client_config']['proxy']['no'] = ['127.0.0.1', 'localhost']; + +/** + * Reverse Proxy Configuration: + * + * Reverse proxy servers are often used to enhance the performance + * of heavily visited sites and may also provide other site caching, + * security, or encryption benefits. In an environment where Drupal + * is behind a reverse proxy, the real IP address of the client should + * be determined such that the correct client IP address is available + * to Drupal's logging, statistics, and access management systems. In + * the most simple scenario, the proxy server will add an + * X-Forwarded-For header to the request that contains the client IP + * address. However, HTTP headers are vulnerable to spoofing, where a + * malicious client could bypass restrictions by setting the + * X-Forwarded-For header directly. Therefore, Drupal's proxy + * configuration requires the IP addresses of all remote proxies to be + * specified in $settings['reverse_proxy_addresses'] to work correctly. + * + * Enable this setting to get Drupal to determine the client IP from the + * X-Forwarded-For header. If you are unsure about this setting, do not have a + * reverse proxy, or Drupal operates in a shared hosting environment, this + * setting should remain commented out. + * + * In order for this setting to be used you must specify every possible + * reverse proxy IP address in $settings['reverse_proxy_addresses']. + * If a complete list of reverse proxies is not available in your + * environment (for example, if you use a CDN) you may set the + * $_SERVER['REMOTE_ADDR'] variable directly in settings.php. + * Be aware, however, that it is likely that this would allow IP + * address spoofing unless more advanced precautions are taken. + */ +# $settings['reverse_proxy'] = TRUE; + +/** + * Specify every reverse proxy IP address in your environment. + * This setting is required if $settings['reverse_proxy'] is TRUE. + */ +# $settings['reverse_proxy_addresses'] = ['a.b.c.d', ...]; + +/** + * Reverse proxy trusted headers. + * + * Sets which headers to trust from your reverse proxy. + * + * Common values are: + * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL + * - \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED + * + * Note the default value of + * @code + * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED + * @endcode + * is not secure by default. The value should be set to only the specific + * headers the reverse proxy uses. For example: + * @code + * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL + * @endcode + * This would trust the following headers: + * - X_FORWARDED_FOR + * - X_FORWARDED_HOST + * - X_FORWARDED_PROTO + * - X_FORWARDED_PORT + * + * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL + * @see \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED + * @see \Symfony\Component\HttpFoundation\Request::setTrustedProxies + */ +# $settings['reverse_proxy_trusted_headers'] = \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_ALL | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED; + + +/** + * Page caching: + * + * By default, Drupal sends a "Vary: Cookie" HTTP header for anonymous page + * views. This tells a HTTP proxy that it may return a page from its local + * cache without contacting the web server, if the user sends the same Cookie + * header as the user who originally requested the cached page. Without "Vary: + * Cookie", authenticated users would also be served the anonymous page from + * the cache. If the site has mostly anonymous users except a few known + * editors/administrators, the Vary header can be omitted. This allows for + * better caching in HTTP proxies (including reverse proxies), i.e. even if + * clients send different cookies, they still get content served from the cache. + * However, authenticated users should access the site directly (i.e. not use an + * HTTP proxy, and bypass the reverse proxy if one is used) in order to avoid + * getting cached pages from the proxy. + */ +# $settings['omit_vary_cookie'] = TRUE; + + +/** + * Cache TTL for client error (4xx) responses. + * + * Items cached per-URL tend to result in a large number of cache items, and + * this can be problematic on 404 pages which by their nature are unbounded. A + * fixed TTL can be set for these items, defaulting to one hour, so that cache + * backends which do not support LRU can purge older entries. To disable caching + * of client error responses set the value to 0. Currently applies only to + * page_cache module. + */ +# $settings['cache_ttl_4xx'] = 3600; + +/** + * Expiration of cached forms. + * + * Drupal's Form API stores details of forms in a cache and these entries are + * kept for at least 6 hours by default. Expired entries are cleared by cron. + * + * @see \Drupal\Core\Form\FormCache::setCache() + */ +# $settings['form_cache_expiration'] = 21600; + +/** + * Class Loader. + * + * If the APC extension is detected, the Symfony APC class loader is used for + * performance reasons. Detection can be prevented by setting + * class_loader_auto_detect to false, as in the example below. + */ +# $settings['class_loader_auto_detect'] = FALSE; + +/* + * If the APC extension is not detected, either because APC is missing or + * because auto-detection has been disabled, auto-loading falls back to + * Composer's ClassLoader, which is good for development as it does not break + * when code is moved in the file system. You can also decorate the base class + * loader with another cached solution than the Symfony APC class loader, as + * all production sites should have a cached class loader of some sort enabled. + * + * To do so, you may decorate and replace the local $class_loader variable. For + * example, to use Symfony's APC class loader without automatic detection, + * uncomment the code below. + */ +/* +if ($settings['hash_salt']) { + $prefix = 'drupal.' . hash('sha256', 'drupal.' . $settings['hash_salt']); + $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $class_loader); + unset($prefix); + $class_loader->unregister(); + $apc_loader->register(); + $class_loader = $apc_loader; +} +*/ + +/** + * Authorized file system operations: + * + * The Update Manager module included with Drupal provides a mechanism for + * site administrators to securely install missing updates for the site + * directly through the web user interface. On securely-configured servers, + * the Update manager will require the administrator to provide SSH or FTP + * credentials before allowing the installation to proceed; this allows the + * site to update the new files as the user who owns all the Drupal files, + * instead of as the user the webserver is running as. On servers where the + * webserver user is itself the owner of the Drupal files, the administrator + * will not be prompted for SSH or FTP credentials (note that these server + * setups are common on shared hosting, but are inherently insecure). + * + * Some sites might wish to disable the above functionality, and only update + * the code directly via SSH or FTP themselves. This setting completely + * disables all functionality related to these authorized file operations. + * + * @see https://www.drupal.org/node/244924 + * + * Remove the leading hash signs to disable. + */ +# $settings['allow_authorize_operations'] = FALSE; + +/** + * Default mode for directories and files written by Drupal. + * + * Value should be in PHP Octal Notation, with leading zero. + */ +# $settings['file_chmod_directory'] = 0775; +# $settings['file_chmod_file'] = 0664; + +/** + * Public file base URL: + * + * An alternative base URL to be used for serving public files. This must + * include any leading directory path. + * + * A different value from the domain used by Drupal to be used for accessing + * public files. This can be used for a simple CDN integration, or to improve + * security by serving user-uploaded files from a different domain or subdomain + * pointing to the same server. Do not include a trailing slash. + */ +# $settings['file_public_base_url'] = 'http://downloads.example.com/files'; + +/** + * Public file path: + * + * A local file system path where public files will be stored. This directory + * must exist and be writable by Drupal. This directory must be relative to + * the Drupal installation directory and be accessible over the web. + */ +# $settings['file_public_path'] = 'sites/default/files'; + +/** + * Private file path: + * + * A local file system path where private files will be stored. This directory + * must be absolute, outside of the Drupal installation directory and not + * accessible over the web. + * + * Note: Caches need to be cleared when this value is changed to make the + * private:// stream wrapper available to the system. + * + * See https://www.drupal.org/documentation/modules/file for more information + * about securing private files. + */ +# $settings['file_private_path'] = ''; + +/** + * Temporary file path: + * + * A local file system path where temporary files will be stored. This directory + * must be absolute, outside of the Drupal installation directory and not + * accessible over the web. + * + * If this is not set, the default for the operating system will be used. + * + * @see \Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory() + */ +# $settings['file_temp_path'] = '/tmp'; + +/** + * Session write interval: + * + * Set the minimum interval between each session write to database. + * For performance reasons it defaults to 180. + */ +# $settings['session_write_interval'] = 180; + +/** + * String overrides: + * + * To override specific strings on your site with or without enabling the Locale + * module, add an entry to this list. This functionality allows you to change + * a small number of your site's default English language interface strings. + * + * Remove the leading hash signs to enable. + * + * The "en" part of the variable name, is dynamic and can be any langcode of + * any added language. (eg locale_custom_strings_de for german). + */ +# $settings['locale_custom_strings_en'][''] = [ +# 'forum' => 'Discussion board', +# '@count min' => '@count minutes', +# ]; + +/** + * A custom theme for the offline page: + * + * This applies when the site is explicitly set to maintenance mode through the + * administration page or when the database is inactive due to an error. + * The template file should also be copied into the theme. It is located inside + * 'core/modules/system/templates/maintenance-page.html.twig'. + * + * Note: This setting does not apply to installation and update pages. + */ +# $settings['maintenance_theme'] = 'bartik'; + +/** + * PHP settings: + * + * To see what PHP settings are possible, including whether they can be set at + * runtime (by using ini_set()), read the PHP documentation: + * http://php.net/manual/ini.list.php + * See \Drupal\Core\DrupalKernel::bootEnvironment() for required runtime + * settings and the .htaccess file for non-runtime settings. + * Settings defined there should not be duplicated here so as to avoid conflict + * issues. + */ + +/** + * If you encounter a situation where users post a large amount of text, and + * the result is stripped out upon viewing but can still be edited, Drupal's + * output filter may not have sufficient memory to process it. If you + * experience this issue, you may wish to uncomment the following two lines + * and increase the limits of these variables. For more information, see + * http://php.net/manual/pcre.configuration.php. + */ +# ini_set('pcre.backtrack_limit', 200000); +# ini_set('pcre.recursion_limit', 200000); + +/** + * Configuration overrides. + * + * To globally override specific configuration values for this site, + * set them here. You usually don't need to use this feature. This is + * useful in a configuration file for a vhost or directory, rather than + * the default settings.php. + * + * Note that any values you provide in these variable overrides will not be + * viewable from the Drupal administration interface. The administration + * interface displays the values stored in configuration so that you can stage + * changes to other environments that don't have the overrides. + * + * There are particular configuration values that are risky to override. For + * example, overriding the list of installed modules in 'core.extension' is not + * supported as module install or uninstall has not occurred. Other examples + * include field storage configuration, because it has effects on database + * structure, and 'core.menu.static_menu_link_overrides' since this is cached in + * a way that is not config override aware. Also, note that changing + * configuration values in settings.php will not fire any of the configuration + * change events. + */ +# $config['system.site']['name'] = 'My Drupal site'; +# $config['user.settings']['anonymous'] = 'Visitor'; + +/** + * Fast 404 pages: + * + * Drupal can generate fully themed 404 pages. However, some of these responses + * are for images or other resource files that are not displayed to the user. + * This can waste bandwidth, and also generate server load. + * + * The options below return a simple, fast 404 page for URLs matching a + * specific pattern: + * - $config['system.performance']['fast_404']['exclude_paths']: A regular + * expression to match paths to exclude, such as images generated by image + * styles, or dynamically-resized images. The default pattern provided below + * also excludes the private file system. If you need to add more paths, you + * can add '|path' to the expression. + * - $config['system.performance']['fast_404']['paths']: A regular expression to + * match paths that should return a simple 404 page, rather than the fully + * themed 404 page. If you don't have any aliases ending in htm or html you + * can add '|s?html?' to the expression. + * - $config['system.performance']['fast_404']['html']: The html to return for + * simple 404 pages. + * + * Remove the leading hash signs if you would like to alter this functionality. + */ +# $config['system.performance']['fast_404']['exclude_paths'] = '/\/(?:styles)|(?:system\/files)\//'; +# $config['system.performance']['fast_404']['paths'] = '/\.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'; +# $config['system.performance']['fast_404']['html'] = '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'; + +/** + * Load services definition file. + */ +$settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml'; + +/** + * Override the default service container class. + * + * This is useful for example to trace the service container for performance + * tracking purposes, for testing a service container with an error condition or + * to test a service container that throws an exception. + */ +# $settings['container_base_class'] = '\Drupal\Core\DependencyInjection\Container'; + +/** + * Override the default yaml parser class. + * + * Provide a fully qualified class name here if you would like to provide an + * alternate implementation YAML parser. The class must implement the + * \Drupal\Component\Serialization\SerializationInterface interface. + */ +# $settings['yaml_parser_class'] = NULL; + +/** + * Trusted host configuration. + * + * Drupal core can use the Symfony trusted host mechanism to prevent HTTP Host + * header spoofing. + * + * To enable the trusted host mechanism, you enable your allowable hosts + * in $settings['trusted_host_patterns']. This should be an array of regular + * expression patterns, without delimiters, representing the hosts you would + * like to allow. + * + * For example: + * @code + * $settings['trusted_host_patterns'] = [ + * '^www\.example\.com$', + * ]; + * @endcode + * will allow the site to only run from www.example.com. + * + * If you are running multisite, or if you are running your site from + * different domain names (eg, you don't redirect http://www.example.com to + * http://example.com), you should specify all of the host patterns that are + * allowed by your site. + * + * For example: + * @code + * $settings['trusted_host_patterns'] = [ + * '^example\.com$', + * '^.+\.example\.com$', + * '^example\.org$', + * '^.+\.example\.org$', + * ]; + * @endcode + * will allow the site to run off of all variants of example.com and + * example.org, with all subdomains included. + */ + +/** + * The default list of directories that will be ignored by Drupal's file API. + * + * By default ignore node_modules and bower_components folders to avoid issues + * with common frontend tools and recursive scanning of directories looking for + * extensions. + * + * @see \Drupal\Core\File\FileSystemInterface::scanDirectory() + * @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory() + */ +$settings['file_scan_ignore_directories'] = [ + 'node_modules', + 'bower_components', +]; + +/** + * The default number of entities to update in a batch process. + * + * This is used by update and post-update functions that need to go through and + * change all the entities on a site, so it is useful to increase this number + * if your hosting configuration (i.e. RAM allocation, CPU speed) allows for a + * larger number of entities to be processed in a single batch run. + */ +$settings['entity_update_batch_size'] = 50; + +/** + * Entity update backup. + * + * This is used to inform the entity storage handler that the backup tables as + * well as the original entity type and field storage definitions should be + * retained after a successful entity update process. + */ +$settings['entity_update_backup'] = TRUE; + +/** + * Load local development override configuration, if available. + * + * Use settings.local.php to override variables on secondary (staging, + * development, etc) installations of this site. Typically used to disable + * caching, JavaScript/CSS compression, re-routing of outgoing emails, and + * other things that should not happen on development and testing sites. + * + * Keep this code block at the end of this file to take full effect. + */ +# +# if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) { +# include $app_root . '/' . $site_path . '/settings.local.php'; +# } diff --git a/core/assets/scaffold/files/development.services.yml b/core/assets/scaffold/files/development.services.yml new file mode 100644 index 000000000..d2857c66f --- /dev/null +++ b/core/assets/scaffold/files/development.services.yml @@ -0,0 +1,9 @@ +# Local development services. +# +# To activate this feature, follow the instructions at the top of the +# 'example.settings.local.php' file, which sits next to this file. +parameters: + http.response.debug_cacheability_headers: true +services: + cache.backend.null: + class: Drupal\Core\Cache\NullBackendFactory diff --git a/core/assets/scaffold/files/drupal.INSTALL.txt b/core/assets/scaffold/files/drupal.INSTALL.txt new file mode 100644 index 000000000..2ee9ad89b --- /dev/null +++ b/core/assets/scaffold/files/drupal.INSTALL.txt @@ -0,0 +1,3 @@ + +Please read core/INSTALL.txt for detailed installation instructions for your +Drupal web site. diff --git a/core/assets/scaffold/files/drupal.README.txt b/core/assets/scaffold/files/drupal.README.txt new file mode 100644 index 000000000..4c8696560 --- /dev/null +++ b/core/assets/scaffold/files/drupal.README.txt @@ -0,0 +1,139 @@ + +CONTENTS OF THIS FILE +--------------------- + + * About Drupal + * Configuration and features + * Installation profiles + * Appearance + * Developing for Drupal + * More information + +ABOUT DRUPAL +------------ + +Drupal is an open source content management platform supporting a variety of +websites ranging from personal weblogs to large community-driven websites. For +more information, see the Drupal website at https://www.drupal.org, and join +the Drupal community at https://www.drupal.org/community. + +Legal information about Drupal: + * Know your rights when using Drupal: + See LICENSE.txt in the "core" directory. + * Learn about the Drupal trademark and logo policy: + https://www.drupal.com/trademark + +CONFIGURATION AND FEATURES +-------------------------- + +Drupal core (what you get when you download and extract a drupal-x.y.tar.gz or +drupal-x.y.zip file from https://www.drupal.org/project/drupal) has what you +need to get started with your website. It includes several modules (extensions +that add functionality) for common website features, such as managing content, +user accounts, image uploading, and search. Core comes with many options that +allow site-specific configuration. In addition to the core modules, there are +thousands of contributed modules (for functionality not included with Drupal +core) available for download. + +More about configuration: + * Install, update, and maintain Drupal: + See INSTALL.txt and UPDATE.txt in the "core" directory. + * Learn about how to use Drupal to create your site: + https://www.drupal.org/documentation + * Follow best practices: + https://www.drupal.org/best-practices + * Download contributed modules to /modules to extend Drupal's functionality: + https://www.drupal.org/project/modules + * See also: "Developing for Drupal" for writing your own modules, below. + + +INSTALLATION PROFILES +--------------------- + +Installation profiles define additional steps (such as enabling modules, +defining content types, etc.) that run after the base installation provided +by core when Drupal is first installed. There are two basic installation +profiles provided with Drupal core. + +Installation profiles from the Drupal community modify the installation process +to provide a website for a specific use case, such as a CMS for media +publishers, a web-based project tracking tool, or a full-fledged CRM for +non-profit organizations raising money and accepting donations. They can be +distributed as bare installation profiles or as "distributions". Distributions +include Drupal core, the installation profile, and all other required +extensions, such as contributed and custom modules, themes, and third-party +libraries. Bare installation profiles require you to download Drupal Core and +the required extensions separately; place the downloaded profile in the +/profiles directory before you start the installation process. + +More about installation profiles and distributions: + * Read about the difference between installation profiles and distributions: + https://www.drupal.org/node/1089736 + * Download contributed installation profiles and distributions: + https://www.drupal.org/project/distributions + * Develop your own installation profile or distribution: + https://www.drupal.org/docs/8/creating-distributions + + +APPEARANCE +---------- + +In Drupal, the appearance of your site is set by the theme (themes are +extensions that set fonts, colors, and layout). Drupal core comes with several +themes. More themes are available for download, and you can also create your own +custom theme. + +More about themes: + * Download contributed themes to /themes to modify Drupal's appearance: + https://www.drupal.org/project/themes + * Develop your own theme: + https://www.drupal.org/docs/8/theming + +DEVELOPING FOR DRUPAL +--------------------- + +Drupal contains an extensive API that allows you to add to and modify the +functionality of your site. The API consists of "hooks", which allow modules to +react to system events and customize Drupal's behavior, and functions that +standardize common operations such as database queries and form generation. The +flexible hook architecture means that you should never need to directly modify +the files that come with Drupal core to achieve the functionality you want; +instead, functionality modifications take the form of modules. + +When you need new functionality for your Drupal site, search for existing +contributed modules. If you find a module that matches except for a bug or an +additional needed feature, change the module and contribute your improvements +back to the project in the form of a "patch". Create new custom modules only +when nothing existing comes close to what you need. + +More about developing: + * Search for existing contributed modules: + https://www.drupal.org/project/modules + * Contribute a patch: + https://www.drupal.org/patch/submit + * Develop your own module: + https://www.drupal.org/developing/modules + * Follow programming best practices: + https://www.drupal.org/developing/best-practices + * Refer to the API documentation: + https://api.drupal.org/api/drupal/8 + * Learn from documented Drupal API examples: + https://www.drupal.org/project/examples + +MORE INFORMATION +---------------- + + * See the Drupal.org online documentation: + https://www.drupal.org/documentation + + * For a list of security announcements, see the "Security advisories" page at + https://www.drupal.org/security (available as an RSS feed). This page also + describes how to subscribe to these announcements via email. + + * For information about the Drupal security process, or to find out how to + report a potential security issue to the Drupal security team, see the + "Security team" page at https://www.drupal.org/security-team + + * For information about the wide range of available support options, visit + https://www.drupal.org and click on Community and Support in the top or + bottom navigation. diff --git a/core/assets/scaffold/files/editorconfig b/core/assets/scaffold/files/editorconfig new file mode 100644 index 000000000..686c443ce --- /dev/null +++ b/core/assets/scaffold/files/editorconfig @@ -0,0 +1,17 @@ +# Drupal editor configuration normalization +# @see http://editorconfig.org/ + +# This is the top-most .editorconfig file; do not search in parent directories. +root = true + +# All files. +[*] +end_of_line = LF +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[composer.{json,lock}] +indent_size = 4 diff --git a/core/assets/scaffold/files/eslintignore b/core/assets/scaffold/files/eslintignore new file mode 100644 index 000000000..9c134873d --- /dev/null +++ b/core/assets/scaffold/files/eslintignore @@ -0,0 +1,8 @@ +core/**/* +vendor/**/* +sites/**/files/**/* +libraries/**/* +sites/**/libraries/**/* +profiles/**/libraries/**/* +**/js_test_files/**/* +**/node_modules/**/* diff --git a/core/assets/scaffold/files/eslintrc.json b/core/assets/scaffold/files/eslintrc.json new file mode 100644 index 000000000..d4bbc9205 --- /dev/null +++ b/core/assets/scaffold/files/eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "./core/.eslintrc.json" +} diff --git a/core/assets/scaffold/files/example.gitignore b/core/assets/scaffold/files/example.gitignore new file mode 100644 index 000000000..7cc322797 --- /dev/null +++ b/core/assets/scaffold/files/example.gitignore @@ -0,0 +1,42 @@ +# This file contains default .gitignore rules. To use it, copy it to .gitignore, +# and it will cause files like your settings.php and user-uploaded files to be +# excluded from Git version control. This is a common strategy to avoid +# accidentally including private information in public repositories and patch +# files. +# +# Because .gitignore can be specific to your site, this file has a different +# name; updating Drupal core will not override your custom .gitignore file. + +# Ignore core when managing all of a project's dependencies with Composer +# including Drupal core. +# core + +# Ignore dependencies that are managed with Composer. +# Generally you should only ignore the root vendor directory. It's important +# that core/assets/vendor and any other vendor directories within contrib or +# custom module, theme, etc., are not ignored unless you purposely do so. +/vendor/ + +# Ignore configuration files that may contain sensitive information. +sites/*/settings*.php +sites/*/services*.yml + +# Ignore paths that contain user-generated content. +sites/*/files +sites/*/private + +# Ignore SimpleTest multi-site environment. +sites/simpletest + +# If you prefer to store your .gitignore file in the sites/ folder, comment +# or delete the previous settings and uncomment the following ones, instead. + +# Ignore configuration files that may contain sensitive information. +# */settings*.php + +# Ignore paths that contain user-generated content. +# */files +# */private + +# Ignore SimpleTest multi-site environment. +# simpletest diff --git a/core/assets/scaffold/files/example.settings.local.php b/core/assets/scaffold/files/example.settings.local.php new file mode 100644 index 000000000..520cb4acd --- /dev/null +++ b/core/assets/scaffold/files/example.settings.local.php @@ -0,0 +1,155 @@ +..' => 'directory'. As an + * example, to map https://www.drupal.org:8080/mysite/test to the configuration + * directory sites/example.com, the array should be defined as: + * @code + * $sites = [ + * '8080.www.drupal.org.mysite.test' => 'example.com', + * ]; + * @endcode + * The URL, https://www.drupal.org:8080/mysite/test/, could be a symbolic link + * or an Apache Alias directive that points to the Drupal root containing + * index.php. An alias could also be created for a subdomain. See the + * @link https://www.drupal.org/documentation/install online Drupal installation guide @endlink + * for more information on setting up domains, subdomains, and subdirectories. + * + * The following examples look for a site configuration in sites/example.com: + * @code + * URL: http://dev.drupal.org + * $sites['dev.drupal.org'] = 'example.com'; + * + * URL: http://localhost/example + * $sites['localhost.example'] = 'example.com'; + * + * URL: http://localhost:8080/example + * $sites['8080.localhost.example'] = 'example.com'; + * + * URL: https://www.drupal.org:8080/mysite/test/ + * $sites['8080.www.drupal.org.mysite.test'] = 'example.com'; + * @endcode + * + * @see default.settings.php + * @see \Drupal\Core\DrupalKernel::getSitePath() + * @see https://www.drupal.org/documentation/install/multi-site + */ diff --git a/core/assets/scaffold/files/gitattributes b/core/assets/scaffold/files/gitattributes new file mode 100644 index 000000000..a37894e8e --- /dev/null +++ b/core/assets/scaffold/files/gitattributes @@ -0,0 +1,61 @@ +# Drupal git normalization +# @see https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html +# @see https://www.drupal.org/node/1542048 + +# Normally these settings would be done with macro attributes for improved +# readability and easier maintenance. However macros can only be defined at the +# repository root directory. Drupal avoids making any assumptions about where it +# is installed. + +# Define text file attributes. +# - Treat them as text. +# - Ensure no CRLF line-endings, neither on checkout nor on checkin. +# - Detect whitespace errors. +# - Exposed by default in `git diff --color` on the CLI. +# - Validate with `git diff --check`. +# - Deny applying with `git apply --whitespace=error-all`. +# - Fix automatically with `git apply --whitespace=fix`. + +*.config text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.css text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.dist text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.engine text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.html text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=html +*.inc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.install text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.js text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.json text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.lock text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.map text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.module text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.php text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.po text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.profile text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.script text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.sh text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.sql text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.svg text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.theme text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php +*.twig text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.txt text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.xml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.yml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 + +# Define binary file attributes. +# - Do not treat them as text. +# - Include binary diff in patches instead of "binary files differ." +*.eot -text diff +*.exe -text diff +*.gif -text diff +*.gz -text diff +*.ico -text diff +*.jpeg -text diff +*.jpg -text diff +*.otf -text diff +*.phar -text diff +*.png -text diff +*.svgz -text diff +*.ttf -text diff +*.woff -text diff +*.woff2 -text diff diff --git a/core/assets/scaffold/files/ht.router.php b/core/assets/scaffold/files/ht.router.php new file mode 100644 index 000000000..054f7119b --- /dev/null +++ b/core/assets/scaffold/files/ht.router.php @@ -0,0 +1,65 @@ + + + Require all denied + + + Order allow,deny + +
+ +# Don't show directory listings for URLs which map to a directory. +Options -Indexes + +# Set the default handler. +DirectoryIndex index.php index.html index.htm + +# Add correct encoding for SVGZ. +AddType image/svg+xml svg svgz +AddEncoding gzip svgz + +# Most of the following PHP settings cannot be changed at runtime. See +# sites/default/default.settings.php and +# Drupal\Core\DrupalKernel::bootEnvironment() for settings that can be +# changed at runtime. + +# PHP 5, Apache 1 and 2. + + php_value assert.active 0 + php_flag session.auto_start off + php_value mbstring.http_input pass + php_value mbstring.http_output pass + php_flag mbstring.encoding_translation off + # PHP 5.6 has deprecated $HTTP_RAW_POST_DATA and produces warnings if this is + # not set. + php_value always_populate_raw_post_data -1 + + +# Requires mod_expires to be enabled. + + # Enable expirations. + ExpiresActive On + + # Cache all files for 2 weeks after access (A). + ExpiresDefault A1209600 + + + # Do not allow PHP scripts to be cached unless they explicitly send cache + # headers themselves. Otherwise all scripts would have to overwrite the + # headers set by mod_expires if they want another caching behavior. This may + # fail if an error occurs early in the bootstrap process, and it may cause + # problems if a non-Drupal PHP file is installed in a subdirectory. + ExpiresActive Off + + + +# Set a fallback resource if mod_rewrite is not enabled. This allows Drupal to +# work without clean URLs. This requires Apache version >= 2.2.16. If Drupal is +# not accessed by the top level URL (i.e.: http://example.com/drupal/ instead of +# http://example.com/), the path to index.php will need to be adjusted. + + FallbackResource /index.php + + +# Various rewrite rules. + + RewriteEngine on + + # Set "protossl" to "s" if we were accessed via https://. This is used later + # if you enable "www." stripping or enforcement, in order to ensure that + # you don't bounce between http and https. + RewriteRule ^ - [E=protossl] + RewriteCond %{HTTPS} on + RewriteRule ^ - [E=protossl:s] + + # Make sure Authorization HTTP header is available to PHP + # even when running as CGI or FastCGI. + RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Block access to "hidden" directories whose names begin with a period. This + # includes directories used by version control systems such as Subversion or + # Git to store control files. Files whose names begin with a period, as well + # as the control files used by CVS, are protected by the FilesMatch directive + # above. + # + # NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is + # not possible to block access to entire directories from .htaccess because + # is not allowed here. + # + # If you do not have mod_rewrite installed, you should remove these + # directories from your webroot or otherwise protect them from being + # downloaded. + RewriteRule "/\.|^\.(?!well-known/)" - [F] + + # If your site can be accessed both with and without the 'www.' prefix, you + # can use one of the following settings to redirect users to your preferred + # URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option: + # + # To redirect all users to access the site WITH the 'www.' prefix, + # (http://example.com/foo will be redirected to http://www.example.com/foo) + # uncomment the following: + # RewriteCond %{HTTP_HOST} . + # RewriteCond %{HTTP_HOST} !^www\. [NC] + # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + # + # To redirect all users to access the site WITHOUT the 'www.' prefix, + # (http://www.example.com/foo will be redirected to http://example.com/foo) + # uncomment the following: + # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] + # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] + + # Modify the RewriteBase if you are using Drupal in a subdirectory or in a + # VirtualDocumentRoot and the rewrite rules are not working properly. + # For example if your site is at http://example.com/drupal uncomment and + # modify the following line: + # RewriteBase /drupal + # + # If your site is running in a VirtualDocumentRoot at http://example.com/, + # uncomment the following line: + # RewriteBase / + + # Redirect common PHP files to their new locations. + RewriteCond %{REQUEST_URI} ^(.*)?/(install.php) [OR] + RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild.php) + RewriteCond %{REQUEST_URI} !core + RewriteRule ^ %1/core/%2 [L,QSA,R=301] + + # Rewrite install.php during installation to see if mod_rewrite is working + RewriteRule ^core/install.php core/install.php?rewrite=ok [QSA,L] + + # Pass all requests not referring directly to files in the filesystem to + # index.php. + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} !=/favicon.ico + RewriteRule ^ index.php [L] + + # For security reasons, deny access to other PHP files on public sites. + # Note: The following URI conditions are not anchored at the start (^), + # because Drupal may be located in a subdirectory. To further improve + # security, you can replace '!/' with '!^/'. + # Allow access to PHP files in /core (like authorize.php or install.php): + RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$ + # Allow access to test-specific PHP files: + RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php + # Allow access to Statistics module's custom front controller. + # Copy and adapt this rule to directly execute PHP files in contributed or + # custom modules or to run another PHP application in the same directory. + RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$ + # Deny access to any other PHP files that do not match the rules above. + # Specifically, disallow autoload.php from being served directly. + RewriteRule "^(.+/.*|autoload)\.php($|/)" - [F] + + # Rules to correctly serve gzip compressed CSS and JS files. + # Requires both mod_rewrite and mod_headers to be enabled. + + # Serve gzip compressed CSS files if they exist and the client accepts gzip. + RewriteCond %{HTTP:Accept-encoding} gzip + RewriteCond %{REQUEST_FILENAME}\.gz -s + RewriteRule ^(.*)\.css $1\.css\.gz [QSA] + + # Serve gzip compressed JS files if they exist and the client accepts gzip. + RewriteCond %{HTTP:Accept-encoding} gzip + RewriteCond %{REQUEST_FILENAME}\.gz -s + RewriteRule ^(.*)\.js $1\.js\.gz [QSA] + + # Serve correct content types, and prevent mod_deflate double gzip. + RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1] + RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1] + + + # Serve correct encoding type. + Header set Content-Encoding gzip + # Force proxies to cache gzipped & non-gzipped css/js files separately. + Header append Vary Accept-Encoding + + + + +# Various header fixes. + + # Disable content sniffing, since it's an attack vector. + Header always set X-Content-Type-Options nosniff + # Disable Proxy header, since it's an attack vector. + RequestHeader unset Proxy + diff --git a/core/assets/scaffold/files/index.php b/core/assets/scaffold/files/index.php new file mode 100644 index 000000000..750dc282d --- /dev/null +++ b/core/assets/scaffold/files/index.php @@ -0,0 +1,22 @@ +handle($request); +$response->send(); + +$kernel->terminate($request, $response); diff --git a/core/assets/scaffold/files/modules.README.txt b/core/assets/scaffold/files/modules.README.txt new file mode 100644 index 000000000..529c31b2b --- /dev/null +++ b/core/assets/scaffold/files/modules.README.txt @@ -0,0 +1,42 @@ +Modules extend your site functionality beyond Drupal core. + +WHAT TO PLACE IN THIS DIRECTORY? +-------------------------------- + +Placing downloaded and custom modules in this directory separates downloaded and +custom modules from Drupal core's modules. This allows Drupal core to be updated +without overwriting these files. + +DOWNLOAD ADDITIONAL MODULES +--------------------------- + +Contributed modules from the Drupal community may be downloaded at +https://www.drupal.org/project/project_module. + +ORGANIZING MODULES IN THIS DIRECTORY +------------------------------------ + +You may create subdirectories in this directory, to organize your added modules, +without breaking the site. Some common subdirectories include "contrib" for +contributed modules, and "custom" for custom modules. Note that if you move a +module to a subdirectory after it has been enabled, you may need to clear the +Drupal cache so it can be found. + +There are number of directories that are ignored when looking for modules. These +are 'src', 'lib', 'vendor', 'assets', 'css', 'files', 'images', 'js', 'misc', +'templates', 'includes', 'fixtures' and 'Drupal'. + +MULTISITE CONFIGURATION +----------------------- + +In multisite configurations, modules found in this directory are available to +all sites. You may also put modules in the sites/all/modules directory, and the +versions in sites/all/modules will take precedence over versions of the same +module that are here. Alternatively, the sites/your_site_name/modules directory +pattern may be used to restrict modules to a specific site instance. + +MORE INFORMATION +---------------- + +Refer to the “Developing for Drupal” section of the README.txt in the Drupal +root directory for further information on extending Drupal with custom modules. diff --git a/core/assets/scaffold/files/profiles.README.txt b/core/assets/scaffold/files/profiles.README.txt new file mode 100644 index 000000000..b0f0c0bac --- /dev/null +++ b/core/assets/scaffold/files/profiles.README.txt @@ -0,0 +1,28 @@ +Installation profiles define additional steps that run after the base +installation of Drupal is completed. They may also offer additional +functionality and change the behavior of the site. + +WHAT TO PLACE IN THIS DIRECTORY? +-------------------------------- + +Place downloaded and custom installation profiles in this directory. +Note that installation profiles are generally provided as part of a Drupal +distribution. + +DOWNLOAD ADDITIONAL DISTRIBUTIONS +--------------------------------- + +Contributed distributions from the Drupal community may be downloaded at +https://www.drupal.org/project/project_distribution. + +MULTISITE CONFIGURATION +----------------------- + +In multisite configurations, installation profiles found in this directory are +available to all sites during their initial site installation. + +MORE INFORMATION +---------------- + +Refer to the "Installation profiles" section of the README.txt in the Drupal +root directory for further information on extending Drupal with custom profiles. diff --git a/core/assets/scaffold/files/robots.txt b/core/assets/scaffold/files/robots.txt new file mode 100644 index 000000000..54da16277 --- /dev/null +++ b/core/assets/scaffold/files/robots.txt @@ -0,0 +1,61 @@ +# +# robots.txt +# +# This file is to prevent the crawling and indexing of certain parts +# of your site by web crawlers and spiders run by sites like Yahoo! +# and Google. By telling these "robots" where not to go on your site, +# you save bandwidth and server resources. +# +# This file will be ignored unless it is at the root of your host: +# Used: http://example.com/robots.txt +# Ignored: http://example.com/site/robots.txt +# +# For more information about the robots.txt standard, see: +# http://www.robotstxt.org/robotstxt.html + +User-agent: * +# CSS, JS, Images +Allow: /core/*.css$ +Allow: /core/*.css? +Allow: /core/*.js$ +Allow: /core/*.js? +Allow: /core/*.gif +Allow: /core/*.jpg +Allow: /core/*.jpeg +Allow: /core/*.png +Allow: /core/*.svg +Allow: /profiles/*.css$ +Allow: /profiles/*.css? +Allow: /profiles/*.js$ +Allow: /profiles/*.js? +Allow: /profiles/*.gif +Allow: /profiles/*.jpg +Allow: /profiles/*.jpeg +Allow: /profiles/*.png +Allow: /profiles/*.svg +# Directories +Disallow: /core/ +Disallow: /profiles/ +# Files +Disallow: /README.txt +Disallow: /web.config +# Paths (clean URLs) +Disallow: /admin/ +Disallow: /comment/reply/ +Disallow: /filter/tips +Disallow: /node/add/ +Disallow: /search/ +Disallow: /user/register/ +Disallow: /user/password/ +Disallow: /user/login/ +Disallow: /user/logout/ +# Paths (no clean URLs) +Disallow: /index.php/admin/ +Disallow: /index.php/comment/reply/ +Disallow: /index.php/filter/tips +Disallow: /index.php/node/add/ +Disallow: /index.php/search/ +Disallow: /index.php/user/password/ +Disallow: /index.php/user/register/ +Disallow: /index.php/user/login/ +Disallow: /index.php/user/logout/ diff --git a/core/assets/scaffold/files/sites.README.txt b/core/assets/scaffold/files/sites.README.txt new file mode 100644 index 000000000..0372902f1 --- /dev/null +++ b/core/assets/scaffold/files/sites.README.txt @@ -0,0 +1,10 @@ +This directory structure contains the settings and configuration files specific +to your site or sites and is an integral part of multisite configurations. + +It is now recommended to place your custom and downloaded extensions in the +/modules, /themes, and /profiles directories located in the Drupal root. The +sites/all/ subdirectory structure, which was recommended in previous versions +of Drupal, is still supported. + +See core/INSTALL.txt for information about single-site installation or +multisite configuration. diff --git a/core/assets/scaffold/files/themes.README.txt b/core/assets/scaffold/files/themes.README.txt new file mode 100644 index 000000000..039aaaf83 --- /dev/null +++ b/core/assets/scaffold/files/themes.README.txt @@ -0,0 +1,31 @@ +Themes allow you to change the look and feel of your Drupal site. You can use +themes contributed by others or create your own. + +WHAT TO PLACE IN THIS DIRECTORY? +-------------------------------- + +Placing downloaded and custom themes in this directory separates downloaded and +custom themes from Drupal core's themes. This allows Drupal core to be updated +without overwriting these files. + +DOWNLOAD ADDITIONAL THEMES +-------------------------- + +Contributed themes from the Drupal community may be downloaded at +https://www.drupal.org/project/project_theme. + +MULTISITE CONFIGURATION +----------------------- + +In multisite configurations, themes found in this directory are available to +all sites. You may also put themes in the sites/all/themes directory, and the +versions in sites/all/themes will take precedence over versions of the same +themes that are here. Alternatively, the sites/your_site_name/themes directory +pattern may be used to restrict themes to a specific site instance. + +MORE INFORMATION +----------------- + +Refer to the "Appearance" section of the README.txt in the Drupal root directory +for further information on customizing the appearance of Drupal with custom +themes. diff --git a/core/assets/scaffold/files/update.php b/core/assets/scaffold/files/update.php new file mode 100644 index 000000000..59e808ed2 --- /dev/null +++ b/core/assets/scaffold/files/update.php @@ -0,0 +1,31 @@ +handle($request); +$response->send(); + +$kernel->terminate($request, $response); diff --git a/core/assets/scaffold/files/web.config b/core/assets/scaffold/files/web.config new file mode 100644 index 000000000..8dff0b27e --- /dev/null +++ b/core/assets/scaffold/files/web.config @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/assets/vendor/ckeditor/CHANGES.md b/core/assets/vendor/ckeditor/CHANGES.md index 8f64f1880..63c82556b 100644 --- a/core/assets/vendor/ckeditor/CHANGES.md +++ b/core/assets/vendor/ckeditor/CHANGES.md @@ -1,6 +1,143 @@ CKEditor 4 Changelog ==================== +## CKEditor 4.13 + +New Features: + +* [#835](https://github.com/ckeditor/ckeditor-dev/issues/835): Extended support for pasting from external applications: + * Added support for pasting rich content from Google Docs with the [Paste from Google Docs](https://ckeditor.com/cke4/addon/pastefromgdocs) plugin. + * Added a new [Paste Tools](https://ckeditor.com/cke4/addon/pastetools) plugin for unified paste handling. +* [#3315](https://github.com/ckeditor/ckeditor-dev/issues/3315): Added support for strikethrough in the [BBCode](https://ckeditor.com/cke4/addon/bbcode) plugin. Thanks to [Alexander Kahl](https://github.com/akahl-owl)! +* [#3175](https://github.com/ckeditor/ckeditor-dev/issues/3175): Introduced selection optimization mechanism for handling incorrect selection behaviors in various browsers: + * [#3256](https://github.com/ckeditor/ckeditor-dev/issues/3256): Triple-clicking in the last table cell and deleting content no longer pulls the content below into the table. + * [#3118](https://github.com/ckeditor/ckeditor-dev/issues/3118): Selecting a paragraph with a triple-click and applying a heading applies the heading only to the selected paragraph. + * [#3161](https://github.com/ckeditor/ckeditor-dev/issues/3161): Double-clicking a `` element containing just one word creates a correct selection including the clicked `` only. +* [#3359](https://github.com/ckeditor/ckeditor-dev/issues/3359): Improved [dialog](https://ckeditor.com/cke4/addon/dialog) positioning and behavior when the dialog is resized or moved, or the browser window is resized. +* [#2227](https://github.com/ckeditor/ckeditor-dev/issues/2227): Added the [`config.linkDefaultProtocol`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-linkDefaultProtocol) configuration option that allows setting the default URL protocol for the [Link](https://ckeditor.com/cke4/addon/link) plugin dialog. +* [#3240](https://github.com/ckeditor/ckeditor-dev/issues/3240): Extended the [`CKEDITOR.plugins.widget#mask`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget.html#property-mask) property to allow masking only the specified part of a [widget](https://ckeditor.com/cke4/addon/widget). +* [#3138](https://github.com/ckeditor/ckeditor-dev/issues/3138): Added the possibility to use the [`widgetDefinition.getClipboardHtml()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget.html#method-getClipboardHtml) method to customize the [widget](https://ckeditor.com/cke4/addon/widget) HTML during copy, cut and drag operations. + +Fixed Issues: + +* [#808](https://github.com/ckeditor/ckeditor-dev/issues/808): Fixed: [Widgets](https://ckeditor.com/cke4/addon/widget) and other content disappear on drag and drop in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html). +* [#3260](https://github.com/ckeditor/ckeditor-dev/issues/3260): Fixed: [Widget](https://ckeditor.com/cke4/addon/widget) drag handler is visible in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html). +* [#3261](https://github.com/ckeditor/ckeditor-dev/issues/3261): Fixed: A [widget](https://ckeditor.com/cke4/addon/widget) initialized using the dialog has an incorrect owner document. +* [#3198](https://github.com/ckeditor/ckeditor-dev/issues/3198): Fixed: Blurring and focusing the editor when a [widget](https://ckeditor.com/cke4/addon/widget) is focused creates an additional undo step. +* [#2859](https://github.com/ckeditor/ckeditor-dev/pull/2859): [IE, Edge] Fixed: Various editor UI elements react to right mouse button click: + * [#2845](https://github.com/ckeditor/ckeditor-dev/issues/2845): [Rich Combo](https://ckeditor.com/cke4/addon/richcombo). + * [#2857](https://github.com/ckeditor/ckeditor-dev/issues/2857): [List Block](https://ckeditor.com/cke4/addon/listblock). + * [#2858](https://github.com/ckeditor/ckeditor-dev/issues/2858): [Menu](https://ckeditor.com/cke4/addon/menu). +* [#3158](https://github.com/ckeditor/ckeditor-dev/issues/3158): [Chrome, Safari] Fixed: [Undo](https://ckeditor.com/cke4/addon/undo) plugin breaks with the filling character. +* [#504](https://github.com/ckeditor/ckeditor-dev/issues/504): [Edge] Fixed: The editor's selection is collapsed to the beginning of the content when focusing the editor for the first time. +* [#3101](https://github.com/ckeditor/ckeditor-dev/issues/3101): Fixed: [`CKEDITOR.dom.range#_getTableElement()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-_getTableElement) returns `null` instead of a table element for edge cases. +* [#3287](https://github.com/ckeditor/ckeditor-dev/issues/3287): Fixed: [`CKEDITOR.tools.promise`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_promise.html) initializes incorrectly if an AMD loader is present. +* [#3379](https://github.com/ckeditor/ckeditor-dev/issues/3379): Fixed: Incorrect [`CKEDITOR.editor#getData()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getData) call when inserting content into the editor. +* [#941](https://github.com/ckeditor/ckeditor-dev/issues/941): Fixed: An error is thrown after styling a table cell text selected using the native selection when the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin is enabled. +* [#3136](https://github.com/ckeditor/ckeditor-dev/issues/3136): [Firefox] Fixed: Clicking [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) items removes the native table selection. +* [#3381](https://github.com/ckeditor/ckeditor-dev/issues/3381): [IE8] Fixed: The [`CKEDITOR.tools.object.keys()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_object.html#method-keys) method does not accept non-objects. +* [#2395](https://github.com/ckeditor/ckeditor-dev/issues/2395): [Android] Fixed: Focused input in a [dialog](https://ckeditor.com/cke4/addon/dialog) is scrolled out of the viewport when the soft keyboard appears. +* [#453](https://github.com/ckeditor/ckeditor-dev/issues/453): Fixed: [Link](https://ckeditor.com/cke4/addon/link) dialog has an invalid width when the editor is maximized and the browser window is resized. +* [#2138](https://github.com/ckeditor/ckeditor-dev/issues/2138): Fixed: An email address containing a question mark is mishandled by the [Link](https://ckeditor.com/cke4/addon/link) plugin. +* [#14613](https://dev.ckeditor.com/ticket/14613): Fixed: Race condition when loading plugins for an already destroyed editor instance throws an error. +* [#2257](https://github.com/ckeditor/ckeditor-dev/issues/2257): Fixed: The editor throws an exception when destroyed shortly after it was created. +* [#3115](https://github.com/ckeditor/ckeditor-dev/issues/3115): Fixed: Destroying the editor during the initialization throws an error. +* [#3354](https://github.com/ckeditor/ckeditor4/issues/3354): [iOS] Fixed: Pasting no longer works on iOS version 13. +* [#3423](https://github.com/ckeditor/ckeditor4/issues/3423) Fixed: [Bookmarks](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-createBookmark) can be created inside temporary elements. + +API Changes: + +* [#3154](https://github.com/ckeditor/ckeditor-dev/issues/3154): Added the [`CKEDITOR.tools.array.some()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_array.html#method-some) method. +* [#3245](https://github.com/ckeditor/ckeditor-dev/issues/3245): Added the [`CKEDITOR.plugins.undo.UndoManager.addFilterRule()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_undo_UndoManager.html#method-addFilterRule) method that allows filtering undo snapshot contents. +* [#2845](https://github.com/ckeditor/ckeditor-dev/issues/2845): Added the [`CKEDITOR.tools.normalizeMouseButton()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-normalizeMouseButton) method. +* [#2975](https://github.com/ckeditor/ckeditor-dev/issues/2975): Added the [`CKEDITOR.dom.element#fireEventHandler()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_element.html#method-fireEventHandler) method. +* [#3247](https://github.com/ckeditor/ckeditor-dev/issues/3247): Extended the [`CKEDITOR.tools.bind()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-bind) method to accept arguments for bound functions. +* [#3326](https://github.com/ckeditor/ckeditor-dev/issues/3326): Added the [`CKEDITOR.dom.text#isEmpty()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_text.html#method-isEmpty) method. +* [#2423](https://github.com/ckeditor/ckeditor-dev/issues/2423): Added the [`CKEDITOR.plugins.dialog.getModel()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dialog.html#method-getModel) and [`CKEDITOR.plugins.dialog.getMode()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dialog.html#method-getMode) methods with their [`CKEDITOR.plugin.definition`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dialog_definition.html) counterparts, allowing to get the dialog subject of a change. +* [#3124](https://github.com/ckeditor/ckeditor-dev/issues/3124): Added the [`CKEDITOR.dom.element#isDetached()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_element.html#method-isDetached) method. + +## CKEditor 4.12.1 + +Fixed Issues: + +* [#3220](https://github.com/ckeditor/ckeditor-dev/issues/3220): Fixed: Prevent [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) filter from deleting [Page Break](https://ckeditor.com/cke4/addon/pagebreak) elements on paste. + +## CKEditor 4.12 + +New Features: + +* [#2598](https://github.com/ckeditor/ckeditor-dev/issues/2598): Added the [Page Break](https://ckeditor.com/cke4/addon/pagebreak) feature support for the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#1490](https://github.com/ckeditor/ckeditor-dev/issues/1490): Improved the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin to retain table cell borders. +* [#2870](https://github.com/ckeditor/ckeditor-dev/issues/2870): Improved support for preserving the indentation of list items for nested lists pasted with the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#2048](https://github.com/ckeditor/ckeditor-dev/issues/2048): New [`CKEDITOR.config.image2_maxSize`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-image2_maxSize) configuration option for the [Enhanced Image](https://ckeditor.com/cke4/addon/image2) plugin that allows setting a maximum size that an image can be resized to with the resizer. +* [#2639](https://github.com/ckeditor/ckeditor-dev/issues/2639): The [Color Dialog](https://ckeditor.com/cke4/addon/colordialog) plugin now shows the current selection's color when opened. +* [#2084](https://github.com/ckeditor/ckeditor-dev/issues/2084): The [Table Tools](https://ckeditor.com/cke4/addon/tabletools) plugin now allows to change the cell height unit type to either pixels or percent. +* [#3164](https://github.com/ckeditor/ckeditor-dev/issues/3164): The [Table Tools](https://ckeditor.com/cke4/addon/tabletools) plugin now accepts floating point values as the table cell width and height. + +Fixed Issues: + +* [#2672](https://github.com/ckeditor/ckeditor-dev/issues/2672): Fixed: When resizing an [Enhanced Image](https://ckeditor.com/cke4/addon/image2) to a minimum size with the resizer, the image dialog does not show actual values. +* [#1478](https://github.com/ckeditor/ckeditor-dev/issues/1478): Fixed: Custom colors added to [Color Button](https://ckeditor.com/cke4/addon/colorbutton) with the [`config.colorButton_colors`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-colorButton_colors) configuration option in the form of a label or code do not work correctly. +* [#1469](https://github.com/ckeditor/ckeditor-dev/issues/1469): Fixed: Trying to get data from a nested editable inside a freshly pasted widget throws an error. +* [#2235](https://github.com/ckeditor/ckeditor-dev/issues/2235): Fixed: An [Image](https://ckeditor.com/cke4/addon/image) in a table cell has an empty URL field when edited from the context menu opened by right-click when the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin is in use. +* [#3098](https://github.com/ckeditor/ckeditor-dev/issues/3098): Fixed: Unit pickers for table cell width and height in the [Table Tools](https://ckeditor.com/cke4/addon/tabletools) plugin have a different width. +* [#2923](https://github.com/ckeditor/ckeditor-dev/issues/2923): Fixed: The CSS `windowtext` color is not correctly recognized by the [`CKEDITOR.tools.style.parse`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_style_parse.html) methods. +* [#3120](https://github.com/ckeditor/ckeditor-dev/issues/3120): [IE8] Fixed: The [`CKEDITOR.tools.extend()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tool.html#method-extend) method does not work with the [`DontEnum`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Properties) object property attribute. +* [#2813](https://github.com/ckeditor/ckeditor-dev/issues/2813): Fixed: Editor HTML insertion methods ([`editor.insertHtml()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-insertHtml), [`editor.insertHtmlIntoRange()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-insertHtmlIntoRange), [`editor.insertElement()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-insertElement) and [`editor.insertElementIntoRange()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-insertElementIntoRange)) pollute the editable with empty `` elements. +* [#2751](https://github.com/ckeditor/ckeditor-dev/issues/2751): Fixed: An editor with [`config.enterMode`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-enterMode) set to [`ENTER_DIV`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#property-ENTER_DIV) alters pasted content. + +API Changes: + +* [#1496](https://github.com/ckeditor/ckeditor-dev/issues/1496): The [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) plugin exposes the [`CKEDITOR.ui.balloonToolbar.reposition()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_ui_balloonToolbar.html#reposition) and [`CKEDITOR.ui.balloonToolbarView.reposition()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_ui_balloonToolbarView.html#reposition) methods. +* [#2021](https://github.com/ckeditor/ckeditor-dev/issues/2021): Added new [`CKEDITOR.dom.documentFragment.find()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_documentFragment.html#method-find) and [`CKEDITOR.dom.documentFragment.findOne()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_documentFragment.html#method-findOne) methods. +* [#2700](https://github.com/ckeditor/ckeditor-dev/issues/2700): Added the [`CKEDITOR.tools.array.find()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_array.html#method-find) method. +* [#3123](https://github.com/ckeditor/ckeditor-dev/issues/3123): Added the [`CKEDITOR.tools.object.keys()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_object.html#method-keys) method. +* [#3123](https://github.com/ckeditor/ckeditor-dev/issues/3123): Added the [`CKEDITOR.tools.object.entries()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_object.html#method-entries) method. +* [#3123](https://github.com/ckeditor/ckeditor-dev/issues/3123): Added the [`CKEDITOR.tools.object.values()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_object.html#method-values) method. +* [#2821](https://github.com/ckeditor/ckeditor-dev/issues/2821): The [`CKEDITOR.template#source`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_template.html#property-source) property can now be a function, so it can return the changed template values during the runtime. Thanks to [Jacek Pulit](https://github.com/jacek-pulit)! +* [#2598](https://github.com/ckeditor/ckeditor-dev/issues/2598): Added the [`CKEDITOR.plugins.pagebreak.createElement()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_pagebreak.html#method-createElement) method allowing to create a [Page Break](https://ckeditor.com/cke4/addon/pagebreak) plugin [`CKEDITOR.dom.element`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_element.html) instance. +* [#2748](https://github.com/ckeditor/ckeditor-dev/issues/2748): Enhanced error messages thrown when creating an editor on a non-existent element or when trying to instantiate the second editor on the same element. Thanks to [Byran Zaugg](https://github.com/blzaugg)! +* [#2698](https://github.com/ckeditor/ckeditor-dev/issues/2698): Added the [`CKEDITOR.htmlParser.element.findOne()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_htmlParser_element.html#method-findOne) method. +* [#2935](https://github.com/ckeditor/ckeditor-dev/issues/2935): Introduced the [`CKEDITOR.config.pasteFromWord_keepZeroMargins`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-pasteFromWord_keepZeroMargins) configuration option that allows for keeping any `margin-*: 0` style that would be otherwise removed when pasting content with the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#2962](https://github.com/ckeditor/ckeditor-dev/issues/2962): Added the [`CKEDITOR.tools.promise`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_promise.html) class. +* [#2924](https://github.com/ckeditor/ckeditor-dev/issues/2924): Added the [`CKEDITOR.tools.style.border`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_style_border.html) object wrapping CSS border style helpers under a single type. +* [#2495](https://github.com/ckeditor/ckeditor-dev/issues/2495): The [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin can now be disabled for the given table with the `data-cke-tableselection-ignored` attribute. +* [#2692](https://github.com/ckeditor/ckeditor-dev/issues/2692): Plugins can now expose information about the supported environment by implementing the [`pluginDefinition.isSupportedEnvironment()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_pluginDefinition.html#method-isSupportedEnvironment) method. + +Other Changes: + +* [#2741](https://github.com/ckeditor/ckeditor-dev/issues/2741): Replaced deprecated `arguments.callee` calls with named function expressions to allow the editor to work in strict mode. +* [#2924](https://github.com/ckeditor/ckeditor-dev/issues/2924): Marked [`CKEDITOR.tools.style.parse.border()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_style_parse.html#method-border) as deprecated in favor of the [`CKEDITOR.tools.style.border.fromCssRule()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_style_border.html#static-method-fromCssRule) method. +* [#3132](https://github.com/ckeditor/ckeditor-dev/issues/2924): Marked [`CKEDITOR.tools.objectKeys()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-objectKeys) as deprecated in favor of the [`CKEDITOR.tools.object.keys()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_object.html#method-keys) method. + +## CKEditor 4.11.4 + +Fixed Issues: + +* [#589](https://github.com/ckeditor/ckeditor-dev/issues/589): Fixed: The editor causes memory leaks in create and destroy cycles. +* [#1397](https://github.com/ckeditor/ckeditor-dev/issues/1397): Fixed: Using the dialog to remove headers from a [table](https://ckeditor.com/cke4/addon/table) with one header row only throws an error. +* [#1479](https://github.com/ckeditor/ckeditor-dev/issues/1479): Fixed: [Justification](https://ckeditor.com/cke4/addon/justify) for styled content in BR mode is disabled. +* [#2816](https://github.com/ckeditor/ckeditor-dev/issues/2816): Fixed: [Enhanced Image](https://ckeditor.com/cke4/addon/image2) resize handler is visible in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html). +* [#2874](https://github.com/ckeditor/ckeditor-dev/issues/2874): Fixed: [Enhanced Image](https://ckeditor.com/cke4/addon/image2) resize handler is not created when the editor is initialized in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html). +* [#2775](https://github.com/ckeditor/ckeditor-dev/issues/2775): Fixed: [Clipboard](https://ckeditor.com/cke4/addon/clipboard) paste buttons have wrong state when [read-only](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html) mode is set by the mouse event listener with the [Div Editing Area](https://ckeditor.com/cke4/addon/divarea) plugin. +* [#1901](https://github.com/ckeditor/ckeditor-dev/issues/1901): Fixed: Cannot open the context menu over a [Widget](https://ckeditor.com/cke4/addon/widget) with the Shift+F10 keyboard shortcut. + +Other Changes: + +* Updated [WebSpellChecker](https://ckeditor.com/cke4/addon/wsc) (WSC) and [SpellCheckAsYouType](https://ckeditor.com/cke4/addon/scayt) (SCAYT) plugins: + * Language dictionary update: German language was extended with over 600k new words. + * Language dictionary update: Swedish language was extended with over 300k new words. + * Grammar support added for Australian and New Zealand English, Polish, Slovak, Slovenian and Austrian languages. + * Changed wavy red and green lines that underline spelling and grammar errors to straight ones. + * [#55](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/55): Fixed: WSC does not use [`CKEDITOR.getUrl()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#method-getUrl) when referencing style sheets. + * [#166](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/166): Fixed: SCAYT does not use [`CKEDITOR.getUrl()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#method-getUrl) when referencing style sheets. + * [#56](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/56): [Chrome] Fixed: SCAYT/WSC throws errors when running inside a Chrome extension. + * Fixed: After removing a dictionary, the words are not underlined and considered as incorrect. + * Fixed: The Slovenian (`sl_SL`) language does not work. + * Fixed: Quotes with code `U+2019` (Right single quotation mark) are considered separators. + * Fixed: Wrong error message formatting when the service ID is invalid. + * Fixed: Absent languages in the Languages tab when using SCAYT with the [Shared Spaces](https://ckeditor.com/cke4/addon/sharedspace) plugin. + ## CKEditor 4.11.3 Fixed Issues: @@ -36,7 +173,7 @@ Fixed Issues: Other Changes: * Updated the [WebSpellChecker](https://ckeditor.com/cke4/addon/wsc) (WSC) plugin: - * [#52](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/52) Fixed: Clicking "Finish Checking" without a prior action would hang the Spell Checking dialog. + * [#52](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/52) Fixed: Clicking "Finish Checking" without a prior action would hang the Spell Checking dialog. * [#2603](https://github.com/ckeditor/ckeditor-dev/issues/2603): Corrected the GPL license entry in the `package.json` file. ## CKEditor 4.11.1 @@ -51,7 +188,7 @@ Fixed Issues: * Fixed XSS vulnerability in the HTML parser reported by [maxarr](https://hackerone.com/maxarr). - Issue summary: It was possible to execute XSS inside CKEditor after persuading the victim to: (i) switch CKEditor to source mode, then (ii) paste a specially crafted HTML code, prepared by the attacker, into the opened CKEditor source area, and (iii) switch back to WYSIWYG mode. + Issue summary: It was possible to execute XSS inside CKEditor after persuading the victim to: (i) switch CKEditor to source mode, then (ii) paste a specially crafted HTML code, prepared by the attacker, into the opened CKEditor source area, and (iii) switch back to WYSIWYG mode. **An upgrade is highly recommended!** @@ -98,8 +235,8 @@ API Changes: * [#2224](https://github.com/ckeditor/ckeditor-dev/issues/2224): The [`CKEDITOR.tools.convertToPx`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-convertToPx) function now converts negative values. * [#2253](https://github.com/ckeditor/ckeditor-dev/issues/2253): The widget definition [`insert`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget_definition.html#property-insert) method now passes `editor` and `commandData`. Thanks to [marcparmet](https://github.com/marcparmet)! * [#2045](https://github.com/ckeditor/ckeditor-dev/issues/2045): Extracted [`tools.eventsBuffer`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-eventsBuffer) and [`tools.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-throttle) functions logic into a separate namespace. - * [`tools.eventsBuffer`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-eventsBuffer) was extracted into [`tools.buffers.event`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_buffers_event.html), - * [`tools.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-throttle) was extracted into [`tools.buffers.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_buffers_throttle.html). + * [`tools.eventsBuffer`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-eventsBuffer) was extracted into [`tools.buffers.event`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_buffers_event.html), + * [`tools.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-throttle) was extracted into [`tools.buffers.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_buffers_throttle.html). * [#2466](https://github.com/ckeditor/ckeditor-dev/issues/2466): The [`CKEDITOR.filter`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-constructor) constructor accepts an additional `rules` parameter allowing to bind the editor and filter together. * [#2493](https://github.com/ckeditor/ckeditor-dev/issues/2493): The [`editor.getCommandKeystroke`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getCommandKeystroke) method accepts an additional `all` parameter allowing to retrieve an array of all command keystrokes. * [#2483](https://github.com/ckeditor/ckeditor-dev/issues/2483): Button's DOM element created with the [`hasArrow`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_ui.html#method-addButton) definition option can by identified by the `.cke_button_expandable` CSS class. @@ -242,6 +379,7 @@ Fixed Issues: * [#1570](https://github.com/ckeditor/ckeditor-dev/issues/1570): Fixed: Fake selection allows cutting content in read-only mode using the Ctrl/Cmd + X keys. * [#1363](https://github.com/ckeditor/ckeditor-dev/issues/1363): Fixed: Paste notification is unclear and it might confuse users. + API Changes: * [#1346](https://github.com/ckeditor/ckeditor-dev/issues/1346): [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) [context manager API](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.plugins.balloontoolbar.contextManager.html) is now available in the [`pluginDefinition.init()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_pluginDefinition.html#method-init) method of the [requiring](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_pluginDefinition.html#property-requires) plugin. @@ -652,7 +790,7 @@ Other Changes: - New features: - CKEditor [Language](https://ckeditor.com/cke4/addon/language) plugin support. - CKEditor [Placeholder](https://ckeditor.com/cke4/addon/placeholder) plugin support. - - [Drag&Drop](https://ckeditor.com/docs/ckeditor4/latest/examples/fileupload.html) support. + - [Drag&Drop](https://ckeditor.com/docs/ckeditor4/latest/examples/fileupload.html) support. - **Experimental** [GRAYT](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-grayt_autoStartup) (Grammar As You Type) functionality. - Fixed issues: * [#98](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/98): SCAYT affects dialog double-click. Fixed in SCAYT core. @@ -871,7 +1009,7 @@ New Features: * Direct access to clipboard could only be implemented in Chrome, Safari on Mac OS, Opera and Firefox. In other browsers the pastebin must still be used. * [#12875](https://dev.ckeditor.com/ticket/12875): Samples and toolbar configuration tools. - * The old set of samples shipped with every CKEditor package was replaced with a shiny new single-page sample. This change concluded a long term plan which started from introducing the [CKEditor SDK](https://ckeditor.com/docs/ckeditor4/latest/examples/index.html) and [CKEditor Functionality Overview](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_features.html) section in the documentation which essentially redefined the old samples. + * The old set of samples shipped with every CKEditor package was replaced with a shiny new single-page sample. This change concluded a long term plan which started from introducing the [CKEditor SDK](https://ckeditor.com/docs/ckeditor4/latest/examples/index.html) and [CKEditor Features Overview](https://ckeditor.com/docs/ckeditor4/latest/features.html) section in the documentation which essentially redefined the old samples. * Toolbar configurators with live previews were introduced. They will be shipped with every CKEditor package and are meant to help in configuring toolbar layouts. * [#10925](https://dev.ckeditor.com/ticket/10925): The [Media Embed](https://ckeditor.com/cke4/addon/embed) and [Semantic Media Embed](https://ckeditor.com/cke4/addon/embedsemantic) plugins were introduced. Read more about the new features in the [Embedding Content](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_media_embed.html) article. diff --git a/core/assets/vendor/ckeditor/LICENSE.md b/core/assets/vendor/ckeditor/LICENSE.md index e83d3cb65..9ab2d1745 100644 --- a/core/assets/vendor/ckeditor/LICENSE.md +++ b/core/assets/vendor/ckeditor/LICENSE.md @@ -40,6 +40,7 @@ The following libraries are included in CKEditor under the MIT license (see Appe * CKSource Samples Framework (included in the samples) - Copyright (c) 2014-2019, CKSource - Frederico Knabben. * PicoModal (included in `samples/js/sf.js`) - Copyright (c) 2012 James Frasca. * CodeMirror (included in the samples) - Copyright (C) 2014 by Marijn Haverbeke and others. +* ES6Promise - Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors. Parts of code taken from the following libraries are included in CKEditor under the MIT license (see Appendix D): diff --git a/core/assets/vendor/ckeditor/build-config.js b/core/assets/vendor/ckeditor/build-config.js index 8af244be5..120b95f1a 100644 --- a/core/assets/vendor/ckeditor/build-config.js +++ b/core/assets/vendor/ckeditor/build-config.js @@ -1,19 +1,18 @@ /** * This is a Drupal-optimized build of CKEditor. * - * You may re-use it at any time at http://ckeditor.com/builder to build - * CKEditor again. Alternatively, use the "build.sh" script to build it locally. - * If you do so, be sure to pass it the "-s" flag. So: "sh build.sh -s". + * To re-create this build: + * 1. Clone the development repo of CKEditor to your machine + * 2. Check out the version you'd like to build + * 3. Copy PATH/TO/DRUPAL/core/assets/vendor/ckeditor/build-config.js ./dev/builder/ + * 4. Run ./dev/builder/build.sh -s * - * If you are developing or debugging CKEditor plugins, you may want to work - * against an unoptimized (unminified) CKEditor build. To do so, you have two - * options: - * 1. Upload build-config.js to http://ckeditor.com/builder and choose the - * "Source (Big N'Slow)" option when downloading. - * 2. Use the "build.sh" script to build it locally, with one additional flag: - * "sh build.sh -s --leave-js-unminified". * Then, replace this directory (core/assets/vendor/ckeditor) with your build. * + * If you are developing or debugging CKEditor plugins, you may want to work + * against an unoptimized (unminified) CKEditor build. To do so, + * run the build command with the --leave-js-unminified flag. + * * NOTE: * This file is not used by CKEditor, you may remove it. * Changing this file will not change your CKEditor configuration. @@ -80,6 +79,7 @@ var CKBUILDER_CONFIG = { list: 1, magicline: 1, maximize: 1, + pastefromgdocs: 1, pastefromword: 1, pastetext: 1, removeformat: 1, diff --git a/core/assets/vendor/ckeditor/ckeditor.js b/core/assets/vendor/ckeditor/ckeditor.js index 956b2c4aa..40f3aa413 100644 --- a/core/assets/vendor/ckeditor/ckeditor.js +++ b/core/assets/vendor/ckeditor/ckeditor.js @@ -2,1158 +2,1189 @@ Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -(function(){window.CKEDITOR&&window.CKEDITOR.dom||(window.CKEDITOR||(window.CKEDITOR=function(){var e=/(^|.*[\\\/])ckeditor\.js(?:\?.*|;.*)?$/i,f={timestamp:"J24I",version:"4.11.3",revision:"8b53603e8",rnd:Math.floor(900*Math.random())+100,_:{pending:[],basePathSrcPattern:e},status:"unloaded",basePath:function(){var b=window.CKEDITOR_BASEPATH||"";if(!b)for(var c=document.getElementsByTagName("script"),h=0;ha.getListenerIndex(b)){a=a.listeners;f||(f=this);isNaN(d)&&(d=10);var n=this;g.fn=b;g.priority=d;for(var w=a.length-1;0<=w;w--)if(a[w].priority<=d)return a.splice(w+1,0,g),{removeListener:m};a.unshift(g)}return{removeListener:m}},once:function(){var c=Array.prototype.slice.call(arguments),b=c[1];c[1]=function(c){c.removeListener();return b.apply(this, -arguments)};return this.on.apply(this,c)},capture:function(){CKEDITOR.event.useCapture=1;var c=this.on.apply(this,arguments);CKEDITOR.event.useCapture=0;return c},fire:function(){var c=0,b=function(){c=1},e=0,k=function(){e=1};return function(d,g,m){var a=f(this)[d];d=c;var n=e;c=e=0;if(a){var w=a.listeners;if(w.length)for(var w=w.slice(0),u,t=0;tdocument.documentMode),mobile:-1h||c.quirks);c.gecko&&(f=e.match(/rv:([\d\.]+)/))&&(f=f[1].split("."),h=1E4*f[0]+100*(f[1]||0)+1*(f[2]||0));c.air&&(h=parseFloat(e.match(/ adobeair\/(\d+)/)[1]));c.webkit&&(h=parseFloat(e.match(/ applewebkit\/(\d+)/)[1]));c.version=h;c.isCompatible=!(c.ie&&7>h)&&!(c.gecko&&4E4>h)&&!(c.webkit&& -534>h);c.hidpi=2<=window.devicePixelRatio;c.needsBrFiller=c.gecko||c.webkit||c.ie&&10h;c.cssClass="cke_browser_"+(c.ie?"ie":c.gecko?"gecko":c.webkit?"webkit":"unknown");c.quirks&&(c.cssClass+=" cke_browser_quirks");c.ie&&(c.cssClass+=" cke_browser_ie"+(c.quirks?"6 cke_browser_iequirks":c.version));c.air&&(c.cssClass+=" cke_browser_air");c.iOS&&(c.cssClass+=" cke_browser_ios");c.hidpi&&(c.cssClass+=" cke_hidpi");return c}()),"unloaded"==CKEDITOR.status&&function(){CKEDITOR.event.implementOn(CKEDITOR); -CKEDITOR.loadFullCore=function(){if("basic_ready"!=CKEDITOR.status)CKEDITOR.loadFullCore._load=1;else{delete CKEDITOR.loadFullCore;var e=document.createElement("script");e.type="text/javascript";e.src=CKEDITOR.basePath+"ckeditor.js";document.getElementsByTagName("head")[0].appendChild(e)}};CKEDITOR.loadFullCoreTimeout=0;CKEDITOR.add=function(e){(this._.pending||(this._.pending=[])).push(e)};(function(){CKEDITOR.domReady(function(){var e=CKEDITOR.loadFullCore,f=CKEDITOR.loadFullCoreTimeout;e&&(CKEDITOR.status= -"basic_ready",e&&e._load?e():f&&setTimeout(function(){CKEDITOR.loadFullCore&&CKEDITOR.loadFullCore()},1E3*f))})})();CKEDITOR.status="basic_loaded"}(),"use strict",CKEDITOR.VERBOSITY_WARN=1,CKEDITOR.VERBOSITY_ERROR=2,CKEDITOR.verbosity=CKEDITOR.VERBOSITY_WARN|CKEDITOR.VERBOSITY_ERROR,CKEDITOR.warn=function(e,f){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_WARN&&CKEDITOR.fire("log",{type:"warn",errorCode:e,additionalData:f})},CKEDITOR.error=function(e,f){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_ERROR&&CKEDITOR.fire("log", -{type:"error",errorCode:e,additionalData:f})},CKEDITOR.on("log",function(e){if(window.console&&window.console.log){var f=console[e.data.type]?e.data.type:"log",c=e.data.errorCode;if(e=e.data.additionalData)console[f]("[CKEDITOR] Error code: "+c+".",e);else console[f]("[CKEDITOR] Error code: "+c+".");console[f]("[CKEDITOR] For more information about this error go to https://ckeditor.com/docs/ckeditor4/latest/guide/dev_errors.html#"+c)}},null,null,999),CKEDITOR.dom={},function(){function e(a,d,g){this._minInterval= -a;this._context=g;this._lastOutput=this._scheduledTimer=0;this._output=CKEDITOR.tools.bind(d,g||{});var b=this;this.input=function(){function a(){b._lastOutput=(new Date).getTime();b._scheduledTimer=0;b._call()}if(!b._scheduledTimer||!1!==b._reschedule()){var n=(new Date).getTime()-b._lastOutput;n/g,k=/|\s) /g, -function(a,n){return n+"\x26nbsp;"}).replace(/ (?=<)/g,"\x26nbsp;")},getNextNumber:function(){var a=0;return function(){return++a}}(),getNextId:function(){return"cke_"+this.getNextNumber()},getUniqueId:function(){for(var a="e",d=0;8>d;d++)a+=Math.floor(65536*(1+Math.random())).toString(16).substring(1);return a},override:function(a,d){var g=d(a);g.prototype=a.prototype;return g},setTimeout:function(a,d,g,b,c){c||(c=window);g||(g=c);return c.setTimeout(function(){b?a.apply(g,[].concat(b)):a.apply(g)}, -d||0)},throttle:function(a,d,g){return new this.buffers.throttle(a,d,g)},trim:function(){var a=/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;return function(d){return d.replace(a,"")}}(),ltrim:function(){var a=/^[ \t\n\r]+/g;return function(d){return d.replace(a,"")}}(),rtrim:function(){var a=/[ \t\n\r]+$/g;return function(d){return d.replace(a,"")}}(),indexOf:function(a,d){if("function"==typeof d)for(var g=0,b=a.length;gparseFloat(d);g&&(d=d.replace("-",""));a.setStyle("width",d);d=a.$.clientWidth;return g?-d:d}return d}}(),repeat:function(a,d){return Array(d+ -1).join(a)},tryThese:function(){for(var a,d=0,g=arguments.length;dd;d++)a[d]=("0"+parseInt(a[d],10).toString(16)).slice(-2);return"#"+a.join("")})},normalizeHex:function(a){return a.replace(/#(([0-9a-f]{3}){1,2})($|;|\s+)/gi,function(a,d,g,n){a=d.toLowerCase();3==a.length&&(a=a.split(""),a=[a[0],a[0],a[1],a[1],a[2],a[2]].join(""));return"#"+a+n})},parseCssText:function(a,d,g){var b={};g&&(a=(new CKEDITOR.dom.element("span")).setAttribute("style",a).getAttribute("style")||"");a&&(a=CKEDITOR.tools.normalizeHex(CKEDITOR.tools.convertRgbToHex(a))); -if(!a||";"==a)return b;a.replace(/"/g,'"').replace(/\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,g,n){d&&(g=g.toLowerCase(),"font-family"==g&&(n=n.replace(/\s*,\s*/g,",")),n=CKEDITOR.tools.trim(n));b[g]=n});return b},writeCssText:function(a,d){var g,b=[];for(g in a)b.push(g+":"+a[g]);d&&b.sort();return b.join("; ")},objectCompare:function(a,d,g){var b;if(!a&&!d)return!0;if(!a||!d)return!1;for(b in a)if(a[b]!=d[b])return!1;if(!g)for(b in d)if(a[b]!=d[b])return!1;return!0},objectKeys:function(a){var d= -[],g;for(g in a)d.push(g);return d},convertArrayToObject:function(a,d){var g={};1==arguments.length&&(d=!0);for(var b=0,c=a.length;bg;g++)a.push(Math.floor(256*Math.random())); -for(g=0;gCKEDITOR.env.version||CKEDITOR.env.ie6Compat)?4===a.button?CKEDITOR.MOUSE_BUTTON_MIDDLE:1===a.button? -CKEDITOR.MOUSE_BUTTON_LEFT:CKEDITOR.MOUSE_BUTTON_RIGHT:a.button:!1},convertHexStringToBytes:function(a){var d=[],g=a.length/2,b;for(b=0;be)for(h=e;3>h;h++)c[h]=0;m[0]=(c[0]&252)>>2;m[1]=(c[0]&3)<<4|c[1]>>4;m[2]=(c[1]&15)<<2|(c[2]&192)>>6;m[3]=c[2]&63;for(h=0;4>h;h++)d=h<=e?d+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(m[h]): -d+"\x3d"}return d},style:{parse:{_colors:{aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aqua:"#00FFFF",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blue:"#0000FF",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9", -darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",fuchsia:"#FF00FF", -gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",gray:"#808080",green:"#008000",greenyellow:"#ADFF2F",grey:"#808080",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgray:"#D3D3D3",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1", -lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",lime:"#00FF00",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",maroon:"#800000",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA", -mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",navy:"#000080",oldlace:"#FDF5E6",olive:"#808000",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",purple:"#800080",rebeccapurple:"#663399",red:"#FF0000",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072", -sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",silver:"#C0C0C0",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",teal:"#008080",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",white:"#FFFFFF",whitesmoke:"#F5F5F5",yellow:"#FFFF00",yellowgreen:"#9ACD32"},_borderStyle:"none hidden dotted dashed solid double groove ridge inset outset".split(" "), -_widthRegExp:/^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/,_rgbaRegExp:/rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi,_hslaRegExp:/hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi,background:function(a){var d={},g=this._findColor(a);g.length&&(d.color=g[0],CKEDITOR.tools.array.forEach(g,function(d){a=a.replace(d,"")}));if(a=CKEDITOR.tools.trim(a))d.unprocessed=a;return d},margin:function(a){function d(a){g.top=b[a[0]];g.right= -b[a[1]];g.bottom=b[a[2]];g.left=b[a[3]]}var g={},b=a.match(/(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset)/g)||["0px"];switch(b.length){case 1:d([0,0,0,0]);break;case 2:d([0,1,0,1]);break;case 3:d([0,1,2,1]);break;case 4:d([0,1,2,3])}return g},border:function(a){var d={},g=a.split(/\s+/g);a=CKEDITOR.tools.style.parse._findColor(a);a.length&&(d.color=a[0]);CKEDITOR.tools.array.forEach(g,function(a){d.style||-1===CKEDITOR.tools.indexOf(CKEDITOR.tools.style.parse._borderStyle,a)?!d.width&&CKEDITOR.tools.style.parse._widthRegExp.test(a)&& -(d.width=a):d.style=a});return d},_findColor:function(a){var d=[],g=CKEDITOR.tools.array,d=d.concat(a.match(this._rgbaRegExp)||[]),d=d.concat(a.match(this._hslaRegExp)||[]);return d=d.concat(g.filter(a.split(/\s+/),function(a){return a.match(/^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi)?!0:a.toLowerCase()in CKEDITOR.tools.style.parse._colors}))}}},array:{filter:function(a,d,g){var b=[];this.forEach(a,function(c,e){d.call(g,c,e,a)&&b.push(c)});return b},forEach:function(a,d,g){var b=a.length,c;for(c=0;cCKEDITOR.env.version&&(this.type==CKEDITOR.NODE_ELEMENT||this.type== -CKEDITOR.NODE_DOCUMENT_FRAGMENT)&&h(b);return b},hasPrevious:function(){return!!this.$.previousSibling},hasNext:function(){return!!this.$.nextSibling},insertAfter:function(e){e.$.parentNode.insertBefore(this.$,e.$.nextSibling);return e},insertBefore:function(e){e.$.parentNode.insertBefore(this.$,e.$);return e},insertBeforeMe:function(e){this.$.parentNode.insertBefore(e.$,this.$);return e},getAddress:function(e){for(var f=[],c=this.getDocument().$.documentElement,h=this.$;h&&h!=c;){var b=h.parentNode; -b&&f.unshift(this.getIndex.call({$:h},e));h=b}return f},getDocument:function(){return new CKEDITOR.dom.document(this.$.ownerDocument||this.$.parentNode.ownerDocument)},getIndex:function(e){function f(b,d){var g=d?b.nextSibling:b.previousSibling;return g&&g.nodeType==CKEDITOR.NODE_TEXT?c(g)?f(g,d):g:null}function c(b){return!b.nodeValue||b.nodeValue==CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE}var h=this.$,b=-1,l;if(!this.$.parentNode||e&&h.nodeType==CKEDITOR.NODE_TEXT&&c(h)&&!f(h)&&!f(h,!0))return-1; -do e&&h!=this.$&&h.nodeType==CKEDITOR.NODE_TEXT&&(l||c(h))||(b++,l=h.nodeType==CKEDITOR.NODE_TEXT);while(h=h.previousSibling);return b},getNextSourceNode:function(e,f,c){if(c&&!c.call){var h=c;c=function(b){return!b.equals(h)}}e=!e&&this.getFirst&&this.getFirst();var b;if(!e){if(this.type==CKEDITOR.NODE_ELEMENT&&c&&!1===c(this,!0))return null;e=this.getNext()}for(;!e&&(b=(b||this).getParent());){if(c&&!1===c(b,!0))return null;e=b.getNext()}return!e||c&&!1===c(e)?null:f&&f!=e.type?e.getNextSourceNode(!1, -f,c):e},getPreviousSourceNode:function(e,f,c){if(c&&!c.call){var h=c;c=function(b){return!b.equals(h)}}e=!e&&this.getLast&&this.getLast();var b;if(!e){if(this.type==CKEDITOR.NODE_ELEMENT&&c&&!1===c(this,!0))return null;e=this.getPrevious()}for(;!e&&(b=(b||this).getParent());){if(c&&!1===c(b,!0))return null;e=b.getPrevious()}return!e||c&&!1===c(e)?null:f&&e.type!=f?e.getPreviousSourceNode(!1,f,c):e},getPrevious:function(e){var f=this.$,c;do c=(f=f.previousSibling)&&10!=f.nodeType&&new CKEDITOR.dom.node(f); -while(c&&e&&!e(c));return c},getNext:function(e){var f=this.$,c;do c=(f=f.nextSibling)&&new CKEDITOR.dom.node(f);while(c&&e&&!e(c));return c},getParent:function(e){var f=this.$.parentNode;return f&&(f.nodeType==CKEDITOR.NODE_ELEMENT||e&&f.nodeType==CKEDITOR.NODE_DOCUMENT_FRAGMENT)?new CKEDITOR.dom.node(f):null},getParents:function(e){var f=this,c=[];do c[e?"push":"unshift"](f);while(f=f.getParent());return c},getCommonAncestor:function(e){if(e.equals(this))return this;if(e.contains&&e.contains(this))return e; -var f=this.contains?this:this.getParent();do if(f.contains(e))return f;while(f=f.getParent());return null},getPosition:function(e){var f=this.$,c=e.$;if(f.compareDocumentPosition)return f.compareDocumentPosition(c);if(f==c)return CKEDITOR.POSITION_IDENTICAL;if(this.type==CKEDITOR.NODE_ELEMENT&&e.type==CKEDITOR.NODE_ELEMENT){if(f.contains){if(f.contains(c))return CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_PRECEDING;if(c.contains(f))return CKEDITOR.POSITION_IS_CONTAINED+CKEDITOR.POSITION_FOLLOWING}if("sourceIndex"in -f)return 0>f.sourceIndex||0>c.sourceIndex?CKEDITOR.POSITION_DISCONNECTED:f.sourceIndex=document.documentMode||!f||(e=f+":"+ -e);return new CKEDITOR.dom.nodeList(this.$.getElementsByTagName(e))},getHead:function(){var e=this.$.getElementsByTagName("head")[0];return e=e?new CKEDITOR.dom.element(e):this.getDocumentElement().append(new CKEDITOR.dom.element("head"),!0)},getBody:function(){return new CKEDITOR.dom.element(this.$.body)},getDocumentElement:function(){return new CKEDITOR.dom.element(this.$.documentElement)},getWindow:function(){return new CKEDITOR.dom.window(this.$.parentWindow||this.$.defaultView)},write:function(e){this.$.open("text/html", -"replace");CKEDITOR.env.ie&&(e=e.replace(/(?:^\s*]*?>)|^/i,'$\x26\n\x3cscript data-cke-temp\x3d"1"\x3e('+CKEDITOR.tools.fixDomain+")();\x3c/script\x3e"));this.$.write(e);this.$.close()},find:function(e){return new CKEDITOR.dom.nodeList(this.$.querySelectorAll(e))},findOne:function(e){return(e=this.$.querySelector(e))?new CKEDITOR.dom.element(e):null},_getHtml5ShivFrag:function(){var e=this.getCustomData("html5ShivFrag");e||(e=this.$.createDocumentFragment(),CKEDITOR.tools.enableHtml5Elements(e, -!0),this.setCustomData("html5ShivFrag",e));return e}}),CKEDITOR.dom.nodeList=function(e){this.$=e},CKEDITOR.dom.nodeList.prototype={count:function(){return this.$.length},getItem:function(e){return 0>e||e>=this.$.length?null:(e=this.$[e])?new CKEDITOR.dom.node(e):null},toArray:function(){return CKEDITOR.tools.array.map(this.$,function(e){return new CKEDITOR.dom.node(e)})}},CKEDITOR.dom.element=function(e,f){"string"==typeof e&&(e=(f?f.$:document).createElement(e));CKEDITOR.dom.domObject.call(this, -e)},CKEDITOR.dom.element.get=function(e){return(e="string"==typeof e?document.getElementById(e)||document.getElementsByName(e)[0]:e)&&(e.$?e:new CKEDITOR.dom.element(e))},CKEDITOR.dom.element.prototype=new CKEDITOR.dom.node,CKEDITOR.dom.element.createFromHtml=function(e,f){var c=new CKEDITOR.dom.element("div",f);c.setHtml(e);return c.getFirst().remove()},CKEDITOR.dom.element.setMarker=function(e,f,c,h){var b=f.getCustomData("list_marker_id")||f.setCustomData("list_marker_id",CKEDITOR.tools.getNextNumber()).getCustomData("list_marker_id"), -l=f.getCustomData("list_marker_names")||f.setCustomData("list_marker_names",{}).getCustomData("list_marker_names");e[b]=f;l[c]=1;return f.setCustomData(c,h)},CKEDITOR.dom.element.clearAllMarkers=function(e){for(var f in e)CKEDITOR.dom.element.clearMarkers(e,e[f],1)},CKEDITOR.dom.element.clearMarkers=function(e,f,c){var h=f.getCustomData("list_marker_names"),b=f.getCustomData("list_marker_id"),l;for(l in h)f.removeCustomData(l);f.removeCustomData("list_marker_names");c&&(f.removeCustomData("list_marker_id"), -delete e[b])},function(){function e(d,g){return-1<(" "+d+" ").replace(l," ").indexOf(" "+g+" ")}function f(d){var g=!0;d.$.id||(d.$.id="cke_tmp_"+CKEDITOR.tools.getNextNumber(),g=!1);return function(){g||d.removeAttribute("id")}}function c(d,g){var b=CKEDITOR.tools.escapeCss(d.$.id);return"#"+b+" "+g.split(/,\s*/).join(", #"+b+" ")}function h(d){for(var g=0,b=0,a=k[d].length;bCKEDITOR.env.version?this.$.text+=d:this.append(new CKEDITOR.dom.text(d))},appendBogus:function(d){if(d||CKEDITOR.env.needsBrFiller){for(d=this.getLast();d&&d.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.rtrim(d.getText());)d=d.getPrevious();d&&d.is&&d.is("br")||(d=this.getDocument().createElement("br"),CKEDITOR.env.gecko&&d.setAttribute("type","_moz"),this.append(d))}},breakParent:function(d,g){var b=new CKEDITOR.dom.range(this.getDocument());b.setStartAfter(this);b.setEndAfter(d); -var a=b.extractContents(!1,g||!1),c;b.insertNode(this.remove());if(CKEDITOR.env.ie&&!CKEDITOR.env.edge){for(b=new CKEDITOR.dom.element("div");c=a.getFirst();)c.$.style.backgroundColor&&(c.$.style.backgroundColor=c.$.style.backgroundColor),b.append(c);b.insertAfter(this);b.remove(!0)}else a.insertAfterNode(this)},contains:document.compareDocumentPosition?function(d){return!!(this.$.compareDocumentPosition(d.$)&16)}:function(d){var g=this.$;return d.type!=CKEDITOR.NODE_ELEMENT?g.contains(d.getParent().$): -g!=d.$&&g.contains(d.$)},focus:function(){function d(){try{this.$.focus()}catch(d){}}return function(g){g?CKEDITOR.tools.setTimeout(d,100,this):d.call(this)}}(),getHtml:function(){var d=this.$.innerHTML;return CKEDITOR.env.ie?d.replace(/<\?[^>]*>/g,""):d},getOuterHtml:function(){if(this.$.outerHTML)return this.$.outerHTML.replace(/<\?[^>]*>/,"");var d=this.$.ownerDocument.createElement("div");d.appendChild(this.$.cloneNode(!0));return d.innerHTML},getClientRect:function(d){var g=CKEDITOR.tools.extend({}, -this.$.getBoundingClientRect());!g.width&&(g.width=g.right-g.left);!g.height&&(g.height=g.bottom-g.top);return d?CKEDITOR.tools.getAbsoluteRectPosition(this.getWindow(),g):g},setHtml:CKEDITOR.env.ie&&9>CKEDITOR.env.version?function(d){try{var g=this.$;if(this.getParent())return g.innerHTML=d;var b=this.getDocument()._getHtml5ShivFrag();b.appendChild(g);g.innerHTML=d;b.removeChild(g);return d}catch(a){this.$.innerHTML="";g=new CKEDITOR.dom.element("body",this.getDocument());g.$.innerHTML=d;for(g=g.getChildren();g.count();)this.append(g.getItem(0)); -return d}}:function(d){return this.$.innerHTML=d},setText:function(){var d=document.createElement("p");d.innerHTML="x";d=d.textContent;return function(g){this.$[d?"textContent":"innerText"]=g}}(),getAttribute:function(){var d=function(d){return this.$.getAttribute(d,2)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(d){switch(d){case "class":d="className";break;case "http-equiv":d="httpEquiv";break;case "name":return this.$.name;case "tabindex":return d=this.$.getAttribute(d, -2),0!==d&&0===this.$.tabIndex&&(d=null),d;case "checked":return d=this.$.attributes.getNamedItem(d),(d.specified?d.nodeValue:this.$.checked)?"checked":null;case "hspace":case "value":return this.$[d];case "style":return this.$.style.cssText;case "contenteditable":case "contentEditable":return this.$.attributes.getNamedItem("contentEditable").specified?this.$.getAttribute("contentEditable"):null}return this.$.getAttribute(d,2)}:d}(),getAttributes:function(d){var g={},b=this.$.attributes,a;d=CKEDITOR.tools.isArray(d)? -d:[];for(a=0;a=document.documentMode){var g=this.$.scopeName;"HTML"!=g&&(d=g.toLowerCase()+":"+d)}this.getName=function(){return d};return this.getName()},getValue:function(){return this.$.value},getFirst:function(d){var g=this.$.firstChild;(g=g&&new CKEDITOR.dom.node(g))&&d&&!d(g)&&(g=g.getNext(d));return g},getLast:function(d){var g=this.$.lastChild;(g=g&&new CKEDITOR.dom.node(g))&&d&&!d(g)&&(g=g.getPrevious(d));return g},getStyle:function(d){return this.$.style[CKEDITOR.tools.cssStyleToDomStyle(d)]}, -is:function(){var d=this.getName();if("object"==typeof arguments[0])return!!arguments[0][d];for(var g=0;gCKEDITOR.env.version&&this.is("a")){var b=this.getParent();b.type==CKEDITOR.NODE_ELEMENT&&(b=b.clone(),b.setHtml(g),g=b.getHtml(),b.setHtml(d),d=b.getHtml())}return g==d},isVisible:function(){var d=(this.$.offsetHeight||this.$.offsetWidth)&&"hidden"!=this.getComputedStyle("visibility"),g,b;d&&CKEDITOR.env.webkit&&(g=this.getWindow(),!g.equals(CKEDITOR.document.getWindow())&&(b=g.$.frameElement)&&(d=(new CKEDITOR.dom.element(b)).isVisible()));return!!d},isEmptyInlineRemoveable:function(){if(!CKEDITOR.dtd.$removeEmpty[this.getName()])return!1; -for(var d=this.getChildren(),g=0,b=d.count();gCKEDITOR.env.version?function(b){return"name"==b?!!this.$.name:d.call(this,b)}:d:function(d){return!!this.$.attributes.getNamedItem(d)}}(),hide:function(){this.setStyle("display","none")},moveChildren:function(d,b){var c=this.$;d=d.$;if(c!=d){var a;if(b)for(;a=c.lastChild;)d.insertBefore(c.removeChild(a),d.firstChild);else for(;a=c.firstChild;)d.appendChild(c.removeChild(a))}},mergeSiblings:function(){function d(d,b,a){if(b&&b.type==CKEDITOR.NODE_ELEMENT){for(var c= -[];b.data("cke-bookmark")||b.isEmptyInlineRemoveable();)if(c.push(b),b=a?b.getNext():b.getPrevious(),!b||b.type!=CKEDITOR.NODE_ELEMENT)return;if(d.isIdentical(b)){for(var e=a?d.getLast():d.getFirst();c.length;)c.shift().move(d,!a);b.moveChildren(d,!a);b.remove();e&&e.type==CKEDITOR.NODE_ELEMENT&&e.mergeSiblings()}}}return function(b){if(!1===b||CKEDITOR.dtd.$removeEmpty[this.getName()]||this.is("a"))d(this,this.getNext(),!0),d(this,this.getPrevious())}}(),show:function(){this.setStyles({display:"", -visibility:""})},setAttribute:function(){var d=function(d,b){this.$.setAttribute(d,b);return this};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(b,c){"class"==b?this.$.className=c:"style"==b?this.$.style.cssText=c:"tabindex"==b?this.$.tabIndex=c:"checked"==b?this.$.checked=c:"contenteditable"==b?d.call(this,"contentEditable",c):d.apply(this,arguments);return this}:CKEDITOR.env.ie8Compat&&CKEDITOR.env.secure?function(b,c){if("src"==b&&c.match(/^http:\/\//))try{d.apply(this, -arguments)}catch(a){}else d.apply(this,arguments);return this}:d}(),setAttributes:function(d){for(var b in d)this.setAttribute(b,d[b]);return this},setValue:function(d){this.$.value=d;return this},removeAttribute:function(){var d=function(d){this.$.removeAttribute(d)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(d){"class"==d?d="className":"tabindex"==d?d="tabIndex":"contenteditable"==d&&(d="contentEditable");this.$.removeAttribute(d)}:d}(),removeAttributes:function(d){if(CKEDITOR.tools.isArray(d))for(var b= -0;bCKEDITOR.env.version?(d=Math.round(100*d),this.setStyle("filter",100<=d?"":"progid:DXImageTransform.Microsoft.Alpha(opacity\x3d"+d+")")):this.setStyle("opacity",d)},unselectable:function(){this.setStyles(CKEDITOR.tools.cssVendorPrefix("user-select", -"none"));if(CKEDITOR.env.ie){this.setAttribute("unselectable","on");for(var d,b=this.getElementsByTag("*"),c=0,a=b.count();cf||0f?f:e);c&&(0>h||0h?h:a,0)},setState:function(d,b,c){b=b||"cke";switch(d){case CKEDITOR.TRISTATE_ON:this.addClass(b+"_on");this.removeClass(b+ -"_off");this.removeClass(b+"_disabled");c&&this.setAttribute("aria-pressed",!0);c&&this.removeAttribute("aria-disabled");break;case CKEDITOR.TRISTATE_DISABLED:this.addClass(b+"_disabled");this.removeClass(b+"_off");this.removeClass(b+"_on");c&&this.setAttribute("aria-disabled",!0);c&&this.removeAttribute("aria-pressed");break;default:this.addClass(b+"_off"),this.removeClass(b+"_on"),this.removeClass(b+"_disabled"),c&&this.removeAttribute("aria-pressed"),c&&this.removeAttribute("aria-disabled")}}, -getFrameDocument:function(){var b=this.$;try{b.contentWindow.document}catch(c){b.src=b.src}return b&&new CKEDITOR.dom.document(b.contentWindow.document)},copyAttributes:function(b,c){var e=this.$.attributes;c=c||{};for(var a=0;a=A.getChildCount()?(A=A.getChild(z-1),E=!0):A=A.getChild(z):F=E=!0;k.type== -CKEDITOR.NODE_TEXT?x?G=!0:k.split(D):0Z)for(;Y;)Y=e(Y,K,!0);K=V}x||m()}}function c(){var a=!1,b=CKEDITOR.dom.walker.whitespaces(),d=CKEDITOR.dom.walker.bookmark(!0),c=CKEDITOR.dom.walker.bogus();return function(g){return d(g)||b(g)?!0:c(g)&&!a?a=!0:g.type==CKEDITOR.NODE_TEXT&&(g.hasAscendant("pre")||CKEDITOR.tools.trim(g.getText()).length)||g.type==CKEDITOR.NODE_ELEMENT&&!g.is(l)?!1:!0}}function h(a){var b=CKEDITOR.dom.walker.whitespaces(), -d=CKEDITOR.dom.walker.bookmark(1);return function(c){return d(c)||b(c)?!0:!a&&k(c)||c.type==CKEDITOR.NODE_ELEMENT&&c.is(CKEDITOR.dtd.$removeEmpty)}}function b(a){return function(){var b;return this[a?"getPreviousNode":"getNextNode"](function(a){!b&&m(a)&&(b=a);return g(a)&&!(k(a)&&a.equals(b))})}}var l={abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,"var":1},k=CKEDITOR.dom.walker.bogus(), -d=/^[\t\r\n ]*(?: |\xa0)$/,g=CKEDITOR.dom.walker.editable(),m=CKEDITOR.dom.walker.ignored(!0);CKEDITOR.dom.range.prototype={clone:function(){var a=new CKEDITOR.dom.range(this.root);a._setStartContainer(this.startContainer);a.startOffset=this.startOffset;a._setEndContainer(this.endContainer);a.endOffset=this.endOffset;a.collapsed=this.collapsed;return a},collapse:function(a){a?(this._setEndContainer(this.startContainer),this.endOffset=this.startOffset):(this._setStartContainer(this.endContainer), -this.startOffset=this.endOffset);this.collapsed=!0},cloneContents:function(a){var b=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||f(this,2,b,!1,"undefined"==typeof a?!0:a);return b},deleteContents:function(a){this.collapsed||f(this,0,null,a)},extractContents:function(a,b){var d=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||f(this,1,d,a,"undefined"==typeof b?!0:b);return d},createBookmark:function(a){var b,d,c,g,e=this.collapsed;b=this.document.createElement("span"); -b.data("cke-bookmark",1);b.setStyle("display","none");b.setHtml("\x26nbsp;");a&&(c="cke_bm_"+CKEDITOR.tools.getNextNumber(),b.setAttribute("id",c+(e?"C":"S")));e||(d=b.clone(),d.setHtml("\x26nbsp;"),a&&d.setAttribute("id",c+"E"),g=this.clone(),g.collapse(),g.insertNode(d));g=this.clone();g.collapse(!0);g.insertNode(b);d?(this.setStartAfter(b),this.setEndBefore(d)):this.moveToPosition(b,CKEDITOR.POSITION_AFTER_END);return{startNode:a?c+(e?"C":"S"):b,endNode:a?c+"E":d,serializable:a,collapsed:e}},createBookmark2:function(){function a(a){var b= -a.container,c=a.offset,g;g=b;var e=c;g=g.type!=CKEDITOR.NODE_ELEMENT||0===e||e==g.getChildCount()?0:g.getChild(e-1).type==CKEDITOR.NODE_TEXT&&g.getChild(e).type==CKEDITOR.NODE_TEXT;g&&(b=b.getChild(c-1),c=b.getLength());if(b.type==CKEDITOR.NODE_ELEMENT&&0=a.offset&&(a.offset=g.getIndex(),a.container=g.getParent()))}}var d=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_TEXT,!0);return function(d){var c=this.collapsed,g={container:this.startContainer, -offset:this.startOffset},e={container:this.endContainer,offset:this.endOffset};d&&(a(g),b(g,this.root),c||(a(e),b(e,this.root)));return{start:g.container.getAddress(d),end:c?null:e.container.getAddress(d),startOffset:g.offset,endOffset:e.offset,normalized:d,collapsed:c,is2:!0}}}(),moveToBookmark:function(a){if(a.is2){var b=this.document.getByAddress(a.start,a.normalized),d=a.startOffset,c=a.end&&this.document.getByAddress(a.end,a.normalized);a=a.endOffset;this.setStart(b,d);c?this.setEnd(c,a):this.collapse(!0)}else b= -(d=a.serializable)?this.document.getById(a.startNode):a.startNode,a=d?this.document.getById(a.endNode):a.endNode,this.setStartBefore(b),b.remove(),a?(this.setEndBefore(a),a.remove()):this.collapse(!0)},getBoundaryNodes:function(){var a=this.startContainer,b=this.endContainer,d=this.startOffset,c=this.endOffset,g;if(a.type==CKEDITOR.NODE_ELEMENT)if(g=a.getChildCount(),g>d)a=a.getChild(d);else if(1>g)a=a.getPreviousSourceNode();else{for(a=a.$;a.lastChild;)a=a.lastChild;a=new CKEDITOR.dom.node(a);a= -a.getNextSourceNode()||a}if(b.type==CKEDITOR.NODE_ELEMENT)if(g=b.getChildCount(),g>c)b=b.getChild(c).getPreviousSourceNode(!0);else if(1>g)b=b.getPreviousSourceNode();else{for(b=b.$;b.lastChild;)b=b.lastChild;b=new CKEDITOR.dom.node(b)}a.getPosition(b)&CKEDITOR.POSITION_FOLLOWING&&(a=b);return{startNode:a,endNode:b}},getCommonAncestor:function(a,b){var d=this.startContainer,c=this.endContainer,d=d.equals(c)?a&&d.type==CKEDITOR.NODE_ELEMENT&&this.startOffset==this.endOffset-1?d.getChild(this.startOffset): -d:d.getCommonAncestor(c);return b&&!d.is?d.getParent():d},optimize:function(){var a=this.startContainer,b=this.startOffset;a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setStartAfter(a):this.setStartBefore(a));a=this.endContainer;b=this.endOffset;a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setEndAfter(a):this.setEndBefore(a))},optimizeBookmark:function(){var a=this.startContainer,b=this.endContainer;a.is&&a.is("span")&&a.data("cke-bookmark")&&this.setStartAt(a,CKEDITOR.POSITION_BEFORE_START); -b&&b.is&&b.is("span")&&b.data("cke-bookmark")&&this.setEndAt(b,CKEDITOR.POSITION_AFTER_END)},trim:function(a,b){var d=this.startContainer,c=this.startOffset,g=this.collapsed;if((!a||g)&&d&&d.type==CKEDITOR.NODE_TEXT){if(c)if(c>=d.getLength())c=d.getIndex()+1,d=d.getParent();else{var e=d.split(c),c=d.getIndex()+1,d=d.getParent();this.startContainer.equals(this.endContainer)?this.setEnd(e,this.endOffset-this.startOffset):d.equals(this.endContainer)&&(this.endOffset+=1)}else c=d.getIndex(),d=d.getParent(); -this.setStart(d,c);if(g){this.collapse(!0);return}}d=this.endContainer;c=this.endOffset;b||g||!d||d.type!=CKEDITOR.NODE_TEXT||(c?(c>=d.getLength()||d.split(c),c=d.getIndex()+1):c=d.getIndex(),d=d.getParent(),this.setEnd(d,c))},enlarge:function(a,b){function d(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")?null:a}var c=new RegExp(/[^\s\ufeff]/);switch(a){case CKEDITOR.ENLARGE_INLINE:var g=1;case CKEDITOR.ENLARGE_ELEMENT:var e=function(a,b){var d=new CKEDITOR.dom.range(m); -d.setStart(a,b);d.setEndAt(m,CKEDITOR.POSITION_BEFORE_END);var d=new CKEDITOR.dom.walker(d),g;for(d.guard=function(a){return!(a.type==CKEDITOR.NODE_ELEMENT&&a.isBlockBoundary())};g=d.next();){if(g.type!=CKEDITOR.NODE_TEXT)return!1;J=g!=a?g.getText():g.substring(b);if(c.test(J))return!1}return!0};if(this.collapsed)break;var h=this.getCommonAncestor(),m=this.root,f,l,k,y,A,D=!1,z,J;z=this.startContainer;var E=this.startOffset;z.type==CKEDITOR.NODE_TEXT?(E&&(z=!CKEDITOR.tools.trim(z.substring(0,E)).length&& -z,D=!!z),z&&((y=z.getPrevious())||(k=z.getParent()))):(E&&(y=z.getChild(E-1)||z.getLast()),y||(k=z));for(k=d(k);k||y;){if(k&&!y){!A&&k.equals(h)&&(A=!0);if(g?k.isBlockBoundary():!m.contains(k))break;D&&"inline"==k.getComputedStyle("display")||(D=!1,A?f=k:this.setStartBefore(k));y=k.getPrevious()}for(;y;)if(z=!1,y.type==CKEDITOR.NODE_COMMENT)y=y.getPrevious();else{if(y.type==CKEDITOR.NODE_TEXT)J=y.getText(),c.test(J)&&(y=null),z=/[\s\ufeff]$/.test(J);else if((y.$.offsetWidth>(CKEDITOR.env.webkit?1: -0)||b&&y.is("br"))&&!y.data("cke-bookmark"))if(D&&CKEDITOR.dtd.$removeEmpty[y.getName()]){J=y.getText();if(c.test(J))y=null;else for(var E=y.$.getElementsByTagName("*"),H=0,F;F=E[H++];)if(!CKEDITOR.dtd.$removeEmpty[F.nodeName.toLowerCase()]){y=null;break}y&&(z=!!J.length)}else y=null;z&&(D?A?f=k:k&&this.setStartBefore(k):D=!0);if(y){z=y.getPrevious();if(!k&&!z){k=y;y=null;break}y=z}else k=null}k&&(k=d(k.getParent()))}z=this.endContainer;E=this.endOffset;k=y=null;A=D=!1;z.type==CKEDITOR.NODE_TEXT? -CKEDITOR.tools.trim(z.substring(E)).length?D=!0:(D=!z.getLength(),E==z.getLength()?(y=z.getNext())||(k=z.getParent()):e(z,E)&&(k=z.getParent())):(y=z.getChild(E))||(k=z);for(;k||y;){if(k&&!y){!A&&k.equals(h)&&(A=!0);if(g?k.isBlockBoundary():!m.contains(k))break;D&&"inline"==k.getComputedStyle("display")||(D=!1,A?l=k:k&&this.setEndAfter(k));y=k.getNext()}for(;y;){z=!1;if(y.type==CKEDITOR.NODE_TEXT)J=y.getText(),e(y,0)||(y=null),z=/^[\s\ufeff]/.test(J);else if(y.type==CKEDITOR.NODE_ELEMENT){if((0=h.getLength()?e.setStartAfter(h):(e.setStartBefore(h),d=0):e.setStartBefore(h));m&&m.type==CKEDITOR.NODE_TEXT&&(k?k>=m.getLength()?e.setEndAfter(m):(e.setEndAfter(m),l=0):e.setEndBefore(m));var e=new CKEDITOR.dom.walker(e),y=CKEDITOR.dom.walker.bookmark(),A=CKEDITOR.dom.walker.bogus();e.evaluator=function(b){return b.type==(a==CKEDITOR.SHRINK_ELEMENT?CKEDITOR.NODE_ELEMENT:CKEDITOR.NODE_TEXT)}; -var D;e.guard=function(b,d){if(g&&A(b)||y(b))return!0;if(a==CKEDITOR.SHRINK_ELEMENT&&b.type==CKEDITOR.NODE_TEXT||d&&b.equals(D)||!1===c&&b.type==CKEDITOR.NODE_ELEMENT&&b.isBlockBoundary()||b.type==CKEDITOR.NODE_ELEMENT&&b.hasAttribute("contenteditable"))return!1;d||b.type!=CKEDITOR.NODE_ELEMENT||(D=b);return!0};d&&(h=e[a==CKEDITOR.SHRINK_ELEMENT?"lastForward":"next"]())&&this.setStartAt(h,b?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_START);l&&(e.reset(),(e=e[a==CKEDITOR.SHRINK_ELEMENT? -"lastBackward":"previous"]())&&this.setEndAt(e,b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_END));return!(!d&&!l)}},insertNode:function(a){this.optimizeBookmark();this.trim(!1,!0);var b=this.startContainer,d=b.getChild(this.startOffset);d?a.insertBefore(d):b.append(a);a.getParent()&&a.getParent().equals(this.endContainer)&&this.endOffset++;this.setStartBefore(a)},moveToPosition:function(a,b){this.setStartAt(a,b);this.collapse(!0)},moveToRange:function(a){this.setStart(a.startContainer,a.startOffset); -this.setEnd(a.endContainer,a.endOffset)},selectNodeContents:function(a){this.setStart(a,0);this.setEnd(a,a.type==CKEDITOR.NODE_TEXT?a.getLength():a.getChildCount())},setStart:function(a,b){a.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[a.getName()]&&(b=a.getIndex(),a=a.getParent());this._setStartContainer(a);this.startOffset=b;this.endContainer||(this._setEndContainer(a),this.endOffset=b);e(this)},setEnd:function(a,b){a.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[a.getName()]&&(b=a.getIndex()+ -1,a=a.getParent());this._setEndContainer(a);this.endOffset=b;this.startContainer||(this._setStartContainer(a),this.startOffset=b);e(this)},setStartAfter:function(a){this.setStart(a.getParent(),a.getIndex()+1)},setStartBefore:function(a){this.setStart(a.getParent(),a.getIndex())},setEndAfter:function(a){this.setEnd(a.getParent(),a.getIndex()+1)},setEndBefore:function(a){this.setEnd(a.getParent(),a.getIndex())},setStartAt:function(a,b){switch(b){case CKEDITOR.POSITION_AFTER_START:this.setStart(a,0); -break;case CKEDITOR.POSITION_BEFORE_END:a.type==CKEDITOR.NODE_TEXT?this.setStart(a,a.getLength()):this.setStart(a,a.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setStartBefore(a);break;case CKEDITOR.POSITION_AFTER_END:this.setStartAfter(a)}e(this)},setEndAt:function(a,b){switch(b){case CKEDITOR.POSITION_AFTER_START:this.setEnd(a,0);break;case CKEDITOR.POSITION_BEFORE_END:a.type==CKEDITOR.NODE_TEXT?this.setEnd(a,a.getLength()):this.setEnd(a,a.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setEndBefore(a); -break;case CKEDITOR.POSITION_AFTER_END:this.setEndAfter(a)}e(this)},fixBlock:function(a,b){var d=this.createBookmark(),c=this.document.createElement(b);this.collapse(a);this.enlarge(CKEDITOR.ENLARGE_BLOCK_CONTENTS);this.extractContents().appendTo(c);c.trim();this.insertNode(c);var g=c.getBogus();g&&g.remove();c.appendBogus();this.moveToBookmark(d);return c},splitBlock:function(a,b){var d=new CKEDITOR.dom.elementPath(this.startContainer,this.root),c=new CKEDITOR.dom.elementPath(this.endContainer,this.root), -g=d.block,e=c.block,h=null;if(!d.blockLimit.equals(c.blockLimit))return null;"br"!=a&&(g||(g=this.fixBlock(!0,a),e=(new CKEDITOR.dom.elementPath(this.endContainer,this.root)).block),e||(e=this.fixBlock(!1,a)));d=g&&this.checkStartOfBlock();c=e&&this.checkEndOfBlock();this.deleteContents();g&&g.equals(e)&&(c?(h=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(e,CKEDITOR.POSITION_AFTER_END),e=null):d?(h=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(g, -CKEDITOR.POSITION_BEFORE_START),g=null):(e=this.splitElement(g,b||!1),g.is("ul","ol")||g.appendBogus()));return{previousBlock:g,nextBlock:e,wasStartOfBlock:d,wasEndOfBlock:c,elementPath:h}},splitElement:function(a,b){if(!this.collapsed)return null;this.setEndAt(a,CKEDITOR.POSITION_BEFORE_END);var d=this.extractContents(!1,b||!1),c=a.clone(!1,b||!1);d.appendTo(c);c.insertAfter(a);this.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);return c},removeEmptyBlocksAtEnd:function(){function a(a){return function(c){return b(c)|| -d(c)||c.type==CKEDITOR.NODE_ELEMENT&&c.isEmptyInlineRemoveable()||a.is("table")&&c.is("caption")?!1:!0}}var b=CKEDITOR.dom.walker.whitespaces(),d=CKEDITOR.dom.walker.bookmark(!1);return function(b){for(var d=this.createBookmark(),c=this[b?"endPath":"startPath"](),g=c.block||c.blockLimit,e;g&&!g.equals(c.root)&&!g.getFirst(a(g));)e=g.getParent(),this[b?"setEndAt":"setStartAt"](g,CKEDITOR.POSITION_AFTER_END),g.remove(1),g=e;this.moveToBookmark(d)}}(),startPath:function(){return new CKEDITOR.dom.elementPath(this.startContainer, -this.root)},endPath:function(){return new CKEDITOR.dom.elementPath(this.endContainer,this.root)},checkBoundaryOfElement:function(a,b){var d=b==CKEDITOR.START,c=this.clone();c.collapse(d);c[d?"setStartAt":"setEndAt"](a,d?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END);c=new CKEDITOR.dom.walker(c);c.evaluator=h(d);return c[d?"checkBackward":"checkForward"]()},checkStartOfBlock:function(){var a=this.startContainer,b=this.startOffset;CKEDITOR.env.ie&&b&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.ltrim(a.substring(0, -b)),d.test(a)&&this.trim(0,1));this.trim();a=new CKEDITOR.dom.elementPath(this.startContainer,this.root);b=this.clone();b.collapse(!0);b.setStartAt(a.block||a.blockLimit,CKEDITOR.POSITION_AFTER_START);a=new CKEDITOR.dom.walker(b);a.evaluator=c();return a.checkBackward()},checkEndOfBlock:function(){var a=this.endContainer,b=this.endOffset;CKEDITOR.env.ie&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.rtrim(a.substring(b)),d.test(a)&&this.trim(1,0));this.trim();a=new CKEDITOR.dom.elementPath(this.endContainer, -this.root);b=this.clone();b.collapse(!1);b.setEndAt(a.block||a.blockLimit,CKEDITOR.POSITION_BEFORE_END);a=new CKEDITOR.dom.walker(b);a.evaluator=c();return a.checkForward()},getPreviousNode:function(a,b,d){var c=this.clone();c.collapse(1);c.setStartAt(d||this.root,CKEDITOR.POSITION_AFTER_START);d=new CKEDITOR.dom.walker(c);d.evaluator=a;d.guard=b;return d.previous()},getNextNode:function(a,b,d){var c=this.clone();c.collapse();c.setEndAt(d||this.root,CKEDITOR.POSITION_BEFORE_END);d=new CKEDITOR.dom.walker(c); -d.evaluator=a;d.guard=b;return d.next()},checkReadOnly:function(){function a(a,b){for(;a;){if(a.type==CKEDITOR.NODE_ELEMENT){if("false"==a.getAttribute("contentEditable")&&!a.data("cke-editable"))return 0;if(a.is("html")||"true"==a.getAttribute("contentEditable")&&(a.contains(b)||a.equals(b)))break}a=a.getParent()}return 1}return function(){var b=this.startContainer,d=this.endContainer;return!(a(b,d)&&a(d,b))}}(),moveToElementEditablePosition:function(a,b){if(a.type==CKEDITOR.NODE_ELEMENT&&!a.isEditable(!1))return this.moveToPosition(a, -b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START),!0;for(var c=0;a;){if(a.type==CKEDITOR.NODE_TEXT){b&&this.endContainer&&this.checkEndOfBlock()&&d.test(a.getText())?this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START):this.moveToPosition(a,b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START);c=1;break}if(a.type==CKEDITOR.NODE_ELEMENT)if(a.isEditable())this.moveToPosition(a,b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_START),c=1;else if(b&&a.is("br")&&this.endContainer&& -this.checkEndOfBlock())this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START);else if("false"==a.getAttribute("contenteditable")&&a.is(CKEDITOR.dtd.$block))return this.setStartBefore(a),this.setEndAfter(a),!0;var g=a,e=c,h=void 0;g.type==CKEDITOR.NODE_ELEMENT&&g.isEditable(!1)&&(h=g[b?"getLast":"getFirst"](m));e||h||(h=g[b?"getPrevious":"getNext"](m));a=h}return!!c},moveToClosestEditablePosition:function(a,b){var d,c=0,g,e,h=[CKEDITOR.POSITION_AFTER_END,CKEDITOR.POSITION_BEFORE_START];a?(d=new CKEDITOR.dom.range(this.root), -d.moveToPosition(a,h[b?0:1])):d=this.clone();if(a&&!a.is(CKEDITOR.dtd.$block))c=1;else if(g=d[b?"getNextEditableNode":"getPreviousEditableNode"]())c=1,(e=g.type==CKEDITOR.NODE_ELEMENT)&&g.is(CKEDITOR.dtd.$block)&&"false"==g.getAttribute("contenteditable")?(d.setStartAt(g,CKEDITOR.POSITION_BEFORE_START),d.setEndAt(g,CKEDITOR.POSITION_AFTER_END)):!CKEDITOR.env.needsBrFiller&&e&&g.is(CKEDITOR.dom.walker.validEmptyBlockContainers)?(d.setEnd(g,0),d.collapse()):d.moveToPosition(g,h[b?1:0]);c&&this.moveToRange(d); -return!!c},moveToElementEditStart:function(a){return this.moveToElementEditablePosition(a)},moveToElementEditEnd:function(a){return this.moveToElementEditablePosition(a,!0)},getEnclosedNode:function(){var a=this.clone();a.optimize();if(a.startContainer.type!=CKEDITOR.NODE_ELEMENT||a.endContainer.type!=CKEDITOR.NODE_ELEMENT)return null;var a=new CKEDITOR.dom.walker(a),b=CKEDITOR.dom.walker.bookmark(!1,!0),d=CKEDITOR.dom.walker.whitespaces(!0);a.evaluator=function(a){return d(a)&&b(a)};var c=a.next(); -a.reset();return c&&c.equals(a.previous())?c:null},getTouchedStartNode:function(){var a=this.startContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.startOffset)||a},getTouchedEndNode:function(){var a=this.endContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.endOffset-1)||a},getNextEditableNode:b(),getPreviousEditableNode:b(1),_getTableElement:function(a){a=a||{td:1,th:1,tr:1,tbody:1,thead:1,tfoot:1,table:1};var b=this.startContainer,d= -this.endContainer,c=b.getAscendant("table",!0),g=d.getAscendant("table",!0);return c&&!this.root.contains(c)?null:CKEDITOR.env.safari&&c&&d.equals(this.root)?b.getAscendant(a,!0):this.getEnclosedNode()?this.getEnclosedNode().getAscendant(a,!0):c&&g&&(c.equals(g)||c.contains(g)||g.contains(c))?b.getAscendant(a,!0):null},scrollIntoView:function(){var a=new CKEDITOR.dom.element.createFromHtml("\x3cspan\x3e\x26nbsp;\x3c/span\x3e",this.document),b,d,c,g=this.clone();g.optimize();(c=g.startContainer.type== -CKEDITOR.NODE_TEXT)?(d=g.startContainer.getText(),b=g.startContainer.split(g.startOffset),a.insertAfter(g.startContainer)):g.insertNode(a);a.scrollIntoView();c&&(g.startContainer.setText(d),b.remove());a.remove()},getClientRects:function(){function a(a,b){var d=CKEDITOR.tools.array.map(a,function(a){return a}),c=new CKEDITOR.dom.range(b.root),g,e,h;b.startContainer instanceof CKEDITOR.dom.element&&(e=0===b.startOffset&&b.startContainer.hasAttribute("data-widget"));b.endContainer instanceof CKEDITOR.dom.element&& -(h=(h=b.endOffset===(b.endContainer.getChildCount?b.endContainer.getChildCount():b.endContainer.length))&&b.endContainer.hasAttribute("data-widget"));e&&c.setStart(b.startContainer.getParent(),b.startContainer.getIndex());h&&c.setEnd(b.endContainer.getParent(),b.endContainer.getIndex()+1);if(e||h)b=c;c=b.cloneContents();c=CKEDITOR.dom.document.prototype.find.call(c,"[data-cke-widget-id]").toArray();if(c=CKEDITOR.tools.array.map(c,function(a){var d=b.root.editor;a=a.getAttribute("data-cke-widget-id"); -return d.widgets.instances[a].element}))return c=CKEDITOR.tools.array.map(c,function(a){var b;b=a.getParent().hasClass("cke_widget_wrapper")?a.getParent():a;g=this.root.getDocument().$.createRange();g.setStart(b.getParent().$,b.getIndex());g.setEnd(b.getParent().$,b.getIndex()+1);b=g.getClientRects();b.widgetRect=a.getClientRect();return b},b),CKEDITOR.tools.array.forEach(c,function(a){function b(g){CKEDITOR.tools.array.forEach(d,function(b,e){var h=CKEDITOR.tools.objectCompare(a[g],b);h||(h=CKEDITOR.tools.objectCompare(a.widgetRect, -b));h&&(Array.prototype.splice.call(d,e,a.length-g,a.widgetRect),c=!0)});c||(garguments.length||(this.range=b,this.forceBrBreak=0,this.enlargeBr=1,this.enforceRealBlocks=0,this._||(this._={}))}function f(b){var a=[];b.forEach(function(b){if("true"==b.getAttribute("contenteditable"))return a.push(b),!1},CKEDITOR.NODE_ELEMENT,!0);return a}function c(b,a,d,g){a:{null==g&&(g=f(d));for(var e;e=g.shift();)if(e.getDtd().p){g={element:e,remaining:g};break a}g=null}if(!g)return 0; -if((e=CKEDITOR.filter.instances[g.element.data("cke-filter")])&&!e.check(a))return c(b,a,d,g.remaining);a=new CKEDITOR.dom.range(g.element);a.selectNodeContents(g.element);a=a.createIterator();a.enlargeBr=b.enlargeBr;a.enforceRealBlocks=b.enforceRealBlocks;a.activeFilter=a.filter=e;b._.nestedEditable={element:g.element,container:d,remaining:g.remaining,iterator:a};return 1}function h(b,a,d){if(!a)return!1;b=b.clone();b.collapse(!d);return b.checkBoundaryOfElement(a,d?CKEDITOR.START:CKEDITOR.END)} -var b=/^[\r\n\t ]+$/,l=CKEDITOR.dom.walker.bookmark(!1,!0),k=CKEDITOR.dom.walker.whitespaces(!0),d=function(b){return l(b)&&k(b)},g={dd:1,dt:1,li:1};e.prototype={getNextParagraph:function(e){var a,f,k,u,t;e=e||"p";if(this._.nestedEditable){if(a=this._.nestedEditable.iterator.getNextParagraph(e))return this.activeFilter=this._.nestedEditable.iterator.activeFilter,a;this.activeFilter=this.filter;if(c(this,e,this._.nestedEditable.container,this._.nestedEditable.remaining))return this.activeFilter=this._.nestedEditable.iterator.activeFilter, -this._.nestedEditable.iterator.getNextParagraph(e);this._.nestedEditable=null}if(!this.range.root.getDtd()[e])return null;if(!this._.started){var p=this.range.clone();f=p.startPath();var r=p.endPath(),v=!p.collapsed&&h(p,f.block),q=!p.collapsed&&h(p,r.block,1);p.shrink(CKEDITOR.SHRINK_ELEMENT,!0);v&&p.setStartAt(f.block,CKEDITOR.POSITION_BEFORE_END);q&&p.setEndAt(r.block,CKEDITOR.POSITION_AFTER_START);f=p.endContainer.hasAscendant("pre",!0)||p.startContainer.hasAscendant("pre",!0);p.enlarge(this.forceBrBreak&& -!f||!this.enlargeBr?CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:CKEDITOR.ENLARGE_BLOCK_CONTENTS);p.collapsed||(f=new CKEDITOR.dom.walker(p.clone()),r=CKEDITOR.dom.walker.bookmark(!0,!0),f.evaluator=r,this._.nextNode=f.next(),f=new CKEDITOR.dom.walker(p.clone()),f.evaluator=r,f=f.previous(),this._.lastNode=f.getNextSourceNode(!0,null,p.root),this._.lastNode&&this._.lastNode.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.trim(this._.lastNode.getText())&&this._.lastNode.getParent().isBlockBoundary()&&(r=this.range.clone(), -r.moveToPosition(this._.lastNode,CKEDITOR.POSITION_AFTER_END),r.checkEndOfBlock()&&(r=new CKEDITOR.dom.elementPath(r.endContainer,r.root),this._.lastNode=(r.block||r.blockLimit).getNextSourceNode(!0))),this._.lastNode&&p.root.contains(this._.lastNode)||(this._.lastNode=this._.docEndMarker=p.document.createText(""),this._.lastNode.insertAfter(f)),p=null);this._.started=1;f=p}r=this._.nextNode;p=this._.lastNode;for(this._.nextNode=null;r;){var v=0,q=r.hasAscendant("pre"),B=r.type!=CKEDITOR.NODE_ELEMENT, -x=0;if(B)r.type==CKEDITOR.NODE_TEXT&&b.test(r.getText())&&(B=0);else{var y=r.getName();if(CKEDITOR.dtd.$block[y]&&"false"==r.getAttribute("contenteditable")){a=r;c(this,e,a);break}else if(r.isBlockBoundary(this.forceBrBreak&&!q&&{br:1})){if("br"==y)B=1;else if(!f&&!r.getChildCount()&&"hr"!=y){a=r;k=r.equals(p);break}f&&(f.setEndAt(r,CKEDITOR.POSITION_BEFORE_START),"br"!=y&&(this._.nextNode=r));v=1}else{if(r.getFirst()){f||(f=this.range.clone(),f.setStartAt(r,CKEDITOR.POSITION_BEFORE_START));r=r.getFirst(); -continue}B=1}}B&&!f&&(f=this.range.clone(),f.setStartAt(r,CKEDITOR.POSITION_BEFORE_START));k=(!v||B)&&r.equals(p);if(f&&!v)for(;!r.getNext(d)&&!k;){y=r.getParent();if(y.isBlockBoundary(this.forceBrBreak&&!q&&{br:1})){v=1;B=0;k||y.equals(p);f.setEndAt(y,CKEDITOR.POSITION_BEFORE_END);break}r=y;B=1;k=r.equals(p);x=1}B&&f.setEndAt(r,CKEDITOR.POSITION_AFTER_END);r=this._getNextSourceNode(r,x,p);if((k=!r)||v&&f)break}if(!a){if(!f)return this._.docEndMarker&&this._.docEndMarker.remove(),this._.nextNode= -null;a=new CKEDITOR.dom.elementPath(f.startContainer,f.root);r=a.blockLimit;v={div:1,th:1,td:1};a=a.block;!a&&r&&!this.enforceRealBlocks&&v[r.getName()]&&f.checkStartOfBlock()&&f.checkEndOfBlock()&&!r.equals(f.root)?a=r:!a||this.enforceRealBlocks&&a.is(g)?(a=this.range.document.createElement(e),f.extractContents().appendTo(a),a.trim(),f.insertNode(a),u=t=!0):"li"!=a.getName()?f.checkStartOfBlock()&&f.checkEndOfBlock()||(a=a.clone(!1),f.extractContents().appendTo(a),a.trim(),t=f.splitBlock(),u=!t.wasStartOfBlock, -t=!t.wasEndOfBlock,f.insertNode(a)):k||(this._.nextNode=a.equals(p)?null:this._getNextSourceNode(f.getBoundaryNodes().endNode,1,p))}u&&(u=a.getPrevious())&&u.type==CKEDITOR.NODE_ELEMENT&&("br"==u.getName()?u.remove():u.getLast()&&"br"==u.getLast().$.nodeName.toLowerCase()&&u.getLast().remove());t&&(u=a.getLast())&&u.type==CKEDITOR.NODE_ELEMENT&&"br"==u.getName()&&(!CKEDITOR.env.needsBrFiller||u.getPrevious(l)||u.getNext(l))&&u.remove();this._.nextNode||(this._.nextNode=k||a.equals(p)||!p?null:this._getNextSourceNode(a, -1,p));return a},_getNextSourceNode:function(b,a,d){function c(a){return!(a.equals(d)||a.equals(g))}var g=this.range.root;for(b=b.getNextSourceNode(a,null,c);!l(b);)b=b.getNextSourceNode(a,null,c);return b}};CKEDITOR.dom.range.prototype.createIterator=function(){return new e(this)}}(),CKEDITOR.command=function(e,f){this.uiItems=[];this.exec=function(c){if(this.state==CKEDITOR.TRISTATE_DISABLED||!this.checkAllowed())return!1;this.editorFocus&&e.focus();return!1===this.fire("exec")?!0:!1!==f.exec.call(this, -e,c)};this.refresh=function(c,b){if(!this.readOnly&&c.readOnly)return!0;if(this.context&&!b.isContextFor(this.context)||!this.checkAllowed(!0))return this.disable(),!0;this.startDisabled||this.enable();this.modes&&!this.modes[c.mode]&&this.disable();return!1===this.fire("refresh",{editor:c,path:b})?!0:f.refresh&&!1!==f.refresh.apply(this,arguments)};var c;this.checkAllowed=function(h){return h||"boolean"!=typeof c?c=e.activeFilter.checkFeature(this):c};CKEDITOR.tools.extend(this,f,{modes:{wysiwyg:1}, -editorFocus:1,contextSensitive:!!f.context,state:CKEDITOR.TRISTATE_DISABLED});CKEDITOR.event.call(this)},CKEDITOR.command.prototype={enable:function(){this.state==CKEDITOR.TRISTATE_DISABLED&&this.checkAllowed()&&this.setState(this.preserveState&&"undefined"!=typeof this.previousState?this.previousState:CKEDITOR.TRISTATE_OFF)},disable:function(){this.setState(CKEDITOR.TRISTATE_DISABLED)},setState:function(e){if(this.state==e||e!=CKEDITOR.TRISTATE_DISABLED&&!this.checkAllowed())return!1;this.previousState= -this.state;this.state=e;this.fire("state");return!0},toggleState:function(){this.state==CKEDITOR.TRISTATE_OFF?this.setState(CKEDITOR.TRISTATE_ON):this.state==CKEDITOR.TRISTATE_ON&&this.setState(CKEDITOR.TRISTATE_OFF)}},CKEDITOR.event.implementOn(CKEDITOR.command.prototype),CKEDITOR.ENTER_P=1,CKEDITOR.ENTER_BR=2,CKEDITOR.ENTER_DIV=3,CKEDITOR.config={customConfig:"config.js",autoUpdateElement:!0,language:"",defaultLanguage:"en",contentsLangDirection:"",enterMode:CKEDITOR.ENTER_P,forceEnterMode:!1,shiftEnterMode:CKEDITOR.ENTER_BR, -docType:"\x3c!DOCTYPE html\x3e",bodyId:"",bodyClass:"",fullPage:!1,height:200,contentsCss:CKEDITOR.getUrl("contents.css"),extraPlugins:"",removePlugins:"",protectedSource:[],tabIndex:0,width:"",baseFloatZIndex:1E4,blockedKeystrokes:[CKEDITOR.CTRL+66,CKEDITOR.CTRL+73,CKEDITOR.CTRL+85]},function(){function e(a,b,d,c,g){var e,h;a=[];for(e in b){h=b[e];h="boolean"==typeof h?{}:"function"==typeof h?{match:h}:H(h);"$"!=e.charAt(0)&&(h.elements=e);d&&(h.featureName=d.toLowerCase());var f=h;f.elements=k(f.elements, -/\s+/)||null;f.propertiesOnly=f.propertiesOnly||!0===f.elements;var m=/\s*,\s*/,r=void 0;for(r in L){f[r]=k(f[r],m)||null;var l=f,n=I[r],x=k(f[I[r]],m),C=f[r],A=[],y=!0,q=void 0;x?y=!1:x={};for(q in C)"!"==q.charAt(0)&&(q=q.slice(1),A.push(q),x[q]=!0,y=!1);for(;q=A.pop();)C[q]=C["!"+q],delete C["!"+q];l[n]=(y?!1:x)||null}f.match=f.match||null;c.push(h);a.push(h)}b=g.elements;g=g.generic;var w;d=0;for(c=a.length;d=--d&&(l&&CKEDITOR.document.getDocumentElement().removeStyle("cursor"),a(c))},w=function(a,b){e[a]=1;var d=f[a];delete f[a];for(var c=0;c=CKEDITOR.env.version||CKEDITOR.env.ie9Compat)?d.$.onreadystatechange=function(){if("loaded"==d.$.readyState||"complete"==d.$.readyState)d.$.onreadystatechange=null,w(a,!0)}:(d.$.onload=function(){setTimeout(function(){w(a,!0)},0)},d.$.onerror=function(){w(a,!1)}));d.appendTo(CKEDITOR.document.getHead())}}};l&&CKEDITOR.document.getDocumentElement().setStyle("cursor","wait"); -for(var t=0;t]+)>)|(?:!--([\S|\s]*?)--\x3e)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/g}}, -function(){var e=/([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g,f={checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};CKEDITOR.htmlParser.prototype={onTagOpen:function(){},onTagClose:function(){},onText:function(){},onCDATA:function(){},onComment:function(){},parse:function(c){for(var h,b,l=0,k;h=this._.htmlPartsRegex.exec(c);){b=h.index;if(b>l)if(l=c.substring(l,b),k)k.push(l);else this.onText(l); -l=this._.htmlPartsRegex.lastIndex;if(b=h[1])if(b=b.toLowerCase(),k&&CKEDITOR.dtd.$cdata[b]&&(this.onCDATA(k.join("")),k=null),!k){this.onTagClose(b);continue}if(k)k.push(h[0]);else if(b=h[3]){if(b=b.toLowerCase(),!/="/.test(b)){var d={},g,m=h[4];h=!!h[5];if(m)for(;g=e.exec(m);){var a=g[1].toLowerCase();g=g[2]||g[3]||g[4]||"";d[a]=!g&&f[a]?a:CKEDITOR.tools.htmlDecodeAttr(g)}this.onTagOpen(b,d,h);!k&&CKEDITOR.dtd.$cdata[b]&&(k=[])}}else if(b=h[2])this.onComment(b)}if(c.length>l)this.onText(c.substring(l, -c.length))}}}(),CKEDITOR.htmlParser.basicWriter=CKEDITOR.tools.createClass({$:function(){this._={output:[]}},proto:{openTag:function(e){this._.output.push("\x3c",e)},openTagClose:function(e,f){f?this._.output.push(" /\x3e"):this._.output.push("\x3e")},attribute:function(e,f){"string"==typeof f&&(f=CKEDITOR.tools.htmlEncodeAttr(f));this._.output.push(" ",e,'\x3d"',f,'"')},closeTag:function(e){this._.output.push("\x3c/",e,"\x3e")},text:function(e){this._.output.push(e)},comment:function(e){this._.output.push("\x3c!--", -e,"--\x3e")},write:function(e){this._.output.push(e)},reset:function(){this._.output=[];this._.indent=!1},getHtml:function(e){var f=this._.output.join("");e&&this.reset();return f}}}),"use strict",function(){CKEDITOR.htmlParser.node=function(){};CKEDITOR.htmlParser.node.prototype={remove:function(){var e=this.parent.children,f=CKEDITOR.tools.indexOf(e,this),c=this.previous,h=this.next;c&&(c.next=h);h&&(h.previous=c);e.splice(f,1);this.parent=null},replaceWith:function(e){var f=this.parent.children, -c=CKEDITOR.tools.indexOf(f,this),h=e.previous=this.previous,b=e.next=this.next;h&&(h.next=e);b&&(b.previous=e);f[c]=e;e.parent=this.parent;this.parent=null},insertAfter:function(e){var f=e.parent.children,c=CKEDITOR.tools.indexOf(f,e),h=e.next;f.splice(c+1,0,this);this.next=e.next;this.previous=e;e.next=this;h&&(h.previous=this);this.parent=e.parent},insertBefore:function(e){var f=e.parent.children,c=CKEDITOR.tools.indexOf(f,e);f.splice(c,0,this);this.next=e;(this.previous=e.previous)&&(e.previous.next= -this);e.previous=this;this.parent=e.parent},getAscendant:function(e){var f="function"==typeof e?e:"string"==typeof e?function(c){return c.name==e}:function(c){return c.name in e},c=this.parent;for(;c&&c.type==CKEDITOR.NODE_ELEMENT;){if(f(c))return c;c=c.parent}return null},wrapWith:function(e){this.replaceWith(e);e.add(this);return e},getIndex:function(){return CKEDITOR.tools.indexOf(this.parent.children,this)},getFilterContext:function(e){return e||{}}}}(),"use strict",CKEDITOR.htmlParser.comment= -function(e){this.value=e;this._={isBlockLike:!1}},CKEDITOR.htmlParser.comment.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_COMMENT,filter:function(e,f){var c=this.value;if(!(c=e.onComment(f,c,this)))return this.remove(),!1;if("string"!=typeof c)return this.replaceWith(c),!1;this.value=c;return!0},writeHtml:function(e,f){f&&this.filter(f);e.comment(this.value)}}),"use strict",function(){CKEDITOR.htmlParser.text=function(e){this.value=e;this._={isBlockLike:!1}};CKEDITOR.htmlParser.text.prototype= -CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(e,f){if(!(this.value=e.onText(f,this.value,this)))return this.remove(),!1},writeHtml:function(e,f){f&&this.filter(f);e.text(this.value)}})}(),"use strict",function(){CKEDITOR.htmlParser.cdata=function(e){this.value=e};CKEDITOR.htmlParser.cdata.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(){},writeHtml:function(e){e.write(this.value)}})}(),"use strict", -CKEDITOR.htmlParser.fragment=function(){this.children=[];this.parent=null;this._={isBlockLike:!0,hasInlineStarted:!1}},function(){function e(b){return b.attributes["data-cke-survive"]?!1:"a"==b.name&&b.attributes.href||CKEDITOR.dtd.$removeEmpty[b.name]}var f=CKEDITOR.tools.extend({table:1,ul:1,ol:1,dl:1},CKEDITOR.dtd.table,CKEDITOR.dtd.ul,CKEDITOR.dtd.ol,CKEDITOR.dtd.dl),c={ol:1,ul:1},h=CKEDITOR.tools.extend({},{html:1},CKEDITOR.dtd.html,CKEDITOR.dtd.body,CKEDITOR.dtd.head,{style:1,script:1}),b={ul:"li", -ol:"li",dl:"dd",table:"tbody",tbody:"tr",thead:"tr",tfoot:"tr",tr:"td"};CKEDITOR.htmlParser.fragment.fromHtml=function(l,k,d){function g(a){var b;if(0k;k++)if(f=b[k]){f= -f.exec(c,e,this);if(!1===f)return null;if(f&&f!=e)return this.onNode(c,f);if(e.parent&&!e.name)break}return e},onNode:function(c,e){var b=e.type;return b==CKEDITOR.NODE_ELEMENT?this.onElement(c,e):b==CKEDITOR.NODE_TEXT?new CKEDITOR.htmlParser.text(this.onText(c,e.value)):b==CKEDITOR.NODE_COMMENT?new CKEDITOR.htmlParser.comment(this.onComment(c,e.value)):null},onAttribute:function(c,e,b,f){return(b=this.attributesRules[b])?b.exec(c,f,e,this):f}}});CKEDITOR.htmlParser.filterRulesGroup=e;e.prototype= -{add:function(c,e,b){this.rules.splice(this.findIndex(e),0,{value:c,priority:e,options:b})},addMany:function(c,e,b){for(var f=[this.findIndex(e),0],k=0,d=c.length;k/g,"\x26gt;")+"\x3c/textarea\x3e");return"\x3ccke:encoded\x3e"+encodeURIComponent(a)+"\x3c/cke:encoded\x3e"})}function n(a){return a.replace(C,function(a,b){return decodeURIComponent(b)})}function w(a){return a.replace(/\x3c!--(?!{cke_protected})[\s\S]+?--\x3e/g, -function(a){return"\x3c!--"+B+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\x3e"})}function u(a){return CKEDITOR.tools.array.reduce(a.split(""),function(a,b){var d=b.toLowerCase(),c=b.toUpperCase(),e=t(d);d!==c&&(e+="|"+t(c));return a+("("+e+")")},"")}function t(a){var b;b=a.charCodeAt(0);var d=b.toString(16);b={htmlCode:"\x26#"+b+";?",hex:"\x26#x0*"+d+";?",entity:{"\x3c":"\x26lt;","\x3e":"\x26gt;",":":"\x26colon;"}[a]};for(var c in b)b[c]&&(a+="|"+b[c]);return a}function p(a){return a.replace(/\x3c!--\{cke_protected\}\{C\}([\s\S]+?)--\x3e/g, -function(a,b){return decodeURIComponent(b)})}function r(a,b){var d=b._.dataStore;return a.replace(/\x3c!--\{cke_protected\}([\s\S]+?)--\x3e/g,function(a,b){return decodeURIComponent(b)}).replace(/\{cke_protected_(\d+)\}/g,function(a,b){return d&&d[b]||""})}function v(a,b){var d=[],c=b.config.protectedSource,e=b._.dataStore||(b._.dataStore={id:1}),g=/<\!--\{cke_temp(comment)?\}(\d*?)--\x3e/g,c=[/|$)/gi,//gi,//gi].concat(c);a=a.replace(/\x3c!--[\s\S]*?--\x3e/g, -function(a){return"\x3c!--{cke_tempcomment}"+(d.push(a)-1)+"--\x3e"});for(var f=0;f]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g,function(a){return a.replace(/\x3c!--\{cke_protected\}([^>]*)--\x3e/g, -function(a,b){e[e.id]=decodeURIComponent(b);return"{cke_protected_"+e.id++ +"}"})});return a=a.replace(/<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g,function(a,d,c,e){return"\x3c"+d+c+"\x3e"+r(p(e),b)+"\x3c/"+d+"\x3e"})}CKEDITOR.htmlDataProcessor=function(b){var d,c,g=this;this.editor=b;this.dataFilter=d=new CKEDITOR.htmlParser.filter;this.htmlFilter=c=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;d.addRules(D);d.addRules(z,{applyToAll:!0});d.addRules(e(b,"data"), -{applyToAll:!0});c.addRules(J);c.addRules(E,{applyToAll:!0});c.addRules(e(b,"html"),{applyToAll:!0});b.on("toHtml",function(d){d=d.data;var c=d.dataValue,e,c=c.replace(S,""),c=v(c,b),c=a(c,I),c=m(c),c=a(c,L),c=c.replace(O,"$1cke:$2"),c=c.replace(K,"\x3ccke:$1$2\x3e\x3c/cke:$1\x3e"),c=c.replace(/(]*>)(\r\n|\n)/g,"$1$2$2"),c=c.replace(/([^a-z0-9<\-])(on\w{3,})(?!>)/gi,"$1data-cke-"+CKEDITOR.rnd+"-$2");e=d.context||b.editable().getName();var g;CKEDITOR.env.ie&&9>CKEDITOR.env.version&&"pre"== -e&&(e="div",c="\x3cpre\x3e"+c+"\x3c/pre\x3e",g=1);e=b.document.createElement(e);e.setHtml("a"+c);c=e.getHtml().substr(1);c=c.replace(new RegExp("data-cke-"+CKEDITOR.rnd+"-","ig"),"");g&&(c=c.replace(/^
|<\/pre>$/gi,""));c=c.replace(P,"$1$2");c=n(c);c=p(c);e=!1===d.fixForBody?!1:f(d.enterMode,b.config.autoParagraph);c=CKEDITOR.htmlParser.fragment.fromHtml(c,d.context,e);e&&(g=c,!g.children.length&&CKEDITOR.dtd[g.name][e]&&(e=new CKEDITOR.htmlParser.element(e),g.add(e)));d.dataValue=c},null,null,
-5);b.on("toHtml",function(a){a.data.filter.applyTo(a.data.dataValue,!0,a.data.dontFilter,a.data.enterMode)&&b.fire("dataFiltered")},null,null,6);b.on("toHtml",function(a){a.data.dataValue.filterChildren(g.dataFilter,!0)},null,null,10);b.on("toHtml",function(a){a=a.data;var b=a.dataValue,d=new CKEDITOR.htmlParser.basicWriter;b.writeChildrenHtml(d);b=d.getHtml(!0);a.dataValue=w(b)},null,null,15);b.on("toDataFormat",function(a){var d=a.data.dataValue;a.data.enterMode!=CKEDITOR.ENTER_BR&&(d=d.replace(/^
/i, -""));a.data.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(d,a.data.context,f(a.data.enterMode,b.config.autoParagraph))},null,null,5);b.on("toDataFormat",function(a){a.data.dataValue.filterChildren(g.htmlFilter,!0)},null,null,10);b.on("toDataFormat",function(a){a.data.filter.applyTo(a.data.dataValue,!1,!0)},null,null,11);b.on("toDataFormat",function(a){var d=a.data.dataValue,c=g.writer;c.reset();d.writeChildrenHtml(c);d=c.getHtml(!0);d=p(d);d=r(d,b);a.data.dataValue=d},null,null,15)};CKEDITOR.htmlDataProcessor.prototype= -{toHtml:function(a,b,d,c){var e=this.editor,g,f,h,m;b&&"object"==typeof b?(g=b.context,d=b.fixForBody,c=b.dontFilter,f=b.filter,h=b.enterMode,m=b.protectedWhitespaces):g=b;g||null===g||(g=e.editable().getName());return e.fire("toHtml",{dataValue:a,context:g,fixForBody:d,dontFilter:c,filter:f||e.filter,enterMode:h||e.enterMode,protectedWhitespaces:m}).dataValue},toDataFormat:function(a,b){var d,c,e;b&&(d=b.context,c=b.filter,e=b.enterMode);d||null===d||(d=this.editor.editable().getName());return this.editor.fire("toDataFormat", -{dataValue:a,filter:c||this.editor.filter,context:d,enterMode:e||this.editor.enterMode}).dataValue}};var q=/(?: |\xa0)$/,B="{cke_protected}",x=CKEDITOR.dtd,y="caption colgroup col thead tfoot tbody".split(" "),A=CKEDITOR.tools.extend({},x.$blockLimit,x.$block),D={elements:{input:d,textarea:d}},z={attributeNames:[[/^on/,"data-cke-pa-on"],[/^srcdoc/,"data-cke-pa-srcdoc"],[/^data-cke-expando$/,""]],elements:{iframe:function(a){if(a.attributes&&a.attributes.src){var b=a.attributes.src.toLowerCase().replace(/[^a-z]/gi, -"");if(0===b.indexOf("javascript")||0===b.indexOf("data"))a.attributes["data-cke-pa-src"]=a.attributes.src,delete a.attributes.src}}}},J={elements:{embed:function(a){var b=a.parent;if(b&&"object"==b.name){var d=b.attributes.width,b=b.attributes.height;d&&(a.attributes.width=d);b&&(a.attributes.height=b)}},a:function(a){var b=a.attributes;if(!(a.children.length||b.name||b.id||a.attributes["data-cke-saved-name"]))return!1}}},E={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]],attributeNames:[[/^data-cke-(saved|pa)-/, -""],[/^data-cke-.*/,""],["hidefocus",""]],elements:{$:function(a){var b=a.attributes;if(b){if(b["data-cke-temp"])return!1;for(var d=["name","href","src"],c,e=0;ec? -1:-1})},param:function(a){a.children=[];a.isEmpty=!0;return a},span:function(a){"Apple-style-span"==a.attributes["class"]&&delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable},style:function(a){var b=a.children[0];b&&b.value&&(b.value=CKEDITOR.tools.trim(b.value));a.attributes.type||(a.attributes.type="text/css")},title:function(a){var b=a.children[0];!b&&k(a,b=new CKEDITOR.htmlParser.text); -b.value=a.attributes["data-cke-title"]||""},input:g,textarea:g},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g,""))||!1}}};CKEDITOR.env.ie&&(E.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})});var H=/<(a|area|img|input|source)\b([^>]*)>/gi,F=/([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,G=/^(href|src|name)$/i,L=/(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi, -I=/(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,C=/([^<]*)<\/cke:encoded>/gi,S=new RegExp("("+u("\x3ccke:encoded\x3e")+"(.*?)"+u("\x3c/cke:encoded\x3e")+")|("+u("\x3c")+u("/")+"?"+u("cke:encoded\x3e")+")","gi"),O=/(<\/?)((?:object|embed|param|html|body|head|title)([\s][^>]*)?>)/gi,P=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,K=/]*?)\/?>(?!\s*<\/cke:\1)/gi}(),"use strict",CKEDITOR.htmlParser.element=function(e,f){this.name=e;this.attributes=f||{};this.children= -[];var c=e||"",h=c.match(/^cke:(.*)/);h&&(c=h[1]);c=!!(CKEDITOR.dtd.$nonBodyContent[c]||CKEDITOR.dtd.$block[c]||CKEDITOR.dtd.$listItem[c]||CKEDITOR.dtd.$tableContent[c]||CKEDITOR.dtd.$nonEditable[c]||"br"==c);this.isEmpty=!!CKEDITOR.dtd.$empty[e];this.isUnknown=!CKEDITOR.dtd[e];this._={isBlockLike:c,hasInlineStarted:this.isEmpty||!c}},CKEDITOR.htmlParser.cssStyle=function(e){var f={};((e instanceof CKEDITOR.htmlParser.element?e.attributes.style:e)||"").replace(/"/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, -function(c,e,b){"font-family"==e&&(b=b.replace(/["']/g,""));f[e.toLowerCase()]=b});return{rules:f,populate:function(c){var e=this.toString();e&&(c instanceof CKEDITOR.dom.element?c.setAttribute("style",e):c instanceof CKEDITOR.htmlParser.element?c.attributes.style=e:c.style=e)},toString:function(){var c=[],e;for(e in f)f[e]&&c.push(e,":",f[e],";");return c.join("")}}},function(){function e(c){return function(b){return b.type==CKEDITOR.NODE_ELEMENT&&("string"==typeof c?b.name==c:b.name in c)}}var f= -function(c,b){c=c[0];b=b[0];return cb?1:0},c=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_ELEMENT,add:c.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(c,b){var e=this,f,d;b=e.getFilterContext(b);if(!e.parent)c.onRoot(b,e);for(;;){f=e.name;if(!(d=c.onElementName(b,f)))return this.remove(),!1;e.name=d;if(!(e=c.onElement(b,e)))return this.remove(), -!1;if(e!==this)return this.replaceWith(e),!1;if(e.name==f)break;if(e.type!=CKEDITOR.NODE_ELEMENT)return this.replaceWith(e),!1;if(!e.name)return this.replaceWithChildren(),!1}f=e.attributes;var g,m;for(g in f){for(d=f[g];;)if(m=c.onAttributeName(b,g))if(m!=g)delete f[g],g=m;else break;else{delete f[g];break}m&&(!1===(d=c.onAttribute(b,e,m,d))?delete f[m]:f[m]=d)}e.isEmpty||this.filterChildren(c,!1,b);return!0},filterChildren:c.filterChildren,writeHtml:function(c,b){b&&this.filter(b);var e=this.name, -k=[],d=this.attributes,g,m;c.openTag(e,d);for(g in d)k.push([g,d[g]]);c.sortAttributes&&k.sort(f);g=0;for(m=k.length;gCKEDITOR.env.version||CKEDITOR.env.quirks))this.hasFocus&&(this.focus(),b());else if(this.hasFocus)this.focus(),a();else this.once("focus",function(){a()},null,null,-999)},getHtmlFromRange:function(a){if(a.collapsed)return new CKEDITOR.dom.documentFragment(a.document); -a={doc:this.getDocument(),range:a.clone()};q.eol.detect(a,this);q.bogus.exclude(a);q.cell.shrink(a);a.fragment=a.range.cloneContents();q.tree.rebuild(a,this);q.eol.fix(a,this);return new CKEDITOR.dom.documentFragment(a.fragment.$)},extractHtmlFromRange:function(a,b){var d=B,c={range:a,doc:a.document},e=this.getHtmlFromRange(a);if(a.collapsed)return a.optimize(),e;a.enlarge(CKEDITOR.ENLARGE_INLINE,1);d.table.detectPurge(c);c.bookmark=a.createBookmark();delete c.range;var g=this.editor.createRange(); -g.moveToPosition(c.bookmark.startNode,CKEDITOR.POSITION_BEFORE_START);c.targetBookmark=g.createBookmark();d.list.detectMerge(c,this);d.table.detectRanges(c,this);d.block.detectMerge(c,this);c.tableContentsRanges?(d.table.deleteRanges(c),a.moveToBookmark(c.bookmark),c.range=a):(a.moveToBookmark(c.bookmark),c.range=a,a.extractContents(d.detectExtractMerge(c)));a.moveToBookmark(c.targetBookmark);a.optimize();d.fixUneditableRangePosition(a);d.list.merge(c,this);d.table.purge(c,this);d.block.merge(c,this); -if(b){d=a.startPath();if(c=a.checkStartOfBlock()&&a.checkEndOfBlock()&&d.block&&!a.root.equals(d.block)){a:{var c=d.block.getElementsByTag("span"),g=0,f;if(c)for(;f=c.getItem(g++);)if(!n(f)){c=!0;break a}c=!1}c=!c}c&&(a.moveToPosition(d.block,CKEDITOR.POSITION_BEFORE_START),d.block.remove())}else d.autoParagraph(this.editor,a),w(a.startContainer)&&a.startContainer.appendBogus();a.startContainer.mergeSiblings();return e},setup:function(){var b=this.editor;this.attachListener(b,"beforeGetData",function(){var a= -this.getData();this.is("textarea")||!1!==b.config.ignoreEmptyParagraph&&(a=a.replace(t,function(a,b){return b}));b.setData(a,null,1)},this);this.attachListener(b,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(b,"afterSetData",function(){this.setData(b.getData(1))},this);this.attachListener(b,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(b,"beforeFocus",function(){var a=b.getSelection();(a=a&&a.getNative())&&"Control"==a.type||this.focus()}, -this);this.attachListener(b,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode,a.data.range)},this);this.attachListener(b,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(b,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(b.readOnly);this.attachClass("cke_editable");b.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?this.attachClass("cke_editable_inline"):b.elementMode!=CKEDITOR.ELEMENT_MODE_REPLACE&&b.elementMode!=CKEDITOR.ELEMENT_MODE_APPENDTO|| -this.attachClass("cke_editable_themed");this.attachClass("cke_contents_"+b.config.contentsLangDirection);b.keystrokeHandler.blockedKeystrokes[8]=+b.readOnly;b.keystrokeHandler.attach(this);this.on("blur",function(){this.hasFocus=!1},null,null,-1);this.on("focus",function(){this.hasFocus=!0},null,null,-1);if(CKEDITOR.env.webkit)this.on("scroll",function(){b._.previousScrollTop=b.editable().$.scrollTop},null,null,-1);if(CKEDITOR.env.edge&&14CKEDITOR.env.version?k.$.styleSheet.cssText=f:k.setText(f)):(f=e.appendStyleText(f),f=new CKEDITOR.dom.element(f.ownerNode||f.owningElement),g.setCustomData("stylesheet",f),f.data("cke-temp",1))}g=e.getCustomData("stylesheet_ref")||0;e.setCustomData("stylesheet_ref", -g+1);this.setCustomData("cke_includeReadonly",!b.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){a=a.data;var b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");b&&2!=a.$.button&&b.isReadOnly()&&a.preventDefault()});var n={8:1,46:1};this.attachListener(b,"key",function(d){if(b.readOnly)return!0;var c=d.data.domEvent.getKey(),e;d=b.getSelection();if(0!==d.getRanges().length){if(c in n){var g,f=d.getRanges()[0],m=f.startPath(),h,k,r,c=8==c;CKEDITOR.env.ie&& -11>CKEDITOR.env.version&&(g=d.getSelectedElement())||(g=l(d))?(b.fire("saveSnapshot"),f.moveToPosition(g,CKEDITOR.POSITION_BEFORE_START),g.remove(),f.select(),b.fire("saveSnapshot"),e=1):f.collapsed&&((h=m.block)&&(r=h[c?"getPrevious":"getNext"](a))&&r.type==CKEDITOR.NODE_ELEMENT&&r.is("table")&&f[c?"checkStartOfBlock":"checkEndOfBlock"]()?(b.fire("saveSnapshot"),f[c?"checkEndOfBlock":"checkStartOfBlock"]()&&h.remove(),f["moveToElementEdit"+(c?"End":"Start")](r),f.select(),b.fire("saveSnapshot"), -e=1):m.blockLimit&&m.blockLimit.is("td")&&(k=m.blockLimit.getAscendant("table"))&&f.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END)&&(r=k[c?"getPrevious":"getNext"](a))?(b.fire("saveSnapshot"),f["moveToElementEdit"+(c?"End":"Start")](r),f.checkStartOfBlock()&&f.checkEndOfBlock()?r.remove():f.select(),b.fire("saveSnapshot"),e=1):(k=m.contains(["td","th","caption"]))&&f.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END)&&(e=1))}return!e}});b.blockless&&CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller&& -this.attachListener(this,"keyup",function(a){a.data.getKeystroke()in n&&!this.getFirst(h)&&(this.appendBogus(),a=b.createRange(),a.moveToPosition(this,CKEDITOR.POSITION_AFTER_START),a.select())});this.attachListener(this,"dblclick",function(a){if(b.readOnly)return!1;a={element:a.data.getTarget()};b.fire("doubleclick",a)});CKEDITOR.env.ie&&this.attachListener(this,"click",c);CKEDITOR.env.ie&&!CKEDITOR.env.edge||this.attachListener(this,"mousedown",function(a){var d=a.data.getTarget();d.is("img","hr", -"input","textarea","select")&&!d.isReadOnly()&&(b.getSelection().selectElement(d),d.is("input","textarea","select")&&a.data.preventDefault())});CKEDITOR.env.edge&&this.attachListener(this,"mouseup",function(a){(a=a.data.getTarget())&&a.is("img")&&!a.isReadOnly()&&b.getSelection().selectElement(a)});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(a){if(2==a.data.$.button&&(a=a.data.getTarget(),!a.getAscendant("table")&&!a.getOuterHtml().replace(t,""))){var d=b.createRange();d.moveToElementEditStart(a); -d.select(!0)}});CKEDITOR.env.webkit&&(this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()}),this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()}));CKEDITOR.env.webkit&&this.attachListener(b,"key",function(a){if(b.readOnly)return!0;var d=a.data.domEvent.getKey();if(d in n&&(a=b.getSelection(),0!==a.getRanges().length)){var d=8==d,c=a.getRanges()[0];a=c.startPath();if(c.collapsed)a:{var e= -a.block;if(e&&c[d?"checkStartOfBlock":"checkEndOfBlock"]()&&c.moveToClosestEditablePosition(e,!d)&&c.collapsed){if(c.startContainer.type==CKEDITOR.NODE_ELEMENT){var g=c.startContainer.getChild(c.startOffset-(d?1:0));if(g&&g.type==CKEDITOR.NODE_ELEMENT&&g.is("hr")){b.fire("saveSnapshot");g.remove();a=!0;break a}}c=c.startPath().block;if(!c||c&&c.contains(e))a=void 0;else{b.fire("saveSnapshot");var f;(f=(d?c:e).getBogus())&&f.remove();f=b.getSelection();g=f.createBookmarks();(d?e:c).moveChildren(d? -c:e,!1);a.lastElement.mergeSiblings();m(e,c,!d);f.selectBookmarks(g);a=!0}}else a=!1}else d=c,f=a.block,c=d.endPath().block,f&&c&&!f.equals(c)?(b.fire("saveSnapshot"),(e=f.getBogus())&&e.remove(),d.enlarge(CKEDITOR.ENLARGE_INLINE),d.deleteContents(),c.getParent()&&(c.moveChildren(f,!1),a.lastElement.mergeSiblings(),m(f,c,!0)),d=b.getSelection().getRanges()[0],d.collapse(1),d.optimize(),""===d.startContainer.getHtml()&&d.startContainer.appendBogus(),d.select(),a=!0):a=!1;if(!a)return;b.getSelection().scrollIntoView(); -b.fire("saveSnapshot");return!1}},this,null,100)}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());if(!this.is("textarea")){a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var d=a.getCustomData("stylesheet_ref");--d?a.setCustomData("stylesheet_ref",d):(a.removeCustomData("stylesheet_ref"),b.removeCustomData("stylesheet").remove())}}this.editor.fire("contentDomUnload"); -delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;arguments.length&&(b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null));return b};CKEDITOR.on("instanceLoaded",function(a){var b=a.editor;b.on("insertElement",function(a){a=a.data;a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))&&("false"!=a.getAttribute("contentEditable")&&a.data("cke-editable",a.hasAttribute("contenteditable")? -"true":"1"),a.setAttribute("contentEditable",!1))});b.on("selectionChange",function(a){if(!b.readOnly){var d=b.getSelection();d&&!d.isLocked&&(d=b.checkDirty(),b.fire("lockSnapshot"),e(a),b.fire("unlockSnapshot"),!d&&b.resetDirty())}})});CKEDITOR.on("instanceCreated",function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var d=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-multiline","true");a.changeAttr("aria-label",d);d&&a.changeAttr("title",d);var c= -b.fire("ariaEditorHelpLabel",{}).label;if(c&&(d=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents"))){var e=CKEDITOR.tools.getNextId(),c=CKEDITOR.dom.element.createFromHtml('\x3cspan id\x3d"'+e+'" class\x3d"cke_voice_label"\x3e'+c+"\x3c/span\x3e");d.append(c);a.changeAttr("aria-describedby",e)}}})});CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");a=CKEDITOR.dom.walker.whitespaces(!0);n=CKEDITOR.dom.walker.bookmark(!1, -!0);w=CKEDITOR.dom.walker.empty();u=CKEDITOR.dom.walker.bogus();t=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi;p=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(d,c){var e,g,f,m,h=[],k=c.range.startContainer;e=c.range.startPath();for(var k=n[k.getName()],l=0,r=d.getChildren(),w=r.count(),p=-1,q=-1,A=0,E=e.contains(n.$list);lCKEDITOR.env.version&&c.getChildCount()&&c.getFirst().remove())}return function(c){var e=c.startContainer,g=e.getAscendant("table",1),f=!1;d(g.getElementsByTag("td"));d(g.getElementsByTag("th"));g=c.clone();g.setStart(e,0);g=a(g).lastBackward();g||(g=c.clone(),g.setEndAt(e,CKEDITOR.POSITION_BEFORE_END), -g=a(g).lastForward(),f=!0);g||(g=e);g.is("table")?(c.setStartAt(g,CKEDITOR.POSITION_BEFORE_START),c.collapse(!0),g.remove()):(g.is({tbody:1,thead:1,tfoot:1})&&(g=b(g,"tr",f)),g.is("tr")&&(g=b(g,g.getParent().is("thead")?"th":"td",f)),(e=g.getBogus())&&e.remove(),c.moveToPosition(g,f?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END))}}();v=function(){function a(b){b=new CKEDITOR.dom.walker(b);b.guard=function(a,b){if(b)return!1;if(a.type==CKEDITOR.NODE_ELEMENT)return a.is(CKEDITOR.dtd.$list)|| -a.is(CKEDITOR.dtd.$listItem)};b.evaluator=function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.is(CKEDITOR.dtd.$listItem)};return b}return function(b){var d=b.startContainer,c=!1,e;e=b.clone();e.setStart(d,0);e=a(e).lastBackward();e||(e=b.clone(),e.setEndAt(d,CKEDITOR.POSITION_BEFORE_END),e=a(e).lastForward(),c=!0);e||(e=d);e.is(CKEDITOR.dtd.$list)?(b.setStartAt(e,CKEDITOR.POSITION_BEFORE_START),b.collapse(!0),e.remove()):((d=e.getBogus())&&d.remove(),b.moveToPosition(e,c?CKEDITOR.POSITION_AFTER_START: -CKEDITOR.POSITION_BEFORE_END),b.select())}}();q={eol:{detect:function(a,b){var d=a.range,c=d.clone(),e=d.clone(),g=new CKEDITOR.dom.elementPath(d.startContainer,b),f=new CKEDITOR.dom.elementPath(d.endContainer,b);c.collapse(1);e.collapse();g.block&&c.checkBoundaryOfElement(g.block,CKEDITOR.END)&&(d.setStartAfter(g.block),a.prependEolBr=1);f.block&&e.checkBoundaryOfElement(f.block,CKEDITOR.START)&&(d.setEndBefore(f.block),a.appendEolBr=1)},fix:function(a,b){var d=b.getDocument(),c;a.appendEolBr&&(c= -this.createEolBr(d),a.fragment.append(c));!a.prependEolBr||c&&!c.getPrevious()||a.fragment.append(this.createEolBr(d),1)},createEolBr:function(a){return a.createElement("br",{attributes:{"data-cke-eol":1}})}},bogus:{exclude:function(a){var b=a.range.getBoundaryNodes(),d=b.startNode,b=b.endNode;!b||!u(b)||d&&d.equals(b)||a.range.setEndBefore(b)}},tree:{rebuild:function(a,b){var d=a.range,c=d.getCommonAncestor(),e=new CKEDITOR.dom.elementPath(c,b),g=new CKEDITOR.dom.elementPath(d.startContainer,b), -d=new CKEDITOR.dom.elementPath(d.endContainer,b),f;c.type==CKEDITOR.NODE_TEXT&&(c=c.getParent());if(e.blockLimit.is({tr:1,table:1})){var m=e.contains("table").getParent();f=function(a){return!a.equals(m)}}else if(e.block&&e.block.is(CKEDITOR.dtd.$listItem)&&(g=g.contains(CKEDITOR.dtd.$list),d=d.contains(CKEDITOR.dtd.$list),!g.equals(d))){var h=e.contains(CKEDITOR.dtd.$list).getParent();f=function(a){return!a.equals(h)}}f||(f=function(a){return!a.equals(e.block)&&!a.equals(e.blockLimit)});this.rebuildFragment(a, -b,c,f)},rebuildFragment:function(a,b,d,c){for(var e;d&&!d.equals(b)&&c(d);)e=d.clone(0,1),a.fragment.appendTo(e),a.fragment=e,d=d.getParent()}},cell:{shrink:function(a){a=a.range;var b=a.startContainer,d=a.endContainer,c=a.startOffset,e=a.endOffset;b.type==CKEDITOR.NODE_ELEMENT&&b.equals(d)&&b.is("tr")&&++c==e&&a.shrink(CKEDITOR.SHRINK_TEXT)}}};B=function(){function a(b,d){var c=b.getParent();if(c.is(CKEDITOR.dtd.$inline))b[d?"insertBefore":"insertAfter"](c)}function b(d,c,e){a(c);a(e,1);for(var g;g= -e.getNext();)g.insertAfter(c),c=g;w(d)&&d.remove()}function c(a,b){var d=new CKEDITOR.dom.range(a);d.setStartAfter(b.startNode);d.setEndBefore(b.endNode);return d}return{list:{detectMerge:function(a,b){var d=c(b,a.bookmark),e=d.startPath(),g=d.endPath(),f=e.contains(CKEDITOR.dtd.$list),m=g.contains(CKEDITOR.dtd.$list);a.mergeList=f&&m&&f.getParent().equals(m.getParent())&&!f.equals(m);a.mergeListItems=e.block&&g.block&&e.block.is(CKEDITOR.dtd.$listItem)&&g.block.is(CKEDITOR.dtd.$listItem);if(a.mergeList|| -a.mergeListItems)d=d.clone(),d.setStartBefore(a.bookmark.startNode),d.setEndAfter(a.bookmark.endNode),a.mergeListBookmark=d.createBookmark()},merge:function(a,d){if(a.mergeListBookmark){var c=a.mergeListBookmark.startNode,e=a.mergeListBookmark.endNode,g=new CKEDITOR.dom.elementPath(c,d),f=new CKEDITOR.dom.elementPath(e,d);if(a.mergeList){var m=g.contains(CKEDITOR.dtd.$list),h=f.contains(CKEDITOR.dtd.$list);m.equals(h)||(h.moveChildren(m),h.remove())}a.mergeListItems&&(g=g.contains(CKEDITOR.dtd.$listItem), -f=f.contains(CKEDITOR.dtd.$listItem),g.equals(f)||b(f,c,e));c.remove();e.remove()}}},block:{detectMerge:function(a,b){if(!a.tableContentsRanges&&!a.mergeListBookmark){var d=new CKEDITOR.dom.range(b);d.setStartBefore(a.bookmark.startNode);d.setEndAfter(a.bookmark.endNode);a.mergeBlockBookmark=d.createBookmark()}},merge:function(a,d){if(a.mergeBlockBookmark&&!a.purgeTableBookmark){var c=a.mergeBlockBookmark.startNode,e=a.mergeBlockBookmark.endNode,g=new CKEDITOR.dom.elementPath(c,d),f=new CKEDITOR.dom.elementPath(e, -d),g=g.block,f=f.block;g&&f&&!g.equals(f)&&b(f,c,e);c.remove();e.remove()}}},table:function(){function a(c){var e=[],g,f=new CKEDITOR.dom.walker(c),m=c.startPath().contains(d),h=c.endPath().contains(d),k={};f.guard=function(a,f){if(a.type==CKEDITOR.NODE_ELEMENT){var n="visited_"+(f?"out":"in");if(a.getCustomData(n))return;CKEDITOR.dom.element.setMarker(k,a,n,1)}if(f&&m&&a.equals(m))g=c.clone(),g.setEndAt(m,CKEDITOR.POSITION_BEFORE_END),e.push(g);else if(!f&&h&&a.equals(h))g=c.clone(),g.setStartAt(h, -CKEDITOR.POSITION_AFTER_START),e.push(g);else{if(n=!f)n=a.type==CKEDITOR.NODE_ELEMENT&&a.is(d)&&(!m||b(a,m))&&(!h||b(a,h));if(!n&&(n=f))if(a.is(d))var n=m&&m.getAscendant("table",!0),l=h&&h.getAscendant("table",!0),r=a.getAscendant("table",!0),n=n&&n.contains(r)||l&&l.contains(r);else n=void 0;n&&(g=c.clone(),g.selectNodeContents(a),e.push(g))}};f.lastForward();CKEDITOR.dom.element.clearAllMarkers(k);return e}function b(a,d){var c=CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_IS_CONTAINED,e=a.getPosition(d); -return e===CKEDITOR.POSITION_IDENTICAL?!1:0===(e&c)}var d={td:1,th:1,caption:1};return{detectPurge:function(a){var b=a.range,c=b.clone();c.enlarge(CKEDITOR.ENLARGE_ELEMENT);var c=new CKEDITOR.dom.walker(c),e=0;c.evaluator=function(a){a.type==CKEDITOR.NODE_ELEMENT&&a.is(d)&&++e};c.checkForward();if(1g&&e&&e.intersectsNode(d.$)){var f=[{node:c.anchorNode,offset:c.anchorOffset},{node:c.focusNode,offset:c.focusOffset}];c.anchorNode==d.$&&c.anchorOffset>g&&(f[0].offset-=g);c.focusNode==d.$&&c.focusOffset>g&&(f[1].offset-=g)}}d.setText(w(d.getText(),1));f&&(d=a.getDocument().$, -c=d.getSelection(),d=d.createRange(),d.setStart(f[0].node,f[0].offset),d.collapse(!0),c.removeAllRanges(),c.addRange(d),c.extend(f[1].node,f[1].offset))}}function w(a,b){return b?a.replace(B,function(a,b){return b?" ":""}):a.replace(q,"")}function u(a,b){var d=b&&CKEDITOR.tools.htmlEncode(b)||"\x26nbsp;",d=CKEDITOR.dom.element.createFromHtml('\x3cdiv data-cke-hidden-sel\x3d"1" data-cke-temp\x3d"1" style\x3d"'+(CKEDITOR.env.ie&&14>CKEDITOR.env.version?"display:none":"position:fixed;top:0;left:-1000px;width:0;height:0;overflow:hidden;")+ -'"\x3e'+d+"\x3c/div\x3e",a.document);a.fire("lockSnapshot");a.editable().append(d);var c=a.getSelection(1),e=a.createRange(),g=c.root.on("selectionchange",function(a){a.cancel()},null,null,0);e.setStartAt(d,CKEDITOR.POSITION_AFTER_START);e.setEndAt(d,CKEDITOR.POSITION_BEFORE_END);c.selectRanges([e]);g.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=d}function t(a){var b={37:1,39:1,8:1,46:1};return function(d){var c=d.data.getKeystroke();if(b[c]){var e=a.getSelection().getRanges(), -g=e[0];1==e.length&&g.collapsed&&(c=g[38>c?"getPreviousEditableNode":"getNextEditableNode"]())&&c.type==CKEDITOR.NODE_ELEMENT&&"false"==c.getAttribute("contenteditable")&&(a.getSelection().fake(c),d.data.preventDefault(),d.cancel())}}}function p(a){for(var b=0;b=c.getLength()?m.setStartAfter(c):m.setStartBefore(c));e&&e.type==CKEDITOR.NODE_TEXT&&(f?m.setEndAfter(e):m.setEndBefore(e));c=new CKEDITOR.dom.walker(m);c.evaluator=function(c){if(c.type==CKEDITOR.NODE_ELEMENT&&c.isReadOnly()){var e=d.clone();d.setEndBefore(c);d.collapsed&&a.splice(b--,1);c.getPosition(m.endContainer)& -CKEDITOR.POSITION_CONTAINS||(e.setStartAfter(c),e.collapsed||a.splice(b+1,0,e));return!0}return!1};c.next()}}return a}var r="function"!=typeof window.getSelection,v=1,q=CKEDITOR.tools.repeat("​",7),B=new RegExp(q+"( )?","g"),x,y,A,D=CKEDITOR.dom.walker.invisible(1),z=function(){function a(b){return function(a){var d=a.editor.createRange();d.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([d]);return!1}}function b(a){return function(b){var d=b.editor,c=d.createRange(), -e;if(!d.readOnly)return(e=c.moveToClosestEditablePosition(b.selected,a))||(e=c.moveToClosestEditablePosition(b.selected,!a)),e&&d.getSelection().selectRanges([c]),d.fire("saveSnapshot"),b.selected.remove(),e||(c.moveToElementEditablePosition(d.editable()),d.getSelection().selectRanges([c])),d.fire("saveSnapshot"),!1}}var d=a(),c=a(1);return{37:d,38:d,39:c,40:c,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(a){function b(){var a=d.getSelection();a&&a.removeAllRanges()}var d=a.editor;d.on("contentDom", -function(){function a(){q=new CKEDITOR.dom.selection(d.getSelection());q.lock()}function b(){g.removeListener("mouseup",b);h.removeListener("mouseup",b);var a=CKEDITOR.document.$.selection,d=a.createRange();"None"!=a.type&&d.parentElement()&&d.parentElement().ownerDocument==e.$&&d.select()}function c(a){a=a.getRanges()[0];return a?(a=a.startContainer.getAscendant(function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")},!0))&&"false"===a.getAttribute("contenteditable")? -a:null:null}var e=d.document,g=CKEDITOR.document,f=d.editable(),m=e.getBody(),h=e.getDocumentElement(),w=f.isInline(),p,q;CKEDITOR.env.gecko&&f.attachListener(f,"focus",function(a){a.removeListener();0!==p&&(a=d.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==f.$&&(a=d.createRange(),a.moveToElementEditStart(f),a.select())},null,null,-2);f.attachListener(f,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){if(p&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){p=d._.previousActive&& -d._.previousActive.equals(e.getActive());var a=null!=d._.previousScrollTop&&d._.previousScrollTop!=f.$.scrollTop;CKEDITOR.env.webkit&&p&&a&&(f.$.scrollTop=d._.previousScrollTop)}d.unlockSelection(p);p=0},null,null,-1);f.attachListener(f,"mousedown",function(){p=0});if(CKEDITOR.env.ie||w)r?f.attachListener(f,"beforedeactivate",a,null,null,-1):f.attachListener(d,"selectionCheck",a,null,null,-1),f.attachListener(f,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusout":"blur",function(){d.lockSelection(q); -p=1},null,null,-1),f.attachListener(f,"mousedown",function(){p=0});if(CKEDITOR.env.ie&&!w){var v;f.attachListener(f,"mousedown",function(a){2==a.data.$.button&&((a=d.document.getSelection())&&a.getType()!=CKEDITOR.SELECTION_NONE||(v=d.window.getScrollPosition()))});f.attachListener(f,"mouseup",function(a){2==a.data.$.button&&v&&(d.document.$.documentElement.scrollLeft=v.x,d.document.$.documentElement.scrollTop=v.y);v=null});if("BackCompat"!=e.$.compatMode){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat){var x, -u;h.on("mousedown",function(a){function b(a){a=a.data.$;if(x){var d=m.$.createTextRange();try{d.moveToPoint(a.clientX,a.clientY)}catch(c){}x.setEndPoint(0>u.compareEndPoints("StartToStart",d)?"EndToEnd":"StartToStart",d);x.select()}}function d(){h.removeListener("mousemove",b);g.removeListener("mouseup",d);h.removeListener("mouseup",d);x.select()}a=a.data;if(a.getTarget().is("html")&&a.$.yCKEDITOR.env.version)h.on("mousedown",function(a){a.data.getTarget().is("html")&&(g.on("mouseup",b),h.on("mouseup",b))})}}f.attachListener(f,"selectionchange",l,d);f.attachListener(f,"keyup",k,d);f.attachListener(f,"touchstart",k,d);f.attachListener(f,"touchend",k,d);CKEDITOR.env.ie&&f.attachListener(f,"keydown",function(a){var b=this.getSelection(1),d=c(b);d&&!d.equals(f)&&(b.selectElement(d),a.data.preventDefault())}, -d);f.attachListener(f,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){d.forceNextSelectionCheck();d.selectionChange(1)});if(w&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var A;f.attachListener(f,"mousedown",function(){A=1});f.attachListener(e.getDocumentElement(),"mouseup",function(){A&&k.call(d);A=0})}else f.attachListener(CKEDITOR.env.ie?f:e.getDocumentElement(),"mouseup",k,d);CKEDITOR.env.webkit&&f.attachListener(e,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:f.hasFocus&& -n(f)}},null,null,-1);f.attachListener(f,"keydown",t(d),null,null,-1)});d.on("setData",function(){d.unlockSelection();CKEDITOR.env.webkit&&b()});d.on("contentDomUnload",function(){d.unlockSelection()});if(CKEDITOR.env.ie9Compat)d.on("beforeDestroy",b,null,null,9);d.on("dataReady",function(){delete d._.fakeSelection;delete d._.hiddenSelectionContainer;d.selectionChange(1)});d.on("loadSnapshot",function(){var a=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT),b=d.editable().getLast(a);b&&b.hasAttribute("data-cke-hidden-sel")&& -(b.remove(),CKEDITOR.env.gecko&&(a=d.editable().getFirst(a))&&a.is("br")&&a.getAttribute("_moz_editor_bogus_node")&&a.remove())},null,null,100);d.on("key",function(a){if("wysiwyg"==d.mode){var b=d.getSelection();if(b.isFake){var c=z[a.data.keyCode];if(c)return c({editor:d,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});if(CKEDITOR.env.webkit)CKEDITOR.on("instanceReady",function(a){var b=a.editor;b.on("selectionChange",function(){var a=b.editable(),d=a.getCustomData("cke-fillingChar"); -d&&(d.getCustomData("ready")?(n(a),a.editor.fire("selectionCheck")):d.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){n(b.editable())},null,null,-1);b.on("getSnapshot",function(a){a.data&&(a.data=w(a.data))},b,null,20);b.on("toDataFormat",function(a){a.data.dataValue=w(a.data.dataValue)},null,null,0)});CKEDITOR.editor.prototype.selectionChange=function(a){(a?l:k).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){return!this._.savedSelection&&!this._.fakeSelection|| -a?(a=this.editable())&&"wysiwyg"==this.mode?new CKEDITOR.dom.selection(a):null:this._.savedSelection||this._.fakeSelection};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);return a.getType()!=CKEDITOR.SELECTION_NONE?(!a.isLocked&&a.lock(),this._.savedSelection=a,!0):!1};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;return b?(b.unlock(a),delete this._.savedSelection,!0):!1};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath}; -CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection){var b=a;a=a.root}var d=a instanceof CKEDITOR.dom.element; -this.rev=b?b.rev:v++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=d?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(b)return CKEDITOR.tools.extend(this._.cache,b._.cache),this.isFake=b.isFake,this.isLocked=b.isLocked,this;a=this.getNative();var c,e;if(a)if(a.getRangeAt)c=(e=a.rangeCount&&a.getRangeAt(0))&&new CKEDITOR.dom.node(e.commonAncestorContainer);else{try{e=a.createRange()}catch(g){}c=e&&CKEDITOR.dom.element.get(e.item&&e.item(0)||e.parentElement())}if(!c|| -c.type!=CKEDITOR.NODE_ELEMENT&&c.type!=CKEDITOR.NODE_TEXT||!this.root.equals(c)&&!this.root.contains(c))this._.cache.type=CKEDITOR.SELECTION_NONE,this._.cache.startElement=null,this._.cache.selectedElement=null,this._.cache.selectedText="",this._.cache.ranges=new CKEDITOR.dom.rangeList;return this};var J={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.tools.extend(CKEDITOR.dom.selection,{_removeFillingCharSequenceString:w, -_createFillingCharSequenceNode:a,FILLING_CHAR_SEQUENCE:q});CKEDITOR.dom.selection.prototype={getNative:function(){return void 0!==this._.cache.nativeSel?this._.cache.nativeSel:this._.cache.nativeSel=r?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:r?function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var d=this.getNative(),c=d.type;"Text"==c&&(b=CKEDITOR.SELECTION_TEXT);"Control"==c&&(b=CKEDITOR.SELECTION_ELEMENT);d.createRange().parentElement()&& -(b=CKEDITOR.SELECTION_TEXT)}catch(e){}return a.type=b}:function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,d=this.getNative();if(!d||!d.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(1==d.rangeCount){var d=d.getRangeAt(0),c=d.startContainer;c==d.endContainer&&1==c.nodeType&&1==d.endOffset-d.startOffset&&J[c.childNodes[d.startOffset].nodeName.toLowerCase()]&&(b=CKEDITOR.SELECTION_ELEMENT)}return a.type=b},getRanges:function(){var a=r?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()} -var b=function(b,d){b=b.duplicate();b.collapse(d);var c=b.parentElement();if(!c.hasChildNodes())return{container:c,offset:0};for(var e=c.children,g,f,m=b.duplicate(),h=0,k=e.length-1,n=-1,l,r;h<=k;)if(n=Math.floor((h+k)/2),g=e[n],m.moveToElementText(g),l=m.compareEndPoints("StartToStart",b),0l)h=n+1;else return{container:c,offset:a(g)};if(-1==n||n==e.length-1&&0>l){m.moveToElementText(c);m.setEndPoint("StartToStart",b);m=m.text.replace(/(\r\n|\r)/g,"\n").length;e=c.childNodes;if(!m)return g= -e[e.length-1],g.nodeType!=CKEDITOR.NODE_TEXT?{container:c,offset:e.length}:{container:g,offset:g.nodeValue.length};for(c=e.length;0]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+| )/g," ");f=f.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var m=b.getDocument().createElement("div"); -m.append(g);g.$.outerHTML="\x3cpre\x3e"+f+"\x3c/pre\x3e";g.copyAttributes(m.getFirst());g=m.getFirst().remove()}else g.setHtml(f);d=g}else f?d=w(c?[b.getHtml()]:a(b),d):b.moveChildren(d);d.replace(b);if(e){var c=d,h;(h=c.getPrevious(G))&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("pre")&&(e=n(h.getHtml(),/\n$/,"")+"\n\n"+n(c.getHtml(),/^\n/,""),CKEDITOR.env.ie?c.$.outerHTML="\x3cpre\x3e"+e+"\x3c/pre\x3e":c.setHtml(e),h.remove())}else c&&r(d)}function a(a){var b=[];n(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi, -function(a,b,d){return b+"\x3c/pre\x3e"+d+"\x3cpre\x3e"}).replace(/([\s\S]*?)<\/pre>/gi,function(a,d){b.push(d)});return b}function n(a,b,d){var c="",e="";a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,d){b&&(c=b);d&&(e=d);return""});return c+a.replace(b,d)+e}function w(a,b){var d;1=h?(l=b.createText(""),l.insertAfter(this)):(e=b.createText(""),e.insertAfter(l),e.remove()));return l},substring:function(e,f){return"number"!=typeof f?this.$.nodeValue.substr(e): -this.$.nodeValue.substring(e,f)}}),function(){function e(c,e,b){var f=c.serializable,k=e[b?"endContainer":"startContainer"],d=b?"endOffset":"startOffset",g=f?e.document.getById(c.startNode):c.startNode;c=f?e.document.getById(c.endNode):c.endNode;k.equals(g.getPrevious())?(e.startOffset=e.startOffset-k.getLength()-c.getPrevious().getLength(),k=c.getNext()):k.equals(c.getPrevious())&&(e.startOffset-=k.getLength(),k=c.getNext());k.equals(g.getParent())&&e[d]++;k.equals(c.getParent())&&e[d]++;e[b?"endContainer": -"startContainer"]=k;return e}CKEDITOR.dom.rangeList=function(c){if(c instanceof CKEDITOR.dom.rangeList)return c;c?c instanceof CKEDITOR.dom.range&&(c=[c]):c=[];return CKEDITOR.tools.extend(c,f)};var f={createIterator:function(){var c=this,e=CKEDITOR.dom.walker.bookmark(),b=[],f;return{getNextRange:function(k){f=void 0===f?0:f+1;var d=c[f];if(d&&1b?-1:1}),c=0,g;cCKEDITOR.env.version?b[f].$.styleSheet.cssText+=g:b[f].$.innerHTML+=g}}var l={};CKEDITOR.skin={path:e,loadPart:function(b,a){CKEDITOR.skin.name!=CKEDITOR.skinName.split(",")[0]?CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(e()+"skin.js"),function(){c(b,a)}):c(b,a)},getPath:function(b){return CKEDITOR.getUrl(f(b))},icons:{},addIcon:function(b,a,d,c){b= -b.toLowerCase();this.icons[b]||(this.icons[b]={path:a,offset:d||0,bgsize:c||"16px"})},getIconStyle:function(b,a,d,c,e){var g;b&&(b=b.toLowerCase(),a&&(g=this.icons[b+"-rtl"]),g||(g=this.icons[b]));b=d||g&&g.path||"";c=c||g&&g.offset;e=e||g&&g.bgsize||"16px";b&&(b=b.replace(/'/g,"\\'"));return b&&"background-image:url('"+CKEDITOR.getUrl(b)+"');background-position:0 "+c+"px;background-size:"+e+";"}};CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{getUiColor:function(){return this.uiColor},setUiColor:function(c){var a= -h(CKEDITOR.document);return(this.setUiColor=function(c){this.uiColor=c;var e=CKEDITOR.skin.chameleon,f="",m="";"function"==typeof e&&(f=e(this,"editor"),m=e(this,"panel"));c=[[g,c]];b([a],f,c);b(d,m,c)}).call(this,c)}});var k="cke_ui_color",d=[],g=/\$color/g;CKEDITOR.on("instanceLoaded",function(c){if(!CKEDITOR.env.ie||!CKEDITOR.env.quirks){var a=c.editor;c=function(c){c=(c.data[0]||c.data).element.getElementsByTag("iframe").getItem(0).getFrameDocument();if(!c.getById("cke_ui_color")){c=h(c);d.push(c); -var e=a.getUiColor();e&&b([c],CKEDITOR.skin.chameleon(a,"panel"),[[g,e]])}};a.on("panelShow",c);a.on("menuShow",c);a.config.uiColor&&a.setUiColor(a.config.uiColor)}})}(),function(){if(CKEDITOR.env.webkit)CKEDITOR.env.hc=!1;else{var e=CKEDITOR.dom.element.createFromHtml('\x3cdiv style\x3d"width:0;height:0;position:absolute;left:-10000px;border:1px solid;border-color:red blue"\x3e\x3c/div\x3e',CKEDITOR.document);e.appendTo(CKEDITOR.document.getHead());try{var f=e.getComputedStyle("border-top-color"), -c=e.getComputedStyle("border-right-color");CKEDITOR.env.hc=!(!f||f!=c)}catch(h){CKEDITOR.env.hc=!1}e.remove()}CKEDITOR.env.hc&&(CKEDITOR.env.cssClass+=" cke_hc");CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(e=CKEDITOR._.pending)for(delete CKEDITOR._.pending,f=0;ff;f++){var k=f,d;d=parseInt(b[f],16);d=("0"+(0>e?0|d*(1+e):0|d+(255-d)*e).toString(16)).slice(-2);b[k]=d}return"#"+b.join("")}}(),f={editor:new CKEDITOR.template("{id}.cke_chrome [border-color:{defaultBorder};] {id} .cke_top [ background-color:{defaultBackground};border-bottom-color:{defaultBorder};] {id} .cke_bottom [background-color:{defaultBackground};border-top-color:{defaultBorder};] {id} .cke_resizer [border-right-color:{ckeResizer}] {id} .cke_dialog_title [background-color:{defaultBackground};border-bottom-color:{defaultBorder};] {id} .cke_dialog_footer [background-color:{defaultBackground};outline-color:{defaultBorder};] {id} .cke_dialog_tab [background-color:{dialogTab};border-color:{defaultBorder};] {id} .cke_dialog_tab:hover [background-color:{lightBackground};] {id} .cke_dialog_contents [border-top-color:{defaultBorder};] {id} .cke_dialog_tab_selected, {id} .cke_dialog_tab_selected:hover [background:{dialogTabSelected};border-bottom-color:{dialogTabSelectedBorder};] {id} .cke_dialog_body [background:{dialogBody};border-color:{defaultBorder};] {id} a.cke_button_off:hover,{id} a.cke_button_off:focus,{id} a.cke_button_off:active [background-color:{darkBackground};border-color:{toolbarElementsBorder};] {id} .cke_button_on [background-color:{ckeButtonOn};border-color:{toolbarElementsBorder};] {id} .cke_toolbar_separator,{id} .cke_toolgroup a.cke_button:last-child:after,{id} .cke_toolgroup a.cke_button.cke_button_disabled:hover:last-child:after [background-color: {toolbarElementsBorder};border-color: {toolbarElementsBorder};] {id} a.cke_combo_button:hover,{id} a.cke_combo_button:focus,{id} .cke_combo_on a.cke_combo_button [border-color:{toolbarElementsBorder};background-color:{darkBackground};] {id} .cke_combo:after [border-color:{toolbarElementsBorder};] {id} .cke_path_item [color:{elementsPathColor};] {id} a.cke_path_item:hover,{id} a.cke_path_item:focus,{id} a.cke_path_item:active [background-color:{darkBackground};] {id}.cke_panel [border-color:{defaultBorder};] "), +(function(){if(!window.CKEDITOR||!window.CKEDITOR.dom){window.CKEDITOR||(window.CKEDITOR=function(){var b=/(^|.*[\\\/])ckeditor\.js(?:\?.*|;.*)?$/i,h={timestamp:"JA5A",version:"4.13.0",revision:"af6f51523",rnd:Math.floor(900*Math.random())+100,_:{pending:[],basePathSrcPattern:b},status:"unloaded",basePath:function(){var a=window.CKEDITOR_BASEPATH||"";if(!a)for(var f=document.getElementsByTagName("script"),g=0;gc.getListenerIndex(a)){c=c.listeners;g||(g=this);isNaN(l)&&(l=10);var n=this;d.fn=a;d.priority=l;for(var w=c.length-1;0<=w;w--)if(c[w].priority<=l)return c.splice(w+1,0,d),{removeListener:e};c.unshift(d)}return{removeListener:e}},once:function(){var f=Array.prototype.slice.call(arguments),a=f[1];f[1]=function(f){f.removeListener();return a.apply(this, +arguments)};return this.on.apply(this,f)},capture:function(){CKEDITOR.event.useCapture=1;var f=this.on.apply(this,arguments);CKEDITOR.event.useCapture=0;return f},fire:function(){var f=0,a=function(){f=1},b=0,g=function(){b=1};return function(l,d,e){var c=h(this)[l];l=f;var n=b;f=b=0;if(c){var w=c.listeners;if(w.length)for(var w=w.slice(0),z,r=0;rdocument.documentMode),mobile:-1f||g.quirks);g.gecko&&(h=b.match(/rv:([\d\.]+)/))&&(h=h[1].split("."),f=1E4*h[0]+100*(h[1]||0)+1*(h[2]||0));g.air&&(f=parseFloat(b.match(/ adobeair\/(\d+)/)[1]));g.webkit&&(f=parseFloat(b.match(/ applewebkit\/(\d+)/)[1]));g.version=f;g.isCompatible=!(g.ie&&7>f)&&!(g.gecko&&4E4>f)&&!(g.webkit&& +534>f);g.hidpi=2<=window.devicePixelRatio;g.needsBrFiller=g.gecko||g.webkit||g.ie&&10f;g.cssClass="cke_browser_"+(g.ie?"ie":g.gecko?"gecko":g.webkit?"webkit":"unknown");g.quirks&&(g.cssClass+=" cke_browser_quirks");g.ie&&(g.cssClass+=" cke_browser_ie"+(g.quirks?"6 cke_browser_iequirks":g.version));g.air&&(g.cssClass+=" cke_browser_air");g.iOS&&(g.cssClass+=" cke_browser_ios");g.hidpi&&(g.cssClass+=" cke_hidpi");return g}());"unloaded"==CKEDITOR.status&&function(){CKEDITOR.event.implementOn(CKEDITOR); +CKEDITOR.loadFullCore=function(){if("basic_ready"!=CKEDITOR.status)CKEDITOR.loadFullCore._load=1;else{delete CKEDITOR.loadFullCore;var b=document.createElement("script");b.type="text/javascript";b.src=CKEDITOR.basePath+"ckeditor.js";document.getElementsByTagName("head")[0].appendChild(b)}};CKEDITOR.loadFullCoreTimeout=0;CKEDITOR.add=function(b){(this._.pending||(this._.pending=[])).push(b)};(function(){CKEDITOR.domReady(function(){var b=CKEDITOR.loadFullCore,h=CKEDITOR.loadFullCoreTimeout;b&&(CKEDITOR.status= +"basic_ready",b&&b._load?b():h&&setTimeout(function(){CKEDITOR.loadFullCore&&CKEDITOR.loadFullCore()},1E3*h))})})();CKEDITOR.status="basic_loaded"}();"use strict";CKEDITOR.VERBOSITY_WARN=1;CKEDITOR.VERBOSITY_ERROR=2;CKEDITOR.verbosity=CKEDITOR.VERBOSITY_WARN|CKEDITOR.VERBOSITY_ERROR;CKEDITOR.warn=function(b,h){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_WARN&&CKEDITOR.fire("log",{type:"warn",errorCode:b,additionalData:h})};CKEDITOR.error=function(b,h){CKEDITOR.verbosity&CKEDITOR.VERBOSITY_ERROR&&CKEDITOR.fire("log", +{type:"error",errorCode:b,additionalData:h})};CKEDITOR.on("log",function(b){if(window.console&&window.console.log){var h=console[b.data.type]?b.data.type:"log",g=b.data.errorCode;if(b=b.data.additionalData)console[h]("[CKEDITOR] Error code: "+g+".",b);else console[h]("[CKEDITOR] Error code: "+g+".");console[h]("[CKEDITOR] For more information about this error go to https://ckeditor.com/docs/ckeditor4/latest/guide/dev_errors.html#"+g)}},null,null,999);CKEDITOR.dom={};(function(){function b(n,c,d){this._minInterval= +n;this._context=d;this._lastOutput=this._scheduledTimer=0;this._output=CKEDITOR.tools.bind(c,d||{});var a=this;this.input=function(){function n(){a._lastOutput=(new Date).getTime();a._scheduledTimer=0;a._call()}if(!a._scheduledTimer||!1!==a._reschedule()){var c=(new Date).getTime()-a._lastOutput;c/g,k=/|\s) /g,function(c,n){return n+"\x26nbsp;"}).replace(/ (?=<)/g,"\x26nbsp;")},getNextNumber:function(){var c=0;return function(){return++c}}(),getNextId:function(){return"cke_"+this.getNextNumber()},getUniqueId:function(){for(var c="e",d=0;8>d;d++)c+=Math.floor(65536*(1+Math.random())).toString(16).substring(1);return c},override:function(c,d){var a=d(c);a.prototype=c.prototype;return a},setTimeout:function(c,d,a,e,f){f||(f=window);a||(a= +f);return f.setTimeout(function(){e?c.apply(a,[].concat(e)):c.apply(a)},d||0)},throttle:function(c,d,a){return new this.buffers.throttle(c,d,a)},trim:function(){var c=/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;return function(d){return d.replace(c,"")}}(),ltrim:function(){var c=/^[ \t\n\r]+/g;return function(d){return d.replace(c,"")}}(),rtrim:function(){var c=/[ \t\n\r]+$/g;return function(d){return d.replace(c,"")}}(),indexOf:function(c,d){if("function"==typeof d)for(var a=0,e=c.length;aparseFloat(d); +a&&(d=d.replace("-",""));c.setStyle("width",d);d=c.$.clientWidth;return a?-d:d}return d}}(),repeat:function(c,d){return Array(d+1).join(c)},tryThese:function(){for(var c,d=0,a=arguments.length;dd;d++)c[d]=("0"+parseInt(c[d],10).toString(16)).slice(-2);return"#"+c.join("")})},normalizeHex:function(c){return c.replace(/#(([0-9a-f]{3}){1,2})($|;|\s+)/gi,function(c,d,n,a){c=d.toLowerCase();3==c.length&&(c=c.split(""),c=[c[0],c[0],c[1],c[1],c[2],c[2]].join(""));return"#"+c+a})},parseCssText:function(c, +d,a){var e={};a&&(c=(new CKEDITOR.dom.element("span")).setAttribute("style",c).getAttribute("style")||"");c&&(c=CKEDITOR.tools.normalizeHex(CKEDITOR.tools.convertRgbToHex(c)));if(!c||";"==c)return e;c.replace(/"/g,'"').replace(/\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(c,n,a){d&&(n=n.toLowerCase(),"font-family"==n&&(a=a.replace(/\s*,\s*/g,",")),a=CKEDITOR.tools.trim(a));e[n]=a});return e},writeCssText:function(c,d){var a,e=[];for(a in c)e.push(a+":"+c[a]);d&&e.sort();return e.join("; ")}, +objectCompare:function(c,d,a){var e;if(!c&&!d)return!0;if(!c||!d)return!1;for(e in c)if(c[e]!=d[e])return!1;if(!a)for(e in d)if(c[e]!=d[e])return!1;return!0},objectKeys:function(c){return CKEDITOR.tools.object.keys(c)},convertArrayToObject:function(c,d){var a={};1==arguments.length&&(d=!0);for(var e=0,f=c.length;ea;a++)c.push(Math.floor(256*Math.random()));for(a=0;ab)for(g=b;3>g;g++)f[g]=0;l[0]=(f[0]&252)>>2;l[1]=(f[0]&3)<<4|f[1]>>4;l[2]=(f[1]&15)<<2|(f[2]&192)>>6;l[3]=f[2]&63;for(g=0;4>g;g++)d=g<=b?d+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(l[g]):d+"\x3d"}return d}, +style:{parse:{_colors:{aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aqua:"#00FFFF",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blue:"#0000FF",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400", +darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",fuchsia:"#FF00FF",gainsboro:"#DCDCDC", +ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",gray:"#808080",green:"#008000",greenyellow:"#ADFF2F",grey:"#808080",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgray:"#D3D3D3",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A", +lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",lime:"#00FF00",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",maroon:"#800000",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1", +moccasin:"#FFE4B5",navajowhite:"#FFDEAD",navy:"#000080",oldlace:"#FDF5E6",olive:"#808000",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",purple:"#800080",rebeccapurple:"#663399",red:"#FF0000",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460", +seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",silver:"#C0C0C0",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",teal:"#008080",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",windowtext:"windowtext",wheat:"#F5DEB3",white:"#FFFFFF",whitesmoke:"#F5F5F5",yellow:"#FFFF00",yellowgreen:"#9ACD32"},_borderStyle:"none hidden dotted dashed solid double groove ridge inset outset".split(" "), +_widthRegExp:/^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/,_rgbaRegExp:/rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi,_hslaRegExp:/hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi,background:function(c){var d={},a=this._findColor(c);a.length&&(d.color=a[0],CKEDITOR.tools.array.forEach(a,function(d){c=c.replace(d,"")}));if(c=CKEDITOR.tools.trim(c))d.unprocessed=c;return d},margin:function(c){return CKEDITOR.tools.style.parse.sideShorthand(c, +function(c){return c.match(/(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset|revert)/g)||["0px"]})},sideShorthand:function(c,d){function a(c){e.top=f[c[0]];e.right=f[c[1]];e.bottom=f[c[2]];e.left=f[c[3]]}var e={},f=d?d(c):c.split(/\s+/);switch(f.length){case 1:a([0,0,0,0]);break;case 2:a([0,1,0,1]);break;case 3:a([0,1,2,1]);break;case 4:a([0,1,2,3])}return e},border:function(c){return CKEDITOR.tools.style.border.fromCssRule(c)},_findColor:function(c){var d=[],a=CKEDITOR.tools.array,d=d.concat(c.match(this._rgbaRegExp)|| +[]),d=d.concat(c.match(this._hslaRegExp)||[]);return d=d.concat(a.filter(c.split(/\s+/),function(c){return c.match(/^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi)?!0:c.toLowerCase()in CKEDITOR.tools.style.parse._colors}))}}},array:{filter:function(c,d,a){var e=[];this.forEach(c,function(f,b){d.call(a,f,b,c)&&e.push(f)});return e},find:function(c,d,a){for(var e=c.length,f=0;fCKEDITOR.env.version&&(!c||"object"!==typeof c)){d=[];if("string"===typeof c)for(a=0;aCKEDITOR.env.version)for(f=0;fCKEDITOR.env.version&&(this.type==CKEDITOR.NODE_ELEMENT||this.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT)&&f(a);return a},hasPrevious:function(){return!!this.$.previousSibling},hasNext:function(){return!!this.$.nextSibling},insertAfter:function(b){b.$.parentNode.insertBefore(this.$,b.$.nextSibling);return b},insertBefore:function(b){b.$.parentNode.insertBefore(this.$,b.$);return b},insertBeforeMe:function(b){this.$.parentNode.insertBefore(b.$,this.$); +return b},getAddress:function(b){for(var h=[],g=this.getDocument().$.documentElement,f=this;f&&f!=g;){var a=f.getParent();a&&h.unshift(this.getIndex.call(f,b));f=a}return h},getDocument:function(){return new CKEDITOR.dom.document(this.$.ownerDocument||this.$.parentNode.ownerDocument)},getIndex:function(b){function h(a,f){var b=f?a.getNext():a.getPrevious();return b&&b.type==CKEDITOR.NODE_TEXT?b.isEmpty()?h(b,f):b:null}var g=this,f=-1,a;if(!this.getParent()||b&&g.type==CKEDITOR.NODE_TEXT&&g.isEmpty()&& +!h(g)&&!h(g,!0))return-1;do if(!b||g.equals(this)||g.type!=CKEDITOR.NODE_TEXT||!a&&!g.isEmpty())f++,a=g.type==CKEDITOR.NODE_TEXT;while(g=g.getPrevious());return f},getNextSourceNode:function(b,h,g){if(g&&!g.call){var f=g;g=function(a){return!a.equals(f)}}b=!b&&this.getFirst&&this.getFirst();var a;if(!b){if(this.type==CKEDITOR.NODE_ELEMENT&&g&&!1===g(this,!0))return null;b=this.getNext()}for(;!b&&(a=(a||this).getParent());){if(g&&!1===g(a,!0))return null;b=a.getNext()}return!b||g&&!1===g(b)?null:h&& +h!=b.type?b.getNextSourceNode(!1,h,g):b},getPreviousSourceNode:function(b,h,g){if(g&&!g.call){var f=g;g=function(a){return!a.equals(f)}}b=!b&&this.getLast&&this.getLast();var a;if(!b){if(this.type==CKEDITOR.NODE_ELEMENT&&g&&!1===g(this,!0))return null;b=this.getPrevious()}for(;!b&&(a=(a||this).getParent());){if(g&&!1===g(a,!0))return null;b=a.getPrevious()}return!b||g&&!1===g(b)?null:h&&b.type!=h?b.getPreviousSourceNode(!1,h,g):b},getPrevious:function(b){var h=this.$,g;do g=(h=h.previousSibling)&& +10!=h.nodeType&&new CKEDITOR.dom.node(h);while(g&&b&&!b(g));return g},getNext:function(b){var h=this.$,g;do g=(h=h.nextSibling)&&new CKEDITOR.dom.node(h);while(g&&b&&!b(g));return g},getParent:function(b){var h=this.$.parentNode;return h&&(h.nodeType==CKEDITOR.NODE_ELEMENT||b&&h.nodeType==CKEDITOR.NODE_DOCUMENT_FRAGMENT)?new CKEDITOR.dom.node(h):null},getParents:function(b){var h=this,g=[];do g[b?"push":"unshift"](h);while(h=h.getParent());return g},getCommonAncestor:function(b){if(b.equals(this))return this; +if(b.contains&&b.contains(this))return b;var h=this.contains?this:this.getParent();do if(h.contains(b))return h;while(h=h.getParent());return null},getPosition:function(b){var h=this.$,g=b.$;if(h.compareDocumentPosition)return h.compareDocumentPosition(g);if(h==g)return CKEDITOR.POSITION_IDENTICAL;if(this.type==CKEDITOR.NODE_ELEMENT&&b.type==CKEDITOR.NODE_ELEMENT){if(h.contains){if(h.contains(g))return CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_PRECEDING;if(g.contains(h))return CKEDITOR.POSITION_IS_CONTAINED+ +CKEDITOR.POSITION_FOLLOWING}if("sourceIndex"in h)return 0>h.sourceIndex||0>g.sourceIndex?CKEDITOR.POSITION_DISCONNECTED:h.sourceIndex=document.documentMode||!h||(b=h+":"+ +b);return new CKEDITOR.dom.nodeList(this.$.getElementsByTagName(b))},getHead:function(){var b=this.$.getElementsByTagName("head")[0];return b=b?new CKEDITOR.dom.element(b):this.getDocumentElement().append(new CKEDITOR.dom.element("head"),!0)},getBody:function(){return new CKEDITOR.dom.element(this.$.body)},getDocumentElement:function(){return new CKEDITOR.dom.element(this.$.documentElement)},getWindow:function(){return new CKEDITOR.dom.window(this.$.parentWindow||this.$.defaultView)},write:function(b){this.$.open("text/html", +"replace");CKEDITOR.env.ie&&(b=b.replace(/(?:^\s*]*?>)|^/i,'$\x26\n\x3cscript data-cke-temp\x3d"1"\x3e('+CKEDITOR.tools.fixDomain+")();\x3c/script\x3e"));this.$.write(b);this.$.close()},find:function(b){return new CKEDITOR.dom.nodeList(this.$.querySelectorAll(b))},findOne:function(b){return(b=this.$.querySelector(b))?new CKEDITOR.dom.element(b):null},_getHtml5ShivFrag:function(){var b=this.getCustomData("html5ShivFrag");b||(b=this.$.createDocumentFragment(),CKEDITOR.tools.enableHtml5Elements(b, +!0),this.setCustomData("html5ShivFrag",b));return b}});CKEDITOR.dom.nodeList=function(b){this.$=b};CKEDITOR.dom.nodeList.prototype={count:function(){return this.$.length},getItem:function(b){return 0>b||b>=this.$.length?null:(b=this.$[b])?new CKEDITOR.dom.node(b):null},toArray:function(){return CKEDITOR.tools.array.map(this.$,function(b){return new CKEDITOR.dom.node(b)})}};CKEDITOR.dom.element=function(b,h){"string"==typeof b&&(b=(h?h.$:document).createElement(b));CKEDITOR.dom.domObject.call(this, +b)};CKEDITOR.dom.element.get=function(b){return(b="string"==typeof b?document.getElementById(b)||document.getElementsByName(b)[0]:b)&&(b.$?b:new CKEDITOR.dom.element(b))};CKEDITOR.dom.element.prototype=new CKEDITOR.dom.node;CKEDITOR.dom.element.createFromHtml=function(b,h){var g=new CKEDITOR.dom.element("div",h);g.setHtml(b);return g.getFirst().remove()};CKEDITOR.dom.element.setMarker=function(b,h,g,f){var a=h.getCustomData("list_marker_id")||h.setCustomData("list_marker_id",CKEDITOR.tools.getNextNumber()).getCustomData("list_marker_id"), +m=h.getCustomData("list_marker_names")||h.setCustomData("list_marker_names",{}).getCustomData("list_marker_names");b[a]=h;m[g]=1;return h.setCustomData(g,f)};CKEDITOR.dom.element.clearAllMarkers=function(b){for(var h in b)CKEDITOR.dom.element.clearMarkers(b,b[h],1)};CKEDITOR.dom.element.clearMarkers=function(b,h,g){var f=h.getCustomData("list_marker_names"),a=h.getCustomData("list_marker_id"),m;for(m in f)h.removeCustomData(m);h.removeCustomData("list_marker_names");g&&(h.removeCustomData("list_marker_id"), +delete b[a])};(function(){function b(a,d){return-1<(" "+a+" ").replace(m," ").indexOf(" "+d+" ")}function h(a){var d=!0;a.$.id||(a.$.id="cke_tmp_"+CKEDITOR.tools.getNextNumber(),d=!1);return function(){d||a.removeAttribute("id")}}function g(a,d){var e=CKEDITOR.tools.escapeCss(a.$.id);return"#"+e+" "+d.split(/,\s*/).join(", #"+e+" ")}function f(a){for(var d=0,e=0,c=k[a].length;eCKEDITOR.env.version?this.$.text+=a:this.append(new CKEDITOR.dom.text(a))},appendBogus:function(a){if(a||CKEDITOR.env.needsBrFiller){for(a=this.getLast();a&&a.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.rtrim(a.getText());)a=a.getPrevious();a&&a.is&&a.is("br")||(a=this.getDocument().createElement("br"),CKEDITOR.env.gecko&&a.setAttribute("type","_moz"),this.append(a))}},breakParent:function(a,d){var e=new CKEDITOR.dom.range(this.getDocument());e.setStartAfter(this);e.setEndAfter(a); +var c=e.extractContents(!1,d||!1),f;e.insertNode(this.remove());if(CKEDITOR.env.ie&&!CKEDITOR.env.edge){for(e=new CKEDITOR.dom.element("div");f=c.getFirst();)f.$.style.backgroundColor&&(f.$.style.backgroundColor=f.$.style.backgroundColor),e.append(f);e.insertAfter(this);e.remove(!0)}else c.insertAfterNode(this)},contains:document.compareDocumentPosition?function(a){return!!(this.$.compareDocumentPosition(a.$)&16)}:function(a){var d=this.$;return a.type!=CKEDITOR.NODE_ELEMENT?d.contains(a.getParent().$): +d!=a.$&&d.contains(a.$)},focus:function(){function a(){try{this.$.focus()}catch(d){}}return function(d){d?CKEDITOR.tools.setTimeout(a,100,this):a.call(this)}}(),getHtml:function(){var a=this.$.innerHTML;return CKEDITOR.env.ie?a.replace(/<\?[^>]*>/g,""):a},getOuterHtml:function(){if(this.$.outerHTML)return this.$.outerHTML.replace(/<\?[^>]*>/,"");var a=this.$.ownerDocument.createElement("div");a.appendChild(this.$.cloneNode(!0));return a.innerHTML},getClientRect:function(a){var d=CKEDITOR.tools.extend({}, +this.$.getBoundingClientRect());!d.width&&(d.width=d.right-d.left);!d.height&&(d.height=d.bottom-d.top);return a?CKEDITOR.tools.getAbsoluteRectPosition(this.getWindow(),d):d},setHtml:CKEDITOR.env.ie&&9>CKEDITOR.env.version?function(a){try{var d=this.$;if(this.getParent())return d.innerHTML=a;var e=this.getDocument()._getHtml5ShivFrag();e.appendChild(d);d.innerHTML=a;e.removeChild(d);return a}catch(c){this.$.innerHTML="";d=new CKEDITOR.dom.element("body",this.getDocument());d.$.innerHTML=a;for(d=d.getChildren();d.count();)this.append(d.getItem(0)); +return a}}:function(a){return this.$.innerHTML=a},setText:function(){var a=document.createElement("p");a.innerHTML="x";a=a.textContent;return function(d){this.$[a?"textContent":"innerText"]=d}}(),getAttribute:function(){var a=function(d){return this.$.getAttribute(d,2)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(d){switch(d){case "class":d="className";break;case "http-equiv":d="httpEquiv";break;case "name":return this.$.name;case "tabindex":return d=this.$.getAttribute(d, +2),0!==d&&0===this.$.tabIndex&&(d=null),d;case "checked":return d=this.$.attributes.getNamedItem(d),(d.specified?d.nodeValue:this.$.checked)?"checked":null;case "hspace":case "value":return this.$[d];case "style":return this.$.style.cssText;case "contenteditable":case "contentEditable":return this.$.attributes.getNamedItem("contentEditable").specified?this.$.getAttribute("contentEditable"):null}return this.$.getAttribute(d,2)}:a}(),getAttributes:function(a){var d={},e=this.$.attributes,c;a=CKEDITOR.tools.isArray(a)? +a:[];for(c=0;c=document.documentMode){var d=this.$.scopeName;"HTML"!=d&&(a=d.toLowerCase()+":"+a)}this.getName=function(){return a};return this.getName()},getValue:function(){return this.$.value},getFirst:function(a){var d=this.$.firstChild;(d=d&&new CKEDITOR.dom.node(d))&&a&&!a(d)&&(d=d.getNext(a));return d},getLast:function(a){var d=this.$.lastChild;(d=d&&new CKEDITOR.dom.node(d))&&a&&!a(d)&&(d=d.getPrevious(a));return d},getStyle:function(a){return this.$.style[CKEDITOR.tools.cssStyleToDomStyle(a)]}, +is:function(){var a=this.getName();if("object"==typeof arguments[0])return!!arguments[0][a];for(var d=0;dCKEDITOR.env.version&&this.is("a")){var e=this.getParent();e.type==CKEDITOR.NODE_ELEMENT&&(e=e.clone(),e.setHtml(d),d=e.getHtml(),e.setHtml(a),a=e.getHtml())}return d==a},isVisible:function(){var a=(this.$.offsetHeight||this.$.offsetWidth)&&"hidden"!=this.getComputedStyle("visibility"),d,e;a&&CKEDITOR.env.webkit&&(d=this.getWindow(),!d.equals(CKEDITOR.document.getWindow())&&(e=d.$.frameElement)&&(a=(new CKEDITOR.dom.element(e)).isVisible()));return!!a},isEmptyInlineRemoveable:function(){if(!CKEDITOR.dtd.$removeEmpty[this.getName()])return!1; +for(var a=this.getChildren(),d=0,e=a.count();dCKEDITOR.env.version?function(d){return"name"==d?!!this.$.name:a.call(this,d)}:a:function(a){return!!this.$.attributes.getNamedItem(a)}}(),hide:function(){this.setStyle("display","none")},moveChildren:function(a,d){var e=this.$;a=a.$;if(e!=a){var c;if(d)for(;c=e.lastChild;)a.insertBefore(e.removeChild(c),a.firstChild);else for(;c=e.firstChild;)a.appendChild(e.removeChild(c))}},mergeSiblings:function(){function a(d,e,c){if(e&&e.type==CKEDITOR.NODE_ELEMENT){for(var f= +[];e.data("cke-bookmark")||e.isEmptyInlineRemoveable();)if(f.push(e),e=c?e.getNext():e.getPrevious(),!e||e.type!=CKEDITOR.NODE_ELEMENT)return;if(d.isIdentical(e)){for(var b=c?d.getLast():d.getFirst();f.length;)f.shift().move(d,!c);e.moveChildren(d,!c);e.remove();b&&b.type==CKEDITOR.NODE_ELEMENT&&b.mergeSiblings()}}}return function(d){if(!1===d||CKEDITOR.dtd.$removeEmpty[this.getName()]||this.is("a"))a(this,this.getNext(),!0),a(this,this.getPrevious())}}(),show:function(){this.setStyles({display:"", +visibility:""})},setAttribute:function(){var a=function(a,e){this.$.setAttribute(a,e);return this};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(d,e){"class"==d?this.$.className=e:"style"==d?this.$.style.cssText=e:"tabindex"==d?this.$.tabIndex=e:"checked"==d?this.$.checked=e:"contenteditable"==d?a.call(this,"contentEditable",e):a.apply(this,arguments);return this}:CKEDITOR.env.ie8Compat&&CKEDITOR.env.secure?function(d,e){if("src"==d&&e.match(/^http:\/\//))try{a.apply(this, +arguments)}catch(c){}else a.apply(this,arguments);return this}:a}(),setAttributes:function(a){for(var d in a)this.setAttribute(d,a[d]);return this},setValue:function(a){this.$.value=a;return this},removeAttribute:function(){var a=function(a){this.$.removeAttribute(a)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(a){"class"==a?a="className":"tabindex"==a?a="tabIndex":"contenteditable"==a&&(a="contentEditable");this.$.removeAttribute(a)}:a}(),removeAttributes:function(a){if(CKEDITOR.tools.isArray(a))for(var d= +0;dCKEDITOR.env.version?(a=Math.round(100*a),this.setStyle("filter",100<=a?"":"progid:DXImageTransform.Microsoft.Alpha(opacity\x3d"+a+")")):this.setStyle("opacity",a)},unselectable:function(){this.setStyles(CKEDITOR.tools.cssVendorPrefix("user-select", +"none"));if(CKEDITOR.env.ie){this.setAttribute("unselectable","on");for(var a,d=this.getElementsByTag("*"),e=0,c=d.count();eg||0g?g:f);e&&(0>b||0b?b:c,0)},setState:function(a,d,e){d=d||"cke";switch(a){case CKEDITOR.TRISTATE_ON:this.addClass(d+"_on");this.removeClass(d+ +"_off");this.removeClass(d+"_disabled");e&&this.setAttribute("aria-pressed",!0);e&&this.removeAttribute("aria-disabled");break;case CKEDITOR.TRISTATE_DISABLED:this.addClass(d+"_disabled");this.removeClass(d+"_off");this.removeClass(d+"_on");e&&this.setAttribute("aria-disabled",!0);e&&this.removeAttribute("aria-pressed");break;default:this.addClass(d+"_off"),this.removeClass(d+"_on"),this.removeClass(d+"_disabled"),e&&this.removeAttribute("aria-pressed"),e&&this.removeAttribute("aria-disabled")}}, +getFrameDocument:function(){var a=this.$;try{a.contentWindow.document}catch(d){a.src=a.src}return a&&new CKEDITOR.dom.document(a.contentWindow.document)},copyAttributes:function(a,d){var e=this.$.attributes;d=d||{};for(var c=0;cCKEDITOR.env.version){var f=c.ownerDocument.createEventObject(),b;for(b in d)f[b]=d[b];c.fireEvent(e, +f)}else c[c[a]?a:e](d)},isDetached:function(){var a=this.getDocument(),d=a.getDocumentElement();return d.equals(this)||d.contains(this)?!CKEDITOR.env.ie||8=B.getChildCount()?(B=B.getChild(A-1),C=!0):B=B.getChild(A):J=C=!0;x.type== +CKEDITOR.NODE_TEXT?l?F=!0:x.split(D):0ea)for(;W;)W=b(W,K,!0);K=X}l||h()}}function g(){var c=!1,a=CKEDITOR.dom.walker.whitespaces(),d=CKEDITOR.dom.walker.bookmark(!0),e=CKEDITOR.dom.walker.bogus();return function(f){return d(f)||a(f)?!0:e(f)&&!c?c=!0:f.type==CKEDITOR.NODE_TEXT&&(f.hasAscendant("pre")||CKEDITOR.tools.trim(f.getText()).length)||f.type==CKEDITOR.NODE_ELEMENT&&!f.is(m)?!1:!0}}function f(c){var a=CKEDITOR.dom.walker.whitespaces(), +d=CKEDITOR.dom.walker.bookmark(1);return function(e){return d(e)||a(e)?!0:!c&&k(e)||e.type==CKEDITOR.NODE_ELEMENT&&e.is(CKEDITOR.dtd.$removeEmpty)}}function a(c){return function(){var a;return this[c?"getPreviousNode":"getNextNode"](function(c){!a&&e(c)&&(a=c);return d(c)&&!(k(c)&&c.equals(a))})}}var m={abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,"var":1},k=CKEDITOR.dom.walker.bogus(), +l=/^[\t\r\n ]*(?: |\xa0)$/,d=CKEDITOR.dom.walker.editable(),e=CKEDITOR.dom.walker.ignored(!0);CKEDITOR.dom.range.prototype={clone:function(){var c=new CKEDITOR.dom.range(this.root);c._setStartContainer(this.startContainer);c.startOffset=this.startOffset;c._setEndContainer(this.endContainer);c.endOffset=this.endOffset;c.collapsed=this.collapsed;return c},collapse:function(c){c?(this._setEndContainer(this.startContainer),this.endOffset=this.startOffset):(this._setStartContainer(this.endContainer), +this.startOffset=this.endOffset);this.collapsed=!0},cloneContents:function(c){var a=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||h(this,2,a,!1,"undefined"==typeof c?!0:c);return a},deleteContents:function(c){this.collapsed||h(this,0,null,c)},extractContents:function(c,a){var d=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||h(this,1,d,c,"undefined"==typeof a?!0:a);return d},equals:function(c){return this.startOffset===c.startOffset&&this.endOffset===c.endOffset&& +this.startContainer.equals(c.startContainer)&&this.endContainer.equals(c.endContainer)},createBookmark:function(c){function a(c){return c.getAscendant(function(c){var a;if(a=c.data&&c.data("cke-temp"))a=-1===CKEDITOR.tools.array.indexOf(["cke_copybin","cke_pastebin"],c.getAttribute("id"));return a},!0)}var d=this.startContainer,e=this.endContainer,f=this.collapsed,b,g,h,k;b=this.document.createElement("span");b.data("cke-bookmark",1);b.setStyle("display","none");b.setHtml("\x26nbsp;");c&&(h="cke_bm_"+ +CKEDITOR.tools.getNextNumber(),b.setAttribute("id",h+(f?"C":"S")));f||(g=b.clone(),g.setHtml("\x26nbsp;"),c&&g.setAttribute("id",h+"E"),k=this.clone(),a(e)&&(e=a(e),k.moveToPosition(e,CKEDITOR.POSITION_AFTER_END)),k.collapse(),k.insertNode(g));k=this.clone();a(d)&&(e=a(d),k.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START));k.collapse(!0);k.insertNode(b);g?(this.setStartAfter(b),this.setEndBefore(g)):this.moveToPosition(b,CKEDITOR.POSITION_AFTER_END);return{startNode:c?h+(f?"C":"S"):b,endNode:c?h+ +"E":g,serializable:c,collapsed:f}},createBookmark2:function(){function c(c){var a=c.container,e=c.offset,f;f=a;var b=e;f=f.type!=CKEDITOR.NODE_ELEMENT||0===b||b==f.getChildCount()?0:f.getChild(b-1).type==CKEDITOR.NODE_TEXT&&f.getChild(b).type==CKEDITOR.NODE_TEXT;f&&(a=a.getChild(e-1),e=a.getLength());if(a.type==CKEDITOR.NODE_ELEMENT&&0=c.offset&&(c.offset=f.getIndex(),c.container=f.getParent()))}}var d=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_TEXT, +!0);return function(d){var e=this.collapsed,f={container:this.startContainer,offset:this.startOffset},b={container:this.endContainer,offset:this.endOffset};d&&(c(f),a(f,this.root),e||(c(b),a(b,this.root)));return{start:f.container.getAddress(d),end:e?null:b.container.getAddress(d),startOffset:f.offset,endOffset:b.offset,normalized:d,collapsed:e,is2:!0}}}(),moveToBookmark:function(c){if(c.is2){var a=this.document.getByAddress(c.start,c.normalized),d=c.startOffset,e=c.end&&this.document.getByAddress(c.end, +c.normalized);c=c.endOffset;this.setStart(a,d);e?this.setEnd(e,c):this.collapse(!0)}else a=(d=c.serializable)?this.document.getById(c.startNode):c.startNode,c=d?this.document.getById(c.endNode):c.endNode,this.setStartBefore(a),a.remove(),c?(this.setEndBefore(c),c.remove()):this.collapse(!0)},getBoundaryNodes:function(){var c=this.startContainer,a=this.endContainer,d=this.startOffset,e=this.endOffset,f;if(c.type==CKEDITOR.NODE_ELEMENT)if(f=c.getChildCount(),f>d)c=c.getChild(d);else if(1>f)c=c.getPreviousSourceNode(); +else{for(c=c.$;c.lastChild;)c=c.lastChild;c=new CKEDITOR.dom.node(c);c=c.getNextSourceNode()||c}if(a.type==CKEDITOR.NODE_ELEMENT)if(f=a.getChildCount(),f>e)a=a.getChild(e).getPreviousSourceNode(!0);else if(1>f)a=a.getPreviousSourceNode();else{for(a=a.$;a.lastChild;)a=a.lastChild;a=new CKEDITOR.dom.node(a)}c.getPosition(a)&CKEDITOR.POSITION_FOLLOWING&&(c=a);return{startNode:c,endNode:a}},getCommonAncestor:function(c,a){var d=this.startContainer,e=this.endContainer,d=d.equals(e)?c&&d.type==CKEDITOR.NODE_ELEMENT&& +this.startOffset==this.endOffset-1?d.getChild(this.startOffset):d:d.getCommonAncestor(e);return a&&!d.is?d.getParent():d},optimize:function(){var c=this.startContainer,a=this.startOffset;c.type!=CKEDITOR.NODE_ELEMENT&&(a?a>=c.getLength()&&this.setStartAfter(c):this.setStartBefore(c));c=this.endContainer;a=this.endOffset;c.type!=CKEDITOR.NODE_ELEMENT&&(a?a>=c.getLength()&&this.setEndAfter(c):this.setEndBefore(c))},optimizeBookmark:function(){var c=this.startContainer,a=this.endContainer;c.is&&c.is("span")&& +c.data("cke-bookmark")&&this.setStartAt(c,CKEDITOR.POSITION_BEFORE_START);a&&a.is&&a.is("span")&&a.data("cke-bookmark")&&this.setEndAt(a,CKEDITOR.POSITION_AFTER_END)},trim:function(c,a){var d=this.startContainer,e=this.startOffset,f=this.collapsed;if((!c||f)&&d&&d.type==CKEDITOR.NODE_TEXT){if(e)if(e>=d.getLength())e=d.getIndex()+1,d=d.getParent();else{var b=d.split(e),e=d.getIndex()+1,d=d.getParent();this.startContainer.equals(this.endContainer)?this.setEnd(b,this.endOffset-this.startOffset):d.equals(this.endContainer)&& +(this.endOffset+=1)}else e=d.getIndex(),d=d.getParent();this.setStart(d,e);if(f){this.collapse(!0);return}}d=this.endContainer;e=this.endOffset;a||f||!d||d.type!=CKEDITOR.NODE_TEXT||(e?(e>=d.getLength()||d.split(e),e=d.getIndex()+1):e=d.getIndex(),d=d.getParent(),this.setEnd(d,e))},enlarge:function(c,a){function d(c){return c&&c.type==CKEDITOR.NODE_ELEMENT&&c.hasAttribute("contenteditable")?null:c}var e=new RegExp(/[^\s\ufeff]/);switch(c){case CKEDITOR.ENLARGE_INLINE:var f=1;case CKEDITOR.ENLARGE_ELEMENT:var b= +function(c,a){var d=new CKEDITOR.dom.range(h);d.setStart(c,a);d.setEndAt(h,CKEDITOR.POSITION_BEFORE_END);var d=new CKEDITOR.dom.walker(d),f;for(d.guard=function(c){return!(c.type==CKEDITOR.NODE_ELEMENT&&c.isBlockBoundary())};f=d.next();){if(f.type!=CKEDITOR.NODE_TEXT)return!1;G=f!=c?f.getText():f.substring(a);if(e.test(G))return!1}return!0};if(this.collapsed)break;var g=this.getCommonAncestor(),h=this.root,k,m,l,x,B,D=!1,A,G;A=this.startContainer;var C=this.startOffset;A.type==CKEDITOR.NODE_TEXT? +(C&&(A=!CKEDITOR.tools.trim(A.substring(0,C)).length&&A,D=!!A),A&&((x=A.getPrevious())||(l=A.getParent()))):(C&&(x=A.getChild(C-1)||A.getLast()),x||(l=A));for(l=d(l);l||x;){if(l&&!x){!B&&l.equals(g)&&(B=!0);if(f?l.isBlockBoundary():!h.contains(l))break;D&&"inline"==l.getComputedStyle("display")||(D=!1,B?k=l:this.setStartBefore(l));x=l.getPrevious()}for(;x;)if(A=!1,x.type==CKEDITOR.NODE_COMMENT)x=x.getPrevious();else{if(x.type==CKEDITOR.NODE_TEXT)G=x.getText(),e.test(G)&&(x=null),A=/[\s\ufeff]$/.test(G); +else if((x.$.offsetWidth>(CKEDITOR.env.webkit?1:0)||a&&x.is("br"))&&!x.data("cke-bookmark"))if(D&&CKEDITOR.dtd.$removeEmpty[x.getName()]){G=x.getText();if(e.test(G))x=null;else for(var C=x.$.getElementsByTagName("*"),I=0,J;J=C[I++];)if(!CKEDITOR.dtd.$removeEmpty[J.nodeName.toLowerCase()]){x=null;break}x&&(A=!!G.length)}else x=null;A&&(D?B?k=l:l&&this.setStartBefore(l):D=!0);if(x){A=x.getPrevious();if(!l&&!A){l=x;x=null;break}x=A}else l=null}l&&(l=d(l.getParent()))}A=this.endContainer;C=this.endOffset; +l=x=null;B=D=!1;A.type==CKEDITOR.NODE_TEXT?CKEDITOR.tools.trim(A.substring(C)).length?D=!0:(D=!A.getLength(),C==A.getLength()?(x=A.getNext())||(l=A.getParent()):b(A,C)&&(l=A.getParent())):(x=A.getChild(C))||(l=A);for(;l||x;){if(l&&!x){!B&&l.equals(g)&&(B=!0);if(f?l.isBlockBoundary():!h.contains(l))break;D&&"inline"==l.getComputedStyle("display")||(D=!1,B?m=l:l&&this.setEndAfter(l));x=l.getNext()}for(;x;){A=!1;if(x.type==CKEDITOR.NODE_TEXT)G=x.getText(),b(x,0)||(x=null),A=/^[\s\ufeff]/.test(G);else if(x.type== +CKEDITOR.NODE_ELEMENT){if((0=g.getLength()?b.setStartAfter(g):(b.setStartBefore(g),d=0):b.setStartBefore(g));h&&h.type==CKEDITOR.NODE_TEXT&&(l?l>=h.getLength()?b.setEndAfter(h):(b.setEndAfter(h),m=0):b.setEndBefore(h));var b=new CKEDITOR.dom.walker(b),x=CKEDITOR.dom.walker.bookmark(),B=CKEDITOR.dom.walker.bogus();b.evaluator= +function(a){return a.type==(c==CKEDITOR.SHRINK_ELEMENT?CKEDITOR.NODE_ELEMENT:CKEDITOR.NODE_TEXT)};var D;b.guard=function(a,d){if(f&&B(a)||x(a))return!0;if(c==CKEDITOR.SHRINK_ELEMENT&&a.type==CKEDITOR.NODE_TEXT||d&&a.equals(D)||!1===e&&a.type==CKEDITOR.NODE_ELEMENT&&a.isBlockBoundary()||a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable"))return!1;d||a.type!=CKEDITOR.NODE_ELEMENT||(D=a);return!0};d&&(g=b[c==CKEDITOR.SHRINK_ELEMENT?"lastForward":"next"]())&&this.setStartAt(g,a?CKEDITOR.POSITION_AFTER_START: +CKEDITOR.POSITION_BEFORE_START);m&&(b.reset(),(b=b[c==CKEDITOR.SHRINK_ELEMENT?"lastBackward":"previous"]())&&this.setEndAt(b,a?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_END));return!(!d&&!m)}},insertNode:function(c){this.optimizeBookmark();this.trim(!1,!0);var a=this.startContainer,d=a.getChild(this.startOffset);d?c.insertBefore(d):a.append(c);c.getParent()&&c.getParent().equals(this.endContainer)&&this.endOffset++;this.setStartBefore(c)},moveToPosition:function(c,a){this.setStartAt(c, +a);this.collapse(!0)},moveToRange:function(c){this.setStart(c.startContainer,c.startOffset);this.setEnd(c.endContainer,c.endOffset)},selectNodeContents:function(c){this.setStart(c,0);this.setEnd(c,c.type==CKEDITOR.NODE_TEXT?c.getLength():c.getChildCount())},setStart:function(c,a){c.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[c.getName()]&&(a=c.getIndex(),c=c.getParent());this._setStartContainer(c);this.startOffset=a;this.endContainer||(this._setEndContainer(c),this.endOffset=a);b(this)},setEnd:function(c, +a){c.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[c.getName()]&&(a=c.getIndex()+1,c=c.getParent());this._setEndContainer(c);this.endOffset=a;this.startContainer||(this._setStartContainer(c),this.startOffset=a);b(this)},setStartAfter:function(c){this.setStart(c.getParent(),c.getIndex()+1)},setStartBefore:function(c){this.setStart(c.getParent(),c.getIndex())},setEndAfter:function(c){this.setEnd(c.getParent(),c.getIndex()+1)},setEndBefore:function(c){this.setEnd(c.getParent(),c.getIndex())},setStartAt:function(c, +a){switch(a){case CKEDITOR.POSITION_AFTER_START:this.setStart(c,0);break;case CKEDITOR.POSITION_BEFORE_END:c.type==CKEDITOR.NODE_TEXT?this.setStart(c,c.getLength()):this.setStart(c,c.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setStartBefore(c);break;case CKEDITOR.POSITION_AFTER_END:this.setStartAfter(c)}b(this)},setEndAt:function(c,a){switch(a){case CKEDITOR.POSITION_AFTER_START:this.setEnd(c,0);break;case CKEDITOR.POSITION_BEFORE_END:c.type==CKEDITOR.NODE_TEXT?this.setEnd(c, +c.getLength()):this.setEnd(c,c.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setEndBefore(c);break;case CKEDITOR.POSITION_AFTER_END:this.setEndAfter(c)}b(this)},fixBlock:function(c,a){var d=this.createBookmark(),e=this.document.createElement(a);this.collapse(c);this.enlarge(CKEDITOR.ENLARGE_BLOCK_CONTENTS);this.extractContents().appendTo(e);e.trim();this.insertNode(e);var f=e.getBogus();f&&f.remove();e.appendBogus();this.moveToBookmark(d);return e},splitBlock:function(c,a){var d= +new CKEDITOR.dom.elementPath(this.startContainer,this.root),e=new CKEDITOR.dom.elementPath(this.endContainer,this.root),f=d.block,b=e.block,g=null;if(!d.blockLimit.equals(e.blockLimit))return null;"br"!=c&&(f||(f=this.fixBlock(!0,c),b=(new CKEDITOR.dom.elementPath(this.endContainer,this.root)).block),b||(b=this.fixBlock(!1,c)));d=f&&this.checkStartOfBlock();e=b&&this.checkEndOfBlock();this.deleteContents();f&&f.equals(b)&&(e?(g=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(b, +CKEDITOR.POSITION_AFTER_END),b=null):d?(g=new CKEDITOR.dom.elementPath(this.startContainer,this.root),this.moveToPosition(f,CKEDITOR.POSITION_BEFORE_START),f=null):(b=this.splitElement(f,a||!1),f.is("ul","ol")||f.appendBogus()));return{previousBlock:f,nextBlock:b,wasStartOfBlock:d,wasEndOfBlock:e,elementPath:g}},splitElement:function(c,a){if(!this.collapsed)return null;this.setEndAt(c,CKEDITOR.POSITION_BEFORE_END);var d=this.extractContents(!1,a||!1),e=c.clone(!1,a||!1);d.appendTo(e);e.insertAfter(c); +this.moveToPosition(c,CKEDITOR.POSITION_AFTER_END);return e},removeEmptyBlocksAtEnd:function(){function a(c){return function(a){return d(a)||e(a)||a.type==CKEDITOR.NODE_ELEMENT&&a.isEmptyInlineRemoveable()||c.is("table")&&a.is("caption")?!1:!0}}var d=CKEDITOR.dom.walker.whitespaces(),e=CKEDITOR.dom.walker.bookmark(!1);return function(d){for(var e=this.createBookmark(),f=this[d?"endPath":"startPath"](),b=f.block||f.blockLimit,g;b&&!b.equals(f.root)&&!b.getFirst(a(b));)g=b.getParent(),this[d?"setEndAt": +"setStartAt"](b,CKEDITOR.POSITION_AFTER_END),b.remove(1),b=g;this.moveToBookmark(e)}}(),startPath:function(){return new CKEDITOR.dom.elementPath(this.startContainer,this.root)},endPath:function(){return new CKEDITOR.dom.elementPath(this.endContainer,this.root)},checkBoundaryOfElement:function(a,d){var e=d==CKEDITOR.START,b=this.clone();b.collapse(e);b[e?"setStartAt":"setEndAt"](a,e?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END);b=new CKEDITOR.dom.walker(b);b.evaluator=f(e);return b[e? +"checkBackward":"checkForward"]()},checkStartOfBlock:function(){var a=this.startContainer,d=this.startOffset;CKEDITOR.env.ie&&d&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.ltrim(a.substring(0,d)),l.test(a)&&this.trim(0,1));this.trim();a=new CKEDITOR.dom.elementPath(this.startContainer,this.root);d=this.clone();d.collapse(!0);d.setStartAt(a.block||a.blockLimit,CKEDITOR.POSITION_AFTER_START);a=new CKEDITOR.dom.walker(d);a.evaluator=g();return a.checkBackward()},checkEndOfBlock:function(){var a=this.endContainer, +d=this.endOffset;CKEDITOR.env.ie&&a.type==CKEDITOR.NODE_TEXT&&(a=CKEDITOR.tools.rtrim(a.substring(d)),l.test(a)&&this.trim(1,0));this.trim();a=new CKEDITOR.dom.elementPath(this.endContainer,this.root);d=this.clone();d.collapse(!1);d.setEndAt(a.block||a.blockLimit,CKEDITOR.POSITION_BEFORE_END);a=new CKEDITOR.dom.walker(d);a.evaluator=g();return a.checkForward()},getPreviousNode:function(a,d,e){var f=this.clone();f.collapse(1);f.setStartAt(e||this.root,CKEDITOR.POSITION_AFTER_START);e=new CKEDITOR.dom.walker(f); +e.evaluator=a;e.guard=d;return e.previous()},getNextNode:function(a,d,e){var f=this.clone();f.collapse();f.setEndAt(e||this.root,CKEDITOR.POSITION_BEFORE_END);e=new CKEDITOR.dom.walker(f);e.evaluator=a;e.guard=d;return e.next()},checkReadOnly:function(){function a(c,d){for(;c;){if(c.type==CKEDITOR.NODE_ELEMENT){if("false"==c.getAttribute("contentEditable")&&!c.data("cke-editable"))return 0;if(c.is("html")||"true"==c.getAttribute("contentEditable")&&(c.contains(d)||c.equals(d)))break}c=c.getParent()}return 1} +return function(){var d=this.startContainer,e=this.endContainer;return!(a(d,e)&&a(e,d))}}(),moveToElementEditablePosition:function(a,d){if(a.type==CKEDITOR.NODE_ELEMENT&&!a.isEditable(!1))return this.moveToPosition(a,d?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START),!0;for(var f=0;a;){if(a.type==CKEDITOR.NODE_TEXT){d&&this.endContainer&&this.checkEndOfBlock()&&l.test(a.getText())?this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START):this.moveToPosition(a,d?CKEDITOR.POSITION_AFTER_END: +CKEDITOR.POSITION_BEFORE_START);f=1;break}if(a.type==CKEDITOR.NODE_ELEMENT)if(a.isEditable())this.moveToPosition(a,d?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_START),f=1;else if(d&&a.is("br")&&this.endContainer&&this.checkEndOfBlock())this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START);else if("false"==a.getAttribute("contenteditable")&&a.is(CKEDITOR.dtd.$block))return this.setStartBefore(a),this.setEndAfter(a),!0;var b=a,g=f,h=void 0;b.type==CKEDITOR.NODE_ELEMENT&&b.isEditable(!1)&& +(h=b[d?"getLast":"getFirst"](e));g||h||(h=b[d?"getPrevious":"getNext"](e));a=h}return!!f},moveToClosestEditablePosition:function(a,d){var e,f=0,b,g,h=[CKEDITOR.POSITION_AFTER_END,CKEDITOR.POSITION_BEFORE_START];a?(e=new CKEDITOR.dom.range(this.root),e.moveToPosition(a,h[d?0:1])):e=this.clone();if(a&&!a.is(CKEDITOR.dtd.$block))f=1;else if(b=e[d?"getNextEditableNode":"getPreviousEditableNode"]())f=1,(g=b.type==CKEDITOR.NODE_ELEMENT)&&b.is(CKEDITOR.dtd.$block)&&"false"==b.getAttribute("contenteditable")? +(e.setStartAt(b,CKEDITOR.POSITION_BEFORE_START),e.setEndAt(b,CKEDITOR.POSITION_AFTER_END)):!CKEDITOR.env.needsBrFiller&&g&&b.is(CKEDITOR.dom.walker.validEmptyBlockContainers)?(e.setEnd(b,0),e.collapse()):e.moveToPosition(b,h[d?1:0]);f&&this.moveToRange(e);return!!f},moveToElementEditStart:function(a){return this.moveToElementEditablePosition(a)},moveToElementEditEnd:function(a){return this.moveToElementEditablePosition(a,!0)},getEnclosedNode:function(){var a=this.clone();a.optimize();if(a.startContainer.type!= +CKEDITOR.NODE_ELEMENT||a.endContainer.type!=CKEDITOR.NODE_ELEMENT)return null;var a=new CKEDITOR.dom.walker(a),d=CKEDITOR.dom.walker.bookmark(!1,!0),e=CKEDITOR.dom.walker.whitespaces(!0);a.evaluator=function(a){return e(a)&&d(a)};var f=a.next();a.reset();return f&&f.equals(a.previous())?f:null},getTouchedStartNode:function(){var a=this.startContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.startOffset)||a},getTouchedEndNode:function(){var a=this.endContainer;return this.collapsed|| +a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.endOffset-1)||a},getNextEditableNode:a(),getPreviousEditableNode:a(1),_getTableElement:function(a){a=a||{td:1,th:1,tr:1,tbody:1,thead:1,tfoot:1,table:1};var d=this.getTouchedStartNode(),e=this.getTouchedEndNode(),f=d.getAscendant("table",!0),e=e.getAscendant("table",!0);return f&&!this.root.contains(f)?null:this.getEnclosedNode()?this.getEnclosedNode().getAscendant(a,!0):f&&e&&(f.equals(e)||f.contains(e)||e.contains(f))?d.getAscendant(a,!0):null},scrollIntoView:function(){var a= +new CKEDITOR.dom.element.createFromHtml("\x3cspan\x3e\x26nbsp;\x3c/span\x3e",this.document),d,e,f,b=this.clone();b.optimize();(f=b.startContainer.type==CKEDITOR.NODE_TEXT)?(e=b.startContainer.getText(),d=b.startContainer.split(b.startOffset),a.insertAfter(b.startContainer)):b.insertNode(a);a.scrollIntoView();f&&(b.startContainer.setText(e),d.remove());a.remove()},getClientRects:function(){function a(c,d){var e=CKEDITOR.tools.array.map(c,function(a){return a}),f=new CKEDITOR.dom.range(d.root),b,g, +h;d.startContainer instanceof CKEDITOR.dom.element&&(g=0===d.startOffset&&d.startContainer.hasAttribute("data-widget"));d.endContainer instanceof CKEDITOR.dom.element&&(h=(h=d.endOffset===(d.endContainer.getChildCount?d.endContainer.getChildCount():d.endContainer.length))&&d.endContainer.hasAttribute("data-widget"));g&&f.setStart(d.startContainer.getParent(),d.startContainer.getIndex());h&&f.setEnd(d.endContainer.getParent(),d.endContainer.getIndex()+1);if(g||h)d=f;f=d.cloneContents().find("[data-cke-widget-id]").toArray(); +if(f=CKEDITOR.tools.array.map(f,function(a){var c=d.root.editor;a=a.getAttribute("data-cke-widget-id");return c.widgets.instances[a].element}))return f=CKEDITOR.tools.array.map(f,function(a){var c;c=a.getParent().hasClass("cke_widget_wrapper")?a.getParent():a;b=this.root.getDocument().$.createRange();b.setStart(c.getParent().$,c.getIndex());b.setEnd(c.getParent().$,c.getIndex()+1);c=b.getClientRects();c.widgetRect=a.getClientRect();return c},d),CKEDITOR.tools.array.forEach(f,function(a){function c(f){CKEDITOR.tools.array.forEach(e, +function(c,b){var g=CKEDITOR.tools.objectCompare(a[f],c);g||(g=CKEDITOR.tools.objectCompare(a.widgetRect,c));g&&(Array.prototype.splice.call(e,b,a.length-f,a.widgetRect),d=!0)});d||(farguments.length||(this.range=a,this.forceBrBreak=0,this.enlargeBr=1,this.enforceRealBlocks=0,this._||(this._={}))}function h(a){var c=[];a.forEach(function(a){if("true"==a.getAttribute("contenteditable"))return c.push(a),!1},CKEDITOR.NODE_ELEMENT,!0);return c}function g(a,c,d,f){a:{null== +f&&(f=h(d));for(var b;b=f.shift();)if(b.getDtd().p){f={element:b,remaining:f};break a}f=null}if(!f)return 0;if((b=CKEDITOR.filter.instances[f.element.data("cke-filter")])&&!b.check(c))return g(a,c,d,f.remaining);c=new CKEDITOR.dom.range(f.element);c.selectNodeContents(f.element);c=c.createIterator();c.enlargeBr=a.enlargeBr;c.enforceRealBlocks=a.enforceRealBlocks;c.activeFilter=c.filter=b;a._.nestedEditable={element:f.element,container:d,remaining:f.remaining,iterator:c};return 1}function f(a,c,d){if(!c)return!1; +a=a.clone();a.collapse(!d);return a.checkBoundaryOfElement(c,d?CKEDITOR.START:CKEDITOR.END)}var a=/^[\r\n\t ]+$/,m=CKEDITOR.dom.walker.bookmark(!1,!0),k=CKEDITOR.dom.walker.whitespaces(!0),l=function(a){return m(a)&&k(a)},d={dd:1,dt:1,li:1};b.prototype={getNextParagraph:function(e){var c,b,h,k,r;e=e||"p";if(this._.nestedEditable){if(c=this._.nestedEditable.iterator.getNextParagraph(e))return this.activeFilter=this._.nestedEditable.iterator.activeFilter,c;this.activeFilter=this.filter;if(g(this,e, +this._.nestedEditable.container,this._.nestedEditable.remaining))return this.activeFilter=this._.nestedEditable.iterator.activeFilter,this._.nestedEditable.iterator.getNextParagraph(e);this._.nestedEditable=null}if(!this.range.root.getDtd()[e])return null;if(!this._.started){var p=this.range.clone();b=p.startPath();var q=p.endPath(),u=!p.collapsed&&f(p,b.block),t=!p.collapsed&&f(p,q.block,1);p.shrink(CKEDITOR.SHRINK_ELEMENT,!0);u&&p.setStartAt(b.block,CKEDITOR.POSITION_BEFORE_END);t&&p.setEndAt(q.block, +CKEDITOR.POSITION_AFTER_START);b=p.endContainer.hasAscendant("pre",!0)||p.startContainer.hasAscendant("pre",!0);p.enlarge(this.forceBrBreak&&!b||!this.enlargeBr?CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:CKEDITOR.ENLARGE_BLOCK_CONTENTS);p.collapsed||(b=new CKEDITOR.dom.walker(p.clone()),q=CKEDITOR.dom.walker.bookmark(!0,!0),b.evaluator=q,this._.nextNode=b.next(),b=new CKEDITOR.dom.walker(p.clone()),b.evaluator=q,b=b.previous(),this._.lastNode=b.getNextSourceNode(!0,null,p.root),this._.lastNode&&this._.lastNode.type== +CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.trim(this._.lastNode.getText())&&this._.lastNode.getParent().isBlockBoundary()&&(q=this.range.clone(),q.moveToPosition(this._.lastNode,CKEDITOR.POSITION_AFTER_END),q.checkEndOfBlock()&&(q=new CKEDITOR.dom.elementPath(q.endContainer,q.root),this._.lastNode=(q.block||q.blockLimit).getNextSourceNode(!0))),this._.lastNode&&p.root.contains(this._.lastNode)||(this._.lastNode=this._.docEndMarker=p.document.createText(""),this._.lastNode.insertAfter(b)),p=null);this._.started= +1;b=p}q=this._.nextNode;p=this._.lastNode;for(this._.nextNode=null;q;){var u=0,t=q.hasAscendant("pre"),y=q.type!=CKEDITOR.NODE_ELEMENT,v=0;if(y)q.type==CKEDITOR.NODE_TEXT&&a.test(q.getText())&&(y=0);else{var x=q.getName();if(CKEDITOR.dtd.$block[x]&&"false"==q.getAttribute("contenteditable")){c=q;g(this,e,c);break}else if(q.isBlockBoundary(this.forceBrBreak&&!t&&{br:1})){if("br"==x)y=1;else if(!b&&!q.getChildCount()&&"hr"!=x){c=q;h=q.equals(p);break}b&&(b.setEndAt(q,CKEDITOR.POSITION_BEFORE_START), +"br"!=x&&(this._.nextNode=q));u=1}else{if(q.getFirst()){b||(b=this.range.clone(),b.setStartAt(q,CKEDITOR.POSITION_BEFORE_START));q=q.getFirst();continue}y=1}}y&&!b&&(b=this.range.clone(),b.setStartAt(q,CKEDITOR.POSITION_BEFORE_START));h=(!u||y)&&q.equals(p);if(b&&!u)for(;!q.getNext(l)&&!h;){x=q.getParent();if(x.isBlockBoundary(this.forceBrBreak&&!t&&{br:1})){u=1;y=0;h||x.equals(p);b.setEndAt(x,CKEDITOR.POSITION_BEFORE_END);break}q=x;y=1;h=q.equals(p);v=1}y&&b.setEndAt(q,CKEDITOR.POSITION_AFTER_END); +q=this._getNextSourceNode(q,v,p);if((h=!q)||u&&b)break}if(!c){if(!b)return this._.docEndMarker&&this._.docEndMarker.remove(),this._.nextNode=null;c=new CKEDITOR.dom.elementPath(b.startContainer,b.root);q=c.blockLimit;u={div:1,th:1,td:1};c=c.block;!c&&q&&!this.enforceRealBlocks&&u[q.getName()]&&b.checkStartOfBlock()&&b.checkEndOfBlock()&&!q.equals(b.root)?c=q:!c||this.enforceRealBlocks&&c.is(d)?(c=this.range.document.createElement(e),b.extractContents().appendTo(c),c.trim(),b.insertNode(c),k=r=!0): +"li"!=c.getName()?b.checkStartOfBlock()&&b.checkEndOfBlock()||(c=c.clone(!1),b.extractContents().appendTo(c),c.trim(),r=b.splitBlock(),k=!r.wasStartOfBlock,r=!r.wasEndOfBlock,b.insertNode(c)):h||(this._.nextNode=c.equals(p)?null:this._getNextSourceNode(b.getBoundaryNodes().endNode,1,p))}k&&(k=c.getPrevious())&&k.type==CKEDITOR.NODE_ELEMENT&&("br"==k.getName()?k.remove():k.getLast()&&"br"==k.getLast().$.nodeName.toLowerCase()&&k.getLast().remove());r&&(k=c.getLast())&&k.type==CKEDITOR.NODE_ELEMENT&& +"br"==k.getName()&&(!CKEDITOR.env.needsBrFiller||k.getPrevious(m)||k.getNext(m))&&k.remove();this._.nextNode||(this._.nextNode=h||c.equals(p)||!p?null:this._getNextSourceNode(c,1,p));return c},_getNextSourceNode:function(a,c,d){function f(a){return!(a.equals(d)||a.equals(b))}var b=this.range.root;for(a=a.getNextSourceNode(c,null,f);!m(a);)a=a.getNextSourceNode(c,null,f);return a}};CKEDITOR.dom.range.prototype.createIterator=function(){return new b(this)}})();CKEDITOR.command=function(b,h){this.uiItems= +[];this.exec=function(f){if(this.state==CKEDITOR.TRISTATE_DISABLED||!this.checkAllowed())return!1;this.editorFocus&&b.focus();return!1===this.fire("exec")?!0:!1!==h.exec.call(this,b,f)};this.refresh=function(f,a){if(!this.readOnly&&f.readOnly)return!0;if(this.context&&!a.isContextFor(this.context)||!this.checkAllowed(!0))return this.disable(),!0;this.startDisabled||this.enable();this.modes&&!this.modes[f.mode]&&this.disable();return!1===this.fire("refresh",{editor:f,path:a})?!0:h.refresh&&!1!==h.refresh.apply(this, +arguments)};var g;this.checkAllowed=function(f){return f||"boolean"!=typeof g?g=b.activeFilter.checkFeature(this):g};CKEDITOR.tools.extend(this,h,{modes:{wysiwyg:1},editorFocus:1,contextSensitive:!!h.context,state:CKEDITOR.TRISTATE_DISABLED});CKEDITOR.event.call(this)};CKEDITOR.command.prototype={enable:function(){this.state==CKEDITOR.TRISTATE_DISABLED&&this.checkAllowed()&&this.setState(this.preserveState&&"undefined"!=typeof this.previousState?this.previousState:CKEDITOR.TRISTATE_OFF)},disable:function(){this.setState(CKEDITOR.TRISTATE_DISABLED)}, +setState:function(b){if(this.state==b||b!=CKEDITOR.TRISTATE_DISABLED&&!this.checkAllowed())return!1;this.previousState=this.state;this.state=b;this.fire("state");return!0},toggleState:function(){this.state==CKEDITOR.TRISTATE_OFF?this.setState(CKEDITOR.TRISTATE_ON):this.state==CKEDITOR.TRISTATE_ON&&this.setState(CKEDITOR.TRISTATE_OFF)}};CKEDITOR.event.implementOn(CKEDITOR.command.prototype);CKEDITOR.ENTER_P=1;CKEDITOR.ENTER_BR=2;CKEDITOR.ENTER_DIV=3;CKEDITOR.config={customConfig:"config.js",autoUpdateElement:!0, +language:"",defaultLanguage:"en",contentsLangDirection:"",enterMode:CKEDITOR.ENTER_P,forceEnterMode:!1,shiftEnterMode:CKEDITOR.ENTER_BR,docType:"\x3c!DOCTYPE html\x3e",bodyId:"",bodyClass:"",fullPage:!1,height:200,contentsCss:CKEDITOR.getUrl("contents.css"),extraPlugins:"",removePlugins:"",protectedSource:[],tabIndex:0,width:"",baseFloatZIndex:1E4,blockedKeystrokes:[CKEDITOR.CTRL+66,CKEDITOR.CTRL+73,CKEDITOR.CTRL+85]};(function(){function b(a,c,d,e,f){var b,g;a=[];for(b in c){g=c[b];g="boolean"== +typeof g?{}:"function"==typeof g?{match:g}:I(g);"$"!=b.charAt(0)&&(g.elements=b);d&&(g.featureName=d.toLowerCase());var h=g;h.elements=k(h.elements,/\s+/)||null;h.propertiesOnly=h.propertiesOnly||!0===h.elements;var n=/\s*,\s*/,l=void 0;for(l in M){h[l]=k(h[l],n)||null;var q=h,m=H[l],E=k(h[H[l]],n),x=h[l],B=[],w=!0,t=void 0;E?w=!1:E={};for(t in x)"!"==t.charAt(0)&&(t=t.slice(1),B.push(t),E[t]=!0,w=!1);for(;t=B.pop();)x[t]=x["!"+t],delete x["!"+t];q[m]=(w?!1:E)||null}h.match=h.match||null;e.push(g); +a.push(g)}c=f.elements;f=f.generic;var p;d=0;for(e=a.length;d=--l&&(m&&CKEDITOR.document.getDocumentElement().removeStyle("cursor"), +c(f))},w=function(a,c){b[a]=1;var d=h[a];delete h[a];for(var e=0;e=CKEDITOR.env.version||CKEDITOR.env.ie9Compat)?d.$.onreadystatechange=function(){if("loaded"==d.$.readyState||"complete"==d.$.readyState)d.$.onreadystatechange=null,w(a,!0)}:(d.$.onload=function(){setTimeout(function(){d.$.onload= +null;d.$.onerror=null;w(a,!0)},0)},d.$.onerror=function(){d.$.onload=null;d.$.onerror=null;w(a,!1)}));d.appendTo(CKEDITOR.document.getHead())}}};m&&CKEDITOR.document.getDocumentElement().setStyle("cursor","wait");for(var r=0;r]+)>)|(?:!--([\S|\s]*?)--\x3e)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/g}};(function(){var b=/([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g,h={checked:1,compact:1,declare:1,defer:1,disabled:1, +ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};CKEDITOR.htmlParser.prototype={onTagOpen:function(){},onTagClose:function(){},onText:function(){},onCDATA:function(){},onComment:function(){},parse:function(g){for(var f,a,m=0,k;f=this._.htmlPartsRegex.exec(g);){a=f.index;if(a>m)if(m=g.substring(m,a),k)k.push(m);else this.onText(m);m=this._.htmlPartsRegex.lastIndex;if(a=f[1])if(a=a.toLowerCase(),k&&CKEDITOR.dtd.$cdata[a]&&(this.onCDATA(k.join("")),k=null),!k){this.onTagClose(a); +continue}if(k)k.push(f[0]);else if(a=f[3]){if(a=a.toLowerCase(),!/="/.test(a)){var l={},d,e=f[4];f=!!f[5];if(e)for(;d=b.exec(e);){var c=d[1].toLowerCase();d=d[2]||d[3]||d[4]||"";l[c]=!d&&h[c]?c:CKEDITOR.tools.htmlDecodeAttr(d)}this.onTagOpen(a,l,f);!k&&CKEDITOR.dtd.$cdata[a]&&(k=[])}}else if(a=f[2])this.onComment(a)}if(g.length>m)this.onText(g.substring(m,g.length))}}})();CKEDITOR.htmlParser.basicWriter=CKEDITOR.tools.createClass({$:function(){this._={output:[]}},proto:{openTag:function(b){this._.output.push("\x3c", +b)},openTagClose:function(b,h){h?this._.output.push(" /\x3e"):this._.output.push("\x3e")},attribute:function(b,h){"string"==typeof h&&(h=CKEDITOR.tools.htmlEncodeAttr(h));this._.output.push(" ",b,'\x3d"',h,'"')},closeTag:function(b){this._.output.push("\x3c/",b,"\x3e")},text:function(b){this._.output.push(b)},comment:function(b){this._.output.push("\x3c!--",b,"--\x3e")},write:function(b){this._.output.push(b)},reset:function(){this._.output=[];this._.indent=!1},getHtml:function(b){var h=this._.output.join(""); +b&&this.reset();return h}}});"use strict";(function(){CKEDITOR.htmlParser.node=function(){};CKEDITOR.htmlParser.node.prototype={remove:function(){var b=this.parent.children,h=CKEDITOR.tools.indexOf(b,this),g=this.previous,f=this.next;g&&(g.next=f);f&&(f.previous=g);b.splice(h,1);this.parent=null},replaceWith:function(b){var h=this.parent.children,g=CKEDITOR.tools.indexOf(h,this),f=b.previous=this.previous,a=b.next=this.next;f&&(f.next=b);a&&(a.previous=b);h[g]=b;b.parent=this.parent;this.parent=null}, +insertAfter:function(b){var h=b.parent.children,g=CKEDITOR.tools.indexOf(h,b),f=b.next;h.splice(g+1,0,this);this.next=b.next;this.previous=b;b.next=this;f&&(f.previous=this);this.parent=b.parent},insertBefore:function(b){var h=b.parent.children,g=CKEDITOR.tools.indexOf(h,b);h.splice(g,0,this);this.next=b;(this.previous=b.previous)&&(b.previous.next=this);b.previous=this;this.parent=b.parent},getAscendant:function(b){var h="function"==typeof b?b:"string"==typeof b?function(f){return f.name==b}:function(f){return f.name in +b},g=this.parent;for(;g&&g.type==CKEDITOR.NODE_ELEMENT;){if(h(g))return g;g=g.parent}return null},wrapWith:function(b){this.replaceWith(b);b.add(this);return b},getIndex:function(){return CKEDITOR.tools.indexOf(this.parent.children,this)},getFilterContext:function(b){return b||{}}}})();"use strict";CKEDITOR.htmlParser.comment=function(b){this.value=b;this._={isBlockLike:!1}};CKEDITOR.htmlParser.comment.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_COMMENT,filter:function(b, +h){var g=this.value;if(!(g=b.onComment(h,g,this)))return this.remove(),!1;if("string"!=typeof g)return this.replaceWith(g),!1;this.value=g;return!0},writeHtml:function(b,h){h&&this.filter(h);b.comment(this.value)}});"use strict";(function(){CKEDITOR.htmlParser.text=function(b){this.value=b;this._={isBlockLike:!1}};CKEDITOR.htmlParser.text.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(b,h){if(!(this.value=b.onText(h,this.value,this)))return this.remove(), +!1},writeHtml:function(b,h){h&&this.filter(h);b.text(this.value)}})})();"use strict";(function(){CKEDITOR.htmlParser.cdata=function(b){this.value=b};CKEDITOR.htmlParser.cdata.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(){},writeHtml:function(b){b.write(this.value)}})})();"use strict";CKEDITOR.htmlParser.fragment=function(){this.children=[];this.parent=null;this._={isBlockLike:!0,hasInlineStarted:!1}};(function(){function b(a){return a.attributes["data-cke-survive"]? +!1:"a"==a.name&&a.attributes.href||CKEDITOR.dtd.$removeEmpty[a.name]}var h=CKEDITOR.tools.extend({table:1,ul:1,ol:1,dl:1},CKEDITOR.dtd.table,CKEDITOR.dtd.ul,CKEDITOR.dtd.ol,CKEDITOR.dtd.dl),g={ol:1,ul:1},f=CKEDITOR.tools.extend({},{html:1},CKEDITOR.dtd.html,CKEDITOR.dtd.body,CKEDITOR.dtd.head,{style:1,script:1}),a={ul:"li",ol:"li",dl:"dd",table:"tbody",tbody:"tr",thead:"tr",tfoot:"tr",tr:"td"};CKEDITOR.htmlParser.fragment.fromHtml=function(m,k,l){function d(a){var c;if(0k;k++)if(h=a[k]){h=h.exec(b,f,this);if(!1===h)return null;if(h&&h!=f)return this.onNode(b,h);if(f.parent&&!f.name)break}return f},onNode:function(b,f){var a=f.type;return a==CKEDITOR.NODE_ELEMENT?this.onElement(b,f):a==CKEDITOR.NODE_TEXT? +new CKEDITOR.htmlParser.text(this.onText(b,f.value)):a==CKEDITOR.NODE_COMMENT?new CKEDITOR.htmlParser.comment(this.onComment(b,f.value)):null},onAttribute:function(b,f,a,h){return(a=this.attributesRules[a])?a.exec(b,h,f,this):h}}});CKEDITOR.htmlParser.filterRulesGroup=b;b.prototype={add:function(b,f,a){this.rules.splice(this.findIndex(f),0,{value:b,priority:f,options:a})},addMany:function(b,f,a){for(var h=[this.findIndex(f),0],k=0,l=b.length;k/g,"\x26gt;")+"\x3c/textarea\x3e");return"\x3ccke:encoded\x3e"+encodeURIComponent(a)+"\x3c/cke:encoded\x3e"})}function n(a){return a.replace(E,function(a,c){return decodeURIComponent(c)})}function w(a){return a.replace(/\x3c!--(?!{cke_protected})[\s\S]+?--\x3e/g,function(a){return"\x3c!--"+y+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\x3e"})}function z(a){return CKEDITOR.tools.array.reduce(a.split(""),function(a,c){var d=c.toLowerCase(),e=c.toUpperCase(), +b=r(d);d!==e&&(b+="|"+r(e));return a+("("+b+")")},"")}function r(a){var c;c=a.charCodeAt(0);var d=c.toString(16);c={htmlCode:"\x26#"+c+";?",hex:"\x26#x0*"+d+";?",entity:{"\x3c":"\x26lt;","\x3e":"\x26gt;",":":"\x26colon;"}[a]};for(var e in c)c[e]&&(a+="|"+c[e]);return a}function p(a){return a.replace(/\x3c!--\{cke_protected\}\{C\}([\s\S]+?)--\x3e/g,function(a,c){return decodeURIComponent(c)})}function q(a,c){var d=c._.dataStore;return a.replace(/\x3c!--\{cke_protected\}([\s\S]+?)--\x3e/g,function(a, +c){return decodeURIComponent(c)}).replace(/\{cke_protected_(\d+)\}/g,function(a,c){return d&&d[c]||""})}function u(a,c){var d=[],e=c.config.protectedSource,b=c._.dataStore||(c._.dataStore={id:1}),f=/<\!--\{cke_temp(comment)?\}(\d*?)--\x3e/g,e=[/|$)/gi,//gi,//gi].concat(e);a=a.replace(/\x3c!--[\s\S]*?--\x3e/g,function(a){return"\x3c!--{cke_tempcomment}"+(d.push(a)-1)+"--\x3e"});for(var g=0;g]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g,function(a){return a.replace(/\x3c!--\{cke_protected\}([^>]*)--\x3e/g,function(a,c){b[b.id]=decodeURIComponent(c);return"{cke_protected_"+b.id++ +"}"})});return a= +a.replace(/<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g,function(a,d,e,b){return"\x3c"+d+e+"\x3e"+q(p(b),c)+"\x3c/"+d+"\x3e"})}CKEDITOR.htmlDataProcessor=function(a){var d,f,g=this;this.editor=a;this.dataFilter=d=new CKEDITOR.htmlParser.filter;this.htmlFilter=f=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;d.addRules(D);d.addRules(A,{applyToAll:!0});d.addRules(b(a,"data"),{applyToAll:!0});f.addRules(G);f.addRules(C,{applyToAll:!0});f.addRules(b(a,"html"),{applyToAll:!0}); +a.on("toHtml",function(d){d=d.data;var b=d.dataValue,f,b=b.replace(Q,""),b=u(b,a),b=c(b,H),b=e(b),b=c(b,M),b=b.replace(O,"$1cke:$2"),b=b.replace(K,"\x3ccke:$1$2\x3e\x3c/cke:$1\x3e"),b=b.replace(/(]*>)(\r\n|\n)/g,"$1$2$2"),b=b.replace(/([^a-z0-9<\-])(on\w{3,})(?!>)/gi,"$1data-cke-"+CKEDITOR.rnd+"-$2");f=d.context||a.editable().getName();var g;CKEDITOR.env.ie&&9>CKEDITOR.env.version&&"pre"==f&&(f="div",b="\x3cpre\x3e"+b+"\x3c/pre\x3e",g=1);f=a.document.createElement(f);f.setHtml("a"+b);b=f.getHtml().substr(1); +b=b.replace(new RegExp("data-cke-"+CKEDITOR.rnd+"-","ig"),"");g&&(b=b.replace(/^
|<\/pre>$/gi,""));b=b.replace(P,"$1$2");b=n(b);b=p(b);f=!1===d.fixForBody?!1:h(d.enterMode,a.config.autoParagraph);b=CKEDITOR.htmlParser.fragment.fromHtml(b,d.context,f);f&&(g=b,!g.children.length&&CKEDITOR.dtd[g.name][f]&&(f=new CKEDITOR.htmlParser.element(f),g.add(f)));d.dataValue=b},null,null,5);a.on("toHtml",function(c){c.data.filter.applyTo(c.data.dataValue,!0,c.data.dontFilter,c.data.enterMode)&&a.fire("dataFiltered")},
+null,null,6);a.on("toHtml",function(a){a.data.dataValue.filterChildren(g.dataFilter,!0)},null,null,10);a.on("toHtml",function(a){a=a.data;var c=a.dataValue,d=new CKEDITOR.htmlParser.basicWriter;c.writeChildrenHtml(d);c=d.getHtml(!0);a.dataValue=w(c)},null,null,15);a.on("toDataFormat",function(c){var d=c.data.dataValue;c.data.enterMode!=CKEDITOR.ENTER_BR&&(d=d.replace(/^
/i,""));c.data.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(d,c.data.context,h(c.data.enterMode,a.config.autoParagraph))}, +null,null,5);a.on("toDataFormat",function(a){a.data.dataValue.filterChildren(g.htmlFilter,!0)},null,null,10);a.on("toDataFormat",function(a){a.data.filter.applyTo(a.data.dataValue,!1,!0)},null,null,11);a.on("toDataFormat",function(c){var d=c.data.dataValue,e=g.writer;e.reset();d.writeChildrenHtml(e);d=e.getHtml(!0);d=p(d);d=q(d,a);c.data.dataValue=d},null,null,15)};CKEDITOR.htmlDataProcessor.prototype={toHtml:function(a,c,d,e){var b=this.editor,f,g,h,k;c&&"object"==typeof c?(f=c.context,d=c.fixForBody, +e=c.dontFilter,g=c.filter,h=c.enterMode,k=c.protectedWhitespaces):f=c;f||null===f||(f=b.editable().getName());return b.fire("toHtml",{dataValue:a,context:f,fixForBody:d,dontFilter:e,filter:g||b.filter,enterMode:h||b.enterMode,protectedWhitespaces:k}).dataValue},toDataFormat:function(a,c){var d,e,b;c&&(d=c.context,e=c.filter,b=c.enterMode);d||null===d||(d=this.editor.editable().getName());return this.editor.fire("toDataFormat",{dataValue:a,filter:e||this.editor.filter,context:d,enterMode:b||this.editor.enterMode}).dataValue}}; +var t=/(?: |\xa0)$/,y="{cke_protected}",v=CKEDITOR.dtd,x="caption colgroup col thead tfoot tbody".split(" "),B=CKEDITOR.tools.extend({},v.$blockLimit,v.$block),D={elements:{input:l,textarea:l}},A={attributeNames:[[/^on/,"data-cke-pa-on"],[/^srcdoc/,"data-cke-pa-srcdoc"],[/^data-cke-expando$/,""]],elements:{iframe:function(a){if(a.attributes&&a.attributes.src){var c=a.attributes.src.toLowerCase().replace(/[^a-z]/gi,"");if(0===c.indexOf("javascript")||0===c.indexOf("data"))a.attributes["data-cke-pa-src"]= +a.attributes.src,delete a.attributes.src}}}},G={elements:{embed:function(a){var c=a.parent;if(c&&"object"==c.name){var d=c.attributes.width,c=c.attributes.height;d&&(a.attributes.width=d);c&&(a.attributes.height=c)}},a:function(a){var c=a.attributes;if(!(a.children.length||c.name||c.id||a.attributes["data-cke-saved-name"]))return!1}}},C={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]],attributeNames:[[/^data-cke-(saved|pa)-/,""],[/^data-cke-.*/,""],["hidefocus",""]],elements:{$:function(a){var c= +a.attributes;if(c){if(c["data-cke-temp"])return!1;for(var d=["name","href","src"],e,b=0;be?1:-1})},param:function(a){a.children=[];a.isEmpty=!0;return a},span:function(a){"Apple-style-span"== +a.attributes["class"]&&delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable},style:function(a){var c=a.children[0];c&&c.value&&(c.value=CKEDITOR.tools.trim(c.value));a.attributes.type||(a.attributes.type="text/css")},title:function(a){var c=a.children[0];!c&&k(a,c=new CKEDITOR.htmlParser.text);c.value=a.attributes["data-cke-title"]||""},input:d,textarea:d},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g, +""))||!1}}};CKEDITOR.env.ie&&(C.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})});var I=/<(a|area|img|input|source)\b([^>]*)>/gi,J=/([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,F=/^(href|src|name)$/i,M=/(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,H=/(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,E=/([^<]*)<\/cke:encoded>/gi,Q=new RegExp("("+z("\x3ccke:encoded\x3e")+"(.*?)"+z("\x3c/cke:encoded\x3e")+ +")|("+z("\x3c")+z("/")+"?"+z("cke:encoded\x3e")+")","gi"),O=/(<\/?)((?:object|embed|param|html|body|head|title)([\s][^>]*)?>)/gi,P=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,K=/]*?)\/?>(?!\s*<\/cke:\1)/gi})();"use strict";CKEDITOR.htmlParser.element=function(b,h){this.name=b;this.attributes=h||{};this.children=[];var g=b||"",f=g.match(/^cke:(.*)/);f&&(g=f[1]);g=!!(CKEDITOR.dtd.$nonBodyContent[g]||CKEDITOR.dtd.$block[g]||CKEDITOR.dtd.$listItem[g]||CKEDITOR.dtd.$tableContent[g]|| +CKEDITOR.dtd.$nonEditable[g]||"br"==g);this.isEmpty=!!CKEDITOR.dtd.$empty[b];this.isUnknown=!CKEDITOR.dtd[b];this._={isBlockLike:g,hasInlineStarted:this.isEmpty||!g}};CKEDITOR.htmlParser.cssStyle=function(b){var h={};((b instanceof CKEDITOR.htmlParser.element?b.attributes.style:b)||"").replace(/"/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(b,f,a){"font-family"==f&&(a=a.replace(/["']/g,""));h[f.toLowerCase()]=a});return{rules:h,populate:function(b){var f=this.toString();f&& +(b instanceof CKEDITOR.dom.element?b.setAttribute("style",f):b instanceof CKEDITOR.htmlParser.element?b.attributes.style=f:b.style=f)},toString:function(){var b=[],f;for(f in h)h[f]&&b.push(f,":",h[f],";");return b.join("")}}};(function(){function b(b){return function(a){return a.type==CKEDITOR.NODE_ELEMENT&&("string"==typeof b?a.name==b:a.name in b)}}var h=function(b,a){b=b[0];a=a[0];return ba?1:0},g=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node, +{type:CKEDITOR.NODE_ELEMENT,add:g.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(b,a){var g=this,h,l;a=g.getFilterContext(a);if(!g.parent)b.onRoot(a,g);for(;;){h=g.name;if(!(l=b.onElementName(a,h)))return this.remove(),!1;g.name=l;if(!(g=b.onElement(a,g)))return this.remove(),!1;if(g!==this)return this.replaceWith(g),!1;if(g.name==h)break;if(g.type!=CKEDITOR.NODE_ELEMENT)return this.replaceWith(g),!1;if(!g.name)return this.replaceWithChildren(), +!1}h=g.attributes;var d,e;for(d in h){for(l=h[d];;)if(e=b.onAttributeName(a,d))if(e!=d)delete h[d],d=e;else break;else{delete h[d];break}e&&(!1===(l=b.onAttribute(a,g,e,l))?delete h[e]:h[e]=l)}g.isEmpty||this.filterChildren(b,!1,a);return!0},filterChildren:g.filterChildren,writeHtml:function(b,a){a&&this.filter(a);var g=this.name,k=[],l=this.attributes,d,e;b.openTag(g,l);for(d in l)k.push([d,l[d]]);b.sortAttributes&&k.sort(h);d=0;for(e=k.length;dCKEDITOR.env.version||CKEDITOR.env.quirks))this.hasFocus&&(this.focus(),c());else if(this.hasFocus)this.focus(),a();else this.once("focus",function(){a()},null,null,-999)},getHtmlFromRange:function(a){if(a.collapsed)return new CKEDITOR.dom.documentFragment(a.document);a={doc:this.getDocument(),range:a.clone()};y.eol.detect(a,this);y.bogus.exclude(a);y.cell.shrink(a);a.fragment=a.range.cloneContents(); +y.tree.rebuild(a,this);y.eol.fix(a,this);return new CKEDITOR.dom.documentFragment(a.fragment.$)},extractHtmlFromRange:function(a,c){var d=v,b={range:a,doc:a.document},e=this.getHtmlFromRange(a);if(a.collapsed)return a.optimize(),e;a.enlarge(CKEDITOR.ENLARGE_INLINE,1);d.table.detectPurge(b);b.bookmark=a.createBookmark();delete b.range;var f=this.editor.createRange();f.moveToPosition(b.bookmark.startNode,CKEDITOR.POSITION_BEFORE_START);b.targetBookmark=f.createBookmark();d.list.detectMerge(b,this); +d.table.detectRanges(b,this);d.block.detectMerge(b,this);b.tableContentsRanges?(d.table.deleteRanges(b),a.moveToBookmark(b.bookmark),b.range=a):(a.moveToBookmark(b.bookmark),b.range=a,a.extractContents(d.detectExtractMerge(b)));a.moveToBookmark(b.targetBookmark);a.optimize();d.fixUneditableRangePosition(a);d.list.merge(b,this);d.table.purge(b,this);d.block.merge(b,this);if(c){d=a.startPath();if(b=a.checkStartOfBlock()&&a.checkEndOfBlock()&&d.block&&!a.root.equals(d.block)){a:{var b=d.block.getElementsByTag("span"), +f=0,g;if(b)for(;g=b.getItem(f++);)if(!w(g)){b=!0;break a}b=!1}b=!b}b&&(a.moveToPosition(d.block,CKEDITOR.POSITION_BEFORE_START),d.block.remove())}else d.autoParagraph(this.editor,a),z(a.startContainer)&&a.startContainer.appendBogus();a.startContainer.mergeSiblings();return e},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var c=this.getData();this.is("textarea")||!1!==a.config.ignoreEmptyParagraph&&(c=c.replace(p,function(a,c){return c}));a.setData(c,null,1)}, +this);this.attachListener(a,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var c=a.getSelection();(c=c&&c.getNative())&&"Control"==c.type||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode,a.data.range)},this);this.attachListener(a, +"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?this.attachClass("cke_editable_inline"):a.elementMode!=CKEDITOR.ELEMENT_MODE_REPLACE&&a.elementMode!=CKEDITOR.ELEMENT_MODE_APPENDTO||this.attachClass("cke_editable_themed");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]= ++a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(){this.hasFocus=!1},null,null,-1);this.on("focus",function(){this.hasFocus=!0},null,null,-1);if(CKEDITOR.env.webkit)this.on("scroll",function(){a._.previousScrollTop=a.editable().$.scrollTop},null,null,-1);if(CKEDITOR.env.edge&&14CKEDITOR.env.version?k.$.styleSheet.cssText=h:k.setText(h)):(h=b.appendStyleText(h),h=new CKEDITOR.dom.element(h.ownerNode||h.owningElement),e.setCustomData("stylesheet",h),h.data("cke-temp",1))}e=b.getCustomData("stylesheet_ref")||0;b.setCustomData("stylesheet_ref",e+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){a=a.data;var c= +(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");c&&2!=a.$.button&&c.isReadOnly()&&a.preventDefault()});var l={8:1,46:1};this.attachListener(a,"key",function(c){if(a.readOnly)return!0;var d=c.data.domEvent.getKey(),b;c=a.getSelection();if(0!==c.getRanges().length){if(d in l){var e,f=c.getRanges()[0],g=f.startPath(),h,k,q,d=8==d;CKEDITOR.env.ie&&11>CKEDITOR.env.version&&(e=c.getSelectedElement())||(e=m(c))?(a.fire("saveSnapshot"),f.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START),e.remove(), +f.select(),a.fire("saveSnapshot"),b=1):f.collapsed&&((h=g.block)&&(q=h[d?"getPrevious":"getNext"](n))&&q.type==CKEDITOR.NODE_ELEMENT&&q.is("table")&&f[d?"checkStartOfBlock":"checkEndOfBlock"]()?(a.fire("saveSnapshot"),f[d?"checkEndOfBlock":"checkStartOfBlock"]()&&h.remove(),f["moveToElementEdit"+(d?"End":"Start")](q),f.select(),a.fire("saveSnapshot"),b=1):g.blockLimit&&g.blockLimit.is("td")&&(k=g.blockLimit.getAscendant("table"))&&f.checkBoundaryOfElement(k,d?CKEDITOR.START:CKEDITOR.END)&&(q=k[d? +"getPrevious":"getNext"](n))?(a.fire("saveSnapshot"),f["moveToElementEdit"+(d?"End":"Start")](q),f.checkStartOfBlock()&&f.checkEndOfBlock()?q.remove():f.select(),a.fire("saveSnapshot"),b=1):(k=g.contains(["td","th","caption"]))&&f.checkBoundaryOfElement(k,d?CKEDITOR.START:CKEDITOR.END)&&(b=1))}return!b}});a.blockless&&CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller&&this.attachListener(this,"keyup",function(c){c.data.getKeystroke()in l&&!this.getFirst(f)&&(this.appendBogus(),c=a.createRange(),c.moveToPosition(this, +CKEDITOR.POSITION_AFTER_START),c.select())});this.attachListener(this,"dblclick",function(c){if(a.readOnly)return!1;c={element:c.data.getTarget()};a.fire("doubleclick",c)});CKEDITOR.env.ie&&this.attachListener(this,"click",g);CKEDITOR.env.ie&&!CKEDITOR.env.edge||this.attachListener(this,"mousedown",function(c){var d=c.data.getTarget();d.is("img","hr","input","textarea","select")&&!d.isReadOnly()&&(a.getSelection().selectElement(d),d.is("input","textarea","select")&&c.data.preventDefault())});CKEDITOR.env.edge&& +this.attachListener(this,"mouseup",function(c){(c=c.data.getTarget())&&c.is("img")&&!c.isReadOnly()&&a.getSelection().selectElement(c)});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(c){if(2==c.data.$.button&&(c=c.data.getTarget(),!c.getAscendant("table")&&!c.getOuterHtml().replace(p,""))){var d=a.createRange();d.moveToElementEditStart(c);d.select(!0)}});CKEDITOR.env.webkit&&(this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()}), +this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()}));CKEDITOR.env.webkit&&this.attachListener(a,"key",function(d){if(a.readOnly)return!0;var b=d.data.domEvent.getKey();if(b in l&&(d=a.getSelection(),0!==d.getRanges().length)){var b=8==b,e=d.getRanges()[0];d=e.startPath();if(e.collapsed)a:{var f=d.block;if(f&&e[b?"checkStartOfBlock":"checkEndOfBlock"]()&&e.moveToClosestEditablePosition(f,!b)&&e.collapsed){if(e.startContainer.type==CKEDITOR.NODE_ELEMENT){var g= +e.startContainer.getChild(e.startOffset-(b?1:0));if(g&&g.type==CKEDITOR.NODE_ELEMENT&&g.is("hr")){a.fire("saveSnapshot");g.remove();d=!0;break a}}e=e.startPath().block;if(!e||e&&e.contains(f))d=void 0;else{a.fire("saveSnapshot");var h;(h=(b?e:f).getBogus())&&h.remove();h=a.getSelection();g=h.createBookmarks();(b?f:e).moveChildren(b?e:f,!1);d.lastElement.mergeSiblings();c(f,e,!b);h.selectBookmarks(g);d=!0}}else d=!1}else b=e,h=d.block,e=b.endPath().block,h&&e&&!h.equals(e)?(a.fire("saveSnapshot"), +(f=h.getBogus())&&f.remove(),b.enlarge(CKEDITOR.ENLARGE_INLINE),b.deleteContents(),e.getParent()&&(e.moveChildren(h,!1),d.lastElement.mergeSiblings(),c(h,e,!0)),b=a.getSelection().getRanges()[0],b.collapse(1),b.optimize(),""===b.startContainer.getHtml()&&b.startContainer.appendBogus(),b.select(),d=!0):d=!1;if(!d)return;a.getSelection().scrollIntoView();a.fire("saveSnapshot");return!1}},this,null,100)}},getUniqueId:function(){var a;try{this._.expandoNumber=a=CKEDITOR.dom.domObject.prototype.getUniqueId.call(this)}catch(c){a= +this._&&this._.expandoNumber}return a}},_:{cleanCustomData:function(){this.removeClass("cke_editable");this.restoreAttrs();for(var a=this.removeCustomData("classes");a&&a.length;)this.removeClass(a.pop());if(!this.is("textarea")){var a=this.getDocument(),c=a.getHead();if(c.getCustomData("stylesheet")){var d=a.getCustomData("stylesheet_ref");--d?a.setCustomData("stylesheet_ref",d):(a.removeCustomData("stylesheet_ref"),c.removeCustomData("stylesheet").remove())}}}}});CKEDITOR.editor.prototype.editable= +function(a){var c=this._.editable;if(c&&a)return 0;if(!arguments.length)return c;a?c=a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(c&&c.detach(),c=null);return this._.editable=c};CKEDITOR.on("instanceLoaded",function(a){var c=a.editor;c.on("insertElement",function(a){a=a.data;a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))&&("false"!=a.getAttribute("contentEditable")&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1"),a.setAttribute("contentEditable", +!1))});c.on("selectionChange",function(a){if(!c.readOnly){var d=c.getSelection();d&&!d.isLocked&&(d=c.checkDirty(),c.fire("lockSnapshot"),b(a),c.fire("unlockSnapshot"),!d&&c.resetDirty())}})});CKEDITOR.on("instanceCreated",function(a){var c=a.editor;c.on("mode",function(){var a=c.editable();if(a&&a.isInline()){var d=c.title;a.changeAttr("role","textbox");a.changeAttr("aria-multiline","true");a.changeAttr("aria-label",d);d&&a.changeAttr("title",d);var b=c.fire("ariaEditorHelpLabel",{}).label;if(b&& +(d=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents"))){var e=CKEDITOR.tools.getNextId(),b=CKEDITOR.dom.element.createFromHtml('\x3cspan id\x3d"'+e+'" class\x3d"cke_voice_label"\x3e'+b+"\x3c/span\x3e");d.append(b);a.changeAttr("aria-describedby",e)}}})});CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");n=CKEDITOR.dom.walker.whitespaces(!0);w=CKEDITOR.dom.walker.bookmark(!1,!0);z=CKEDITOR.dom.walker.empty(); +r=CKEDITOR.dom.walker.bogus();p=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi;q=function(){function a(c){return c.type==CKEDITOR.NODE_ELEMENT}function c(d,b){var e,f,g,h,n=[],k=b.range.startContainer;e=b.range.startPath();for(var k=m[k.getName()],l=0,q=d.getChildren(),w=q.count(),p=-1,t=-1,C=0,r=e.contains(m.$list);lCKEDITOR.env.version&&b.getChildCount()&&b.getFirst().remove())}return function(b){var e=b.startContainer,f=e.getAscendant("table",1),g=!1;d(f.getElementsByTag("td"));d(f.getElementsByTag("th"));f=b.clone();f.setStart(e,0);f= +a(f).lastBackward();f||(f=b.clone(),f.setEndAt(e,CKEDITOR.POSITION_BEFORE_END),f=a(f).lastForward(),g=!0);f||(f=e);f.is("table")?(b.setStartAt(f,CKEDITOR.POSITION_BEFORE_START),b.collapse(!0),f.remove()):(f.is({tbody:1,thead:1,tfoot:1})&&(f=c(f,"tr",g)),f.is("tr")&&(f=c(f,f.getParent().is("thead")?"th":"td",g)),(e=f.getBogus())&&e.remove(),b.moveToPosition(f,g?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END))}}();t=function(){function a(c){c=new CKEDITOR.dom.walker(c);c.guard=function(a, +c){if(c)return!1;if(a.type==CKEDITOR.NODE_ELEMENT)return a.is(CKEDITOR.dtd.$list)||a.is(CKEDITOR.dtd.$listItem)};c.evaluator=function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.is(CKEDITOR.dtd.$listItem)};return c}return function(c){var d=c.startContainer,b=!1,e;e=c.clone();e.setStart(d,0);e=a(e).lastBackward();e||(e=c.clone(),e.setEndAt(d,CKEDITOR.POSITION_BEFORE_END),e=a(e).lastForward(),b=!0);e||(e=d);e.is(CKEDITOR.dtd.$list)?(c.setStartAt(e,CKEDITOR.POSITION_BEFORE_START),c.collapse(!0),e.remove()): +((d=e.getBogus())&&d.remove(),c.moveToPosition(e,b?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END),c.select())}}();y={eol:{detect:function(a,c){var d=a.range,b=d.clone(),e=d.clone(),f=new CKEDITOR.dom.elementPath(d.startContainer,c),g=new CKEDITOR.dom.elementPath(d.endContainer,c);b.collapse(1);e.collapse();f.block&&b.checkBoundaryOfElement(f.block,CKEDITOR.END)&&(d.setStartAfter(f.block),a.prependEolBr=1);g.block&&e.checkBoundaryOfElement(g.block,CKEDITOR.START)&&(d.setEndBefore(g.block), +a.appendEolBr=1)},fix:function(a,c){var d=c.getDocument(),b;a.appendEolBr&&(b=this.createEolBr(d),a.fragment.append(b));!a.prependEolBr||b&&!b.getPrevious()||a.fragment.append(this.createEolBr(d),1)},createEolBr:function(a){return a.createElement("br",{attributes:{"data-cke-eol":1}})}},bogus:{exclude:function(a){var c=a.range.getBoundaryNodes(),d=c.startNode,c=c.endNode;!c||!r(c)||d&&d.equals(c)||a.range.setEndBefore(c)}},tree:{rebuild:function(a,c){var d=a.range,b=d.getCommonAncestor(),e=new CKEDITOR.dom.elementPath(b, +c),f=new CKEDITOR.dom.elementPath(d.startContainer,c),d=new CKEDITOR.dom.elementPath(d.endContainer,c),g;b.type==CKEDITOR.NODE_TEXT&&(b=b.getParent());if(e.blockLimit.is({tr:1,table:1})){var h=e.contains("table").getParent();g=function(a){return!a.equals(h)}}else if(e.block&&e.block.is(CKEDITOR.dtd.$listItem)&&(f=f.contains(CKEDITOR.dtd.$list),d=d.contains(CKEDITOR.dtd.$list),!f.equals(d))){var n=e.contains(CKEDITOR.dtd.$list).getParent();g=function(a){return!a.equals(n)}}g||(g=function(a){return!a.equals(e.block)&& +!a.equals(e.blockLimit)});this.rebuildFragment(a,c,b,g)},rebuildFragment:function(a,c,d,b){for(var e;d&&!d.equals(c)&&b(d);)e=d.clone(0,1),a.fragment.appendTo(e),a.fragment=e,d=d.getParent()}},cell:{shrink:function(a){a=a.range;var c=a.startContainer,d=a.endContainer,b=a.startOffset,e=a.endOffset;c.type==CKEDITOR.NODE_ELEMENT&&c.equals(d)&&c.is("tr")&&++b==e&&a.shrink(CKEDITOR.SHRINK_TEXT)}}};v=function(){function a(c,d){var b=c.getParent();if(b.is(CKEDITOR.dtd.$inline))c[d?"insertBefore":"insertAfter"](b)} +function c(d,b,e){a(b);a(e,1);for(var f;f=e.getNext();)f.insertAfter(b),b=f;z(d)&&d.remove()}function d(a,c){var b=new CKEDITOR.dom.range(a);b.setStartAfter(c.startNode);b.setEndBefore(c.endNode);return b}return{list:{detectMerge:function(a,c){var b=d(c,a.bookmark),e=b.startPath(),f=b.endPath(),g=e.contains(CKEDITOR.dtd.$list),h=f.contains(CKEDITOR.dtd.$list);a.mergeList=g&&h&&g.getParent().equals(h.getParent())&&!g.equals(h);a.mergeListItems=e.block&&f.block&&e.block.is(CKEDITOR.dtd.$listItem)&& +f.block.is(CKEDITOR.dtd.$listItem);if(a.mergeList||a.mergeListItems)b=b.clone(),b.setStartBefore(a.bookmark.startNode),b.setEndAfter(a.bookmark.endNode),a.mergeListBookmark=b.createBookmark()},merge:function(a,d){if(a.mergeListBookmark){var b=a.mergeListBookmark.startNode,e=a.mergeListBookmark.endNode,f=new CKEDITOR.dom.elementPath(b,d),g=new CKEDITOR.dom.elementPath(e,d);if(a.mergeList){var h=f.contains(CKEDITOR.dtd.$list),n=g.contains(CKEDITOR.dtd.$list);h.equals(n)||(n.moveChildren(h),n.remove())}a.mergeListItems&& +(f=f.contains(CKEDITOR.dtd.$listItem),g=g.contains(CKEDITOR.dtd.$listItem),f.equals(g)||c(g,b,e));b.remove();e.remove()}}},block:{detectMerge:function(a,c){if(!a.tableContentsRanges&&!a.mergeListBookmark){var d=new CKEDITOR.dom.range(c);d.setStartBefore(a.bookmark.startNode);d.setEndAfter(a.bookmark.endNode);a.mergeBlockBookmark=d.createBookmark()}},merge:function(a,d){if(a.mergeBlockBookmark&&!a.purgeTableBookmark){var b=a.mergeBlockBookmark.startNode,e=a.mergeBlockBookmark.endNode,f=new CKEDITOR.dom.elementPath(b, +d),g=new CKEDITOR.dom.elementPath(e,d),f=f.block,g=g.block;f&&g&&!f.equals(g)&&c(g,b,e);b.remove();e.remove()}}},table:function(){function a(d){var e=[],f,g=new CKEDITOR.dom.walker(d),h=d.startPath().contains(b),n=d.endPath().contains(b),k={};g.guard=function(a,g){if(a.type==CKEDITOR.NODE_ELEMENT){var l="visited_"+(g?"out":"in");if(a.getCustomData(l))return;CKEDITOR.dom.element.setMarker(k,a,l,1)}if(g&&h&&a.equals(h))f=d.clone(),f.setEndAt(h,CKEDITOR.POSITION_BEFORE_END),e.push(f);else if(!g&&n&& +a.equals(n))f=d.clone(),f.setStartAt(n,CKEDITOR.POSITION_AFTER_START),e.push(f);else{if(l=!g)l=a.type==CKEDITOR.NODE_ELEMENT&&a.is(b)&&(!h||c(a,h))&&(!n||c(a,n));if(!l&&(l=g))if(a.is(b))var l=h&&h.getAscendant("table",!0),m=n&&n.getAscendant("table",!0),q=a.getAscendant("table",!0),l=l&&l.contains(q)||m&&m.contains(q);else l=void 0;l&&(f=d.clone(),f.selectNodeContents(a),e.push(f))}};g.lastForward();CKEDITOR.dom.element.clearAllMarkers(k);return e}function c(a,d){var b=CKEDITOR.POSITION_CONTAINS+ +CKEDITOR.POSITION_IS_CONTAINED,e=a.getPosition(d);return e===CKEDITOR.POSITION_IDENTICAL?!1:0===(e&b)}var b={td:1,th:1,caption:1};return{detectPurge:function(a){var c=a.range,d=c.clone();d.enlarge(CKEDITOR.ENLARGE_ELEMENT);var d=new CKEDITOR.dom.walker(d),e=0;d.evaluator=function(a){a.type==CKEDITOR.NODE_ELEMENT&&a.is(b)&&++e};d.checkForward();if(1f&&e&&e.intersectsNode(d.$)){var g=[{node:b.anchorNode,offset:b.anchorOffset},{node:b.focusNode,offset:b.focusOffset}];b.anchorNode==d.$&&b.anchorOffset>f&&(g[0].offset-=f);b.focusNode==d.$&&b.focusOffset>f&&(g[1].offset-=f)}}d.setText(w(d.getText(),1));g&&(d=a.getDocument().$, +b=d.getSelection(),d=d.createRange(),d.setStart(g[0].node,g[0].offset),d.collapse(!0),b.removeAllRanges(),b.addRange(d),b.extend(g[1].node,g[1].offset))}}function w(a,c){return c?a.replace(y,function(a,c){return c?" ":""}):a.replace(t,"")}function z(a,c){var d=c&&CKEDITOR.tools.htmlEncode(c)||"\x26nbsp;",d=CKEDITOR.dom.element.createFromHtml('\x3cdiv data-cke-hidden-sel\x3d"1" data-cke-temp\x3d"1" style\x3d"'+(CKEDITOR.env.ie&&14>CKEDITOR.env.version?"display:none":"position:fixed;top:0;left:-1000px;width:0;height:0;overflow:hidden;")+ +'"\x3e'+d+"\x3c/div\x3e",a.document);a.fire("lockSnapshot");a.editable().append(d);var b=a.getSelection(1),e=a.createRange(),f=b.root.on("selectionchange",function(a){a.cancel()},null,null,0);e.setStartAt(d,CKEDITOR.POSITION_AFTER_START);e.setEndAt(d,CKEDITOR.POSITION_BEFORE_END);b.selectRanges([e]);f.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=d}function r(a){var c={37:1,39:1,8:1,46:1};return function(d){var b=d.data.getKeystroke();if(c[b]){var e=a.getSelection().getRanges(), +f=e[0];1==e.length&&f.collapsed&&(b=f[38>b?"getPreviousEditableNode":"getNextEditableNode"]())&&b.type==CKEDITOR.NODE_ELEMENT&&"false"==b.getAttribute("contenteditable")&&(a.getSelection().fake(b),d.data.preventDefault(),d.cancel())}}}function p(a){for(var c=0;c=b.getLength()?h.setStartAfter(b):h.setStartBefore(b));e&&e.type==CKEDITOR.NODE_TEXT&&(g?h.setEndAfter(e):h.setEndBefore(e));b=new CKEDITOR.dom.walker(h);b.evaluator=function(b){if(b.type==CKEDITOR.NODE_ELEMENT&&b.isReadOnly()){var e=d.clone();d.setEndBefore(b);d.collapsed&&a.splice(c--,1);b.getPosition(h.endContainer)& +CKEDITOR.POSITION_CONTAINS||(e.setStartAfter(b),e.collapsed||a.splice(c+1,0,e));return!0}return!1};b.next()}}return a}var q="function"!=typeof window.getSelection,u=1,t=CKEDITOR.tools.repeat("​",7),y=new RegExp(t+"( )?","g"),v,x,B,D=CKEDITOR.dom.walker.invisible(1),A=function(){function a(c){return function(a){var d=a.editor.createRange();d.moveToClosestEditablePosition(a.selected,c)&&a.editor.getSelection().selectRanges([d]);return!1}}function c(a){return function(c){var d=c.editor,b=d.createRange(), +e;if(!d.readOnly)return(e=b.moveToClosestEditablePosition(c.selected,a))||(e=b.moveToClosestEditablePosition(c.selected,!a)),e&&d.getSelection().selectRanges([b]),d.fire("saveSnapshot"),c.selected.remove(),e||(b.moveToElementEditablePosition(d.editable()),d.getSelection().selectRanges([b])),d.fire("saveSnapshot"),!1}}var d=a(),b=a(1);return{37:d,38:d,39:b,40:b,8:c(),46:c(1)}}();CKEDITOR.on("instanceCreated",function(a){function c(){var a=d.getSelection();a&&a.removeAllRanges()}var d=a.editor;d.on("contentDom", +function(){function a(){t=new CKEDITOR.dom.selection(d.getSelection());t.lock()}function c(){f.removeListener("mouseup",c);l.removeListener("mouseup",c);var a=CKEDITOR.document.$.selection,d=a.createRange();"None"!=a.type&&d.parentElement()&&d.parentElement().ownerDocument==e.$&&d.select()}function b(a){a=a.getRanges()[0];return a?(a=a.startContainer.getAscendant(function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")},!0))&&"false"===a.getAttribute("contenteditable")? +a:null:null}var e=d.document,f=CKEDITOR.document,g=d.editable(),h=e.getBody(),l=e.getDocumentElement(),w=g.isInline(),p,t;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();0!==p&&(a=d.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==g.$&&(a=d.createRange(),a.moveToElementEditStart(g),a.select())},null,null,-2);g.attachListener(g,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){if(p&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){p=d._.previousActive&& +d._.previousActive.equals(e.getActive());var a=null!=d._.previousScrollTop&&d._.previousScrollTop!=g.$.scrollTop;CKEDITOR.env.webkit&&p&&a&&(g.$.scrollTop=d._.previousScrollTop)}d.unlockSelection(p);p=0},null,null,-1);g.attachListener(g,"mousedown",function(){p=0});if(CKEDITOR.env.ie||CKEDITOR.env.gecko||w)q?g.attachListener(g,"beforedeactivate",a,null,null,-1):g.attachListener(d,"selectionCheck",a,null,null,-1),g.attachListener(g,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusout":"blur",function(){var a= +t&&(t.isFake||2>t.getRanges());CKEDITOR.env.gecko&&!w&&a||(d.lockSelection(t),p=1)},null,null,-1),g.attachListener(g,"mousedown",function(){p=0});if(CKEDITOR.env.ie&&!w){var u;g.attachListener(g,"mousedown",function(a){2==a.data.$.button&&((a=d.document.getSelection())&&a.getType()!=CKEDITOR.SELECTION_NONE||(u=d.window.getScrollPosition()))});g.attachListener(g,"mouseup",function(a){2==a.data.$.button&&u&&(d.document.$.documentElement.scrollLeft=u.x,d.document.$.documentElement.scrollTop=u.y);u=null}); +if("BackCompat"!=e.$.compatMode){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat){var v,B;l.on("mousedown",function(a){function c(a){a=a.data.$;if(v){var d=h.$.createTextRange();try{d.moveToPoint(a.clientX,a.clientY)}catch(b){}v.setEndPoint(0>B.compareEndPoints("StartToStart",d)?"EndToEnd":"StartToStart",d);v.select()}}function d(){l.removeListener("mousemove",c);f.removeListener("mouseup",d);l.removeListener("mouseup",d);v.select()}a=a.data;if(a.getTarget().is("html")&&a.$.yCKEDITOR.env.version)l.on("mousedown",function(a){a.data.getTarget().is("html")&&(f.on("mouseup",c),l.on("mouseup",c))})}}g.attachListener(g,"selectionchange",m,d);g.attachListener(g,"keyup",k,d);g.attachListener(g,"touchstart",k,d);g.attachListener(g,"touchend",k,d);CKEDITOR.env.ie&&g.attachListener(g, +"keydown",function(a){var c=this.getSelection(1),d=b(c);d&&!d.equals(g)&&(c.selectElement(d),a.data.preventDefault())},d);g.attachListener(g,CKEDITOR.env.webkit||CKEDITOR.env.gecko?"focusin":"focus",function(){d.forceNextSelectionCheck();d.selectionChange(1)});if(w&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var y;g.attachListener(g,"mousedown",function(){y=1});g.attachListener(e.getDocumentElement(),"mouseup",function(){y&&k.call(d);y=0})}else g.attachListener(CKEDITOR.env.ie?g:e.getDocumentElement(), +"mouseup",k,d);CKEDITOR.env.webkit&&g.attachListener(e,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:g.hasFocus&&n(g)}},null,null,-1);g.attachListener(g,"keydown",r(d),null,null,-1)});d.on("setData",function(){d.unlockSelection();CKEDITOR.env.webkit&&c()});d.on("contentDomUnload",function(){d.unlockSelection()});if(CKEDITOR.env.ie9Compat)d.on("beforeDestroy",c,null,null,9);d.on("dataReady",function(){delete d._.fakeSelection; +delete d._.hiddenSelectionContainer;d.selectionChange(1)});d.on("loadSnapshot",function(){var a=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT),c=d.editable().getLast(a);c&&c.hasAttribute("data-cke-hidden-sel")&&(c.remove(),CKEDITOR.env.gecko&&(a=d.editable().getFirst(a))&&a.is("br")&&a.getAttribute("_moz_editor_bogus_node")&&a.remove())},null,null,100);d.on("key",function(a){if("wysiwyg"==d.mode){var c=d.getSelection();if(c.isFake){var b=A[a.data.keyCode];if(b)return b({editor:d,selected:c.getSelectedElement(), +selection:c,keyEvent:a})}}})});if(CKEDITOR.env.webkit)CKEDITOR.on("instanceReady",function(a){var c=a.editor;c.on("selectionChange",function(){var a=c.editable(),d=a.getCustomData("cke-fillingChar");d&&(d.getCustomData("ready")?(n(a),a.editor.fire("selectionCheck")):d.setCustomData("ready",1))},null,null,-1);c.on("beforeSetMode",function(){n(c.editable())},null,null,-1);c.on("getSnapshot",function(a){a.data&&(a.data=w(a.data))},c,null,20);c.on("toDataFormat",function(a){a.data.dataValue=w(a.data.dataValue)}, +null,null,0)});CKEDITOR.editor.prototype.selectionChange=function(a){(a?m:k).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){return!this._.savedSelection&&!this._.fakeSelection||a?(a=this.editable())&&"wysiwyg"==this.mode?new CKEDITOR.dom.selection(a):null:this._.savedSelection||this._.fakeSelection};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);return a.getType()!=CKEDITOR.SELECTION_NONE?(!a.isLocked&&a.lock(),this._.savedSelection=a,!0):!1};CKEDITOR.editor.prototype.unlockSelection= +function(a){var c=this._.savedSelection;return c?(c.unlock(a),delete this._.savedSelection,!0):!1};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE= +1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection){var c=a;a=a.root}var d=a instanceof CKEDITOR.dom.element;this.rev=c?c.rev:u++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=d?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(c)return CKEDITOR.tools.extend(this._.cache,c._.cache),this.isFake=c.isFake,this.isLocked=c.isLocked,this;a=this.getNative();var b,e;if(a)if(a.getRangeAt)b= +(e=a.rangeCount&&a.getRangeAt(0))&&new CKEDITOR.dom.node(e.commonAncestorContainer);else{try{e=a.createRange()}catch(f){}b=e&&CKEDITOR.dom.element.get(e.item&&e.item(0)||e.parentElement())}if(!b||b.type!=CKEDITOR.NODE_ELEMENT&&b.type!=CKEDITOR.NODE_TEXT||!this.root.equals(b)&&!this.root.contains(b))this._.cache.type=CKEDITOR.SELECTION_NONE,this._.cache.startElement=null,this._.cache.selectedElement=null,this._.cache.selectedText="",this._.cache.ranges=new CKEDITOR.dom.rangeList;return this};var G= +{img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.tools.extend(CKEDITOR.dom.selection,{_removeFillingCharSequenceString:w,_createFillingCharSequenceNode:c,FILLING_CHAR_SEQUENCE:t});CKEDITOR.dom.selection.prototype={getNative:function(){return void 0!==this._.cache.nativeSel?this._.cache.nativeSel:this._.cache.nativeSel=q?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:q? +function(){var a=this._.cache;if(a.type)return a.type;var c=CKEDITOR.SELECTION_NONE;try{var d=this.getNative(),b=d.type;"Text"==b&&(c=CKEDITOR.SELECTION_TEXT);"Control"==b&&(c=CKEDITOR.SELECTION_ELEMENT);d.createRange().parentElement()&&(c=CKEDITOR.SELECTION_TEXT)}catch(e){}return a.type=c}:function(){var a=this._.cache;if(a.type)return a.type;var c=CKEDITOR.SELECTION_TEXT,d=this.getNative();if(!d||!d.rangeCount)c=CKEDITOR.SELECTION_NONE;else if(1==d.rangeCount){var d=d.getRangeAt(0),b=d.startContainer; +b==d.endContainer&&1==b.nodeType&&1==d.endOffset-d.startOffset&&G[b.childNodes[d.startOffset].nodeName.toLowerCase()]&&(c=CKEDITOR.SELECTION_ELEMENT)}return a.type=c},getRanges:function(){var a=q?function(){function a(c){return(new CKEDITOR.dom.node(c)).getIndex()}var c=function(c,d){c=c.duplicate();c.collapse(d);var b=c.parentElement();if(!b.hasChildNodes())return{container:b,offset:0};for(var e=b.children,f,g,h=c.duplicate(),n=0,l=e.length-1,k=-1,m,q;n<=l;)if(k=Math.floor((n+l)/2),f=e[k],h.moveToElementText(f), +m=h.compareEndPoints("StartToStart",c),0m)n=k+1;else return{container:b,offset:a(f)};if(-1==k||k==e.length-1&&0>m){h.moveToElementText(b);h.setEndPoint("StartToStart",c);h=h.text.replace(/(\r\n|\r)/g,"\n").length;e=b.childNodes;if(!h)return f=e[e.length-1],f.nodeType!=CKEDITOR.NODE_TEXT?{container:b,offset:e.length}:{container:f,offset:f.nodeValue.length};for(b=e.length;0b.length?this.selectElement(d):this.selectRanges(b))}},reset:function(){this._.cache={};this.isFake=0;var a=this.root.editor;if(a&&a._.fakeSelection)if(this.rev==a._.fakeSelection.rev){delete a._.fakeSelection; +var c=a._.hiddenSelectionContainer;if(c){var d=a.checkDirty();a.fire("lockSnapshot");c.remove();a.fire("unlockSnapshot");!d&&a.resetDirty()}delete a._.hiddenSelectionContainer}else CKEDITOR.warn("selection-fake-reset");this.rev=u++},selectElement:function(a){var c=new CKEDITOR.dom.range(this.root);c.setStartBefore(a);c.setEndAfter(a);this.selectRanges([c])},selectRanges:function(d){var b=this.root.editor,f=b&&b._.hiddenSelectionContainer;this.reset();if(f)for(var f=this.root,g,l=0;l]*>)[ \t\r\n]*/gi,"$1");g=g.replace(/([ \t\n\r]+| )/g," ");g=g.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var h=a.getDocument().createElement("div"); +h.append(f);f.$.outerHTML="\x3cpre\x3e"+g+"\x3c/pre\x3e";f.copyAttributes(h.getFirst());f=h.getFirst().remove()}else f.setHtml(g);d=f}else g?d=w(b?[a.getHtml()]:c(a),d):a.moveChildren(d);d.replace(a);if(e){var b=d,k;(k=b.getPrevious(F))&&k.type==CKEDITOR.NODE_ELEMENT&&k.is("pre")&&(e=n(k.getHtml(),/\n$/,"")+"\n\n"+n(b.getHtml(),/^\n/,""),CKEDITOR.env.ie?b.$.outerHTML="\x3cpre\x3e"+e+"\x3c/pre\x3e":b.setHtml(e),k.remove())}else b&&q(d)}function c(a){var c=[];n(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi, +function(a,c,d){return c+"\x3c/pre\x3e"+d+"\x3cpre\x3e"}).replace(/([\s\S]*?)<\/pre>/gi,function(a,d){c.push(d)});return c}function n(a,c,d){var b="",e="";a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,c,d){c&&(b=c);d&&(e=d);return""});return b+a.replace(c,d)+e}function w(a,c){var d;1=f?(m=a.createText(""),m.insertAfter(this)):(b=a.createText(""),b.insertAfter(m),b.remove()));return m},substring:function(b,h){return"number"!=typeof h?this.$.nodeValue.substr(b):this.$.nodeValue.substring(b,h)}});(function(){function b(b,f,a){var h=b.serializable,k=f[a?"endContainer":"startContainer"],l=a?"endOffset":"startOffset",d=h?f.document.getById(b.startNode):b.startNode;b=h? +f.document.getById(b.endNode):b.endNode;k.equals(d.getPrevious())?(f.startOffset=f.startOffset-k.getLength()-b.getPrevious().getLength(),k=b.getNext()):k.equals(b.getPrevious())&&(f.startOffset-=k.getLength(),k=b.getNext());k.equals(d.getParent())&&f[l]++;k.equals(b.getParent())&&f[l]++;f[a?"endContainer":"startContainer"]=k;return f}CKEDITOR.dom.rangeList=function(b){if(b instanceof CKEDITOR.dom.rangeList)return b;b?b instanceof CKEDITOR.dom.range&&(b=[b]):b=[];return CKEDITOR.tools.extend(b,h)}; +var h={createIterator:function(){var b=this,f=CKEDITOR.dom.walker.bookmark(),a=[],h;return{getNextRange:function(k){h=void 0===h?0:h+1;var l=b[h];if(l&&1c?-1:1}),f=0,g;fCKEDITOR.env.version?a[h].$.styleSheet.cssText+=g:a[h].$.innerHTML+=g}}var m={};CKEDITOR.skin= +{path:b,loadPart:function(a,c){CKEDITOR.skin.name!=CKEDITOR.skinName.split(",")[0]?CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(b()+"skin.js"),function(){g(a,c)}):g(a,c)},getPath:function(a){return CKEDITOR.getUrl(h(a))},icons:{},addIcon:function(a,c,d,b){a=a.toLowerCase();this.icons[a]||(this.icons[a]={path:c,offset:d||0,bgsize:b||"16px"})},getIconStyle:function(a,c,d,b,f){var g;a&&(a=a.toLowerCase(),c&&(g=this.icons[a+"-rtl"]),g||(g=this.icons[a]));a=d||g&&g.path||"";b=b||g&&g.offset;f=f||g&&g.bgsize|| +"16px";a&&(a=a.replace(/'/g,"\\'"));return a&&"background-image:url('"+CKEDITOR.getUrl(a)+"');background-position:0 "+b+"px;background-size:"+f+";"}};CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{getUiColor:function(){return this.uiColor},setUiColor:function(b){var c=f(CKEDITOR.document);return(this.setUiColor=function(b){this.uiColor=b;var e=CKEDITOR.skin.chameleon,f="",g="";"function"==typeof e&&(f=e(this,"editor"),g=e(this,"panel"));b=[[d,b]];a([c],f,b);a(l,g,b)}).call(this,b)}});var k="cke_ui_color", +l=[],d=/\$color/g;CKEDITOR.on("instanceLoaded",function(b){if(!CKEDITOR.env.ie||!CKEDITOR.env.quirks){var c=b.editor;b=function(b){b=(b.data[0]||b.data).element.getElementsByTag("iframe").getItem(0).getFrameDocument();if(!b.getById("cke_ui_color")){var e=f(b);l.push(e);c.on("destroy",function(){l=CKEDITOR.tools.array.filter(l,function(a){return e!==a})});(b=c.getUiColor())&&a([e],CKEDITOR.skin.chameleon(c,"panel"),[[d,b]])}};c.on("panelShow",b);c.on("menuShow",b);c.config.uiColor&&c.setUiColor(c.config.uiColor)}})})(); +(function(){if(CKEDITOR.env.webkit)CKEDITOR.env.hc=!1;else{var b=CKEDITOR.dom.element.createFromHtml('\x3cdiv style\x3d"width:0;height:0;position:absolute;left:-10000px;border:1px solid;border-color:red blue"\x3e\x3c/div\x3e',CKEDITOR.document);b.appendTo(CKEDITOR.document.getHead());try{var h=b.getComputedStyle("border-top-color"),g=b.getComputedStyle("border-right-color");CKEDITOR.env.hc=!(!h||h!=g)}catch(f){CKEDITOR.env.hc=!1}b.remove()}CKEDITOR.env.hc&&(CKEDITOR.env.cssClass+=" cke_hc");CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}"); +CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(b=CKEDITOR._.pending)for(delete CKEDITOR._.pending,h=0;hh;h++){var k=h,l;l=parseInt(a[h],16);l=("0"+(0>f?0|l*(1+f): +0|l+(255-l)*f).toString(16)).slice(-2);a[k]=l}return"#"+a.join("")}}(),h={editor:new CKEDITOR.template("{id}.cke_chrome [border-color:{defaultBorder};] {id} .cke_top [ background-color:{defaultBackground};border-bottom-color:{defaultBorder};] {id} .cke_bottom [background-color:{defaultBackground};border-top-color:{defaultBorder};] {id} .cke_resizer [border-right-color:{ckeResizer}] {id} .cke_dialog_title [background-color:{defaultBackground};border-bottom-color:{defaultBorder};] {id} .cke_dialog_footer [background-color:{defaultBackground};outline-color:{defaultBorder};] {id} .cke_dialog_tab [background-color:{dialogTab};border-color:{defaultBorder};] {id} .cke_dialog_tab:hover [background-color:{lightBackground};] {id} .cke_dialog_contents [border-top-color:{defaultBorder};] {id} .cke_dialog_tab_selected, {id} .cke_dialog_tab_selected:hover [background:{dialogTabSelected};border-bottom-color:{dialogTabSelectedBorder};] {id} .cke_dialog_body [background:{dialogBody};border-color:{defaultBorder};] {id} a.cke_button_off:hover,{id} a.cke_button_off:focus,{id} a.cke_button_off:active [background-color:{darkBackground};border-color:{toolbarElementsBorder};] {id} .cke_button_on [background-color:{ckeButtonOn};border-color:{toolbarElementsBorder};] {id} .cke_toolbar_separator,{id} .cke_toolgroup a.cke_button:last-child:after,{id} .cke_toolgroup a.cke_button.cke_button_disabled:hover:last-child:after [background-color: {toolbarElementsBorder};border-color: {toolbarElementsBorder};] {id} a.cke_combo_button:hover,{id} a.cke_combo_button:focus,{id} .cke_combo_on a.cke_combo_button [border-color:{toolbarElementsBorder};background-color:{darkBackground};] {id} .cke_combo:after [border-color:{toolbarElementsBorder};] {id} .cke_path_item [color:{elementsPathColor};] {id} a.cke_path_item:hover,{id} a.cke_path_item:focus,{id} a.cke_path_item:active [background-color:{darkBackground};] {id}.cke_panel [border-color:{defaultBorder};] "), panel:new CKEDITOR.template(".cke_panel_grouptitle [background-color:{lightBackground};border-color:{defaultBorder};] .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active [background-color:{menubuttonHover};] .cke_menubutton:hover .cke_menubutton_icon, .cke_menubutton:focus .cke_menubutton_icon, .cke_menubutton:active .cke_menubutton_icon [background-color:{menubuttonIconHover};] .cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menuseparator [background-color:{menubuttonIcon};] a:hover.cke_colorbox, a:active.cke_colorbox [border-color:{defaultBorder};] a:hover.cke_colorauto, a:hover.cke_colormore, a:active.cke_colorauto, a:active.cke_colormore [background-color:{ckeColorauto};border-color:{defaultBorder};] ")}; -return function(c,h){var b=e(c.uiColor,.4),b={id:"."+c.id,defaultBorder:e(b,-.2),toolbarElementsBorder:e(b,-.25),defaultBackground:b,lightBackground:e(b,.8),darkBackground:e(b,-.15),ckeButtonOn:e(b,.4),ckeResizer:e(b,-.4),ckeColorauto:e(b,.8),dialogBody:e(b,.7),dialogTab:e(b,.65),dialogTabSelected:"#FFF",dialogTabSelectedBorder:"#FFF",elementsPathColor:e(b,-.6),menubuttonHover:e(b,.1),menubuttonIcon:e(b,.5),menubuttonIconHover:e(b,.3)};return f[h].output(b).replace(/\[/g,"{").replace(/\]/g,"}")}}(), -CKEDITOR.plugins.add("dialogui",{onLoad:function(){var e=function(b){this._||(this._={});this._["default"]=this._.initValue=b["default"]||"";this._.required=b.required||!1;for(var d=[this._],a=1;aarguments.length)){var f=e.call(this,d);f.labelId=CKEDITOR.tools.getNextId()+ -"_label";this._.children=[];var h={role:d.role||"presentation"};d.includeLabel&&(h["aria-labelledby"]=f.labelId);CKEDITOR.ui.dialog.uiElement.call(this,b,d,a,"div",null,h,function(){var a=[],e=d.required?" cke_required":"";"horizontal"!=d.labelLayout?a.push('\x3clabel class\x3d"cke_dialog_ui_labeled_label'+e+'" ',' id\x3d"'+f.labelId+'"',f.inputId?' for\x3d"'+f.inputId+'"':"",(d.labelStyle?' style\x3d"'+d.labelStyle+'"':"")+"\x3e",d.label,"\x3c/label\x3e",'\x3cdiv class\x3d"cke_dialog_ui_labeled_content"', -d.controlStyle?' style\x3d"'+d.controlStyle+'"':"",' role\x3d"presentation"\x3e',c.call(this,b,d),"\x3c/div\x3e"):(e={type:"hbox",widths:d.widths,padding:0,children:[{type:"html",html:'\x3clabel class\x3d"cke_dialog_ui_labeled_label'+e+'" id\x3d"'+f.labelId+'" for\x3d"'+f.inputId+'"'+(d.labelStyle?' style\x3d"'+d.labelStyle+'"':"")+"\x3e"+CKEDITOR.tools.htmlEncode(d.label)+"\x3c/label\x3e"},{type:"html",html:'\x3cspan class\x3d"cke_dialog_ui_labeled_content"'+(d.controlStyle?' style\x3d"'+d.controlStyle+ -'"':"")+"\x3e"+c.call(this,b,d)+"\x3c/span\x3e"}]},CKEDITOR.dialog._.uiElementBuilders.hbox.build(b,e,a));return a.join("")})}},textInput:function(b,c,a){if(!(3>arguments.length)){e.call(this,c);var f=this._.inputId=CKEDITOR.tools.getNextId()+"_textInput",h={"class":"cke_dialog_ui_input_"+c.type,id:f,type:c.type};c.validate&&(this.validate=c.validate);c.maxLength&&(h.maxlength=c.maxLength);c.size&&(h.size=c.size);c.inputStyle&&(h.style=c.inputStyle);var k=this,l=!1;b.on("load",function(){k.getInputElement().on("keydown", -function(a){13==a.data.getKeystroke()&&(l=!0)});k.getInputElement().on("keyup",function(a){13==a.data.getKeystroke()&&l&&(b.getButton("ok")&&setTimeout(function(){b.getButton("ok").click()},0),l=!1);k.bidi&&d.call(k,a)},null,null,1E3)});CKEDITOR.ui.dialog.labeledElement.call(this,b,c,a,function(){var a=['\x3cdiv class\x3d"cke_dialog_ui_input_',c.type,'" role\x3d"presentation"'];c.width&&a.push('style\x3d"width:'+c.width+'" ');a.push("\x3e\x3cinput ");h["aria-labelledby"]=this._.labelId;this._.required&& -(h["aria-required"]=this._.required);for(var b in h)a.push(b+'\x3d"'+h[b]+'" ');a.push(" /\x3e\x3c/div\x3e");return a.join("")})}},textarea:function(b,c,a){if(!(3>arguments.length)){e.call(this,c);var f=this,h=this._.inputId=CKEDITOR.tools.getNextId()+"_textarea",k={};c.validate&&(this.validate=c.validate);k.rows=c.rows||5;k.cols=c.cols||20;k["class"]="cke_dialog_ui_input_textarea "+(c["class"]||"");"undefined"!=typeof c.inputStyle&&(k.style=c.inputStyle);c.dir&&(k.dir=c.dir);if(f.bidi)b.on("load", -function(){f.getInputElement().on("keyup",d)},f);CKEDITOR.ui.dialog.labeledElement.call(this,b,c,a,function(){k["aria-labelledby"]=this._.labelId;this._.required&&(k["aria-required"]=this._.required);var a=['\x3cdiv class\x3d"cke_dialog_ui_input_textarea" role\x3d"presentation"\x3e\x3ctextarea id\x3d"',h,'" '],b;for(b in k)a.push(b+'\x3d"'+CKEDITOR.tools.htmlEncode(k[b])+'" ');a.push("\x3e",CKEDITOR.tools.htmlEncode(f._["default"]),"\x3c/textarea\x3e\x3c/div\x3e");return a.join("")})}},checkbox:function(b, -d,a){if(!(3>arguments.length)){var c=e.call(this,d,{"default":!!d["default"]});d.validate&&(this.validate=d.validate);CKEDITOR.ui.dialog.uiElement.call(this,b,d,a,"span",null,null,function(){var a=CKEDITOR.tools.extend({},d,{id:d.id?d.id+"_checkbox":CKEDITOR.tools.getNextId()+"_checkbox"},!0),e=[],f=CKEDITOR.tools.getNextId()+"_label",h={"class":"cke_dialog_ui_checkbox_input",type:"checkbox","aria-labelledby":f};k(a);d["default"]&&(h.checked="checked");"undefined"!=typeof a.inputStyle&&(a.style=a.inputStyle); -c.checkbox=new CKEDITOR.ui.dialog.uiElement(b,a,e,"input",null,h);e.push(' \x3clabel id\x3d"',f,'" for\x3d"',h.id,'"'+(d.labelStyle?' style\x3d"'+d.labelStyle+'"':"")+"\x3e",CKEDITOR.tools.htmlEncode(d.label),"\x3c/label\x3e");return e.join("")})}},radio:function(b,d,a){if(!(3>arguments.length)){e.call(this,d);this._["default"]||(this._["default"]=this._.initValue=d.items[0][1]);d.validate&&(this.validate=d.validate);var c=[],f=this;d.role="radiogroup";d.includeLabel=!0;CKEDITOR.ui.dialog.labeledElement.call(this, -b,d,a,function(){for(var a=[],e=[],h=(d.id?d.id:CKEDITOR.tools.getNextId())+"_radio",l=0;larguments.length)){var c=e.call(this,d);d.validate&&(this.validate=d.validate);c.inputId=CKEDITOR.tools.getNextId()+"_select";CKEDITOR.ui.dialog.labeledElement.call(this,b,d,a,function(){var a=CKEDITOR.tools.extend({},d,{id:d.id?d.id+"_select":CKEDITOR.tools.getNextId()+"_select"},!0),e=[],f=[],h={id:c.inputId,"class":"cke_dialog_ui_input_select","aria-labelledby":this._.labelId};e.push('\x3cdiv class\x3d"cke_dialog_ui_input_', -d.type,'" role\x3d"presentation"');d.width&&e.push('style\x3d"width:'+d.width+'" ');e.push("\x3e");void 0!==d.size&&(h.size=d.size);void 0!==d.multiple&&(h.multiple=d.multiple);k(a);for(var l=0,v;larguments.length)){void 0===d["default"]&&(d["default"]="");var c=CKEDITOR.tools.extend(e.call(this,d),{definition:d,buttons:[]});d.validate&&(this.validate=d.validate);b.on("load",function(){CKEDITOR.document.getById(c.frameId).getParent().addClass("cke_dialog_ui_input_file")});CKEDITOR.ui.dialog.labeledElement.call(this,b,d,a,function(){c.frameId=CKEDITOR.tools.getNextId()+"_fileInput";var a=['\x3ciframe frameborder\x3d"0" allowtransparency\x3d"0" class\x3d"cke_dialog_ui_input_file" role\x3d"presentation" id\x3d"', -c.frameId,'" title\x3d"',d.label,'" src\x3d"javascript:void('];a.push(CKEDITOR.env.ie?"(function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+"})()":"0");a.push(')"\x3e\x3c/iframe\x3e');return a.join("")})}},fileButton:function(b,d,a){var c=this;if(!(3>arguments.length)){e.call(this,d);d.validate&&(this.validate=d.validate);var f=CKEDITOR.tools.extend({},d),h=f.onClick;f.className=(f.className?f.className+" ":"")+"cke_dialog_ui_button";f.onClick=function(a){var c= -d["for"];a=h?h.call(this,a):!1;!1!==a&&("xhr"!==a&&b.getContentElement(c[0],c[1]).submit(),this.disable())};b.on("load",function(){b.getContentElement(d["for"][0],d["for"][1])._.buttons.push(c)});CKEDITOR.ui.dialog.button.call(this,b,f,a)}},html:function(){var b=/^\s*<[\w:]+\s+([^>]*)?>/,d=/^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,a=/\/$/;return function(c,e,f){if(!(3>arguments.length)){var h=[],k=e.html;"\x3c"!=k.charAt(0)&&(k="\x3cspan\x3e"+k+"\x3c/span\x3e");var l=e.focus;if(l){var v=this.focus; -this.focus=function(){("function"==typeof l?l:v).call(this);this.fire("focus")};e.isFocusable&&(this.isFocusable=this.isFocusable);this.keyboardFocusable=!0}CKEDITOR.ui.dialog.uiElement.call(this,c,e,h,"span",null,null,"");h=h.join("").match(b);k=k.match(d)||["","",""];a.test(k[1])&&(k[1]=k[1].slice(0,-1),k[2]="/"+k[2]);f.push([k[1]," ",h[1]||"",k[2]].join(""))}}}(),fieldset:function(b,d,a,c,e){var f=e.label;this._={children:d};CKEDITOR.ui.dialog.uiElement.call(this,b,e,c,"fieldset",null,null,function(){var b= -[];f&&b.push("\x3clegend"+(e.labelStyle?' style\x3d"'+e.labelStyle+'"':"")+"\x3e"+f+"\x3c/legend\x3e");for(var d=0;dd.getChildCount()?(new CKEDITOR.dom.text(b,CKEDITOR.document)).appendTo(d):d.getChild(0).$.nodeValue= -b;return this},getLabel:function(){var b=CKEDITOR.document.getById(this._.labelId);return!b||1>b.getChildCount()?"":b.getChild(0).getText()},eventProcessors:b},!0);CKEDITOR.ui.dialog.button.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{click:function(){return this._.disabled?!1:this.fire("click",{dialog:this._.dialog})},enable:function(){this._.disabled=!1;var b=this.getElement();b&&b.removeClass("cke_disabled")},disable:function(){this._.disabled=!0;this.getElement().addClass("cke_disabled")}, -isVisible:function(){return this.getElement().getFirst().isVisible()},isEnabled:function(){return!this._.disabled},eventProcessors:CKEDITOR.tools.extend({},CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,{onClick:function(b,d){this.on("click",function(){d.apply(this,arguments)})}},!0),accessKeyUp:function(){this.click()},accessKeyDown:function(){this.focus()},keyboardFocusable:!0},!0);CKEDITOR.ui.dialog.textInput.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return CKEDITOR.document.getById(this._.inputId)}, -focus:function(){var b=this.selectParentTab();setTimeout(function(){var d=b.getInputElement();d&&d.$.focus()},0)},select:function(){var b=this.selectParentTab();setTimeout(function(){var d=b.getInputElement();d&&(d.$.focus(),d.$.select())},0)},accessKeyUp:function(){this.select()},setValue:function(b){if(this.bidi){var d=b&&b.charAt(0);(d="‪"==d?"ltr":"‫"==d?"rtl":null)&&(b=b.slice(1));this.setDirectionMarker(d)}b||(b="");return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply(this,arguments)}, -getValue:function(){var b=CKEDITOR.ui.dialog.uiElement.prototype.getValue.call(this);if(this.bidi&&b){var d=this.getDirectionMarker();d&&(b=("ltr"==d?"‪":"‫")+b)}return b},setDirectionMarker:function(b){var d=this.getInputElement();b?d.setAttributes({dir:b,"data-cke-dir-marker":b}):this.getDirectionMarker()&&d.removeAttributes(["dir","data-cke-dir-marker"])},getDirectionMarker:function(){return this.getInputElement().data("cke-dir-marker")},keyboardFocusable:!0},h,!0);CKEDITOR.ui.dialog.textarea.prototype= -new CKEDITOR.ui.dialog.textInput;CKEDITOR.ui.dialog.select.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return this._.select.getElement()},add:function(b,d,a){var c=new CKEDITOR.dom.element("option",this.getDialog().getParentEditor().document),e=this.getInputElement().$;c.$.text=b;c.$.value=void 0===d||null===d?b:d;void 0===a||null===a?CKEDITOR.env.ie?e.add(c.$):e.add(c.$,null):e.add(c.$,a);return this},remove:function(b){this.getInputElement().$.remove(b); -return this},clear:function(){for(var b=this.getInputElement().$;0b-a;d--)if(this._.tabs[this._.tabIdList[d%a]][0].$.offsetHeight)return this._.tabIdList[d%a];return null}function f(){for(var a=this._.tabIdList.length,b=CKEDITOR.tools.indexOf(this._.tabIdList,this._.currentTabId),d=b+1;dm.width-k.width-f?m.width-k.width+("rtl"==g.lang.dir?0:h[1]):e.x,e.y+h[0]m.height-k.height-f?m.height-k.height+h[2]:e.y,1);d.data.preventDefault()} -function d(){CKEDITOR.document.removeListener("mousemove",b);CKEDITOR.document.removeListener("mouseup",d);if(CKEDITOR.env.ie6Compat){var a=D.getChild(0).getFrameDocument();a.removeListener("mousemove",b);a.removeListener("mouseup",d)}}var c=null,e=null,g=a.getParentEditor(),f=g.config.dialog_magnetDistance,h=CKEDITOR.skin.margins||[0,0,0,0];"undefined"==typeof f&&(f=20);a.parts.title.on("mousedown",function(g){c={x:g.data.$.screenX,y:g.data.$.screenY};CKEDITOR.document.on("mousemove",b);CKEDITOR.document.on("mouseup", -d);e=a.getPosition();if(CKEDITOR.env.ie6Compat){var f=D.getChild(0).getFrameDocument();f.on("mousemove",b);f.on("mouseup",d)}g.data.preventDefault()},a)}function a(a){function b(d){var n="rtl"==g.lang.dir,r=l.width,q=l.height,p=r+(d.data.$.screenX-m.x)*(n?-1:1)*(a._.moved?1:2),w=q+(d.data.$.screenY-m.y)*(a._.moved?1:2),v=a._.element.getFirst(),v=n&&v.getComputedStyle("right"),x=a.getPosition();x.y+w>k.height&&(w=k.height-x.y);(n?v:x.x)+p>k.width&&(p=k.width-(n?v:x.x));if(e==CKEDITOR.DIALOG_RESIZE_WIDTH|| -e==CKEDITOR.DIALOG_RESIZE_BOTH)r=Math.max(c.minWidth||0,p-f);if(e==CKEDITOR.DIALOG_RESIZE_HEIGHT||e==CKEDITOR.DIALOG_RESIZE_BOTH)q=Math.max(c.minHeight||0,w-h);a.resize(r,q);a._.moved||a.layout();d.data.preventDefault()}function d(){CKEDITOR.document.removeListener("mouseup",d);CKEDITOR.document.removeListener("mousemove",b);n&&(n.remove(),n=null);if(CKEDITOR.env.ie6Compat){var a=D.getChild(0).getFrameDocument();a.removeListener("mouseup",d);a.removeListener("mousemove",b)}}var c=a.definition,e=c.resizable; -if(e!=CKEDITOR.DIALOG_RESIZE_NONE){var g=a.getParentEditor(),f,h,k,m,l,n,r=CKEDITOR.tools.addFunction(function(c){l=a.getSize();var e=a.parts.contents;e.$.getElementsByTagName("iframe").length&&(n=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_dialog_resize_cover" style\x3d"height: 100%; position: absolute; width: 100%;"\x3e\x3c/div\x3e'),e.append(n));h=l.height-a.parts.contents.getSize("height",!(CKEDITOR.env.gecko||CKEDITOR.env.ie&&CKEDITOR.env.quirks));f=l.width-a.parts.contents.getSize("width", -1);m={x:c.screenX,y:c.screenY};k=CKEDITOR.document.getWindow().getViewPaneSize();CKEDITOR.document.on("mousemove",b);CKEDITOR.document.on("mouseup",d);CKEDITOR.env.ie6Compat&&(e=D.getChild(0).getFrameDocument(),e.on("mousemove",b),e.on("mouseup",d));c.preventDefault&&c.preventDefault()});a.on("load",function(){var b="";e==CKEDITOR.DIALOG_RESIZE_WIDTH?b=" cke_resizer_horizontal":e==CKEDITOR.DIALOG_RESIZE_HEIGHT&&(b=" cke_resizer_vertical");b=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_resizer'+ -b+" cke_resizer_"+g.lang.dir+'" title\x3d"'+CKEDITOR.tools.htmlEncode(g.lang.common.resize)+'" onmousedown\x3d"CKEDITOR.tools.callFunction('+r+', event )"\x3e'+("ltr"==g.lang.dir?"◢":"◣")+"\x3c/div\x3e");a.parts.footer.append(b,1)});g.on("destroy",function(){CKEDITOR.tools.removeFunction(r)})}}function n(a){a.data.preventDefault(1)}function w(a){var b=CKEDITOR.document.getWindow(),d=a.config,c=CKEDITOR.skinName||a.config.skin,e=d.dialog_backgroundCoverColor||("moono-lisa"==c?"black":"white"),c=d.dialog_backgroundCoverOpacity, -g=d.baseFloatZIndex,d=CKEDITOR.tools.genKey(e,c,g),f=A[d];f?f.show():(g=['\x3cdiv tabIndex\x3d"-1" style\x3d"position: ',CKEDITOR.env.ie6Compat?"absolute":"fixed","; z-index: ",g,"; top: 0px; left: 0px; ",CKEDITOR.env.ie6Compat?"":"background-color: "+e,'" class\x3d"cke_dialog_background_cover"\x3e'],CKEDITOR.env.ie6Compat&&(e="\x3chtml\x3e\x3cbody style\x3d\\'background-color:"+e+";\\'\x3e\x3c/body\x3e\x3c/html\x3e",g.push('\x3ciframe hidefocus\x3d"true" frameborder\x3d"0" id\x3d"cke_dialog_background_iframe" src\x3d"javascript:'), -g.push("void((function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.write( '"+e+"' );document.close();")+"})())"),g.push('" style\x3d"position:absolute;left:0;top:0;width:100%;height: 100%;filter: progid:DXImageTransform.Microsoft.Alpha(opacity\x3d0)"\x3e\x3c/iframe\x3e')),g.push("\x3c/div\x3e"),f=CKEDITOR.dom.element.createFromHtml(g.join("")),f.setOpacity(void 0!==c?c:.5),f.on("keydown",n),f.on("keypress",n),f.on("keyup",n),f.appendTo(CKEDITOR.document.getBody()), -A[d]=f);a.focusManager.add(f);D=f;a=function(){var a=b.getViewPaneSize();f.setStyles({width:a.width+"px",height:a.height+"px"})};var h=function(){var a=b.getScrollPosition(),d=CKEDITOR.dialog._.currentTop;f.setStyles({left:a.x+"px",top:a.y+"px"});if(d){do a=d.getPosition(),d.move(a.x,a.y);while(d=d._.parentDialog)}};y=a;b.on("resize",a);a();CKEDITOR.env.mac&&CKEDITOR.env.webkit||f.focus();if(CKEDITOR.env.ie6Compat){var k=function(){h();arguments.callee.prevScrollHandler.apply(this,arguments)};b.$.setTimeout(function(){k.prevScrollHandler= -window.onscroll||function(){};window.onscroll=k},0);h()}}function u(a){D&&(a.focusManager.remove(D),a=CKEDITOR.document.getWindow(),D.hide(),a.removeListener("resize",y),CKEDITOR.env.ie6Compat&&a.$.setTimeout(function(){window.onscroll=window.onscroll&&window.onscroll.prevScrollHandler||null},0),y=null)}var t=CKEDITOR.tools.cssLength,p='\x3cdiv class\x3d"cke_reset_all {editorId} {editorDialogClass} {hidpi}" dir\x3d"{langDir}" lang\x3d"{langCode}" role\x3d"dialog" aria-labelledby\x3d"cke_dialog_title_{id}"\x3e\x3ctable class\x3d"cke_dialog '+ -CKEDITOR.env.cssClass+' cke_{langDir}" style\x3d"position:absolute" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd role\x3d"presentation"\x3e\x3cdiv class\x3d"cke_dialog_body" role\x3d"presentation"\x3e\x3cdiv id\x3d"cke_dialog_title_{id}" class\x3d"cke_dialog_title" role\x3d"presentation"\x3e\x3c/div\x3e\x3ca id\x3d"cke_dialog_close_button_{id}" class\x3d"cke_dialog_close_button" href\x3d"javascript:void(0)" title\x3d"{closeTitle}" role\x3d"button"\x3e\x3cspan class\x3d"cke_label"\x3eX\x3c/span\x3e\x3c/a\x3e\x3cdiv id\x3d"cke_dialog_tabs_{id}" class\x3d"cke_dialog_tabs" role\x3d"tablist"\x3e\x3c/div\x3e\x3ctable class\x3d"cke_dialog_contents" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_contents_{id}" class\x3d"cke_dialog_contents_body" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_footer_{id}" class\x3d"cke_dialog_footer" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e'; -CKEDITOR.dialog=function(d,c){function g(){var a=z._.focusList;a.sort(function(a,b){return a.tabIndex!=b.tabIndex?b.tabIndex-a.tabIndex:a.focusIndex-b.focusIndex});for(var b=a.length,d=0;db.length)){var d=z._.currentFocusIndex;z._.tabBarMode&&0>a&&(d=0);try{b[d].getInputElement().$.blur()}catch(c){}var e=d,g=1d.height||b.width+(0d.width?a.setStyle("position","absolute"):a.setStyle("position","fixed"));this.move(this._.moved?this._.position.x:c,this._.moved?this._.position.y:e)},foreach:function(a){for(var b in this._.contents)for(var d in this._.contents[b])a.call(this,this._.contents[b][d]);return this},reset:function(){var a=function(a){a.reset&&a.reset(1)};return function(){this.foreach(a);return this}}(), -setupContent:function(){var a=arguments;this.foreach(function(b){b.setup&&b.setup.apply(b,a)})},commitContent:function(){var a=arguments;this.foreach(function(b){CKEDITOR.env.ie&&this._.currentFocusIndex==b.focusIndex&&b.getInputElement().$.blur();b.commit&&b.commit.apply(b,a)})},hide:function(){if(this.parts.dialog.isVisible()){this.fire("hide",{});this._.editor.fire("dialogHide",this);this.selectPage(this._.tabIdList[0]);var a=this._.element;a.setStyle("display","none");this.parts.dialog.setStyle("visibility", -"hidden");for(F(this);CKEDITOR.dialog._.currentTop!=this;)CKEDITOR.dialog._.currentTop.hide();if(this._.parentDialog){var b=this._.parentDialog.getElement().getFirst();b.setStyle("z-index",parseInt(b.$.style.zIndex,10)+Math.floor(this._.editor.config.baseFloatZIndex/2))}else u(this._.editor);if(CKEDITOR.dialog._.currentTop=this._.parentDialog)CKEDITOR.dialog._.currentZIndex-=10;else{CKEDITOR.dialog._.currentZIndex=null;a.removeListener("keydown",J);a.removeListener("keyup",E);var d=this._.editor; -d.focus();setTimeout(function(){d.focusManager.unlock();CKEDITOR.env.iOS&&d.window.focus()},0)}delete this._.parentDialog;this.foreach(function(a){a.resetInitValue&&a.resetInitValue()});this.setState(CKEDITOR.DIALOG_STATE_IDLE)}},addPage:function(a){if(!a.requiredContent||this._.editor.filter.check(a.requiredContent)){for(var b=[],d=a.label?' title\x3d"'+CKEDITOR.tools.htmlEncode(a.label)+'"':"",c=CKEDITOR.dialog._.uiElementBuilders.vbox.build(this,{type:"vbox",className:"cke_dialog_page_contents", -children:a.elements,expand:!!a.expand,padding:a.padding,style:a.style||"width: 100%;"},b),e=this._.contents[a.id]={},g=c.getChild(),f=0;c=g.shift();)c.notAllowed||"hbox"==c.type||"vbox"==c.type||f++,e[c.id]=c,"function"==typeof c.getChild&&g.push.apply(g,c.getChild());f||(a.hidden=!0);b=CKEDITOR.dom.element.createFromHtml(b.join(""));b.setAttribute("role","tabpanel");c=CKEDITOR.env;e="cke_"+a.id+"_"+CKEDITOR.tools.getNextNumber();d=CKEDITOR.dom.element.createFromHtml(['\x3ca class\x3d"cke_dialog_tab"', -0arguments.length)){var h=(c.call?c(b):c)||"div",k=["\x3c",h," "],m=(e&&e.call?e(b):e)||{},l=(g&&g.call?g(b):g)||{},n=(f&&f.call?f.call(this,a,b):f)||"",r=this.domId=l.id||CKEDITOR.tools.getNextId()+"_uiElement";b.requiredContent&&!a.getParentEditor().filter.check(b.requiredContent)&&(m.display="none",this.notAllowed=!0);l.id=r;var q={};b.type&&(q["cke_dialog_ui_"+ -b.type]=1);b.className&&(q[b.className]=1);b.disabled&&(q.cke_disabled=1);for(var p=l["class"]&&l["class"].split?l["class"].split(" "):[],r=0;rCKEDITOR.env.version?"cke_dialog_ui_focused":"";b.on("focus",function(){a._.tabBarMode=!1;a._.hasFocus=!0;w.fire("focus"); -d&&this.addClass(d)});b.on("blur",function(){w.fire("blur");d&&this.removeClass(d)})}});CKEDITOR.tools.extend(this,b);this.keyboardFocusable&&(this.tabIndex=b.tabIndex||0,this.focusIndex=a._.focusList.push(this)-1,this.on("focus",function(){a._.currentFocusIndex=w.focusIndex}))}},hbox:function(a,b,d,c,e){if(!(4>arguments.length)){this._||(this._={});var g=this._.children=b,f=e&&e.widths||null,h=e&&e.height||null,k,m={role:"presentation"};e&&e.align&&(m.align=e.align);CKEDITOR.ui.dialog.uiElement.call(this, -a,e||{type:"hbox"},c,"table",{},m,function(){var a=['\x3ctbody\x3e\x3ctr class\x3d"cke_dialog_ui_hbox"\x3e'];for(k=0;karguments.length)){this._||(this._={});var g=this._.children=b,f=e&&e.width||null,h=e&&e.heights||null;CKEDITOR.ui.dialog.uiElement.call(this,a,e||{type:"vbox"},c,"div",null,{role:"presentation"},function(){var b=['\x3ctable role\x3d"presentation" cellspacing\x3d"0" border\x3d"0" ']; -b.push('style\x3d"');e&&e.expand&&b.push("height:100%;");b.push("width:"+t(f||"100%"),";");CKEDITOR.env.webkit&&b.push("float:none;");b.push('"');b.push('align\x3d"',CKEDITOR.tools.htmlEncode(e&&e.align||("ltr"==a.getParentEditor().lang.dir?"left":"right")),'" ');b.push("\x3e\x3ctbody\x3e");for(var c=0;carguments.length)){var g=b.call(this,e);g.labelId=CKEDITOR.tools.getNextId()+ +"_label";this._.children=[];var h={role:e.role||"presentation"};e.includeLabel&&(h["aria-labelledby"]=g.labelId);CKEDITOR.ui.dialog.uiElement.call(this,a,e,c,"div",null,h,function(){var c=[],b=e.required?" cke_required":"";"horizontal"!=e.labelLayout?c.push('\x3clabel class\x3d"cke_dialog_ui_labeled_label'+b+'" ',' id\x3d"'+g.labelId+'"',g.inputId?' for\x3d"'+g.inputId+'"':"",(e.labelStyle?' style\x3d"'+e.labelStyle+'"':"")+"\x3e",e.label,"\x3c/label\x3e",'\x3cdiv class\x3d"cke_dialog_ui_labeled_content"', +e.controlStyle?' style\x3d"'+e.controlStyle+'"':"",' role\x3d"presentation"\x3e',f.call(this,a,e),"\x3c/div\x3e"):(b={type:"hbox",widths:e.widths,padding:0,children:[{type:"html",html:'\x3clabel class\x3d"cke_dialog_ui_labeled_label'+b+'" id\x3d"'+g.labelId+'" for\x3d"'+g.inputId+'"'+(e.labelStyle?' style\x3d"'+e.labelStyle+'"':"")+"\x3e"+CKEDITOR.tools.htmlEncode(e.label)+"\x3c/label\x3e"},{type:"html",html:'\x3cspan class\x3d"cke_dialog_ui_labeled_content"'+(e.controlStyle?' style\x3d"'+e.controlStyle+ +'"':"")+"\x3e"+f.call(this,a,e)+"\x3c/span\x3e"}]},CKEDITOR.dialog._.uiElementBuilders.hbox.build(a,b,c));return c.join("")})}},textInput:function(a,e,c){if(!(3>arguments.length)){b.call(this,e);var f=this._.inputId=CKEDITOR.tools.getNextId()+"_textInput",g={"class":"cke_dialog_ui_input_"+e.type,id:f,type:e.type};e.validate&&(this.validate=e.validate);e.maxLength&&(g.maxlength=e.maxLength);e.size&&(g.size=e.size);e.inputStyle&&(g.style=e.inputStyle);var h=this,k=!1;a.on("load",function(){h.getInputElement().on("keydown", +function(a){13==a.data.getKeystroke()&&(k=!0)});h.getInputElement().on("keyup",function(c){13==c.data.getKeystroke()&&k&&(a.getButton("ok")&&setTimeout(function(){a.getButton("ok").click()},0),k=!1);h.bidi&&l.call(h,c)},null,null,1E3)});CKEDITOR.ui.dialog.labeledElement.call(this,a,e,c,function(){var a=['\x3cdiv class\x3d"cke_dialog_ui_input_',e.type,'" role\x3d"presentation"'];e.width&&a.push('style\x3d"width:'+e.width+'" ');a.push("\x3e\x3cinput ");g["aria-labelledby"]=this._.labelId;this._.required&& +(g["aria-required"]=this._.required);for(var c in g)a.push(c+'\x3d"'+g[c]+'" ');a.push(" /\x3e\x3c/div\x3e");return a.join("")})}},textarea:function(a,e,c){if(!(3>arguments.length)){b.call(this,e);var f=this,g=this._.inputId=CKEDITOR.tools.getNextId()+"_textarea",h={};e.validate&&(this.validate=e.validate);h.rows=e.rows||5;h.cols=e.cols||20;h["class"]="cke_dialog_ui_input_textarea "+(e["class"]||"");"undefined"!=typeof e.inputStyle&&(h.style=e.inputStyle);e.dir&&(h.dir=e.dir);if(f.bidi)a.on("load", +function(){f.getInputElement().on("keyup",l)},f);CKEDITOR.ui.dialog.labeledElement.call(this,a,e,c,function(){h["aria-labelledby"]=this._.labelId;this._.required&&(h["aria-required"]=this._.required);var a=['\x3cdiv class\x3d"cke_dialog_ui_input_textarea" role\x3d"presentation"\x3e\x3ctextarea id\x3d"',g,'" '],c;for(c in h)a.push(c+'\x3d"'+CKEDITOR.tools.htmlEncode(h[c])+'" ');a.push("\x3e",CKEDITOR.tools.htmlEncode(f._["default"]),"\x3c/textarea\x3e\x3c/div\x3e");return a.join("")})}},checkbox:function(a, +e,c){if(!(3>arguments.length)){var f=b.call(this,e,{"default":!!e["default"]});e.validate&&(this.validate=e.validate);CKEDITOR.ui.dialog.uiElement.call(this,a,e,c,"span",null,null,function(){var c=CKEDITOR.tools.extend({},e,{id:e.id?e.id+"_checkbox":CKEDITOR.tools.getNextId()+"_checkbox"},!0),b=[],g=CKEDITOR.tools.getNextId()+"_label",h={"class":"cke_dialog_ui_checkbox_input",type:"checkbox","aria-labelledby":g};k(c);e["default"]&&(h.checked="checked");"undefined"!=typeof c.inputStyle&&(c.style=c.inputStyle); +f.checkbox=new CKEDITOR.ui.dialog.uiElement(a,c,b,"input",null,h);b.push(' \x3clabel id\x3d"',g,'" for\x3d"',h.id,'"'+(e.labelStyle?' style\x3d"'+e.labelStyle+'"':"")+"\x3e",CKEDITOR.tools.htmlEncode(e.label),"\x3c/label\x3e");return b.join("")})}},radio:function(a,e,c){if(!(3>arguments.length)){b.call(this,e);this._["default"]||(this._["default"]=this._.initValue=e.items[0][1]);e.validate&&(this.validate=e.validate);var f=[],g=this;e.role="radiogroup";e.includeLabel=!0;CKEDITOR.ui.dialog.labeledElement.call(this, +a,e,c,function(){for(var c=[],b=[],h=(e.id?e.id:CKEDITOR.tools.getNextId())+"_radio",l=0;larguments.length)){var f=b.call(this,e);e.validate&&(this.validate=e.validate);f.inputId=CKEDITOR.tools.getNextId()+"_select";CKEDITOR.ui.dialog.labeledElement.call(this,a,e,c,function(){var c=CKEDITOR.tools.extend({},e,{id:e.id?e.id+"_select":CKEDITOR.tools.getNextId()+"_select"},!0),b=[],g=[],h={id:f.inputId,"class":"cke_dialog_ui_input_select","aria-labelledby":this._.labelId};b.push('\x3cdiv class\x3d"cke_dialog_ui_input_', +e.type,'" role\x3d"presentation"');e.width&&b.push('style\x3d"width:'+e.width+'" ');b.push("\x3e");void 0!==e.size&&(h.size=e.size);void 0!==e.multiple&&(h.multiple=e.multiple);k(c);for(var l=0,m;larguments.length)){void 0===e["default"]&&(e["default"]="");var f=CKEDITOR.tools.extend(b.call(this,e),{definition:e,buttons:[]});e.validate&&(this.validate=e.validate);a.on("load",function(){CKEDITOR.document.getById(f.frameId).getParent().addClass("cke_dialog_ui_input_file")});CKEDITOR.ui.dialog.labeledElement.call(this,a,e,c,function(){f.frameId=CKEDITOR.tools.getNextId()+"_fileInput";var a=['\x3ciframe frameborder\x3d"0" allowtransparency\x3d"0" class\x3d"cke_dialog_ui_input_file" role\x3d"presentation" id\x3d"', +f.frameId,'" title\x3d"',e.label,'" src\x3d"javascript:void('];a.push(CKEDITOR.env.ie?"(function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+"})()":"0");a.push(')"\x3e\x3c/iframe\x3e');return a.join("")})}},fileButton:function(a,e,c){var f=this;if(!(3>arguments.length)){b.call(this,e);e.validate&&(this.validate=e.validate);var g=CKEDITOR.tools.extend({},e),h=g.onClick;g.className=(g.className?g.className+" ":"")+"cke_dialog_ui_button";g.onClick=function(c){var b= +e["for"];c=h?h.call(this,c):!1;!1!==c&&("xhr"!==c&&a.getContentElement(b[0],b[1]).submit(),this.disable())};a.on("load",function(){a.getContentElement(e["for"][0],e["for"][1])._.buttons.push(f)});CKEDITOR.ui.dialog.button.call(this,a,g,c)}},html:function(){var a=/^\s*<[\w:]+\s+([^>]*)?>/,b=/^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,c=/\/$/;return function(f,g,h){if(!(3>arguments.length)){var k=[],l=g.html;"\x3c"!=l.charAt(0)&&(l="\x3cspan\x3e"+l+"\x3c/span\x3e");var m=g.focus;if(m){var u=this.focus; +this.focus=function(){("function"==typeof m?m:u).call(this);this.fire("focus")};g.isFocusable&&(this.isFocusable=this.isFocusable);this.keyboardFocusable=!0}CKEDITOR.ui.dialog.uiElement.call(this,f,g,k,"span",null,null,"");k=k.join("").match(a);l=l.match(b)||["","",""];c.test(l[1])&&(l[1]=l[1].slice(0,-1),l[2]="/"+l[2]);h.push([l[1]," ",k[1]||"",l[2]].join(""))}}}(),fieldset:function(a,b,c,f,g){var h=g.label;this._={children:b};CKEDITOR.ui.dialog.uiElement.call(this,a,g,f,"fieldset",null,null,function(){var a= +[];h&&a.push("\x3clegend"+(g.labelStyle?' style\x3d"'+g.labelStyle+'"':"")+"\x3e"+h+"\x3c/legend\x3e");for(var d=0;db.getChildCount()?(new CKEDITOR.dom.text(a,CKEDITOR.document)).appendTo(b):b.getChild(0).$.nodeValue= +a;return this},getLabel:function(){var a=CKEDITOR.document.getById(this._.labelId);return!a||1>a.getChildCount()?"":a.getChild(0).getText()},eventProcessors:a},!0);CKEDITOR.ui.dialog.button.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{click:function(){return this._.disabled?!1:this.fire("click",{dialog:this._.dialog})},enable:function(){this._.disabled=!1;var a=this.getElement();a&&a.removeClass("cke_disabled")},disable:function(){this._.disabled=!0;this.getElement().addClass("cke_disabled")}, +isVisible:function(){return this.getElement().getFirst().isVisible()},isEnabled:function(){return!this._.disabled},eventProcessors:CKEDITOR.tools.extend({},CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,{onClick:function(a,b){this.on("click",function(){b.apply(this,arguments)})}},!0),accessKeyUp:function(){this.click()},accessKeyDown:function(){this.focus()},keyboardFocusable:!0},!0);CKEDITOR.ui.dialog.textInput.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return CKEDITOR.document.getById(this._.inputId)}, +focus:function(){var a=this.selectParentTab();setTimeout(function(){var b=a.getInputElement();b&&b.$.focus()},0)},select:function(){var a=this.selectParentTab();setTimeout(function(){var b=a.getInputElement();b&&(b.$.focus(),b.$.select())},0)},accessKeyUp:function(){this.select()},setValue:function(a){if(this.bidi){var b=a&&a.charAt(0);(b="‪"==b?"ltr":"‫"==b?"rtl":null)&&(a=a.slice(1));this.setDirectionMarker(b)}a||(a="");return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply(this,arguments)}, +getValue:function(){var a=CKEDITOR.ui.dialog.uiElement.prototype.getValue.call(this);if(this.bidi&&a){var b=this.getDirectionMarker();b&&(a=("ltr"==b?"‪":"‫")+a)}return a},setDirectionMarker:function(a){var b=this.getInputElement();a?b.setAttributes({dir:a,"data-cke-dir-marker":a}):this.getDirectionMarker()&&b.removeAttributes(["dir","data-cke-dir-marker"])},getDirectionMarker:function(){return this.getInputElement().data("cke-dir-marker")},keyboardFocusable:!0},f,!0);CKEDITOR.ui.dialog.textarea.prototype= +new CKEDITOR.ui.dialog.textInput;CKEDITOR.ui.dialog.select.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return this._.select.getElement()},add:function(a,b,c){var f=new CKEDITOR.dom.element("option",this.getDialog().getParentEditor().document),g=this.getInputElement().$;f.$.text=a;f.$.value=void 0===b||null===b?a:b;void 0===c||null===c?CKEDITOR.env.ie?g.add(f.$):g.add(f.$,null):g.add(f.$,c);return this},remove:function(a){this.getInputElement().$.remove(a); +return this},clear:function(){for(var a=this.getInputElement().$;0c-a;d--)if(this._.tabs[this._.tabIdList[d%a]][0].$.offsetHeight)return this._.tabIdList[d%a];return null}function h(){for(var a=this._.tabIdList.length,c=CKEDITOR.tools.indexOf(this._.tabIdList,this._.currentTabId),d=c+1;dl.width-k.width-g?l.width-k.width+("rtl"==f.lang.dir?0:h[1]):e.x;k=e.y+h[0]l.height-k.height-g?l.height-k.height+h[2]:e.y;n=Math.floor(n);k=Math.floor(k); +a.move(n,k,1);d.data.preventDefault()}function d(){CKEDITOR.document.removeListener("mousemove",c);CKEDITOR.document.removeListener("mouseup",d);if(CKEDITOR.env.ie6Compat){var a=A.getChild(0).getFrameDocument();a.removeListener("mousemove",c);a.removeListener("mouseup",d)}}var b=null,e=null,f=a.getParentEditor(),g=f.config.dialog_magnetDistance,h=CKEDITOR.skin.margins||[0,0,0,0];"undefined"==typeof g&&(g=20);a.parts.title.on("mousedown",function(f){if(!a._.moved){var g=a._.element;g.getFirst().setStyle("position", +"absolute");g.removeStyle("display");a._.moved=!0;a.layout()}b={x:f.data.$.screenX,y:f.data.$.screenY};CKEDITOR.document.on("mousemove",c);CKEDITOR.document.on("mouseup",d);e=a.getPosition();CKEDITOR.env.ie6Compat&&(g=A.getChild(0).getFrameDocument(),g.on("mousemove",c),g.on("mouseup",d));f.data.preventDefault()},a)}function c(a){function c(d){var q="rtl"==f.lang.dir,t=m.width,p=m.height,w=t+(d.data.$.screenX-l.x)*(q?-1:1)*(a._.moved?1:2),N=p+(d.data.$.screenY-l.y)*(a._.moved?1:2),v=a._.element.getFirst(), +v=q&&parseInt(v.getComputedStyle("right")),Y=a.getPosition();Y.x=Y.x||0;Y.y=Y.y||0;Y.y+N>k.height&&(N=k.height-Y.y);(q?v:Y.x)+w>k.width&&(w=k.width-(q?v:Y.x));N=Math.floor(N);w=Math.floor(w);if(e==CKEDITOR.DIALOG_RESIZE_WIDTH||e==CKEDITOR.DIALOG_RESIZE_BOTH)t=Math.max(b.minWidth||0,w-g);if(e==CKEDITOR.DIALOG_RESIZE_HEIGHT||e==CKEDITOR.DIALOG_RESIZE_BOTH)p=Math.max(b.minHeight||0,N-h);a.resize(t,p);a._.moved&&n(a,a._.position.x,a._.position.y);a._.moved||a.layout();d.data.preventDefault()}function d(){CKEDITOR.document.removeListener("mouseup", +d);CKEDITOR.document.removeListener("mousemove",c);q&&(q.remove(),q=null);if(CKEDITOR.env.ie6Compat){var a=A.getChild(0).getFrameDocument();a.removeListener("mouseup",d);a.removeListener("mousemove",c)}}var b=a.definition,e=b.resizable;if(e!=CKEDITOR.DIALOG_RESIZE_NONE){var f=a.getParentEditor(),g,h,k,l,m,q,t=CKEDITOR.tools.addFunction(function(b){function e(a){return a.isVisible()}m=a.getSize();var f=a.parts.contents,n=f.$.getElementsByTagName("iframe").length,t=!(CKEDITOR.env.gecko||CKEDITOR.env.ie&& +CKEDITOR.env.quirks);n&&(q=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_dialog_resize_cover" style\x3d"height: 100%; position: absolute; width: 100%; left:0; top:0;"\x3e\x3c/div\x3e'),f.append(q));h=m.height-a.parts.contents.getFirst(e).getSize("height",t);g=m.width-a.parts.contents.getFirst(e).getSize("width",1);l={x:b.screenX,y:b.screenY};k=CKEDITOR.document.getWindow().getViewPaneSize();CKEDITOR.document.on("mousemove",c);CKEDITOR.document.on("mouseup",d);CKEDITOR.env.ie6Compat&& +(f=A.getChild(0).getFrameDocument(),f.on("mousemove",c),f.on("mouseup",d));b.preventDefault&&b.preventDefault()});a.on("load",function(){var c="";e==CKEDITOR.DIALOG_RESIZE_WIDTH?c=" cke_resizer_horizontal":e==CKEDITOR.DIALOG_RESIZE_HEIGHT&&(c=" cke_resizer_vertical");c=CKEDITOR.dom.element.createFromHtml('\x3cdiv class\x3d"cke_resizer'+c+" cke_resizer_"+f.lang.dir+'" title\x3d"'+CKEDITOR.tools.htmlEncode(f.lang.common.resize)+'" onmousedown\x3d"CKEDITOR.tools.callFunction('+t+', event )"\x3e'+("ltr"== +f.lang.dir?"◢":"◣")+"\x3c/div\x3e");a.parts.footer.append(c,1)});f.on("destroy",function(){CKEDITOR.tools.removeFunction(t)})}}function n(a,c,d){var b=a.parts.dialog.getParent().getClientSize(),e=a.getSize(),f=a._.viewportRatio,g=Math.max(b.width-e.width,0),b=Math.max(b.height-e.height,0);f.width=g?c/g:f.width;f.height=b?d/b:f.height;a._.viewportRatio=f}function w(a){a.data.preventDefault(1)}function z(a){var c=a.config,d=CKEDITOR.skinName||a.config.skin,b=c.dialog_backgroundCoverColor||("moono-lisa"== +d?"black":"white"),d=c.dialog_backgroundCoverOpacity,e=c.baseFloatZIndex,c=CKEDITOR.tools.genKey(b,d,e),f=D[c];CKEDITOR.document.getBody().addClass("cke_dialog_open");f?f.show():(e=['\x3cdiv tabIndex\x3d"-1" style\x3d"position: ',CKEDITOR.env.ie6Compat?"absolute":"fixed","; z-index: ",e,"; top: 0px; left: 0px; ","; width: 100%; height: 100%;",CKEDITOR.env.ie6Compat?"":"background-color: "+b,'" class\x3d"cke_dialog_background_cover"\x3e'],CKEDITOR.env.ie6Compat&&(b="\x3chtml\x3e\x3cbody style\x3d\\'background-color:"+ +b+";\\'\x3e\x3c/body\x3e\x3c/html\x3e",e.push('\x3ciframe hidefocus\x3d"true" frameborder\x3d"0" id\x3d"cke_dialog_background_iframe" src\x3d"javascript:'),e.push("void((function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.write( '"+b+"' );document.close();")+"})())"),e.push('" style\x3d"position:absolute;left:0;top:0;width:100%;height: 100%;filter: progid:DXImageTransform.Microsoft.Alpha(opacity\x3d0)"\x3e\x3c/iframe\x3e')),e.push("\x3c/div\x3e"),f=CKEDITOR.dom.element.createFromHtml(e.join("")), +f.setOpacity(void 0!==d?d:.5),f.on("keydown",w),f.on("keypress",w),f.on("keyup",w),f.appendTo(CKEDITOR.document.getBody()),D[c]=f);a.focusManager.add(f);A=f;CKEDITOR.env.mac&&CKEDITOR.env.webkit||f.focus()}function r(a){CKEDITOR.document.getBody().removeClass("cke_dialog_open");A&&(a.focusManager.remove(A),A.hide())}var p=CKEDITOR.tools.cssLength,q=!CKEDITOR.env.ie||CKEDITOR.env.edge,u='\x3cdiv class\x3d"cke_reset_all cke_dialog_container {editorId} {editorDialogClass} {hidpi}" dir\x3d"{langDir}" style\x3d"'+ +(q?"display:flex":"")+'" lang\x3d"{langCode}" role\x3d"dialog" aria-labelledby\x3d"cke_dialog_title_{id}"\x3e\x3ctable class\x3d"cke_dialog '+CKEDITOR.env.cssClass+' cke_{langDir}" style\x3d"'+(q?"margin:auto":"position:absolute")+'" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd role\x3d"presentation"\x3e\x3cdiv class\x3d"cke_dialog_body" role\x3d"presentation"\x3e\x3cdiv id\x3d"cke_dialog_title_{id}" class\x3d"cke_dialog_title" role\x3d"presentation"\x3e\x3c/div\x3e\x3ca id\x3d"cke_dialog_close_button_{id}" class\x3d"cke_dialog_close_button" href\x3d"javascript:void(0)" title\x3d"{closeTitle}" role\x3d"button"\x3e\x3cspan class\x3d"cke_label"\x3eX\x3c/span\x3e\x3c/a\x3e\x3cdiv id\x3d"cke_dialog_tabs_{id}" class\x3d"cke_dialog_tabs" role\x3d"tablist"\x3e\x3c/div\x3e\x3ctable class\x3d"cke_dialog_contents" role\x3d"presentation"\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_contents_{id}" class\x3d"cke_dialog_contents_body" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3ctr\x3e\x3ctd id\x3d"cke_dialog_footer_{id}" class\x3d"cke_dialog_footer" role\x3d"presentation"\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e\x3c/td\x3e\x3c/tr\x3e\x3c/table\x3e\x3c/div\x3e'; +CKEDITOR.dialog=function(d,g){function k(){var a=A._.focusList;a.sort(function(a,c){return a.tabIndex!=c.tabIndex?c.tabIndex-a.tabIndex:a.focusIndex-c.focusIndex});for(var c=a.length,d=0;dc.length)){var d=A._.currentFocusIndex;A._.tabBarMode&&0>a&&(d=0);try{c[d].getInputElement().$.blur()}catch(b){}var e=d,f=1arguments.length)){var h=(b.call?b(c):b)||"div",k=["\x3c",h," "],l=(e&&e.call?e(c):e)||{},n=(f&&f.call?f(c):f)||{},m=(g&&g.call?g.call(this,a,c):g)||"",q=this.domId=n.id||CKEDITOR.tools.getNextId()+"_uiElement";c.requiredContent&&!a.getParentEditor().filter.check(c.requiredContent)&&(l.display="none",this.notAllowed=!0);n.id=q;var t={};c.type&&(t["cke_dialog_ui_"+ +c.type]=1);c.className&&(t[c.className]=1);c.disabled&&(t.cke_disabled=1);for(var p=n["class"]&&n["class"].split?n["class"].split(" "):[],q=0;qCKEDITOR.env.version?"cke_dialog_ui_focused":"";c.on("focus",function(){a._.tabBarMode=!1;a._.hasFocus=!0;w.fire("focus"); +d&&this.addClass(d)});c.on("blur",function(){w.fire("blur");d&&this.removeClass(d)})}});CKEDITOR.tools.extend(this,c);this.keyboardFocusable&&(this.tabIndex=c.tabIndex||0,this.focusIndex=a._.focusList.push(this)-1,this.on("focus",function(){a._.currentFocusIndex=w.focusIndex}))}},hbox:function(a,c,d,b,e){if(!(4>arguments.length)){this._||(this._={});var f=this._.children=c,g=e&&e.widths||null,h=e&&e.height||null,k,l={role:"presentation"};e&&e.align&&(l.align=e.align);CKEDITOR.ui.dialog.uiElement.call(this, +a,e||{type:"hbox"},b,"table",{},l,function(){var a=['\x3ctbody\x3e\x3ctr class\x3d"cke_dialog_ui_hbox"\x3e'];for(k=0;karguments.length)){this._||(this._={});var f=this._.children=c,g=e&&e.width||null,h=e&&e.heights||null;CKEDITOR.ui.dialog.uiElement.call(this,a,e||{type:"vbox"},b,"div",null,{role:"presentation"},function(){var c=['\x3ctable role\x3d"presentation" cellspacing\x3d"0" border\x3d"0" ']; +c.push('style\x3d"');e&&e.expand&&c.push("height:100%;");c.push("width:"+p(g||"100%"),";");CKEDITOR.env.webkit&&c.push("float:none;");c.push('"');c.push('align\x3d"',CKEDITOR.tools.htmlEncode(e&&e.align||("ltr"==a.getParentEditor().lang.dir?"left":"right")),'" ');c.push("\x3e\x3ctbody\x3e");for(var b=0;barguments.length)return this._.children.concat();a.splice||(a=[a]);return 2>a.length?this._.children[a[0]]:this._.children[a[0]]&&this._.children[a[0]].getChild?this._.children[a[0]].getChild(a.slice(1,a.length)):null}},!0);CKEDITOR.ui.dialog.vbox.prototype=new CKEDITOR.ui.dialog.hbox;(function(){var a={build:function(a,b,d){for(var c=b.children,e,g=[],f=[],h=0;hd.$.clientHeight?d.setStyle("overflow-y","hidden"):d.removeStyle("overflow-y"))}var b,l,k,d,g,m=e.config.autoGrow_bottomSpace|| -0,a=void 0!==e.config.autoGrow_minHeight?e.config.autoGrow_minHeight:200,n=e.config.autoGrow_maxHeight||Infinity,w=!e.config.autoGrow_maxHeight;e.addCommand("autogrow",{exec:h,modes:{wysiwyg:1},readOnly:1,canUndo:!1,editorFocus:!1});var u={contentDom:1,key:1,selectionChange:1,insertElement:1,mode:1},t;for(t in u)e.on(t,function(a){"wysiwyg"==a.editor.mode&&setTimeout(function(){var a=e.getCommand("maximize");!e.window||a&&a.state==CKEDITOR.TRISTATE_ON?b=null:(h(),w||h())},100)});e.on("afterCommandExec", -function(a){"maximize"==a.data.name&&"wysiwyg"==a.editor.mode&&(a.data.command.state==CKEDITOR.TRISTATE_ON?d.removeStyle("overflow-y"):h())});e.on("contentDom",c);c();e.config.autoGrow_onStartup&&e.editable().isVisible()&&e.execCommand("autogrow")}CKEDITOR.plugins.add("autogrow",{init:function(f){if(f.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE)f.on("instanceReady",function(){f.editable().isInline()?f.ui.space("contents").setStyle("height","auto"):e(f)})}})}(),CKEDITOR.plugins.add("basicstyles",{init:function(e){var f= -0,c=function(b,d,c,m){if(m){m=new CKEDITOR.style(m);var a=h[c];a.unshift(m);e.attachStyleStateChange(m,function(a){!e.readOnly&&e.getCommand(c).setState(a)});e.addCommand(c,new CKEDITOR.styleCommand(m,{contentForms:a}));e.ui.addButton&&e.ui.addButton(b,{label:d,command:c,toolbar:"basicstyles,"+(f+=10)})}},h={bold:["strong","b",["span",function(b){b=b.styles["font-weight"];return"bold"==b||700<=+b}]],italic:["em","i",["span",function(b){return"italic"==b.styles["font-style"]}]],underline:["u",["span", -function(b){return"underline"==b.styles["text-decoration"]}]],strike:["s","strike",["span",function(b){return"line-through"==b.styles["text-decoration"]}]],subscript:["sub"],superscript:["sup"]},b=e.config,l=e.lang.basicstyles;c("Bold",l.bold,"bold",b.coreStyles_bold);c("Italic",l.italic,"italic",b.coreStyles_italic);c("Underline",l.underline,"underline",b.coreStyles_underline);c("Strike",l.strike,"strike",b.coreStyles_strike);c("Subscript",l.subscript,"subscript",b.coreStyles_subscript);c("Superscript", -l.superscript,"superscript",b.coreStyles_superscript);e.setKeystroke([[CKEDITOR.CTRL+66,"bold"],[CKEDITOR.CTRL+73,"italic"],[CKEDITOR.CTRL+85,"underline"]])}}),CKEDITOR.config.coreStyles_bold={element:"strong",overrides:"b"},CKEDITOR.config.coreStyles_italic={element:"em",overrides:"i"},CKEDITOR.config.coreStyles_underline={element:"u"},CKEDITOR.config.coreStyles_strike={element:"s",overrides:"strike"},CKEDITOR.config.coreStyles_subscript={element:"sub"},CKEDITOR.config.coreStyles_superscript={element:"sup"}, -function(){var e={exec:function(e){var c=e.getCommand("blockquote").state,h=e.getSelection(),b=h&&h.getRanges()[0];if(b){var l=h.createBookmarks();if(CKEDITOR.env.ie){var k=l[0].startNode,d=l[0].endNode,g;if(k&&"blockquote"==k.getParent().getName())for(g=k;g=g.getNext();)if(g.type==CKEDITOR.NODE_ELEMENT&&g.isBlockBoundary()){k.move(g,!0);break}if(d&&"blockquote"==d.getParent().getName())for(g=d;g=g.getPrevious();)if(g.type==CKEDITOR.NODE_ELEMENT&&g.isBlockBoundary()){d.move(g);break}}var m=b.createIterator(); -m.enlargeBr=e.config.enterMode!=CKEDITOR.ENTER_BR;if(c==CKEDITOR.TRISTATE_OFF){for(k=[];c=m.getNextParagraph();)k.push(c);1>k.length&&(c=e.document.createElement(e.config.enterMode==CKEDITOR.ENTER_P?"p":"div"),d=l.shift(),b.insertNode(c),c.append(new CKEDITOR.dom.text("",e.document)),b.moveToBookmark(d),b.selectNodeContents(c),b.collapse(!0),d=b.createBookmark(),k.push(c),l.unshift(d));g=k[0].getParent();b=[];for(d=0;de||(this.notifications.splice(e,1),c.element.remove(),this.element.getChildCount()||(this._removeListeners(),this.element.remove()))},_createElement:function(){var c=this.editor,e=c.config,b=new CKEDITOR.dom.element("div");b.addClass("cke_notifications_area"); -b.setAttribute("id","cke_notifications_area_"+c.name);b.setStyle("z-index",e.baseFloatZIndex-2);return b},_attachListeners:function(){var c=CKEDITOR.document.getWindow(),e=this.editor;c.on("scroll",this._uiBuffer.input);c.on("resize",this._uiBuffer.input);e.on("change",this._changeBuffer.input);e.on("floatingSpaceLayout",this._layout,this,null,20);e.on("blur",this._layout,this,null,20)},_removeListeners:function(){var c=CKEDITOR.document.getWindow(),e=this.editor;c.removeListener("scroll",this._uiBuffer.input); -c.removeListener("resize",this._uiBuffer.input);e.removeListener("change",this._changeBuffer.input);e.removeListener("floatingSpaceLayout",this._layout);e.removeListener("blur",this._layout)},_layout:function(){function c(){e.setStyle("left",v(q+f.width-n-w))}var e=this.element,b=this.editor,f=b.ui.contentsElement.getClientRect(),k=b.ui.contentsElement.getDocumentPosition(),d,g,m=e.getClientRect(),a,n=this._notificationWidth,w=this._notificationMargin;a=CKEDITOR.document.getWindow();var u=a.getScrollPosition(), -t=a.getViewPaneSize(),p=CKEDITOR.document.getBody(),r=p.getDocumentPosition(),v=CKEDITOR.tools.cssLength;n&&w||(a=this.element.getChild(0),n=this._notificationWidth=a.getClientRect().width,w=this._notificationMargin=parseInt(a.getComputedStyle("margin-left"),10)+parseInt(a.getComputedStyle("margin-right"),10));b.toolbar&&(d=b.ui.space("top"),g=d.getClientRect());d&&d.isVisible()&&g.bottom>f.top&&g.bottomu.y?e.setStyles({position:"fixed",top:0}):e.setStyles({position:"absolute",top:v(k.y+f.height-m.height)});var q="fixed"==e.getStyle("position")?f.left:"static"!=p.getComputedStyle("position")?k.x-r.x:k.x;f.widthu.x+t.width?c():e.setStyle("left",v(q)):k.x+n+w>u.x+t.width?e.setStyle("left",v(q)):k.x+f.width/2+n/2+w>u.x+t.width?e.setStyle("left",v(q-k.x+u.x+t.width-n-w)):0>f.left+f.width-n-w?c():0>f.left+f.width/2-n/2?e.setStyle("left",v(q-k.x+u.x)):e.setStyle("left", -v(q+f.width/2-n/2-w/2))}};CKEDITOR.plugins.notification=e}(),function(){var e='\x3ca id\x3d"{id}" class\x3d"cke_button cke_button__{name} cke_button_{state} {cls}"'+(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' title\x3d"{title}" tabindex\x3d"-1" hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-describedby\x3d"{id}_description" aria-haspopup\x3d"{hasArrow}" aria-disabled\x3d"{ariaDisabled}"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(e+= -' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(e+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');var f="";CKEDITOR.env.ie&&(f='return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26');var e=e+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" onclick\x3d"'+f+'CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan class\x3d"cke_button_icon cke_button__{iconName}_icon" style\x3d"{style}"')+ -'\x3e\x26nbsp;\x3c/span\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_button_label cke_button__{name}_label" aria-hidden\x3d"false"\x3e{label}\x3c/span\x3e\x3cspan id\x3d"{id}_description" class\x3d"cke_button_label" aria-hidden\x3d"false"\x3e{ariaShortcut}\x3c/span\x3e{arrowHtml}\x3c/a\x3e',c=CKEDITOR.addTemplate("buttonArrow",'\x3cspan class\x3d"cke_button_arrow"\x3e'+(CKEDITOR.env.hc?"\x26#9660;":"")+"\x3c/span\x3e"),h=CKEDITOR.addTemplate("button",e);CKEDITOR.plugins.add("button",{beforeInit:function(b){b.ui.addHandler(CKEDITOR.UI_BUTTON, -CKEDITOR.ui.button.handler)}});CKEDITOR.UI_BUTTON="button";CKEDITOR.ui.button=function(b){CKEDITOR.tools.extend(this,b,{title:b.label,click:b.click||function(c){c.execCommand(b.command)}});this._={}};CKEDITOR.ui.button.handler={create:function(b){return new CKEDITOR.ui.button(b)}};CKEDITOR.ui.button.prototype={render:function(b,e){function f(){var a=b.mode;a&&(a=this.modes[a]?void 0!==d[a]?d[a]:CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,a=b.readOnly&&!this.readOnly?CKEDITOR.TRISTATE_DISABLED: -a,this.setState(a),this.refresh&&this.refresh())}var d=null,g=CKEDITOR.env,m=this._.id=CKEDITOR.tools.getNextId(),a="",n=this.command,w,u,t;this._.editor=b;var p={id:m,button:this,editor:b,focus:function(){CKEDITOR.document.getById(m).focus()},execute:function(){this.button.click(b)},attach:function(a){this.button.attach(a)}},r=CKEDITOR.tools.addFunction(function(a){if(p.onkey)return a=new CKEDITOR.dom.event(a),!1!==p.onkey(p,a.getKeystroke())}),v=CKEDITOR.tools.addFunction(function(a){var b;p.onfocus&& -(b=!1!==p.onfocus(p,new CKEDITOR.dom.event(a)));return b}),q=0;p.clickFn=w=CKEDITOR.tools.addFunction(function(){q&&(b.unlockSelection(1),q=0);p.execute();g.iOS&&b.focus()});this.modes?(d={},b.on("beforeModeUnload",function(){b.mode&&this._.state!=CKEDITOR.TRISTATE_DISABLED&&(d[b.mode]=this._.state)},this),b.on("activeFilterChange",f,this),b.on("mode",f,this),!this.readOnly&&b.on("readOnly",f,this)):n&&(n=b.getCommand(n))&&(n.on("state",function(){this.setState(n.state)},this),a+=n.state==CKEDITOR.TRISTATE_ON? -"on":n.state==CKEDITOR.TRISTATE_DISABLED?"disabled":"off");var B;if(this.directional)b.on("contentDirChanged",function(a){var d=CKEDITOR.document.getById(this._.id),c=d.getFirst();a=a.data;a!=b.lang.dir?d.addClass("cke_"+a):d.removeClass("cke_ltr").removeClass("cke_rtl");c.setAttribute("style",CKEDITOR.skin.getIconStyle(B,"rtl"==a,this.icon,this.iconOffset))},this);n?(u=b.getCommandKeystroke(n))&&(t=CKEDITOR.tools.keystrokeToString(b.lang.common.keyboard,u)):a+="off";u=this.name||this.command;var x= -null,y=this.icon;B=u;this.icon&&!/\./.test(this.icon)?(B=this.icon,y=null):(this.icon&&(x=this.icon),CKEDITOR.env.hidpi&&this.iconHiDpi&&(x=this.iconHiDpi));x?(CKEDITOR.skin.addIcon(x,x),y=null):x=B;a={id:m,name:u,iconName:B,label:this.label,cls:(this.hasArrow?"cke_button_expandable ":"")+(this.className||""),state:a,ariaDisabled:"disabled"==a?"true":"false",title:this.title+(t?" ("+t.display+")":""),ariaShortcut:t?b.lang.common.keyboardShortcut+" "+t.aria:"",titleJs:g.gecko&&!g.hc?"":(this.title|| -"").replace("'",""),hasArrow:"string"===typeof this.hasArrow&&this.hasArrow||(this.hasArrow?"true":"false"),keydownFn:r,focusFn:v,clickFn:w,style:CKEDITOR.skin.getIconStyle(x,"rtl"==b.lang.dir,y,this.iconOffset),arrowHtml:this.hasArrow?c.output():""};h.output(a,e);if(this.onRender)this.onRender();return p},setState:function(b){if(this._.state==b)return!1;this._.state=b;var c=CKEDITOR.document.getById(this._.id);return c?(c.setState(b,"cke_button"),c.setAttribute("aria-disabled",b==CKEDITOR.TRISTATE_DISABLED), -this.hasArrow?c.setAttribute("aria-expanded",b==CKEDITOR.TRISTATE_ON):b===CKEDITOR.TRISTATE_ON?c.setAttribute("aria-pressed",!0):c.removeAttribute("aria-pressed"),!0):!1},getState:function(){return this._.state},toFeature:function(b){if(this._.feature)return this._.feature;var c=this;this.allowedContent||this.requiredContent||!this.command||(c=b.getCommand(this.command)||c);return this._.feature=c}};CKEDITOR.ui.prototype.addButton=function(b,c){this.add(b,CKEDITOR.UI_BUTTON,c)}}(),function(){function e(b){function c(){for(var a= -e(),g=CKEDITOR.tools.clone(b.config.toolbarGroups)||f(b),h=0;hb.order?-1:0>a.order? -1:a.order]+data-cke-bookmark[^<]*?<\/span>/ig,"");c&&e(a,d)})}function A(){if("wysiwyg"==a.mode){var b=D("paste");a.getCommand("cut").setState(D("cut"));a.getCommand("copy").setState(D("copy"));a.getCommand("paste").setState(b);a.fire("pasteState",b)}}function D(b){if(H&& -b in{paste:1,cut:1})return CKEDITOR.TRISTATE_DISABLED;if("paste"==b)return CKEDITOR.TRISTATE_OFF;b=a.getSelection();var d=b.getRanges();return b.getType()==CKEDITOR.SELECTION_NONE||1==d.length&&d[0].collapsed?CKEDITOR.TRISTATE_DISABLED:CKEDITOR.TRISTATE_OFF}var z=CKEDITOR.plugins.clipboard,J=0,E=0,H=0;(function(){a.on("key",x);a.on("contentDom",b);a.on("selectionChange",function(a){H=a.data.selection.getRanges()[0].checkReadOnly();A()});if(a.contextMenu){a.contextMenu.addListener(function(a,b){H= -b.getRanges()[0].checkReadOnly();return{cut:D("cut"),copy:D("copy"),paste:D("paste")}});var d=null;a.on("menuShow",function(){d&&(d.removeListener(),d=null);var b=a.contextMenu.findItemByCommandName("paste");b&&b.element&&(d=b.element.on("touchend",function(){a._.forcePasteDialog=!0}))})}if(a.ui.addButton)a.once("instanceReady",function(){a._.pasteButtons&&CKEDITOR.tools.array.forEach(a._.pasteButtons,function(b){if(b=a.ui.get(b))if(b=CKEDITOR.document.getById(b._.id))b.on("touchend",function(){a._.forcePasteDialog= -!0})})})})();(function(){function b(d,c,e,g,f){var h=a.lang.clipboard[c];a.addCommand(c,e);a.ui.addButton&&a.ui.addButton(d,{label:h,command:c,toolbar:"clipboard,"+g});a.addMenuItems&&a.addMenuItem(c,{label:h,command:c,group:"clipboard",order:f})}b("Cut","cut",d("cut"),10,1);b("Copy","copy",d("copy"),20,4);b("Paste","paste",c(),30,8);a._.pasteButtons||(a._.pasteButtons=[]);a._.pasteButtons.push("Paste")})();a.getClipboardData=function(b,d){function c(a){a.removeListener();a.cancel();d(a.data)}function e(a){a.removeListener(); -a.cancel();d({type:f,dataValue:a.data.dataValue,dataTransfer:a.data.dataTransfer,method:"paste"})}var g=!1,f="auto";d||(d=b,b=null);a.on("beforePaste",function(a){a.removeListener();g=!0;f=a.data.type},null,null,1E3);a.on("paste",c,null,null,0);!1===l()&&(a.removeListener("paste",c),a._.forcePasteDialog&&g&&a.fire("pasteDialog")?(a.on("pasteDialogCommit",e),a.on("dialogHide",function(a){a.removeListener();a.data.removeListener("pasteDialogCommit",e);a.data._.committed||d(null)})):d(null))}}function c(a){if(CKEDITOR.env.webkit){if(!a.match(/^[^<]*$/g)&& -!a.match(/^(
<\/div>|
[^<]*<\/div>)*$/gi))return"html"}else if(CKEDITOR.env.ie){if(!a.match(/^([^<]|)*$/gi)&&!a.match(/^(

([^<]|)*<\/p>|(\r\n))*$/gi))return"html"}else if(CKEDITOR.env.gecko){if(!a.match(/^([^<]|)*$/gi))return"html"}else return"html";return"htmlifiedtext"}function h(a,b){function d(a){return CKEDITOR.tools.repeat("\x3c/p\x3e\x3cp\x3e",~~(a/2))+(1==a%2?"\x3cbr\x3e":"")}b=b.replace(/(?!\u3000)\s+/g," ").replace(/> +/gi, -"\x3cbr\x3e");b=b.replace(/<\/?[A-Z]+>/g,function(a){return a.toLowerCase()});if(b.match(/^[^<]$/))return b;CKEDITOR.env.webkit&&-1(
|)<\/div>)(?!$|(

(
|)<\/div>))/g,"\x3cbr\x3e").replace(/^(
(
|)<\/div>){2}(?!$)/g,"\x3cdiv\x3e\x3c/div\x3e"),b.match(/
(
|)<\/div>/)&&(b="\x3cp\x3e"+b.replace(/(
(
|)<\/div>)+/g,function(a){return d(a.split("\x3c/div\x3e\x3cdiv\x3e").length+1)})+"\x3c/p\x3e"),b=b.replace(/<\/div>
/g,"\x3cbr\x3e"), -b=b.replace(/<\/?div>/g,""));CKEDITOR.env.gecko&&a.enterMode!=CKEDITOR.ENTER_BR&&(CKEDITOR.env.gecko&&(b=b.replace(/^

$/,"\x3cbr\x3e")),-1){2,}/g,function(a){return d(a.length/4)})+"\x3c/p\x3e"));return k(a,b)}function b(a){function b(){var a={},d;for(d in CKEDITOR.dtd)"$"!=d.charAt(0)&&"div"!=d&&"span"!=d&&(a[d]=1);return a}var d={};return{get:function(c){return"plain-text"==c?d.plainText||(d.plainText=new CKEDITOR.filter(a, -"br")):"semantic-content"==c?((c=d.semanticContent)||(c=new CKEDITOR.filter(a,{}),c.allow({$1:{elements:b(),attributes:!0,styles:!1,classes:!1}}),c=d.semanticContent=c),c):c?new CKEDITOR.filter(a,c):null}}}function l(a,b,d){b=CKEDITOR.htmlParser.fragment.fromHtml(b);var c=new CKEDITOR.htmlParser.basicWriter;d.applyTo(b,!0,!1,a.activeEnterMode);b.writeHtml(c);return c.getHtml()}function k(a,b){a.enterMode==CKEDITOR.ENTER_BR?b=b.replace(/(<\/p>

)+/g,function(a){return CKEDITOR.tools.repeat("\x3cbr\x3e", -a.length/7*2)}).replace(/<\/?p>/g,""):a.enterMode==CKEDITOR.ENTER_DIV&&(b=b.replace(/<(\/)?p>/g,"\x3c$1div\x3e"));return b}function d(a){a.data.preventDefault();a.data.$.dataTransfer.dropEffect="none"}function g(a){var b=CKEDITOR.plugins.clipboard;a.on("contentDom",function(){function d(b,c,g){c.select();e(a,{dataTransfer:g,method:"drop"},1);g.sourceEditor.fire("saveSnapshot");g.sourceEditor.editable().extractHtmlFromRange(b);g.sourceEditor.getSelection().selectRanges([b]);g.sourceEditor.fire("saveSnapshot")} -function c(d,g){d.select();e(a,{dataTransfer:g,method:"drop"},1);b.resetDragDataTransfer()}function g(b,d,c){var e={$:b.data.$,target:b.data.getTarget()};d&&(e.dragRange=d);c&&(e.dropRange=c);!1===a.fire(b.name,e)&&b.data.preventDefault()}function f(a){a.type!=CKEDITOR.NODE_ELEMENT&&(a=a.getParent());return a.getChildCount()}var h=a.editable(),k=CKEDITOR.plugins.clipboard.getDropTarget(a),m=a.ui.space("top"),l=a.ui.space("bottom");b.preventDefaultDropOnElement(m);b.preventDefaultDropOnElement(l); -h.attachListener(k,"dragstart",g);h.attachListener(a,"dragstart",b.resetDragDataTransfer,b,null,1);h.attachListener(a,"dragstart",function(d){b.initDragDataTransfer(d,a)},null,null,2);h.attachListener(a,"dragstart",function(){var d=b.dragRange=a.getSelection().getRanges()[0];CKEDITOR.env.ie&&10>CKEDITOR.env.version&&(b.dragStartContainerChildCount=d?f(d.startContainer):null,b.dragEndContainerChildCount=d?f(d.endContainer):null)},null,null,100);h.attachListener(k,"dragend",g);h.attachListener(a,"dragend", -b.initDragDataTransfer,b,null,1);h.attachListener(a,"dragend",b.resetDragDataTransfer,b,null,100);h.attachListener(k,"dragover",function(a){if(CKEDITOR.env.edge)a.data.preventDefault();else{var b=a.data.getTarget();b&&b.is&&b.is("html")?a.data.preventDefault():CKEDITOR.env.ie&&CKEDITOR.plugins.clipboard.isFileApiSupported&&a.data.$.dataTransfer.types.contains("Files")&&a.data.preventDefault()}});h.attachListener(k,"drop",function(d){if(!d.data.$.defaultPrevented){d.data.preventDefault();var c=d.data.getTarget(); -if(!c.isReadOnly()||c.type==CKEDITOR.NODE_ELEMENT&&c.is("html")){var c=b.getRangeAtDropPosition(d,a),e=b.dragRange;c&&g(d,e,c)}}},null,null,9999);h.attachListener(a,"drop",b.initDragDataTransfer,b,null,1);h.attachListener(a,"drop",function(e){if(e=e.data){var g=e.dropRange,f=e.dragRange,h=e.dataTransfer;h.getTransferType(a)==CKEDITOR.DATA_TRANSFER_INTERNAL?setTimeout(function(){b.internalDrop(f,g,h,a)},0):h.getTransferType(a)==CKEDITOR.DATA_TRANSFER_CROSS_EDITORS?d(f,g,h):c(g,h)}},null,null,9999)})} -var m;CKEDITOR.plugins.add("clipboard",{requires:"dialog,notification,toolbar",init:function(a){var d,e=b(a);a.config.forcePasteAsPlainText?d="plain-text":a.config.pasteFilter?d=a.config.pasteFilter:!CKEDITOR.env.webkit||"pasteFilter"in a.config||(d="semantic-content");a.pasteFilter=e.get(d);f(a);g(a);CKEDITOR.dialog.add("paste",CKEDITOR.getUrl(this.path+"dialogs/paste.js"));if(CKEDITOR.env.gecko){var k=["image/png","image/jpeg","image/gif"],m;a.on("paste",function(b){var d=b.data,c=d.dataTransfer; -if(!d.dataValue&&"paste"==d.method&&c&&1==c.getFilesCount()&&m!=c.id&&(c=c.getFile(0),-1!=CKEDITOR.tools.indexOf(k,c.type))){var e=new FileReader;e.addEventListener("load",function(){b.data.dataValue='\x3cimg src\x3d"'+e.result+'" /\x3e';a.fire("paste",b.data)},!1);e.addEventListener("abort",function(){a.fire("paste",b.data)},!1);e.addEventListener("error",function(){a.fire("paste",b.data)},!1);e.readAsDataURL(c);m=d.dataTransfer.id;b.stop()}},null,null,1)}a.on("paste",function(b){b.data.dataTransfer|| -(b.data.dataTransfer=new CKEDITOR.plugins.clipboard.dataTransfer);if(!b.data.dataValue){var d=b.data.dataTransfer,c=d.getData("text/html");if(c)b.data.dataValue=c,b.data.type="html";else if(c=d.getData("text/plain"))b.data.dataValue=a.editable().transformPlainTextToHtml(c),b.data.type="text"}},null,null,1);a.on("paste",function(a){var b=a.data.dataValue,d=CKEDITOR.dtd.$block;-1 <\/span>/gi," "),"html"!=a.data.type&&(b=b.replace(/]*>([^<]*)<\/span>/gi, -function(a,b){return b.replace(/\t/g,"\x26nbsp;\x26nbsp; \x26nbsp;")})),-1/,"")),b=b.replace(/(<[^>]+) class="Apple-[^"]*"/gi,"$1"));if(b.match(/^<[^<]+cke_(editable|contents)/i)){var c,e,g=new CKEDITOR.dom.element("div");for(g.setHtml(b);1==g.getChildCount()&&(c=g.getFirst())&&c.type==CKEDITOR.NODE_ELEMENT&&(c.hasClass("cke_editable")|| -c.hasClass("cke_contents"));)g=e=c;e&&(b=e.getHtml().replace(/
$/i,""))}CKEDITOR.env.ie?b=b.replace(/^ (?: |\r\n)?<(\w+)/g,function(b,c){return c.toLowerCase()in d?(a.data.preSniffing="html","\x3c"+c):b}):CKEDITOR.env.webkit?b=b.replace(/<\/(\w+)>


<\/div>$/,function(b,c){return c in d?(a.data.endsWithEOL=1,"\x3c/"+c+"\x3e"):b}):CKEDITOR.env.gecko&&(b=b.replace(/(\s)
$/,"$1"));a.data.dataValue=b},null,null,3);a.on("paste",function(b){b=b.data;var d=a._.nextPasteType||b.type,g=b.dataValue, -f,k=a.config.clipboard_defaultContentType||"html",m=b.dataTransfer.getTransferType(a)==CKEDITOR.DATA_TRANSFER_EXTERNAL,n=!0===a.config.forcePasteAsPlainText;f="html"==d||"html"==b.preSniffing?"html":c(g);delete a._.nextPasteType;"htmlifiedtext"==f&&(g=h(a.config,g));if("text"==d&&"html"==f)g=l(a,g,e.get("plain-text"));else if(m&&a.pasteFilter&&!b.dontFilter||n)g=l(a,g,a.pasteFilter);b.startsWithEOL&&(g='\x3cbr data-cke-eol\x3d"1"\x3e'+g);b.endsWithEOL&&(g+='\x3cbr data-cke-eol\x3d"1"\x3e');"auto"== -d&&(d="html"==f||"html"==k?"html":"text");b.type=d;b.dataValue=g;delete b.preSniffing;delete b.startsWithEOL;delete b.endsWithEOL},null,null,6);a.on("paste",function(b){b=b.data;b.dataValue&&(a.insertHtml(b.dataValue,b.type,b.range),setTimeout(function(){a.fire("afterPaste")},0))},null,null,1E3);a.on("pasteDialog",function(b){setTimeout(function(){a.openDialog("paste",b.data)},0)})}});CKEDITOR.plugins.clipboard={isCustomCopyCutSupported:(!CKEDITOR.env.ie||16<=CKEDITOR.env.version)&&!CKEDITOR.env.iOS, -isCustomDataTypesSupported:!CKEDITOR.env.ie||16<=CKEDITOR.env.version,isFileApiSupported:!CKEDITOR.env.ie||9CKEDITOR.env.version||b.isInline()?b:a.document},fixSplitNodesAfterDrop:function(a,b,d,c){function e(a,d,c){var g=a;g.type==CKEDITOR.NODE_TEXT&&(g=a.getParent());if(g.equals(d)&&c!=d.getChildCount())return a=b.startContainer.getChild(b.startOffset-1),d=b.startContainer.getChild(b.startOffset), -a&&a.type==CKEDITOR.NODE_TEXT&&d&&d.type==CKEDITOR.NODE_TEXT&&(c=a.getLength(),a.setText(a.getText()+d.getText()),d.remove(),b.setStart(a,c),b.collapse(!0)),!0}var g=b.startContainer;"number"==typeof c&&"number"==typeof d&&g.type==CKEDITOR.NODE_ELEMENT&&(e(a.startContainer,g,d)||e(a.endContainer,g,c))},isDropRangeAffectedByDragRange:function(a,b){var d=b.startContainer,c=b.endOffset;return a.endContainer.equals(d)&&a.endOffset<=c||a.startContainer.getParent().equals(d)&&a.startContainer.getIndex()< -c||a.endContainer.getParent().equals(d)&&a.endContainer.getIndex()CKEDITOR.env.version&&this.fixSplitNodesAfterDrop(a,b,g.dragStartContainerChildCount,g.dragEndContainerChildCount);(k=this.isDropRangeAffectedByDragRange(a,b))||(h=a.createBookmark(!1));g=b.clone().createBookmark(!1);k&&(h=a.createBookmark(!1));a=h.startNode;b= -h.endNode;k=g.startNode;b&&a.getPosition(k)&CKEDITOR.POSITION_PRECEDING&&b.getPosition(k)&CKEDITOR.POSITION_FOLLOWING&&k.insertBefore(a);a=c.createRange();a.moveToBookmark(h);f.extractHtmlFromRange(a,1);b=c.createRange();g.startNode.getCommonAncestor(f)||(g=c.getSelection().createBookmarks()[0]);b.moveToBookmark(g);e(c,{dataTransfer:d,method:"drop",range:b},1);c.fire("unlockSnapshot")},getRangeAtDropPosition:function(a,b){var d=a.data.$,c=d.clientX,e=d.clientY,g=b.getSelection(!0).getRanges()[0], -f=b.createRange();if(a.data.testRange)return a.data.testRange;if(document.caretRangeFromPoint&&b.document.$.caretRangeFromPoint(c,e))d=b.document.$.caretRangeFromPoint(c,e),f.setStart(CKEDITOR.dom.node(d.startContainer),d.startOffset),f.collapse(!0);else if(d.rangeParent)f.setStart(CKEDITOR.dom.node(d.rangeParent),d.rangeOffset),f.collapse(!0);else{if(CKEDITOR.env.ie&&8k&&!h;k++){if(!h)try{d.moveToPoint(c,e-k),h=!0}catch(m){}if(!h)try{d.moveToPoint(c,e+k),h=!0}catch(l){}}if(h){var y="cke-temp-"+(new Date).getTime();d.pasteHTML('\x3cspan id\x3d"'+y+'"\x3e​\x3c/span\x3e');var A=b.document.getById(y);f.moveToPosition(A,CKEDITOR.POSITION_BEFORE_START);A.remove()}else{var D=b.document.$.elementFromPoint(c,e),z=new CKEDITOR.dom.element(D),J;if(z.equals(b.editable())||"html"==z.getName())return g&&g.startContainer&&!g.startContainer.equals(b.editable())? -g:null;J=z.getClientRect();c/i,bodyRegExp:/([\s\S]*)<\/body>/i,fragmentRegExp:/\x3c!--(?:Start|End)Fragment--\x3e/g,data:{},files:[],nativeHtmlCache:"",normalizeType:function(a){a=a.toLowerCase();return"text"==a||"text/plain"==a?"Text":"url"==a?"URL":a}};this._.fallbackDataTransfer=new CKEDITOR.plugins.clipboard.fallbackDataTransfer(this);this.id=this.getData(m);this.id||(this.id="Text"==m?"":"cke-"+ -CKEDITOR.tools.getUniqueId());b&&(this.sourceEditor=b,this.setData("text/html",b.getSelectedHtml(1)),"Text"==m||this.getData("text/plain")||this.setData("text/plain",b.getSelection().getSelectedText()))};CKEDITOR.DATA_TRANSFER_INTERNAL=1;CKEDITOR.DATA_TRANSFER_CROSS_EDITORS=2;CKEDITOR.DATA_TRANSFER_EXTERNAL=3;CKEDITOR.plugins.clipboard.dataTransfer.prototype={getData:function(a,b){a=this._.normalizeType(a);var d="text/html"==a&&b?this._.nativeHtmlCache:this._.data[a];if(void 0===d||null===d||""=== -d){if(this._.fallbackDataTransfer.isRequired())d=this._.fallbackDataTransfer.getData(a,b);else try{d=this.$.getData(a)||""}catch(c){d=""}"text/html"!=a||b||(d=this._stripHtml(d))}"Text"==a&&CKEDITOR.env.gecko&&this.getFilesCount()&&"file://"==d.substring(0,7)&&(d="");if("string"===typeof d)var e=d.indexOf("\x3c/html\x3e"),d=-1!==e?d.substring(0,e+7):d;return d},setData:function(a,b){a=this._.normalizeType(a);"text/html"==a?(this._.data[a]=this._stripHtml(b),this._.nativeHtmlCache=b):this._.data[a]= -b;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported||"URL"==a||"Text"==a)if("Text"==m&&"Text"==a&&(this.id=b),this._.fallbackDataTransfer.isRequired())this._.fallbackDataTransfer.setData(a,b);else try{this.$.setData(a,b)}catch(d){}},storeId:function(){"Text"!==m&&this.setData(m,this.id)},getTransferType:function(a){return this.sourceEditor?this.sourceEditor==a?CKEDITOR.DATA_TRANSFER_INTERNAL:CKEDITOR.DATA_TRANSFER_CROSS_EDITORS:CKEDITOR.DATA_TRANSFER_EXTERNAL},cacheData:function(){function a(a){a= -b._.normalizeType(a);var d=b.getData(a);"text/html"==a&&(b._.nativeHtmlCache=b.getData(a,!0),d=b._stripHtml(d));d&&(b._.data[a]=d)}if(this.$){var b=this,d,c;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported){if(this.$.types)for(d=0;dc?r+c:b.width>c?r-a.left:r-a.right+b.width):fc?r-c:b.width>c?r-a.right+b.width:r-a.left);c=a.top;b.height-a.tope?v-e:b.height>e?v-a.bottom+b.height:v-a.top);CKEDITOR.env.ie&&!CKEDITOR.env.edge&&(b=a=new CKEDITOR.dom.element(n.$.offsetParent),"html"==b.getName()&&(b=b.getDocument().getBody()),"rtl"==b.getComputedStyle("direction")&&(r=CKEDITOR.env.ie8Compat?r-2*n.getDocument().getDocumentElement().$.scrollLeft:r-(a.$.scrollWidth- -a.$.clientWidth)));var a=n.getFirst(),k;(k=a.getCustomData("activePanel"))&&k.onHide&&k.onHide.call(this,1);a.setCustomData("activePanel",this);n.setStyles({top:v+"px",left:r+"px"});n.setOpacity(1);d&&d()},this);g.isLoaded?a():g.onLoad=a;CKEDITOR.tools.setTimeout(function(){var a=CKEDITOR.env.webkit&&CKEDITOR.document.getWindow().getScrollPosition().y;this.focus();m.element.focus();CKEDITOR.env.webkit&&(CKEDITOR.document.getBody().$.scrollTop=a);this.allowBlur(!0);this._.markFirst&&(CKEDITOR.env.ie? -CKEDITOR.tools.setTimeout(function(){m.markFirstDisplayed?m.markFirstDisplayed():m._.markFirstDisplayed()},0):m.markFirstDisplayed?m.markFirstDisplayed():m._.markFirstDisplayed());this._.editor.fire("panelShow",this)},0,this)},CKEDITOR.env.air?200:0,this);this.visible=1;this.onShow&&this.onShow.call(this)},reposition:function(){var c=this._.showBlockParams;this.visible&&this._.showBlockParams&&(this.hide(),this.showBlock.apply(this,c))},focus:function(){if(CKEDITOR.env.webkit){var c=CKEDITOR.document.getActive(); -c&&!c.equals(this._.iframe)&&c.$.blur()}(this._.lastFocused||this._.iframe.getFrameDocument().getWindow()).focus()},blur:function(){var c=this._.iframe.getFrameDocument().getActive();c&&c.is("a")&&(this._.lastFocused=c)},hide:function(c){if(this.visible&&(!this.onHide||!0!==this.onHide.call(this))){this.hideChild();CKEDITOR.env.gecko&&this._.iframe.getFrameDocument().$.activeElement.blur();this.element.setStyle("display","none");this.visible=0;this.element.getFirst().removeCustomData("activePanel"); -if(c=c&&this._.returnFocus)CKEDITOR.env.webkit&&c.type&&c.getWindow().$.focus(),c.focus();delete this._.lastFocused;this._.showBlockParams=null;this._.editor.fire("panelHide",this)}},allowBlur:function(c){var e=this._.panel;void 0!==c&&(e.allowBlur=c);return e.allowBlur},showAsChild:function(c,e,b,f,k,d){if(this._.activeChild!=c||c._.panel._.offsetParentId!=b.getId())this.hideChild(),c.onHide=CKEDITOR.tools.bind(function(){CKEDITOR.tools.setTimeout(function(){this._.focused||this.hide()},0,this)}, -this),this._.activeChild=c,this._.focused=!1,c.showBlock(e,b,f,k,d),this.blur(),(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)&&setTimeout(function(){c.element.getChild(0).$.style.cssText+=""},100)},hideChild:function(c){var e=this._.activeChild;e&&(delete e.onHide,delete this._.activeChild,e.hide(),c&&this.focus())}}});CKEDITOR.on("instanceDestroyed",function(){var c=CKEDITOR.tools.isEmpty(CKEDITOR.instances),e;for(e in f){var b=f[e];c?b.destroy():b.element.hide()}c&&(f={})})}(),CKEDITOR.plugins.add("menu", -{requires:"floatpanel",beforeInit:function(e){for(var f=e.config.menu_groups.split(","),c=e._.menuGroups={},h=e._.menuItems={},b=0;bd.group?1:b.orderd.order?1:0})}var f='\x3cspan class\x3d"cke_menuitem"\x3e\x3ca id\x3d"{id}" class\x3d"cke_menubutton cke_menubutton__{name} cke_menubutton_{state} {cls}" href\x3d"{href}" title\x3d"{title}" tabindex\x3d"-1" _cke_focus\x3d1 hidefocus\x3d"true" role\x3d"{role}" aria-label\x3d"{label}" aria-describedby\x3d"{id}_description" aria-haspopup\x3d"{hasPopup}" aria-disabled\x3d"{disabled}" {ariaChecked} draggable\x3d"false"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&& -(f+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(f+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;" ondragstart\x3d"return false;"');var f=f+(' onmouseover\x3d"CKEDITOR.tools.callFunction({hoverFn},{index});" onmouseout\x3d"CKEDITOR.tools.callFunction({moveOutFn},{index});" '+(CKEDITOR.env.ie?'onclick\x3d"return false;" onmouseup':"onclick")+'\x3d"CKEDITOR.tools.callFunction({clickFn},{index}); return false;"\x3e'),c=CKEDITOR.addTemplate("menuItem",f+'\x3cspan class\x3d"cke_menubutton_inner"\x3e\x3cspan class\x3d"cke_menubutton_icon"\x3e\x3cspan class\x3d"cke_button_icon cke_button__{iconName}_icon" style\x3d"{iconStyle}"\x3e\x3c/span\x3e\x3c/span\x3e\x3cspan class\x3d"cke_menubutton_label"\x3e{label}\x3c/span\x3e{shortcutHtml}{arrowHtml}\x3c/span\x3e\x3c/a\x3e\x3cspan id\x3d"{id}_description" class\x3d"cke_voice_label" aria-hidden\x3d"false"\x3e{ariaShortcut}\x3c/span\x3e\x3c/span\x3e'), -h=CKEDITOR.addTemplate("menuArrow",'\x3cspan class\x3d"cke_menuarrow"\x3e\x3cspan\x3e{label}\x3c/span\x3e\x3c/span\x3e'),b=CKEDITOR.addTemplate("menuShortcut",'\x3cspan class\x3d"cke_menubutton_label cke_menubutton_shortcut"\x3e{shortcut}\x3c/span\x3e');CKEDITOR.menu=CKEDITOR.tools.createClass({$:function(b,c){c=this._.definition=c||{};this.id=CKEDITOR.tools.getNextId();this.editor=b;this.items=[];this._.listeners=[];this._.level=c.level||1;var d=CKEDITOR.tools.extend({},c.panel,{css:[CKEDITOR.skin.getPath("editor")], -level:this._.level-1,block:{}}),e=d.block.attributes=d.attributes||{};!e.role&&(e.role="menu");this._.panelDefinition=d},_:{onShow:function(){var b=this.editor.getSelection(),c=b&&b.getStartElement(),d=this.editor.elementPath(),e=this._.listeners;this.removeAll();for(var f=0;fCKEDITOR.env.version?h.createText("\r"):h.createElement("br"),c.deleteContents(),c.insertNode(b),CKEDITOR.env.needsBrFiller?(h.createText("").insertAfter(b),k&&(r||l.blockLimit).appendBogus(),b.getNext().$.nodeValue="",c.setStartAt(b.getNext(),CKEDITOR.POSITION_AFTER_START)):c.setStartAt(b,CKEDITOR.POSITION_AFTER_END)),c.collapse(!0),c.select(),c.scrollIntoView()):d(b,a,c,e)}}};l=CKEDITOR.plugins.enterkey;k=l.enterBr;d=l.enterBlock;g=/^h[1-6]$/}(),function(){function e(e,c){var h={},b=[],l={nbsp:" ", -shy:"­",gt:"\x3e",lt:"\x3c",amp:"\x26",apos:"'",quot:'"'};e=e.replace(/\b(nbsp|shy|gt|lt|amp|apos|quot)(?:,|$)/g,function(d,a){var e=c?"\x26"+a+";":l[a];h[e]=c?l[a]:"\x26"+a+";";b.push(e);return""});e=e.replace(/,$/,"");if(!c&&e){e=e.split(",");var k=document.createElement("div"),d;k.innerHTML="\x26"+e.join(";\x26")+";";d=k.innerHTML;k=null;for(k=0;kf&&(f=640);420>c&&(c=420);var b=parseInt((window.screen.height-c)/2,10),l=parseInt((window.screen.width- -f)/2,10);h=(h||"location\x3dno,menubar\x3dno,toolbar\x3dno,dependent\x3dyes,minimizable\x3dno,modal\x3dyes,alwaysRaised\x3dyes,resizable\x3dyes,scrollbars\x3dyes")+",width\x3d"+f+",height\x3d"+c+",top\x3d"+b+",left\x3d"+l;var k=window.open("",null,h,!0);if(!k)return!1;try{-1==navigator.userAgent.toLowerCase().indexOf(" chrome/")&&(k.moveTo(l,b),k.resizeTo(f,c)),k.focus(),k.location.href=e}catch(d){window.open(e,null,h,!0)}return!0}}),"use strict",function(){function e(b){this.editor=b;this.loaders= -[]}function f(b,e,f){var d=b.config.fileTools_defaultFileName;this.editor=b;this.lang=b.lang;"string"===typeof e?(this.data=e,this.file=c(this.data),this.loaded=this.total=this.file.size):(this.data=null,this.file=e,this.total=this.file.size,this.loaded=0);f?this.fileName=f:this.file.name?this.fileName=this.file.name:(b=this.file.type.split("/"),d&&(b[0]=d),this.fileName=b.join("."));this.uploaded=0;this.responseData=this.uploadTotal=null;this.status="created";this.abort=function(){this.changeStatus("abort")}} -function c(b){var c=b.match(h)[1];b=b.replace(h,"");b=atob(b);var e=[],d,g,f,a;for(d=0;dd.status||299v.height-r.bottom?d("pin"):d("bottom"),a=v.width/2,a=b.floatSpacePreferRight?"right":0p.width?"rtl"==b.contentsLangDirection?"right":"left":a-r.left>r.right-a?"left":"right",p.width>v.width?(a="left",n=0):(n="left"==a?0v.width&&(a="left"==a?"right":"left",n=0)),g.setStyle(a,c(("pin"==m?A:x)+n+("pin"==m?0:"left"==a?B:-B)))):(m="pin",d("pin"),k(a))}}}();if(l){var d=new CKEDITOR.template('\x3cdiv id\x3d"cke_{name}" class\x3d"cke {id} cke_reset_all cke_chrome cke_editor_{name} cke_float cke_{langDir} '+ -CKEDITOR.env.cssClass+'" dir\x3d"{langDir}" title\x3d"'+(CKEDITOR.env.gecko?" ":"")+'" lang\x3d"{langCode}" role\x3d"application" style\x3d"{style}"'+(e.title?' aria-labelledby\x3d"cke_{name}_arialbl"':" ")+"\x3e"+(e.title?'\x3cspan id\x3d"cke_{name}_arialbl" class\x3d"cke_voice_label"\x3e{voiceLabel}\x3c/span\x3e':" ")+'\x3cdiv class\x3d"cke_inner"\x3e\x3cdiv id\x3d"{topId}" class\x3d"cke_top" role\x3d"presentation"\x3e{content}\x3c/div\x3e\x3c/div\x3e\x3c/div\x3e'),g=CKEDITOR.document.getBody().append(CKEDITOR.dom.element.createFromHtml(d.output({content:l, -id:e.id,langDir:e.lang.dir,langCode:e.langCode,name:e.name,style:"display:none;z-index:"+(b.baseFloatZIndex-1),topId:e.ui.spaceId("top"),voiceLabel:e.title}))),m=CKEDITOR.tools.eventsBuffer(500,k),a=CKEDITOR.tools.eventsBuffer(100,k);g.unselectable();g.on("mousedown",function(a){a=a.data;a.getTarget().hasAscendant("a",1)||a.preventDefault()});e.on("focus",function(b){k(b);e.on("change",m.input);f.on("scroll",a.input);f.on("resize",a.input)});e.on("blur",function(){g.hide();e.removeListener("change", -m.input);f.removeListener("scroll",a.input);f.removeListener("resize",a.input)});e.on("destroy",function(){f.removeListener("scroll",a.input);f.removeListener("resize",a.input);g.clearCustomData();g.remove()});e.focusManager.hasFocus&&g.show();e.focusManager.add(g,1)}}var f=CKEDITOR.document.getWindow(),c=CKEDITOR.tools.cssLength;CKEDITOR.plugins.add("floatingspace",{init:function(c){c.on("loaded",function(){e(this)},null,null,20)}})}(),CKEDITOR.plugins.add("listblock",{requires:"panel",onLoad:function(){var e= -CKEDITOR.addTemplate("panel-list",'\x3cul role\x3d"presentation" class\x3d"cke_panel_list"\x3e{items}\x3c/ul\x3e'),f=CKEDITOR.addTemplate("panel-list-item",'\x3cli id\x3d"{id}" class\x3d"cke_panel_listItem" role\x3dpresentation\x3e\x3ca id\x3d"{id}_option" _cke_focus\x3d1 hidefocus\x3dtrue title\x3d"{title}" draggable\x3d"false" ondragstart\x3d"return false;" href\x3d"javascript:void(\'{val}\')" {onclick}\x3d"CKEDITOR.tools.callFunction({clickFn},\'{val}\'); return false;" role\x3d"option"\x3e{text}\x3c/a\x3e\x3c/li\x3e'), -c=CKEDITOR.addTemplate("panel-list-group",'\x3ch1 id\x3d"{id}" draggable\x3d"false" ondragstart\x3d"return false;" class\x3d"cke_panel_grouptitle" role\x3d"presentation" \x3e{label}\x3c/h1\x3e'),h=/\'/g;CKEDITOR.ui.panel.prototype.addListBlock=function(b,c){return this.addBlock(b,new CKEDITOR.ui.listBlock(this.getHolderElement(),c))};CKEDITOR.ui.listBlock=CKEDITOR.tools.createClass({base:CKEDITOR.ui.panel.block,$:function(b,c){c=c||{};var e=c.attributes||(c.attributes={});(this.multiSelect=!!c.multiSelect)&& -(e["aria-multiselectable"]=!0);!e.role&&(e.role="listbox");this.base.apply(this,arguments);this.element.setAttribute("role",e.role);e=this.keys;e[40]="next";e[9]="next";e[38]="prev";e[CKEDITOR.SHIFT+9]="prev";e[32]=CKEDITOR.env.ie?"mouseup":"click";CKEDITOR.env.ie&&(e[13]="mouseup");this._.pendingHtml=[];this._.pendingList=[];this._.items={};this._.groups={}},_:{close:function(){if(this._.started){var b=e.output({items:this._.pendingList.join("")});this._.pendingList=[];this._.pendingHtml.push(b); -delete this._.started}},getClick:function(){this._.click||(this._.click=CKEDITOR.tools.addFunction(function(b){var c=this.toggle(b);if(this.onClick)this.onClick(b,c)},this));return this._.click}},proto:{add:function(b,c,e){var d=CKEDITOR.tools.getNextId();this._.started||(this._.started=1,this._.size=this._.size||0);this._.items[b]=d;var g;g=CKEDITOR.tools.htmlEncodeAttr(b).replace(h,"\\'");b={id:d,val:g,onclick:CKEDITOR.env.ie?'onclick\x3d"return false;" onmouseup':"onclick",clickFn:this._.getClick(), -title:CKEDITOR.tools.htmlEncodeAttr(e||b),text:c||b};this._.pendingList.push(f.output(b))},startGroup:function(b){this._.close();var e=CKEDITOR.tools.getNextId();this._.groups[b]=e;this._.pendingHtml.push(c.output({id:e,label:b}))},commit:function(){this._.close();this.element.appendHtml(this._.pendingHtml.join(""));delete this._.size;this._.pendingHtml=[]},toggle:function(b){var c=this.isMarked(b);c?this.unmark(b):this.mark(b);return!c},hideGroup:function(b){var c=(b=this.element.getDocument().getById(this._.groups[b]))&& -b.getNext();b&&(b.setStyle("display","none"),c&&"ul"==c.getName()&&c.setStyle("display","none"))},hideItem:function(b){this.element.getDocument().getById(this._.items[b]).setStyle("display","none")},showAll:function(){var b=this._.items,c=this._.groups,e=this.element.getDocument(),d;for(d in b)e.getById(b[d]).setStyle("display","");for(var g in c)b=e.getById(c[g]),d=b.getNext(),b.setStyle("display",""),d&&"ul"==d.getName()&&d.setStyle("display","")},mark:function(b){this.multiSelect||this.unmarkAll(); -b=this._.items[b];var c=this.element.getDocument().getById(b);c.addClass("cke_selected");this.element.getDocument().getById(b+"_option").setAttribute("aria-selected",!0);this.onMark&&this.onMark(c)},markFirstDisplayed:function(){var b=this;this._.markFirstDisplayed(function(){b.multiSelect||b.unmarkAll()})},unmark:function(b){var c=this.element.getDocument();b=this._.items[b];var e=c.getById(b);e.removeClass("cke_selected");c.getById(b+"_option").removeAttribute("aria-selected");this.onUnmark&&this.onUnmark(e)}, -unmarkAll:function(){var b=this._.items,c=this.element.getDocument(),e;for(e in b){var d=b[e];c.getById(d).removeClass("cke_selected");c.getById(d+"_option").removeAttribute("aria-selected")}this.onUnmark&&this.onUnmark()},isMarked:function(b){return this.element.getDocument().getById(this._.items[b]).hasClass("cke_selected")},focus:function(b){this._.focusIndex=-1;var c=this.element.getElementsByTag("a"),e,d=-1;if(b)for(e=this.element.getDocument().getById(this._.items[b]).getFirst();b=c.getItem(++d);){if(b.equals(e)){this._.focusIndex= -d;break}}else this.element.focus();e&&setTimeout(function(){e.focus()},0)}}})}}),CKEDITOR.plugins.add("richcombo",{requires:"floatpanel,listblock,button",beforeInit:function(e){e.ui.addHandler(CKEDITOR.UI_RICHCOMBO,CKEDITOR.ui.richCombo.handler)}}),function(){var e='\x3cspan id\x3d"{id}" class\x3d"cke_combo cke_combo__{name} {cls}" role\x3d"presentation"\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_combo_label"\x3e{label}\x3c/span\x3e\x3ca class\x3d"cke_combo_button" title\x3d"{title}" tabindex\x3d"-1"'+ -(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-haspopup\x3d"listbox"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(e+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(e+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');var e=e+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event,this);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" '+(CKEDITOR.env.ie? -'onclick\x3d"return false;" onmouseup':"onclick")+'\x3d"CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan id\x3d"{id}_text" class\x3d"cke_combo_text cke_combo_inlinelabel"\x3e{label}\x3c/span\x3e\x3cspan class\x3d"cke_combo_open"\x3e\x3cspan class\x3d"cke_combo_arrow"\x3e'+(CKEDITOR.env.hc?"\x26#9660;":CKEDITOR.env.air?"\x26nbsp;":"")+"\x3c/span\x3e\x3c/span\x3e\x3c/a\x3e\x3c/span\x3e"),f=CKEDITOR.addTemplate("combo",e);CKEDITOR.UI_RICHCOMBO="richcombo";CKEDITOR.ui.richCombo= -CKEDITOR.tools.createClass({$:function(c){CKEDITOR.tools.extend(this,c,{canGroup:!1,title:c.label,modes:{wysiwyg:1},editorFocus:1});c=this.panel||{};delete this.panel;this.id=CKEDITOR.tools.getNextNumber();this.document=c.parent&&c.parent.getDocument()||CKEDITOR.document;c.className="cke_combopanel";c.block={multiSelect:c.multiSelect,attributes:c.attributes};c.toolbarRelated=!0;this._={panelDefinition:c,items:{},listeners:[]}},proto:{renderHtml:function(c){var e=[];this.render(c,e);return e.join("")}, -render:function(c,e){function b(){if(this.getState()!=CKEDITOR.TRISTATE_ON){var a=this.modes[c.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED;c.readOnly&&!this.readOnly&&(a=CKEDITOR.TRISTATE_DISABLED);this.setState(a);this.setValue("");a!=CKEDITOR.TRISTATE_DISABLED&&this.refresh&&this.refresh()}}var l=CKEDITOR.env,k="cke_"+this.id,d=CKEDITOR.tools.addFunction(function(a){w&&(c.unlockSelection(1),w=0);m.execute(a)},this),g=this,m={id:k,combo:this,focus:function(){CKEDITOR.document.getById(k).getChild(1).focus()}, -execute:function(a){var b=g._;if(b.state!=CKEDITOR.TRISTATE_DISABLED)if(g.createPanel(c),b.on)b.panel.hide();else{g.commit();var d=g.getValue();d?b.list.mark(d):b.list.unmarkAll();b.panel.showBlock(g.id,new CKEDITOR.dom.element(a),4)}},clickFn:d};this._.listeners.push(c.on("activeFilterChange",b,this));this._.listeners.push(c.on("mode",b,this));this._.listeners.push(c.on("selectionChange",b,this));!this.readOnly&&this._.listeners.push(c.on("readOnly",b,this));var a=CKEDITOR.tools.addFunction(function(a, -b){a=new CKEDITOR.dom.event(a);var c=a.getKeystroke();switch(c){case 13:case 32:case 40:CKEDITOR.tools.callFunction(d,b);break;default:m.onkey(m,c)}a.preventDefault()}),n=CKEDITOR.tools.addFunction(function(){m.onfocus&&m.onfocus()}),w=0;m.keyDownFn=a;l={id:k,name:this.name||this.command,label:this.label,title:this.title,cls:this.className||"",titleJs:l.gecko&&!l.hc?"":(this.title||"").replace("'",""),keydownFn:a,focusFn:n,clickFn:d};f.output(l,e);if(this.onRender)this.onRender();return m},createPanel:function(c){if(!this._.panel){var e= -this._.panelDefinition,b=this._.panelDefinition.block,f=e.parent||CKEDITOR.document.getBody(),k="cke_combopanel__"+this.name,d=new CKEDITOR.ui.floatPanel(c,f,e),e=d.addListBlock(this.id,b),g=this;d.onShow=function(){this.element.addClass(k);g.setState(CKEDITOR.TRISTATE_ON);g._.on=1;g.editorFocus&&!c.focusManager.hasFocus&&c.focus();if(g.onOpen)g.onOpen()};d.onHide=function(b){this.element.removeClass(k);g.setState(g.modes&&g.modes[c.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);g._.on=0; -if(!b&&g.onClose)g.onClose()};d.onEscape=function(){d.hide(1)};e.onClick=function(b,a){g.onClick&&g.onClick.call(g,b,a);d.hide()};this._.panel=d;this._.list=e;d.getBlock(this.id).onHide=function(){g._.on=0;g.setState(CKEDITOR.TRISTATE_OFF)};this.init&&this.init()}},setValue:function(c,e){this._.value=c;var b=this.document.getById("cke_"+this.id+"_text");b&&(c||e?b.removeClass("cke_combo_inlinelabel"):(e=this.label,b.addClass("cke_combo_inlinelabel")),b.setText("undefined"!=typeof e?e:c))},getValue:function(){return this._.value|| -""},unmarkAll:function(){this._.list.unmarkAll()},mark:function(c){this._.list.mark(c)},hideItem:function(c){this._.list.hideItem(c)},hideGroup:function(c){this._.list.hideGroup(c)},showAll:function(){this._.list.showAll()},add:function(c,e,b){this._.items[c]=b||c;this._.list.add(c,e,b)},startGroup:function(c){this._.list.startGroup(c)},commit:function(){this._.committed||(this._.list.commit(),this._.committed=1,CKEDITOR.ui.fire("ready",this));this._.committed=1},setState:function(c){if(this._.state!= -c){var e=this.document.getById("cke_"+this.id);e.setState(c,"cke_combo");c==CKEDITOR.TRISTATE_DISABLED?e.setAttribute("aria-disabled",!0):e.removeAttribute("aria-disabled");this._.state=c}},getState:function(){return this._.state},enable:function(){this._.state==CKEDITOR.TRISTATE_DISABLED&&this.setState(this._.lastState)},disable:function(){this._.state!=CKEDITOR.TRISTATE_DISABLED&&(this._.lastState=this._.state,this.setState(CKEDITOR.TRISTATE_DISABLED))},destroy:function(){CKEDITOR.tools.array.forEach(this._.listeners, -function(c){c.removeListener()});this._.listeners=[]}},statics:{handler:{create:function(c){return new CKEDITOR.ui.richCombo(c)}}}});CKEDITOR.ui.prototype.addRichCombo=function(c,e){this.add(c,CKEDITOR.UI_RICHCOMBO,e)}}(),CKEDITOR.plugins.add("format",{requires:"richcombo",init:function(e){if(!e.blockless){for(var f=e.config,c=e.lang.format,h=f.format_tags.split(";"),b={},l=0,k=[],d=0;d=this.rect.right||a<=this.rect.top||a>=this.rect.bottom)&&this.hideVisible();(0>=b||b>=this.winTopPane.width||0>=a||a>=this.winTopPane.height)&&this.hideVisible()},this);a.attachListener(d,"resize",e);a.attachListener(d,"mode",h);d.on("destroy",h);this.lineTpl=(new CKEDITOR.template('\x3cdiv data-cke-lineutils-line\x3d"1" class\x3d"cke_reset_all" style\x3d"{lineStyle}"\x3e\x3cspan style\x3d"{tipLeftStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3cspan style\x3d"{tipRightStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3c/div\x3e')).output({lineStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({}, -l,this.lineStyle,!0)),tipLeftStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},b,{left:"0px","border-left-color":"red","border-width":"6px 0 6px 6px"},this.tipCss,this.tipLeftStyle,!0)),tipRightStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},b,{right:"0px","border-right-color":"red","border-width":"6px 6px 6px 0"},this.tipCss,this.tipRightStyle,!0))})}function h(b){var c;if(c=b&&b.type==CKEDITOR.NODE_ELEMENT)c=!(k[b.getComputedStyle("float")]||k[b.getAttribute("align")]);return c&& -!d[b.getComputedStyle("position")]}CKEDITOR.plugins.add("lineutils");CKEDITOR.LINEUTILS_BEFORE=1;CKEDITOR.LINEUTILS_AFTER=2;CKEDITOR.LINEUTILS_INSIDE=4;e.prototype={start:function(b){var d=this,a=this.editor,c=this.doc,e,f,h,k,l=CKEDITOR.tools.eventsBuffer(50,function(){a.readOnly||"wysiwyg"!=a.mode||(d.relations={},(f=c.$.elementFromPoint(h,k))&&f.nodeType&&(e=new CKEDITOR.dom.element(f),d.traverseSearch(e),isNaN(h+k)||d.pixelSearch(e,h,k),b&&b(d.relations,h,k)))});this.listener=this.editable.attachListener(this.target, -"mousemove",function(a){h=a.data.$.clientX;k=a.data.$.clientY;l.input()});this.editable.attachListener(this.inline?this.editable:this.frame,"mouseout",function(){l.reset()})},stop:function(){this.listener&&this.listener.removeListener()},getRange:function(){var b={};b[CKEDITOR.LINEUTILS_BEFORE]=CKEDITOR.POSITION_BEFORE_START;b[CKEDITOR.LINEUTILS_AFTER]=CKEDITOR.POSITION_AFTER_END;b[CKEDITOR.LINEUTILS_INSIDE]=CKEDITOR.POSITION_AFTER_START;return function(d){var a=this.editor.createRange();a.moveToPosition(this.relations[d.uid].element, -b[d.type]);return a}}(),store:function(){function b(d,a,c){var e=d.getUniqueId();e in c?c[e].type|=a:c[e]={element:d,type:a}}return function(d,a){var c;a&CKEDITOR.LINEUTILS_AFTER&&h(c=d.getNext())&&c.isVisible()&&(b(c,CKEDITOR.LINEUTILS_BEFORE,this.relations),a^=CKEDITOR.LINEUTILS_AFTER);a&CKEDITOR.LINEUTILS_INSIDE&&h(c=d.getFirst())&&c.isVisible()&&(b(c,CKEDITOR.LINEUTILS_BEFORE,this.relations),a^=CKEDITOR.LINEUTILS_INSIDE);b(d,a,this.relations)}}(),traverseSearch:function(b){var d,a,c;do if(c=b.$["data-cke-expando"], -!(c&&c in this.relations)){if(b.equals(this.editable))break;if(h(b))for(d in this.lookups)(a=this.lookups[d](b))&&this.store(b,a)}while((!b||b.type!=CKEDITOR.NODE_ELEMENT||"true"!=b.getAttribute("contenteditable"))&&(b=b.getParent()))},pixelSearch:function(){function b(a,c,e,g,f){for(var k=0,l;f(e);){e+=g;if(25==++k)break;if(l=this.doc.$.elementFromPoint(c,e))if(l==a)k=0;else if(d(a,l)&&(k=0,h(l=new CKEDITOR.dom.element(l))))return l}}var d=CKEDITOR.env.ie||CKEDITOR.env.webkit?function(a,b){return a.contains(b)}: -function(a,b){return!!(a.compareDocumentPosition(b)&16)};return function(a,d,c){var e=this.win.getViewPaneSize().height,f=b.call(this,a.$,d,c,-1,function(a){return 0this.rect.bottom)return!1;this.inline? -e.left=a.elementRect.left-this.rect.relativeX:(0[^<]*e});0>g&&(g=a._.upcasts.length);a._.upcasts.splice(g,0,[CKEDITOR.tools.bind(b,d),d.name,e])}var e=b.upcast,g=b.upcastPriority||10;e&&("string"==typeof e?c(d, -b,g):c(e,b,g))}function l(a,b){a.focused=null;if(b.isInited()){var d=b.editor.checkDirty();a.fire("widgetBlurred",{widget:b});b.setFocused(!1);!d&&b.editor.resetDirty()}}function k(a){a=a.data;if("wysiwyg"==this.editor.mode){var b=this.editor.editable(),d=this.instances,c,e,g,h;if(b){for(c in d)d[c].isReady()&&!b.contains(d[c].wrapper)&&this.destroy(d[c],!0);if(a&&a.initOnlyNew)d=this.initOnAll();else{var k=b.find(".cke_widget_wrapper"),d=[];c=0;for(e=k.count();cCKEDITOR.tools.indexOf(b,a)&&d.push(a);a=CKEDITOR.tools.indexOf(c,a);0<=a&&c.splice(a,1);return this},focus:function(a){e=a;return this}, -commit:function(){var g=a.focused!==e,f,h;a.editor.fire("lockSnapshot");for(g&&(f=a.focused)&&l(a,f);f=c.pop();)b.splice(CKEDITOR.tools.indexOf(b,f),1),f.isInited()&&(h=f.editor.checkDirty(),f.setSelected(!1),!h&&f.editor.resetDirty());g&&e&&(h=a.editor.checkDirty(),a.focused=e,a.fire("widgetFocused",{widget:e}),e.setFocused(!0),!h&&a.editor.resetDirty());for(;f=d.pop();)b.push(f),f.setSelected(!0);a.editor.fire("unlockSnapshot")}}}function H(a,b,d){var c=0;b=L(b);var e=a.data.classes||{},g;if(b){for(e= -CKEDITOR.tools.clone(e);g=b.pop();)d?e[g]||(c=e[g]=1):e[g]&&(delete e[g],c=1);c&&a.setData("classes",e)}}function F(a){a.cancel()}function G(a,b){var d=a.editor,c=d.document,e=CKEDITOR.env.edge&&16<=CKEDITOR.env.version;if(!c.getById("cke_copybin")){var g=!d.blockless&&!CKEDITOR.env.ie||e?"div":"span",e=c.createElement(g),f=c.createElement(g),g=CKEDITOR.env.ie&&9>CKEDITOR.env.version;f.setAttributes({id:"cke_copybin","data-cke-temp":"1"});e.setStyles({position:"absolute",width:"1px",height:"1px", -overflow:"hidden"});e.setStyle("ltr"==d.config.contentsLangDirection?"left":"right","-5000px");var h=d.createRange();h.setStartBefore(a.wrapper);h.setEndAfter(a.wrapper);e.setHtml('\x3cspan data-cke-copybin-start\x3d"1"\x3e​\x3c/span\x3e'+d.editable().getHtmlFromRange(h).getHtml()+'\x3cspan data-cke-copybin-end\x3d"1"\x3e​\x3c/span\x3e');d.fire("saveSnapshot");d.fire("lockSnapshot");f.append(e);d.editable().append(f);var k=d.on("selectionChange",F,null,null,0),m=a.repository.on("checkSelection",F, -null,null,0);if(g)var l=c.getDocumentElement().$,r=l.scrollTop;h=d.createRange();h.selectNodeContents(e);h.select();g&&(l.scrollTop=r);setTimeout(function(){b||a.focus();f.remove();k.removeListener();m.removeListener();d.fire("unlockSnapshot");b&&!d.readOnly&&(a.repository.del(a),d.fire("saveSnapshot"))},100)}}function L(a){return(a=(a=a.getDefinition().attributes)&&a["class"])?a.split(/\s+/):null}function I(){var a=CKEDITOR.document.getActive(),b=this.editor,d=b.editable();(d.isInline()?d:b.document.getWindow().getFrame()).equals(a)&& -b.focusManager.focus(d)}function C(){CKEDITOR.env.gecko&&this.editor.unlockSelection();CKEDITOR.env.webkit||(this.editor.forceNextSelectionCheck(),this.editor.selectionChange(1))}function S(a){var b=null;a.on("data",function(){var a=this.data.classes,d;if(b!=a){for(d in b)a&&a[d]||this.removeClass(d);for(d in a)this.addClass(d);b=a}})}function O(a){a.on("data",function(){if(a.wrapper){var b=this.getLabel?this.getLabel():this.editor.lang.widget.label.replace(/%1/,this.pathName||this.element.getName()); -a.wrapper.setAttribute("role","region");a.wrapper.setAttribute("aria-label",b)}},null,null,9999)}function P(a){if(a.draggable){var b=a.editor,d=a.wrapper.getLast(f.isDomDragHandlerContainer),c;d?c=d.findOne("img"):(d=new CKEDITOR.dom.element("span",b.document),d.setAttributes({"class":"cke_reset cke_widget_drag_handler_container",style:"background:rgba(220,220,220,0.5);background-image:url("+b.plugins.widget.path+"images/handle.png)"}),c=new CKEDITOR.dom.element("img",b.document),c.setAttributes({"class":"cke_reset cke_widget_drag_handler", -"data-cke-widget-drag-handler":"1",src:CKEDITOR.tools.transparentImageData,width:15,title:b.lang.widget.move,height:15,role:"presentation"}),a.inline&&c.setAttribute("draggable","true"),d.append(c),a.wrapper.append(d));a.wrapper.on("dragover",function(a){a.data.preventDefault()});a.wrapper.on("mouseenter",a.updateDragHandlerPosition,a);setTimeout(function(){a.on("data",a.updateDragHandlerPosition,a)},50);if(!a.inline&&(c.on("mousedown",K,a),CKEDITOR.env.ie&&9>CKEDITOR.env.version))c.on("dragstart", -function(a){a.data.preventDefault(!0)});a.dragHandlerContainer=d}}function K(a){function b(){var d;for(q.reset();d=h.pop();)d.removeListener();var c=k;d=a.sender;var e=this.repository.finder,g=this.repository.liner,f=this.editor,m=this.editor.editable();CKEDITOR.tools.isEmpty(g.visible)||(c=e.getRange(c[0]),this.focus(),f.fire("drop",{dropRange:c,target:c.startContainer}));m.removeClass("cke_widget_dragging");g.hideVisible();f.fire("dragend",{target:d})}if(CKEDITOR.tools.getMouseButton(a)===CKEDITOR.MOUSE_BUTTON_LEFT){var d= -this.repository.finder,c=this.repository.locator,e=this.repository.liner,g=this.editor,f=g.editable(),h=[],k=[],m,l;this.repository._.draggedWidget=this;var r=d.greedySearch(),q=CKEDITOR.tools.eventsBuffer(50,function(){m=c.locate(r);k=c.sort(l,1);k.length&&(e.prepare(r,m),e.placeLine(k[0]),e.cleanup())});f.addClass("cke_widget_dragging");h.push(f.on("mousemove",function(a){l=a.data.$.clientY;q.input()}));g.fire("dragstart",{target:a.sender});h.push(g.document.once("mouseup",b,this));f.isInline()|| -h.push(CKEDITOR.document.once("mouseup",b,this))}}function V(a){var b,d,c=a.editables;a.editables={};if(a.editables)for(b in c)d=c[b],a.initEditable(b,"string"==typeof d?{selector:d}:d)}function T(a){if(a.mask){var b=a.wrapper.findOne(".cke_widget_mask");b||(b=new CKEDITOR.dom.element("img",a.editor.document),b.setAttributes({src:CKEDITOR.tools.transparentImageData,"class":"cke_reset cke_widget_mask"}),a.wrapper.append(b));a.mask=b}}function Y(a){if(a.parts){var b={},d,c;for(c in a.parts)d=a.wrapper.findOne(a.parts[c]), -b[c]=d;a.parts=b}}function Z(a,b){M(a);Y(a);V(a);T(a);P(a);S(a);O(a);if(CKEDITOR.env.ie&&9>CKEDITOR.env.version)a.wrapper.on("dragstart",function(b){var d=b.data.getTarget();f.getNestedEditable(a,d)||a.inline&&f.isDomDragHandler(d)||b.data.preventDefault()});a.wrapper.removeClass("cke_widget_new");a.element.addClass("cke_widget_element");a.on("key",function(b){b=b.data.keyCode;if(13==b)a.edit();else{if(b==CKEDITOR.CTRL+67||b==CKEDITOR.CTRL+88){G(a,b==CKEDITOR.CTRL+88);return}if(b in U||CKEDITOR.CTRL& -b||CKEDITOR.ALT&b)return}return!1},null,null,999);a.on("doubleclick",function(b){a.edit()&&b.cancel()});if(b.data)a.on("data",b.data);if(b.edit)a.on("edit",b.edit)}function M(a){(a.wrapper=a.element.getParent()).setAttribute("data-cke-widget-id",a.id)}function R(a){a.element.data("cke-widget-data",encodeURIComponent(JSON.stringify(a.data)))}function N(){function a(){}function b(a,d,c){return c&&this.checkElement(a)?(a=c.widgets.getByElement(a,!0))&&a.checkStyleActive(this):!1}var d={};CKEDITOR.style.addCustomHandler({type:"widget", -setup:function(a){this.widget=a.widget;if(this.group="string"==typeof a.group?[a.group]:a.group){a=this.widget;var b;d[a]||(d[a]={});for(var c=0,e=this.group.length;c)?(?:<(?:div|span)(?: style="[^"]+")?>)?]*data-cke-copybin-start="1"[^>]*>.?<\/span>([\s\S]+)]*data-cke-copybin-end="1"[^>]*>.?<\/span>(?:<\/(?:div|span)>)?(?:<\/(?:div|span)>)?$/i,U={37:1,38:1,39:1,40:1,8:1,46:1};CKEDITOR.plugins.widget=f;f.repository=e;f.nestedEditable=c}(),"use strict",function(){function e(b){function d(){this.deflated|| -(b.widgets.focused==this.widget&&(this.focused=!0),b.widgets.destroy(this.widget),this.deflated=!0)}function e(){var a=b.editable(),d=b.document;if(this.deflated)this.widget=b.widgets.initOn(this.element,"image",this.widget.data),this.widget.inline&&!(new CKEDITOR.dom.elementPath(this.widget.wrapper,a)).block&&(a=d.createElement(b.activeEnterMode==CKEDITOR.ENTER_P?"p":"div"),a.replace(this.widget.wrapper),this.widget.wrapper.move(a)),this.focused&&(this.widget.focus(),delete this.focused),delete this.deflated; -else{var c=this.widget,a=g,d=c.wrapper,f=c.data.align,c=c.data.hasCaption;if(a){for(var h=3;h--;)d.removeClass(a[h]);"center"==f?c&&d.addClass(a[1]):"none"!=f&&d.addClass(a[u[f]])}else"center"==f?(c?d.setStyle("text-align","center"):d.removeStyle("text-align"),d.removeStyle("float")):("none"==f?d.removeStyle("float"):d.setStyle("float",f),d.removeStyle("text-align"))}}var g=b.config.image2_alignClasses,h=b.config.image2_captionedClass;return{allowedContent:m(b),requiredContent:"img[src,alt]",features:a(b), -styleableElements:"img figure",contentTransformations:[["img[width]: sizeToAttribute"]],editables:{caption:{selector:"figcaption",allowedContent:"br em strong sub sup u s; a[!href,target]"}},parts:{image:"img",caption:"figcaption"},dialog:"image2",template:'\x3cimg alt\x3d"" src\x3d"" /\x3e',data:function(){var a=this.features;this.data.hasCaption&&!b.filter.checkFeature(a.caption)&&(this.data.hasCaption=!1);"none"==this.data.align||b.filter.checkFeature(a.align)||(this.data.align="none");this.shiftState({widget:this, -element:this.element,oldData:this.oldData,newData:this.data,deflate:d,inflate:e});this.data.link?this.parts.link||(this.parts.link=this.parts.image.getParent()):this.parts.link&&delete this.parts.link;this.parts.image.setAttributes({src:this.data.src,"data-cke-saved-src":this.data.src,alt:this.data.alt});if(this.oldData&&!this.oldData.hasCaption&&this.data.hasCaption)for(var c in this.data.classes)this.parts.image.removeClass(c);if(b.filter.checkFeature(a.dimension)){a=this.data;a={width:a.width, -height:a.height};c=this.parts.image;for(var g in a)a[g]?c.setAttribute(g,a[g]):c.removeAttribute(g)}this.oldData=CKEDITOR.tools.extend({},this.data)},init:function(){var a=CKEDITOR.plugins.image2,d=this.parts.image,c={hasCaption:!!this.parts.caption,src:d.getAttribute("src"),alt:d.getAttribute("alt")||"",width:d.getAttribute("width")||"",height:d.getAttribute("height")||"",lock:this.ready?a.checkHasNaturalRatio(d):!0},e=d.getAscendant("a");e&&this.wrapper.contains(e)&&(this.parts.link=e);c.align|| -(d=c.hasCaption?this.element:d,g?(d.hasClass(g[0])?c.align="left":d.hasClass(g[2])&&(c.align="right"),c.align?d.removeClass(g[u[c.align]]):c.align="none"):(c.align=d.getStyle("float")||"none",d.removeStyle("float")));b.plugins.link&&this.parts.link&&(c.link=a.getLinkAttributesParser()(b,this.parts.link),(d=c.link.advanced)&&d.advCSSClasses&&(d.advCSSClasses=CKEDITOR.tools.trim(d.advCSSClasses.replace(/cke_\S+/,""))));this.wrapper[(c.hasCaption?"remove":"add")+"Class"]("cke_image_nocaption");this.setData(c); -b.filter.checkFeature(this.features.dimension)&&!0!==b.config.image2_disableResizer&&1!=b.readOnly&&l(this);this.shiftState=a.stateShifter(this.editor);this.on("contextMenu",function(a){a.data.image=CKEDITOR.TRISTATE_OFF;if(this.parts.link||this.wrapper.getAscendant("a"))a.data.link=a.data.unlink=CKEDITOR.TRISTATE_OFF});this.on("dialog",function(a){a.data.widget=this},this)},addClass:function(a){n(this).addClass(a)},hasClass:function(a){return n(this).hasClass(a)},removeClass:function(a){n(this).removeClass(a)}, -getClasses:function(){var a=new RegExp("^("+[].concat(h,g).join("|")+")$");return function(){var b=this.repository.parseElementClasses(n(this).getAttribute("class")),d;for(d in b)a.test(d)&&delete b[d];return b}}(),upcast:f(b),downcast:c(b),getLabel:function(){return this.editor.lang.widget.label.replace(/%1/,(this.data.alt||"")+" "+this.pathName)}}}function f(a){var d=h(a),c=a.config.image2_captionedClass;return function(a,e){var g={width:1,height:1},f=a.name,h;if(!a.attributes["data-cke-realelement"]&& -(d(a)?("div"==f&&(h=a.getFirst("figure"))&&(a.replaceWith(h),a=h),e.align="center",h=a.getFirst("img")||a.getFirst("a").getFirst("img")):"figure"==f&&a.hasClass(c)?h=a.find(function(a){return"img"===a.name&&-1!==CKEDITOR.tools.array.indexOf(["figure","a"],a.parent.name)},!0)[0]:b(a)&&(h="a"==a.name?a.children[0]:a),h)){for(var k in g)(g=h.attributes[k])&&g.match(t)&&delete h.attributes[k];return a}}}function c(a){var b=a.config.image2_alignClasses;return function(a){var d="a"==a.name?a.getFirst(): -a,c=d.attributes,e=this.data.align;if(!this.inline){var g=a.getFirst("span");g&&g.replaceWith(g.getFirst({img:1,a:1}))}e&&"none"!=e&&(g=CKEDITOR.tools.parseCssText(c.style||""),"center"==e&&"figure"==a.name?a=a.wrapWith(new CKEDITOR.htmlParser.element("div",b?{"class":b[1]}:{style:"text-align:center"})):e in{left:1,right:1}&&(b?d.addClass(b[u[e]]):g["float"]=e),b||CKEDITOR.tools.isEmpty(g)||(c.style=CKEDITOR.tools.writeCssText(g)));return a}}function h(a){var d=a.config.image2_captionedClass,c=a.config.image2_alignClasses, -e={figure:1,a:1,img:1};return function(g){if(!(g.name in{div:1,p:1}))return!1;var f=g.children;if(1!==f.length)return!1;f=f[0];if(!(f.name in e))return!1;if("p"==g.name){if(!b(f))return!1}else if("figure"==f.name){if(!f.hasClass(d))return!1}else if(a.enterMode==CKEDITOR.ENTER_P||!b(f))return!1;return(c?g.hasClass(c[1]):"center"==CKEDITOR.tools.parseCssText(g.attributes.style||"",!0)["text-align"])?!0:!1}}function b(a){return"img"==a.name?!0:"a"==a.name?1==a.children.length&&a.getFirst("img"):!1}function l(a){var b= -a.editor,d=b.editable(),c=b.document,e=a.resizer=c.createElement("span");e.addClass("cke_image_resizer");e.setAttribute("title",b.lang.image2.resizer);e.append(new CKEDITOR.dom.text("​",c));if(a.inline)a.wrapper.append(e);else{var g=a.parts.link||a.parts.image,f=g.getParent(),h=c.createElement("span");h.addClass("cke_image_resizer_wrapper");h.append(g);h.append(e);a.element.append(h,!0);f.is("span")&&f.remove()}e.on("mousedown",function(g){function f(a,b,d){var e=CKEDITOR.document,g=[];c.equals(e)|| -g.push(e.on(a,b));g.push(c.on(a,b));if(d)for(a=g.length;a--;)d.push(g.pop())}function h(){V=x+l*Z;T=Math.round(V/w)}function k(){T=y-M;V=Math.round(T*w)}var m=a.parts.image,l="right"==a.data.align?-1:1,n=g.data.$.screenX,A=g.data.$.screenY,x=m.$.clientWidth,y=m.$.clientHeight,w=x/y,u=[],t="cke_image_s"+(~l?"e":"w"),K,V,T,Y,Z,M,R;b.fire("saveSnapshot");f("mousemove",function(a){K=a.data.$;Z=K.screenX-n;M=A-K.screenY;R=Math.abs(Z/M);1==l?0>=Z?0>=M?h():R>=w?h():k():0>=M?R>=w?k():h():k():0>=Z?0>=M?R>= -w?k():h():k():0>=M?h():R>=w?h():k();15<=V&&15<=T?(m.setAttributes({width:V,height:T}),Y=!0):Y=!1},u);f("mouseup",function(){for(var c;c=u.pop();)c.removeListener();d.removeClass(t);e.removeClass("cke_image_resizing");Y&&(a.setData({width:V,height:T}),b.fire("saveSnapshot"));Y=!1},u);d.addClass(t);e.addClass("cke_image_resizing")});a.on("data",function(){e["right"==a.data.align?"addClass":"removeClass"]("cke_image_resizer_left")})}function k(a){var b=[],d;return function(c){var e=a.getCommand("justify"+ -c);if(e){b.push(function(){e.refresh(a,a.elementPath())});if(c in{right:1,left:1,center:1})e.on("exec",function(d){var e=g(a);if(e){e.setData("align",c);for(e=b.length;e--;)b[e]();d.cancel()}});e.on("refresh",function(b){var e=g(a),f={right:1,left:1,center:1};e&&(void 0===d&&(d=a.filter.checkFeature(a.widgets.registered.image.features.align)),d?this.setState(e.data.align==c?CKEDITOR.TRISTATE_ON:c in f?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED):this.setState(CKEDITOR.TRISTATE_DISABLED),b.cancel())})}}} -function d(a){a.plugins.link&&(CKEDITOR.on("dialogDefinition",function(b){b=b.data;if("link"==b.name){b=b.definition;var d=b.onShow,c=b.onOk;b.onShow=function(){var b=g(a),c=this.getContentElement("info","linkDisplayText").getElement().getParent().getParent();b&&(b.inline?!b.wrapper.getAscendant("a"):1)?(this.setupContent(b.data.link||{}),c.hide()):(c.show(),d.apply(this,arguments))};b.onOk=function(){var b=g(a);if(b&&(b.inline?!b.wrapper.getAscendant("a"):1)){var d={};this.commitContent(d);b.setData("link", -d)}else c.apply(this,arguments)}}}),a.getCommand("unlink").on("exec",function(b){var d=g(a);d&&d.parts.link&&(d.setData("link",null),this.refresh(a,a.elementPath()),b.cancel())}),a.getCommand("unlink").on("refresh",function(b){var d=g(a);d&&(this.setState(d.data.link||d.wrapper.getAscendant("a")?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED),b.cancel())}))}function g(a){return(a=a.widgets.focused)&&"image"==a.name?a:null}function m(a){var b=a.config.image2_alignClasses;a={div:{match:h(a)},p:{match:h(a)}, -img:{attributes:"!src,alt,width,height"},figure:{classes:"!"+a.config.image2_captionedClass},figcaption:!0};b?(a.div.classes=b[1],a.p.classes=a.div.classes,a.img.classes=b[0]+","+b[2],a.figure.classes+=","+a.img.classes):(a.div.styles="text-align",a.p.styles="text-align",a.img.styles="float",a.figure.styles="float,display");return a}function a(a){a=a.config.image2_alignClasses;return{dimension:{requiredContent:"img[width,height]"},align:{requiredContent:"img"+(a?"("+a[0]+")":"{float}")},caption:{requiredContent:"figcaption"}}} -function n(a){return a.data.hasCaption?a.element:a.parts.image}var w=new CKEDITOR.template('\x3cfigure class\x3d"{captionedClass}"\x3e\x3cimg alt\x3d"" src\x3d"" /\x3e\x3cfigcaption\x3e{captionPlaceholder}\x3c/figcaption\x3e\x3c/figure\x3e'),u={left:0,center:1,right:2},t=/^\s*(\d+\%)\s*$/i;CKEDITOR.plugins.add("image2",{requires:"widget,dialog",icons:"image",hidpi:!0,onLoad:function(){CKEDITOR.addCss(".cke_image_nocaption{line-height:0}.cke_editable.cke_image_sw, .cke_editable.cke_image_sw *{cursor:sw-resize !important}.cke_editable.cke_image_se, .cke_editable.cke_image_se *{cursor:se-resize !important}.cke_image_resizer{display:none;position:absolute;width:10px;height:10px;bottom:-5px;right:-5px;background:#000;outline:1px solid #fff;line-height:0;cursor:se-resize;}.cke_image_resizer_wrapper{position:relative;display:inline-block;line-height:0;}.cke_image_resizer.cke_image_resizer_left{right:auto;left:-5px;cursor:sw-resize;}.cke_widget_wrapper:hover .cke_image_resizer,.cke_image_resizer.cke_image_resizing{display:block}.cke_widget_wrapper\x3ea{display:inline-block}")}, -init:function(a){if(!a.plugins.detectConflict("image2",["easyimage"])){var b=a.config,d=a.lang.image2,c=e(a);b.filebrowserImage2BrowseUrl=b.filebrowserImageBrowseUrl;b.filebrowserImage2UploadUrl=b.filebrowserImageUploadUrl;c.pathName=d.pathName;c.editables.caption.pathName=d.pathNameCaption;a.widgets.add("image",c);a.ui.addButton&&a.ui.addButton("Image",{label:a.lang.common.image,command:"image",toolbar:"insert,10"});a.contextMenu&&(a.addMenuGroup("image",10),a.addMenuItem("image",{label:d.menu,command:"image", -group:"image"}));CKEDITOR.dialog.add("image2",this.path+"dialogs/image2.js")}},afterInit:function(a){var b={left:1,right:1,center:1,block:1},c=k(a),e;for(e in b)c(e);d(a)}});CKEDITOR.plugins.image2={stateShifter:function(a){function b(a,g){var f={};e?f.attributes={"class":e[1]}:f.styles={"text-align":"center"};f=c.createElement(a.activeEnterMode==CKEDITOR.ENTER_P?"p":"div",f);d(f,g);g.move(f);return f}function d(b,c){if(c.getParent()){var e=a.createRange();e.moveToPosition(c,CKEDITOR.POSITION_BEFORE_START); -c.remove();f.insertElementIntoRange(b,e)}else b.replace(c)}var c=a.document,e=a.config.image2_alignClasses,g=a.config.image2_captionedClass,f=a.editable(),h=["hasCaption","align","link"],k={align:function(d,c,g){var f=d.element;d.changed.align?d.newData.hasCaption||("center"==g&&(d.deflate(),d.element=b(a,f)),d.changed.hasCaption||"center"!=c||"center"==g||(d.deflate(),c=f.findOne("a,img"),c.replace(f),d.element=c)):"center"==g&&d.changed.hasCaption&&!d.newData.hasCaption&&(d.deflate(),d.element= -b(a,f));!e&&f.is("figure")&&("center"==g?f.setStyle("display","inline-block"):f.removeStyle("display"))},hasCaption:function(b,e,f){b.changed.hasCaption&&(e=b.element.is({img:1,a:1})?b.element:b.element.findOne("a,img"),b.deflate(),f?(f=CKEDITOR.dom.element.createFromHtml(w.output({captionedClass:g,captionPlaceholder:a.lang.image2.captionPlaceholder}),c),d(f,b.element),e.replace(f.findOne("img")),b.element=f):(e.replace(b.element),b.element=e))},link:function(b,d,e){if(b.changed.link){var g=b.element.is("img")? -b.element:b.element.findOne("img"),f=b.element.is("a")?b.element:b.element.findOne("a"),h=b.element.is("a")&&!e||b.element.is("img")&&e,k;h&&b.deflate();e?(d||(k=c.createElement("a",{attributes:{href:b.newData.link.url}}),k.replace(g),g.move(k)),e=CKEDITOR.plugins.image2.getLinkAttributesGetter()(a,e),CKEDITOR.tools.isEmpty(e.set)||(k||f).setAttributes(e.set),e.removed.length&&(k||f).removeAttributes(e.removed)):(e=f.findOne("img"),e.replace(f),k=e);h&&(b.element=k)}}};return function(a){var b,d; -a.changed={};for(d=0;dB.length)return!1;f=g.getParents(!0);for(q=0;qp;q++)x[q].indent+=f;f=CKEDITOR.plugins.list.arrayToList(x,a,null,b.config.enterMode,g.getDirection());if(!h.isIndent){var z;if((z=g.getParent())&&z.is("li"))for(var B=f.listNode.getChildren(),t=[],E,q=B.count()-1;0<=q;q--)(E=B.getItem(q))&& -E.is&&E.is("li")&&t.push(E)}f&&f.listNode.replace(g);if(t&&t.length)for(q=0;qg.length)){c=g[g.length-1].getNext();f=e.createElement(this.type);for(d.push(f);g.length;)d=g.shift(),a=e.createElement("li"),m=d,m.is("pre")||t.test(m.getName())||"false"==m.getAttribute("contenteditable")?d.appendTo(a):(d.copyAttributes(a),h&&d.getDirection()&&(a.removeStyle("direction"),a.removeAttribute("dir")),d.moveChildren(a),d.remove()),a.appendTo(f); -h&&k&&f.setAttribute("dir",h);c?f.insertBefore(c):f.appendTo(b)}}function c(a,b,d){function c(d){if(!(!(m=k[d?"getFirst":"getLast"]())||m.is&&m.isBlockBoundary()||!(l=b.root[d?"getPrevious":"getNext"](CKEDITOR.dom.walker.invisible(!0)))||l.is&&l.isBlockBoundary({br:1})))a.document.createElement("br")[d?"insertBefore":"insertAfter"](m)}for(var e=CKEDITOR.plugins.list.listToArray(b.root,d),g=[],f=0;fe[f-1].indent+1){g=e[f-1].indent+1-e[f].indent;for(h=e[f].indent;e[f]&&e[f].indent>=h;)e[f].indent+=g,f++;f--}var k=CKEDITOR.plugins.list.arrayToList(e,d,null,a.config.enterMode,b.root.getAttribute("dir")).listNode,m,l;c(!0);c();k.replace(b.root);a.fire("contentDomInvalidated")}function h(a,b){this.name= -a;this.context=this.type=b;this.allowedContent=b+" li";this.requiredContent=b}function b(a,b,d,c){for(var e,g;e=a[c?"getLast":"getFirst"](p);)(g=e.getDirection(1))!==b.getDirection(1)&&e.setAttribute("dir",g),e.remove(),d?e[c?"insertBefore":"insertAfter"](d):b.append(e,c),d=e}function l(a){function d(c){var e=a[c?"getPrevious":"getNext"](w);e&&e.type==CKEDITOR.NODE_ELEMENT&&e.is(a.getName())&&(b(a,e,null,!c),a.remove(),a=e)}d();d(1)}function k(a){return a.type==CKEDITOR.NODE_ELEMENT&&(a.getName()in -CKEDITOR.dtd.$block||a.getName()in CKEDITOR.dtd.$listItem)&&CKEDITOR.dtd[a.getName()]["#"]}function d(a,d,c){a.fire("saveSnapshot");c.enlarge(CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS);var e=c.extractContents();d.trim(!1,!0);var f=d.createBookmark(),h=new CKEDITOR.dom.elementPath(d.startContainer),k=h.block,h=h.lastElement.getAscendant("li",1)||k,m=new CKEDITOR.dom.elementPath(c.startContainer),n=m.contains(CKEDITOR.dtd.$listItem),m=m.contains(CKEDITOR.dtd.$list);k?(k=k.getBogus())&&k.remove():m&&(k=m.getPrevious(w))&& -u(k)&&k.remove();(k=e.getLast())&&k.type==CKEDITOR.NODE_ELEMENT&&k.is("br")&&k.remove();(k=d.startContainer.getChild(d.startOffset))?e.insertBefore(k):d.startContainer.append(e);n&&(e=g(n))&&(h.contains(n)?(b(e,n.getParent(),n),e.remove()):h.append(e));for(;c.checkStartOfBlock()&&c.checkEndOfBlock();){m=c.startPath();e=m.block;if(!e)break;e.is("li")&&(h=e.getParent(),e.equals(h.getLast(w))&&e.equals(h.getFirst(w))&&(e=h));c.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START);e.remove()}c=c.clone();e= -a.editable();c.setEndAt(e,CKEDITOR.POSITION_BEFORE_END);c=new CKEDITOR.dom.walker(c);c.evaluator=function(a){return w(a)&&!u(a)};(c=c.next())&&c.type==CKEDITOR.NODE_ELEMENT&&c.getName()in CKEDITOR.dtd.$list&&l(c);d.moveToBookmark(f);d.select();a.fire("saveSnapshot")}function g(a){return(a=a.getLast(w))&&a.type==CKEDITOR.NODE_ELEMENT&&a.getName()in m?a:null}var m={ol:1,ul:1},a=CKEDITOR.dom.walker.whitespaces(),n=CKEDITOR.dom.walker.bookmark(),w=function(b){return!(a(b)||n(b))},u=CKEDITOR.dom.walker.bogus(); -CKEDITOR.plugins.list={listToArray:function(a,b,d,c,e){if(!m[a.getName()])return[];c||(c=0);d||(d=[]);for(var g=0,f=a.getChildCount();g= -f.$.documentMode&&u.append(f.createText(" ")),u.append(l.listNode),l=l.nextIndex;else if(-1==I.indent&&!d&&g){m[g.getName()]?(u=I.element.clone(!1,!0),t!=g.getDirection(1)&&u.setAttribute("dir",t)):u=new CKEDITOR.dom.documentFragment(f);var k=g.getDirection(1)!=t,C=I.element,S=C.getAttribute("class"),O=C.getAttribute("style"),P=u.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT&&(c!=CKEDITOR.ENTER_BR||k||O||S),K,V=I.contents.length,T;for(g=0;gb&&aH.version?" ":T,f=a.hotNode&&a.hotNode.getText()==c&&a.element.equals(a.hotNode)&&a.lastCmdDirection===!!d;g(a,function(c){f&& -a.hotNode&&a.hotNode.remove();c[d?"insertAfter":"insertBefore"](b);c.setAttributes({"data-cke-magicline-hot":1,"data-cke-magicline-dir":!!d});a.lastCmdDirection=!!d});H.ie||a.enterMode==CKEDITOR.ENTER_BR||a.hotNode.scrollIntoView();a.line.detach()}return function(g){g=g.getSelection().getStartElement();var f;g=g.getAscendant(R,1);if(!p(a,g)&&g&&!g.equals(a.editable)&&!g.contains(a.editable)){(f=k(g))&&"false"==f.getAttribute("contenteditable")&&(g=f);a.element=g;f=b(a,g,!d);var h;n(f)&&f.is(a.triggers)&& -f.is(M)&&(!b(a,f,!d)||(h=b(a,f,!d))&&n(h)&&h.is(a.triggers))?e(f):(h=c(a,g),n(h)&&(b(a,h,!d)?(g=b(a,h,!d))&&n(g)&&g.is(a.triggers)&&e(h):e(h)))}}}()}}function a(a,b){if(!b||b.type!=CKEDITOR.NODE_ELEMENT||!b.$)return!1;var d=a.line;return d.wrap.equals(b)||d.wrap.contains(b)}function n(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.$}function w(a){if(!n(a))return!1;var b;(b=u(a))||(n(a)?(b={left:1,right:1,center:1},b=!(!b[a.getComputedStyle("float")]&&!b[a.getAttribute("align")])):b=!1);return b}function u(a){return!!{absolute:1, -fixed:1}[a.getComputedStyle("position")]}function t(a,b){return n(b)?b.is(a.triggers):null}function p(a,b){if(!b)return!1;for(var d=b.getParents(1),c=d.length;c--;)for(var e=a.tabuList.length;e--;)if(d[c].hasAttribute(a.tabuList[e]))return!0;return!1}function r(a,b,d){b=b[d?"getLast":"getFirst"](function(b){return a.isRelevant(b)&&!b.is(Z)});if(!b)return!1;x(a,b);return d?b.size.top>a.mouse.y:b.size.bottom -(b.inInlineMode?e.editable.top+e.editable.height/2:Math.min(e.editable.height,e.pane.height)/2),d=d[h?"getLast":"getFirst"](function(a){return!(ba(a)||ca(a))});if(!d)return null;a(b,d)&&(d=b.line.wrap[h?"getPrevious":"getNext"](function(a){return!(ba(a)||ca(a))}));if(!n(d)||w(d)||!t(b,d))return null;x(b,d);return!h&&0<=d.size.top&&l(c.y,0,d.size.top+g)?(b=b.inInlineMode||0===e.scroll.y?P:V,new f([null,d,I,O,b])):h&&d.size.bottom<=e.pane.height&&l(c.y,d.size.bottom-g,e.pane.height)?(b=b.inInlineMode|| -l(d.size.bottom,e.pane.height-g,e.pane.height)?K:V,new f([d,null,C,O,b])):null}function q(a){var d=a.mouse,e=a.view,g=a.triggerOffset,h=c(a);if(!h)return null;x(a,h);var g=Math.min(g,0|h.size.outerHeight/2),k=[],m,q;if(l(d.y,h.size.top-1,h.size.top+g))q=!1;else if(l(d.y,h.size.bottom-g,h.size.bottom+1))q=!0;else return null;if(w(h)||r(a,h,q)||h.getParent().is(Y))return null;var p=b(a,h,!q);if(p){if(p&&p.type==CKEDITOR.NODE_TEXT)return null;if(n(p)){if(w(p)||!t(a,p)||p.getParent().is(Y))return null; -k=[p,h][q?"reverse":"concat"]().concat([S,O])}}else h.equals(a.editable[q?"getLast":"getFirst"](a.isRelevant))?(y(a),q&&l(d.y,h.size.bottom-g,e.pane.height)&&l(h.size.bottom,e.pane.height-g,e.pane.height)?m=K:l(d.y,0,h.size.top+g)&&(m=P)):m=V,k=[null,h][q?"reverse":"concat"]().concat([q?C:I,O,m,h.equals(a.editable[q?"getLast":"getFirst"](a.isRelevant))?q?K:P:V]);return 0 in k?new f(k):null}function B(a,b,d,c){for(var e=b.getDocumentPosition(),g={},f={},h={},k={},m=da.length;m--;)g[da[m]]=parseInt(b.getComputedStyle.call(b, -"border-"+da[m]+"-width"),10)||0,h[da[m]]=parseInt(b.getComputedStyle.call(b,"padding-"+da[m]),10)||0,f[da[m]]=parseInt(b.getComputedStyle.call(b,"margin-"+da[m]),10)||0;d&&!c||A(a,c);k.top=e.y-(d?0:a.view.scroll.y);k.left=e.x-(d?0:a.view.scroll.x);k.outerWidth=b.$.offsetWidth;k.outerHeight=b.$.offsetHeight;k.height=k.outerHeight-(h.top+h.bottom+g.top+g.bottom);k.width=k.outerWidth-(h.left+h.right+g.left+g.right);k.bottom=k.top+k.outerHeight;k.right=k.left+k.outerWidth;a.inInlineMode&&(k.scroll={top:b.$.scrollTop, -left:b.$.scrollLeft});return z({border:g,padding:h,margin:f,ignoreScroll:d},k,!0)}function x(a,b,d){if(!n(b))return b.size=null;if(!b.size)b.size={};else if(b.size.ignoreScroll==d&&b.size.date>new Date-N)return null;return z(b.size,B(a,b,d),{date:+new Date},!0)}function y(a,b){a.view.editable=B(a,a.editable,b,!0)}function A(a,b){a.view||(a.view={});var d=a.view;if(!(!b&&d&&d.date>new Date-N)){var c=a.win,d=c.getScrollPosition(),c=c.getViewPaneSize();z(a.view,{scroll:{x:d.x,y:d.y,width:a.doc.$.documentElement.scrollWidth- -c.width,height:a.doc.$.documentElement.scrollHeight-c.height},pane:{width:c.width,height:c.height,bottom:c.height+d.y},date:+new Date},!0)}}function D(a,b,d,c){for(var e=c,g=c,h=0,k=!1,m=!1,l=a.view.pane.height,q=a.mouse;q.y+hd.left-c.x&&bd.top-c.y&&aCKEDITOR.env.version, -G=CKEDITOR.dtd,L={},I=128,C=64,S=32,O=16,P=4,K=2,V=1,T=" ",Y=G.$listItem,Z=G.$tableContent,M=z({},G.$nonEditable,G.$empty),R=G.$block,N=100,W="width:0px;height:0px;padding:0px;margin:0px;display:block;z-index:9999;color:#fff;position:absolute;font-size: 0px;line-height:0px;",U=W+"border-color:transparent;display:block;border-style:solid;",Q="\x3cspan\x3e"+T+"\x3c/span\x3e";L[CKEDITOR.ENTER_BR]="br";L[CKEDITOR.ENTER_P]="p";L[CKEDITOR.ENTER_DIV]="div";f.prototype={set:function(a,b,d){this.properties= -a+b+(d||V);return this},is:function(a){return(this.properties&a)==a}};var X=function(){function b(a,d){var c=a.$.elementFromPoint(d.x,d.y);return c&&c.nodeType?new CKEDITOR.dom.element(c):null}return function(d,c,e){if(!d.mouse)return null;var g=d.doc,f=d.line.wrap;e=e||d.mouse;var h=b(g,e);c&&a(d,h)&&(f.hide(),h=b(g,e),f.show());return!h||h.type!=CKEDITOR.NODE_ELEMENT||!h.$||H.ie&&9>H.version&&!d.boundary.equals(h)&&!d.boundary.contains(h)?null:h}}(),ba=CKEDITOR.dom.walker.whitespaces(),ca=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_COMMENT), -aa=function(){function b(a){var c=a.element,g,f,k;if(!n(c)||c.contains(a.editable)||c.isReadOnly())return null;k=D(a,function(a,b){return!b.equals(a)},function(a,b){return X(a,!0,b)},c);g=k.upper;f=k.lower;if(e(a,g,f))return k.set(S,8);if(g&&c.contains(g))for(;!g.getParent().equals(c);)g=g.getParent();else g=c.getFirst(function(b){return d(a,b)});if(f&&c.contains(f))for(;!f.getParent().equals(c);)f=f.getParent();else f=c.getLast(function(b){return d(a,b)});if(!g||!f)return null;x(a,g);x(a,f);if(!l(a.mouse.y, -g.size.top,f.size.bottom))return null;for(var c=Number.MAX_VALUE,m,q,r,p;f&&!f.equals(g)&&(q=g.getNext(a.isRelevant));)m=Math.abs(h(a,g,q)-a.mouse.y),m|<\/font>)/,m=/CKEDITOR.env.version),g=d?":not([contenteditable\x3dfalse]):not(.cke_show_blocks_off)":"",m,a;for(c=h=b=l="";m=e.pop();)a=e.length?",":"",c+=".cke_show_blocks "+m+g+a,b+=".cke_show_blocks.cke_contents_ltr "+m+g+a,l+=".cke_show_blocks.cke_contents_rtl "+m+g+a,h+=".cke_show_blocks "+m+g+"{background-image:url("+CKEDITOR.getUrl(k+"images/block_"+m+".png")+")}";CKEDITOR.addCss((c+"{background-repeat:no-repeat;border:1px dotted gray;padding-top:8px}").concat(h, -b+"{background-position:top left;padding-left:8px}",l+"{background-position:top right;padding-right:8px}"));d||CKEDITOR.addCss(".cke_show_blocks [contenteditable\x3dfalse],.cke_show_blocks .cke_show_blocks_off{border:none;padding-top:0;background-image:none}.cke_show_blocks.cke_contents_rtl [contenteditable\x3dfalse],.cke_show_blocks.cke_contents_rtl .cke_show_blocks_off{padding-right:0}.cke_show_blocks.cke_contents_ltr [contenteditable\x3dfalse],.cke_show_blocks.cke_contents_ltr .cke_show_blocks_off{padding-left:0}")}, -init:function(f){function c(){h.refresh(f)}if(!f.blockless){var h=f.addCommand("showblocks",e);h.canUndo=!1;f.config.startupOutlineBlocks&&h.setState(CKEDITOR.TRISTATE_ON);f.ui.addButton&&f.ui.addButton("ShowBlocks",{label:f.lang.showblocks.toolbar,command:"showblocks",toolbar:"tools,20"});f.on("mode",function(){h.state!=CKEDITOR.TRISTATE_DISABLED&&h.refresh(f)});f.elementMode==CKEDITOR.ELEMENT_MODE_INLINE&&(f.on("focus",c),f.on("blur",c));f.on("contentDom",function(){h.state!=CKEDITOR.TRISTATE_DISABLED&& -h.refresh(f)})}}})}(),function(){var e={preserveState:!0,editorFocus:!1,readOnly:1,exec:function(e){this.toggleState();this.refresh(e)},refresh:function(e){if(e.document){var c=this.state==CKEDITOR.TRISTATE_ON?"attachClass":"removeClass";e.editable()[c]("cke_show_borders")}}};CKEDITOR.plugins.add("showborders",{modes:{wysiwyg:1},onLoad:function(){var e;e=(CKEDITOR.env.ie6Compat?[".%1 table.%2,",".%1 table.%2 td, .%1 table.%2 th","{","border : #d3d3d3 1px dotted","}"]:".%1 table.%2,;.%1 table.%2 \x3e tr \x3e td, .%1 table.%2 \x3e tr \x3e th,;.%1 table.%2 \x3e tbody \x3e tr \x3e td, .%1 table.%2 \x3e tbody \x3e tr \x3e th,;.%1 table.%2 \x3e thead \x3e tr \x3e td, .%1 table.%2 \x3e thead \x3e tr \x3e th,;.%1 table.%2 \x3e tfoot \x3e tr \x3e td, .%1 table.%2 \x3e tfoot \x3e tr \x3e th;{;border : #d3d3d3 1px dotted;}".split(";")).join("").replace(/%2/g, -"cke_show_border").replace(/%1/g,"cke_show_borders ");CKEDITOR.addCss(e)},init:function(f){var c=f.addCommand("showborders",e);c.canUndo=!1;!1!==f.config.startupShowBorders&&c.setState(CKEDITOR.TRISTATE_ON);f.on("mode",function(){c.state!=CKEDITOR.TRISTATE_DISABLED&&c.refresh(f)},null,null,100);f.on("contentDom",function(){c.state!=CKEDITOR.TRISTATE_DISABLED&&c.refresh(f)});f.on("removeFormatCleanup",function(c){c=c.data;f.getCommand("showborders").state==CKEDITOR.TRISTATE_ON&&c.is("table")&&(!c.hasAttribute("border")|| -0>=parseInt(c.getAttribute("border"),10))&&c.addClass("cke_show_border")})},afterInit:function(e){var c=e.dataProcessor;e=c&&c.dataFilter;c=c&&c.htmlFilter;e&&e.addRules({elements:{table:function(c){c=c.attributes;var b=c["class"],e=parseInt(c.border,10);e&&!(0>=e)||b&&-1!=b.indexOf("cke_show_border")||(c["class"]=(b||"")+" cke_show_border")}}});c&&c.addRules({elements:{table:function(c){c=c.attributes;var b=c["class"];b&&(c["class"]=b.replace("cke_show_border","").replace(/\s{2}/," ").replace(/^\s+|\s+$/, -""))}}})}});CKEDITOR.on("dialogDefinition",function(e){var c=e.data.name;if("table"==c||"tableProperties"==c)if(e=e.data.definition,c=e.getContents("info").get("txtBorder"),c.commit=CKEDITOR.tools.override(c.commit,function(c){return function(b,e){c.apply(this,arguments);var f=parseInt(this.getValue(),10);e[!f||0>=f?"addClass":"removeClass"]("cke_show_border")}}),e=(e=e.getContents("advanced"))&&e.get("advCSSClasses"))e.setup=CKEDITOR.tools.override(e.setup,function(c){return function(){c.apply(this, -arguments);this.setValue(this.getValue().replace(/cke_show_border/,""))}}),e.commit=CKEDITOR.tools.override(e.commit,function(c){return function(b,e){c.apply(this,arguments);parseInt(e.getAttribute("border"),10)||e.addClass("cke_show_border")}})})}(),function(){CKEDITOR.plugins.add("sourcearea",{init:function(f){function c(){var c=b&&this.equals(CKEDITOR.document.getActive());this.hide();this.setStyle("height",this.getParent().$.clientHeight+"px");this.setStyle("width",this.getParent().$.clientWidth+ -"px");this.show();c&&this.focus()}if(f.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE){var h=CKEDITOR.plugins.sourcearea;f.addMode("source",function(b){var h=f.ui.space("contents").getDocument().createElement("textarea");h.setStyles(CKEDITOR.tools.extend({width:CKEDITOR.env.ie7Compat?"99%":"100%",height:"100%",resize:"none",outline:"none","text-align":"left"},CKEDITOR.tools.cssVendorPrefix("tab-size",f.config.sourceAreaTabSize||4)));h.setAttribute("dir","ltr");h.addClass("cke_source").addClass("cke_reset").addClass("cke_enable_context_menu"); -f.ui.space("contents").append(h);h=f.editable(new e(f,h));h.setData(f.getData(1));CKEDITOR.env.ie&&(h.attachListener(f,"resize",c,h),h.attachListener(CKEDITOR.document.getWindow(),"resize",c,h),CKEDITOR.tools.setTimeout(c,0,h));f.fire("ariaWidget",this);b()});f.addCommand("source",h.commands.source);f.ui.addButton&&f.ui.addButton("Source",{label:f.lang.sourcearea.toolbar,command:"source",toolbar:"mode,10"});f.on("mode",function(){f.getCommand("source").setState("source"==f.mode?CKEDITOR.TRISTATE_ON: -CKEDITOR.TRISTATE_OFF)});var b=CKEDITOR.env.ie&&9==CKEDITOR.env.version}}});var e=CKEDITOR.tools.createClass({base:CKEDITOR.editable,proto:{setData:function(e){this.setValue(e);this.status="ready";this.editor.fire("dataReady")},getData:function(){return this.getValue()},insertHtml:function(){},insertElement:function(){},insertText:function(){},setReadOnly:function(e){this[(e?"set":"remove")+"Attribute"]("readOnly","readonly")},detach:function(){e.baseProto.detach.call(this);this.clearCustomData(); -this.remove()}}})}(),CKEDITOR.plugins.sourcearea={commands:{source:{modes:{wysiwyg:1,source:1},editorFocus:!1,readOnly:1,exec:function(e){"wysiwyg"==e.mode&&e.fire("saveSnapshot");e.getCommand("source").setState(CKEDITOR.TRISTATE_DISABLED);e.setMode("source"==e.mode?"wysiwyg":"source")},canUndo:!1}}},CKEDITOR.plugins.add("sourcedialog",{requires:"dialog",init:function(e){e.addCommand("sourcedialog",new CKEDITOR.dialogCommand("sourcedialog"));CKEDITOR.dialog.add("sourcedialog",this.path+"dialogs/sourcedialog.js"); -e.ui.addButton&&e.ui.addButton("Sourcedialog",{label:e.lang.sourcedialog.toolbar,command:"sourcedialog",toolbar:"mode,10"})}}),CKEDITOR.plugins.add("specialchar",{availableLangs:{af:1,ar:1,az:1,bg:1,ca:1,cs:1,cy:1,da:1,de:1,"de-ch":1,el:1,en:1,"en-au":1,"en-ca":1,"en-gb":1,eo:1,es:1,"es-mx":1,et:1,eu:1,fa:1,fi:1,fr:1,"fr-ca":1,gl:1,he:1,hr:1,hu:1,id:1,it:1,ja:1,km:1,ko:1,ku:1,lt:1,lv:1,nb:1,nl:1,no:1,oc:1,pl:1,pt:1,"pt-br":1,ro:1,ru:1,si:1,sk:1,sl:1,sq:1,sv:1,th:1,tr:1,tt:1,ug:1,uk:1,vi:1,zh:1,"zh-cn":1}, -requires:"dialog",init:function(e){var f=this;CKEDITOR.dialog.add("specialchar",this.path+"dialogs/specialchar.js");e.addCommand("specialchar",{exec:function(){var c=e.langCode,c=f.availableLangs[c]?c:f.availableLangs[c.replace(/-.*/,"")]?c.replace(/-.*/,""):"en";CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(f.path+"dialogs/lang/"+c+".js"),function(){CKEDITOR.tools.extend(e.lang.specialchar,f.langEntries[c]);e.openDialog("specialchar")})},modes:{wysiwyg:1},canUndo:!1});e.ui.addButton&&e.ui.addButton("SpecialChar", -{label:e.lang.specialchar.toolbar,command:"specialchar",toolbar:"insert,50"})}}),CKEDITOR.config.specialChars="! \x26quot; # $ % \x26amp; ' ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 : ; \x26lt; \x3d \x26gt; ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ \x26euro; \x26lsquo; \x26rsquo; \x26ldquo; \x26rdquo; \x26ndash; \x26mdash; \x26iexcl; \x26cent; \x26pound; \x26curren; \x26yen; \x26brvbar; \x26sect; \x26uml; \x26copy; \x26ordf; \x26laquo; \x26not; \x26reg; \x26macr; \x26deg; \x26sup2; \x26sup3; \x26acute; \x26micro; \x26para; \x26middot; \x26cedil; \x26sup1; \x26ordm; \x26raquo; \x26frac14; \x26frac12; \x26frac34; \x26iquest; \x26Agrave; \x26Aacute; \x26Acirc; \x26Atilde; \x26Auml; \x26Aring; \x26AElig; \x26Ccedil; \x26Egrave; \x26Eacute; \x26Ecirc; \x26Euml; \x26Igrave; \x26Iacute; \x26Icirc; \x26Iuml; \x26ETH; \x26Ntilde; \x26Ograve; \x26Oacute; \x26Ocirc; \x26Otilde; \x26Ouml; \x26times; \x26Oslash; \x26Ugrave; \x26Uacute; \x26Ucirc; \x26Uuml; \x26Yacute; \x26THORN; \x26szlig; \x26agrave; \x26aacute; \x26acirc; \x26atilde; \x26auml; \x26aring; \x26aelig; \x26ccedil; \x26egrave; \x26eacute; \x26ecirc; \x26euml; \x26igrave; \x26iacute; \x26icirc; \x26iuml; \x26eth; \x26ntilde; \x26ograve; \x26oacute; \x26ocirc; \x26otilde; \x26ouml; \x26divide; \x26oslash; \x26ugrave; \x26uacute; \x26ucirc; \x26uuml; \x26yacute; \x26thorn; \x26yuml; \x26OElig; \x26oelig; \x26#372; \x26#374 \x26#373 \x26#375; \x26sbquo; \x26#8219; \x26bdquo; \x26hellip; \x26trade; \x26#9658; \x26bull; \x26rarr; \x26rArr; \x26hArr; \x26diams; \x26asymp;".split(" "), -function(){CKEDITOR.plugins.add("stylescombo",{requires:"richcombo",init:function(e){var f=e.config,c=e.lang.stylescombo,h={},b=[],l=[];e.on("stylesSet",function(c){if(c=c.data.styles){for(var d,g,m,a=0,n=c.length;a=c)for(d=this.getNextSourceNode(e,CKEDITOR.NODE_ELEMENT);d;){if(d.isVisible()&&0===d.getTabIndex()){l=d;break}d=d.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT)}else for(d=this.getDocument().getBody().getFirst();d=d.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT);){if(!h)if(!b&&d.equals(this)){if(b=!0,e){if(!(d=d.getNextSourceNode(!0,CKEDITOR.NODE_ELEMENT)))break;h=1}}else b&&!this.contains(d)&&(h=1);if(d.isVisible()&&!(0>(g=d.getTabIndex()))){if(h&&g==c){l= -d;break}g>c&&(!l||!k||g(d=g.getTabIndex())))if(0>=c){if(h&&0===d){l=g;break}d>k&& -(l=g,k=d)}else{if(h&&d==c){l=g;break}dk)&&(l=g,k=d)}}l&&l.focus()},CKEDITOR.plugins.add("table",{requires:"dialog",init:function(e){function f(c){return CKEDITOR.tools.extend(c||{},{contextSensitive:1,refresh:function(b,c){this.setState(c.contains("table",1)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)}})}if(!e.blockless){var c=e.lang.table;e.addCommand("table",new CKEDITOR.dialogCommand("table",{context:"table",allowedContent:"table{width,height}[align,border,cellpadding,cellspacing,summary];caption tbody thead tfoot;th td tr[scope];"+ -(e.plugins.dialogadvtab?"table"+e.plugins.dialogadvtab.allowedContent():""),requiredContent:"table",contentTransformations:[["table{width}: sizeToStyle","table[width]: sizeToAttribute"],["td: splitBorderShorthand"],[{element:"table",right:function(c){if(c.styles){var b;if(c.styles.border)b=CKEDITOR.tools.style.parse.border(c.styles.border);else if(CKEDITOR.env.ie&&8===CKEDITOR.env.version){var e=c.styles;e["border-left"]&&e["border-left"]===e["border-right"]&&e["border-right"]===e["border-top"]&& -e["border-top"]===e["border-bottom"]&&(b=CKEDITOR.tools.style.parse.border(e["border-top"]))}b&&b.style&&"solid"===b.style&&b.width&&0!==parseFloat(b.width)&&(c.attributes.border=1);"collapse"==c.styles["border-collapse"]&&(c.attributes.cellspacing=0)}}}]]}));e.addCommand("tableProperties",new CKEDITOR.dialogCommand("tableProperties",f()));e.addCommand("tableDelete",f({exec:function(c){var b=c.elementPath().contains("table",1);if(b){var e=b.getParent(),f=c.editable();1!=e.getChildCount()||e.is("td", -"th")||e.equals(f)||(b=e);c=c.createRange();c.moveToPosition(b,CKEDITOR.POSITION_BEFORE_START);b.remove();c.select()}}}));e.ui.addButton&&e.ui.addButton("Table",{label:c.toolbar,command:"table",toolbar:"insert,30"});CKEDITOR.dialog.add("table",this.path+"dialogs/table.js");CKEDITOR.dialog.add("tableProperties",this.path+"dialogs/table.js");e.addMenuItems&&e.addMenuItems({table:{label:c.menu,command:"tableProperties",group:"table",order:5},tabledelete:{label:c.deleteTable,command:"tableDelete",group:"table", -order:1}});e.on("doubleclick",function(c){c.data.element.is("table")&&(c.data.dialog="tableProperties")});e.contextMenu&&e.contextMenu.addListener(function(){return{tabledelete:CKEDITOR.TRISTATE_OFF,table:CKEDITOR.TRISTATE_OFF}})}}}),function(){function e(a,b){function d(a){return b?b.contains(a)&&a.getAscendant("table",!0).equals(b):!0}function c(a){0d)d=e}return d}function l(a,d){for(var c=p(a)?a:e(a),g=c[0].getAscendant("table"),f=b(c,1),c=b(c),h=d?f:c,k=CKEDITOR.tools.buildTableMap(g),g=[],f=[],c=[],m=k.length,l=0;lm?new CKEDITOR.dom.element(h[0][m+1]):k&&-1!==h[0][k-1].cellIndex?new CKEDITOR.dom.element(h[0][k-1]):new CKEDITOR.dom.element(c.$.parentNode);l.length==a&&(d[0].moveToPosition(c,CKEDITOR.POSITION_AFTER_END),d[0].select(),c.remove());return k}function d(a,b){var d=a.getStartElement().getAscendant({td:1,th:1},!0);if(d){var c=d.clone();c.appendBogus();b?c.insertBefore(d):c.insertAfter(d)}}function g(a){if(a instanceof CKEDITOR.dom.selection){var b= -a.getRanges(),d=e(a),c=d[0]&&d[0].getAscendant("table"),f;a:{var h=0;f=d.length-1;for(var k={},l,n;l=d[h++];)CKEDITOR.dom.element.setMarker(k,l,"delete_cell",!0);for(h=0;l=d[h++];)if((n=l.getPrevious())&&!n.getCustomData("delete_cell")||(n=l.getNext())&&!n.getCustomData("delete_cell")){CKEDITOR.dom.element.clearAllMarkers(k);f=n;break a}CKEDITOR.dom.element.clearAllMarkers(k);h=d[0].getParent();(h=h.getPrevious())?f=h.getLast():(h=d[f].getParent(),f=(h=h.getNext())?h.getChild(0):null)}a.reset();for(a= -d.length-1;0<=a;a--)g(d[a]);f?m(f,!0):c&&(b[0].moveToPosition(c,CKEDITOR.POSITION_BEFORE_START),b[0].select(),c.remove())}else a instanceof CKEDITOR.dom.element&&(b=a.getParent(),1==b.getChildCount()?b.remove():a.remove())}function m(a,b){var d=a.getDocument(),c=CKEDITOR.document;CKEDITOR.env.ie&&10==CKEDITOR.env.version&&(c.focus(),d.focus());d=new CKEDITOR.dom.range(d);d["moveToElementEdit"+(b?"End":"Start")](a)||(d.selectNodeContents(a),d.collapse(b?!1:!0));d.select(!0)}function a(a,b,d){a=a[b]; -if("undefined"==typeof d)return a;for(b=0;a&&bg.length)||(f=b.getCommonAncestor())&&f.type==CKEDITOR.NODE_ELEMENT&&f.is("table"))return!1;var h;b=g[0];f=b.getAscendant("table");var k=CKEDITOR.tools.buildTableMap(f),m=k.length,l=k[0].length,n=b.getParent().$.rowIndex,p=a(k,n,b);if(d){var t;try{var u=parseInt(b.getAttribute("rowspan"),10)||1; -h=parseInt(b.getAttribute("colspan"),10)||1;t=k["up"==d?n-u:"down"==d?n+u:n]["left"==d?p-h:"right"==d?p+h:p]}catch(w){return!1}if(!t||b.$==t)return!1;g["up"==d||"left"==d?"unshift":"push"](new CKEDITOR.dom.element(t))}d=b.getDocument();var L=n,u=t=0,I=!c&&new CKEDITOR.dom.documentFragment(d),C=0;for(d=0;d=l?b.removeAttribute("rowSpan"):b.$.rowSpan=t;t>=m?b.removeAttribute("colSpan"):b.$.colSpan=u;c=new CKEDITOR.dom.nodeList(f.$.rows);g=c.count();for(d=g-1;0<=d;d--)f=c.getItem(d),f.$.cells.length||(f.remove(),g++);return b} -function w(b,d){var c=e(b);if(1m){g.insertBefore(new CKEDITOR.dom.element(p));break}else p=null;p||f.append(g)}else for(l=n=1,f=g.clone(),f.insertAfter(g),f.append(g=c.clone()), -p=a(h,k),m=0;marguments.length)return this._.children.concat();a.splice||(a=[a]);return 2>a.length?this._.children[a[0]]:this._.children[a[0]]&&this._.children[a[0]].getChild?this._.children[a[0]].getChild(a.slice(1,a.length)):null}},!0);CKEDITOR.ui.dialog.vbox.prototype=new CKEDITOR.ui.dialog.hbox;(function(){var a={build:function(a,c,d){for(var b=c.children,e,f=[],g=[],h=0;hl.$.clientHeight?l.setStyle("overflow-y","hidden"):l.removeStyle("overflow-y"))}var a,m,k,l,d,e=b.config.autoGrow_bottomSpace||0,c=void 0!==b.config.autoGrow_minHeight?b.config.autoGrow_minHeight:200,n=b.config.autoGrow_maxHeight||Infinity,w=!b.config.autoGrow_maxHeight;b.addCommand("autogrow",{exec:f,modes:{wysiwyg:1},readOnly:1,canUndo:!1,editorFocus:!1});var z={contentDom:1,key:1,selectionChange:1,insertElement:1,mode:1},r;for(r in z)b.on(r,function(c){"wysiwyg"== +c.editor.mode&&setTimeout(function(){var c=b.getCommand("maximize");!b.window||c&&c.state==CKEDITOR.TRISTATE_ON?a=null:(f(),w||f())},100)});b.on("afterCommandExec",function(a){"maximize"==a.data.name&&"wysiwyg"==a.editor.mode&&(a.data.command.state==CKEDITOR.TRISTATE_ON?l.removeStyle("overflow-y"):f())});b.on("contentDom",g);g();b.config.autoGrow_onStartup&&b.editable().isVisible()&&b.execCommand("autogrow")}CKEDITOR.plugins.add("autogrow",{init:function(h){if(h.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE)h.on("instanceReady", +function(){h.editable().isInline()?h.ui.space("contents").setStyle("height","auto"):b(h)})}})})();CKEDITOR.plugins.add("basicstyles",{init:function(b){var h=0,g=function(a,g,d,e){if(e){e=new CKEDITOR.style(e);var c=f[d];c.unshift(e);b.attachStyleStateChange(e,function(a){!b.readOnly&&b.getCommand(d).setState(a)});b.addCommand(d,new CKEDITOR.styleCommand(e,{contentForms:c}));b.ui.addButton&&b.ui.addButton(a,{label:g,command:d,toolbar:"basicstyles,"+(h+=10)})}},f={bold:["strong","b",["span",function(a){a= +a.styles["font-weight"];return"bold"==a||700<=+a}]],italic:["em","i",["span",function(a){return"italic"==a.styles["font-style"]}]],underline:["u",["span",function(a){return"underline"==a.styles["text-decoration"]}]],strike:["s","strike",["span",function(a){return"line-through"==a.styles["text-decoration"]}]],subscript:["sub"],superscript:["sup"]},a=b.config,m=b.lang.basicstyles;g("Bold",m.bold,"bold",a.coreStyles_bold);g("Italic",m.italic,"italic",a.coreStyles_italic);g("Underline",m.underline,"underline", +a.coreStyles_underline);g("Strike",m.strike,"strike",a.coreStyles_strike);g("Subscript",m.subscript,"subscript",a.coreStyles_subscript);g("Superscript",m.superscript,"superscript",a.coreStyles_superscript);b.setKeystroke([[CKEDITOR.CTRL+66,"bold"],[CKEDITOR.CTRL+73,"italic"],[CKEDITOR.CTRL+85,"underline"]])}});CKEDITOR.config.coreStyles_bold={element:"strong",overrides:"b"};CKEDITOR.config.coreStyles_italic={element:"em",overrides:"i"};CKEDITOR.config.coreStyles_underline={element:"u"};CKEDITOR.config.coreStyles_strike= +{element:"s",overrides:"strike"};CKEDITOR.config.coreStyles_subscript={element:"sub"};CKEDITOR.config.coreStyles_superscript={element:"sup"};(function(){var b={exec:function(b){var g=b.getCommand("blockquote").state,f=b.getSelection(),a=f&&f.getRanges()[0];if(a){var m=f.createBookmarks();if(CKEDITOR.env.ie){var k=m[0].startNode,l=m[0].endNode,d;if(k&&"blockquote"==k.getParent().getName())for(d=k;d=d.getNext();)if(d.type==CKEDITOR.NODE_ELEMENT&&d.isBlockBoundary()){k.move(d,!0);break}if(l&&"blockquote"== +l.getParent().getName())for(d=l;d=d.getPrevious();)if(d.type==CKEDITOR.NODE_ELEMENT&&d.isBlockBoundary()){l.move(d);break}}var e=a.createIterator();e.enlargeBr=b.config.enterMode!=CKEDITOR.ENTER_BR;if(g==CKEDITOR.TRISTATE_OFF){for(k=[];g=e.getNextParagraph();)k.push(g);1>k.length&&(g=b.document.createElement(b.config.enterMode==CKEDITOR.ENTER_P?"p":"div"),l=m.shift(),a.insertNode(g),g.append(new CKEDITOR.dom.text("",b.document)),a.moveToBookmark(l),a.selectNodeContents(g),a.collapse(!0),l=a.createBookmark(), +k.push(g),m.unshift(l));d=k[0].getParent();a=[];for(l=0;lf||(this.notifications.splice(f, +1),b.element.remove(),this.element.getChildCount()||(this._removeListeners(),this.element.remove()))},_createElement:function(){var b=this.editor,f=b.config,a=new CKEDITOR.dom.element("div");a.addClass("cke_notifications_area");a.setAttribute("id","cke_notifications_area_"+b.name);a.setStyle("z-index",f.baseFloatZIndex-2);return a},_attachListeners:function(){var b=CKEDITOR.document.getWindow(),f=this.editor;b.on("scroll",this._uiBuffer.input);b.on("resize",this._uiBuffer.input);f.on("change",this._changeBuffer.input); +f.on("floatingSpaceLayout",this._layout,this,null,20);f.on("blur",this._layout,this,null,20)},_removeListeners:function(){var b=CKEDITOR.document.getWindow(),f=this.editor;b.removeListener("scroll",this._uiBuffer.input);b.removeListener("resize",this._uiBuffer.input);f.removeListener("change",this._changeBuffer.input);f.removeListener("floatingSpaceLayout",this._layout);f.removeListener("blur",this._layout)},_layout:function(){function b(){f.setStyle("left",u(t+h.width-n-w))}var f=this.element,a= +this.editor,h=a.ui.contentsElement.getClientRect(),k=a.ui.contentsElement.getDocumentPosition(),l,d,e=f.getClientRect(),c,n=this._notificationWidth,w=this._notificationMargin;c=CKEDITOR.document.getWindow();var z=c.getScrollPosition(),r=c.getViewPaneSize(),p=CKEDITOR.document.getBody(),q=p.getDocumentPosition(),u=CKEDITOR.tools.cssLength;n&&w||(c=this.element.getChild(0),n=this._notificationWidth=c.getClientRect().width,w=this._notificationMargin=parseInt(c.getComputedStyle("margin-left"),10)+parseInt(c.getComputedStyle("margin-right"), +10));a.toolbar&&(l=a.ui.space("top"),d=l.getClientRect());l&&l.isVisible()&&d.bottom>h.top&&d.bottomz.y?f.setStyles({position:"fixed",top:0}):f.setStyles({position:"absolute",top:u(k.y+h.height-e.height)});var t="fixed"==f.getStyle("position")?h.left:"static"!=p.getComputedStyle("position")?k.x-q.x:k.x;h.widthz.x+r.width?b():f.setStyle("left", +u(t)):k.x+n+w>z.x+r.width?f.setStyle("left",u(t)):k.x+h.width/2+n/2+w>z.x+r.width?f.setStyle("left",u(t-k.x+z.x+r.width-n-w)):0>h.left+h.width-n-w?b():0>h.left+h.width/2-n/2?f.setStyle("left",u(t-k.x+z.x)):f.setStyle("left",u(t+h.width/2-n/2-w/2))}};CKEDITOR.plugins.notification=b})();(function(){var b='\x3ca id\x3d"{id}" class\x3d"cke_button cke_button__{name} cke_button_{state} {cls}"'+(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' title\x3d"{title}" tabindex\x3d"-1" hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-describedby\x3d"{id}_description" aria-haspopup\x3d"{hasArrow}" aria-disabled\x3d"{ariaDisabled}"'; +CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(b+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(b+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');var h="";CKEDITOR.env.ie&&(h='return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26');var b=b+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" onclick\x3d"'+h+'CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan class\x3d"cke_button_icon cke_button__{iconName}_icon" style\x3d"{style}"')+ +'\x3e\x26nbsp;\x3c/span\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_button_label cke_button__{name}_label" aria-hidden\x3d"false"\x3e{label}\x3c/span\x3e\x3cspan id\x3d"{id}_description" class\x3d"cke_button_label" aria-hidden\x3d"false"\x3e{ariaShortcut}\x3c/span\x3e{arrowHtml}\x3c/a\x3e',g=CKEDITOR.addTemplate("buttonArrow",'\x3cspan class\x3d"cke_button_arrow"\x3e'+(CKEDITOR.env.hc?"\x26#9660;":"")+"\x3c/span\x3e"),f=CKEDITOR.addTemplate("button",b);CKEDITOR.plugins.add("button",{beforeInit:function(a){a.ui.addHandler(CKEDITOR.UI_BUTTON, +CKEDITOR.ui.button.handler)}});CKEDITOR.UI_BUTTON="button";CKEDITOR.ui.button=function(a){CKEDITOR.tools.extend(this,a,{title:a.label,click:a.click||function(b){b.execCommand(a.command)}});this._={}};CKEDITOR.ui.button.handler={create:function(a){return new CKEDITOR.ui.button(a)}};CKEDITOR.ui.button.prototype={render:function(a,b){function h(){var c=a.mode;c&&(c=this.modes[c]?void 0!==l[c]?l[c]:CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,c=a.readOnly&&!this.readOnly?CKEDITOR.TRISTATE_DISABLED: +c,this.setState(c),this.refresh&&this.refresh())}var l=null,d=CKEDITOR.env,e=this._.id=CKEDITOR.tools.getNextId(),c="",n=this.command,w,z,r;this._.editor=a;var p={id:e,button:this,editor:a,focus:function(){CKEDITOR.document.getById(e).focus()},execute:function(){this.button.click(a)},attach:function(a){this.button.attach(a)}},q=CKEDITOR.tools.addFunction(function(a){if(p.onkey)return a=new CKEDITOR.dom.event(a),!1!==p.onkey(p,a.getKeystroke())}),u=CKEDITOR.tools.addFunction(function(a){var c;p.onfocus&& +(c=!1!==p.onfocus(p,new CKEDITOR.dom.event(a)));return c}),t=0;p.clickFn=w=CKEDITOR.tools.addFunction(function(){t&&(a.unlockSelection(1),t=0);p.execute();d.iOS&&a.focus()});this.modes?(l={},a.on("beforeModeUnload",function(){a.mode&&this._.state!=CKEDITOR.TRISTATE_DISABLED&&(l[a.mode]=this._.state)},this),a.on("activeFilterChange",h,this),a.on("mode",h,this),!this.readOnly&&a.on("readOnly",h,this)):n&&(n=a.getCommand(n))&&(n.on("state",function(){this.setState(n.state)},this),c+=n.state==CKEDITOR.TRISTATE_ON? +"on":n.state==CKEDITOR.TRISTATE_DISABLED?"disabled":"off");var y;if(this.directional)a.on("contentDirChanged",function(c){var d=CKEDITOR.document.getById(this._.id),b=d.getFirst();c=c.data;c!=a.lang.dir?d.addClass("cke_"+c):d.removeClass("cke_ltr").removeClass("cke_rtl");b.setAttribute("style",CKEDITOR.skin.getIconStyle(y,"rtl"==c,this.icon,this.iconOffset))},this);n?(z=a.getCommandKeystroke(n))&&(r=CKEDITOR.tools.keystrokeToString(a.lang.common.keyboard,z)):c+="off";z=this.name||this.command;var v= +null,x=this.icon;y=z;this.icon&&!/\./.test(this.icon)?(y=this.icon,x=null):(this.icon&&(v=this.icon),CKEDITOR.env.hidpi&&this.iconHiDpi&&(v=this.iconHiDpi));v?(CKEDITOR.skin.addIcon(v,v),x=null):v=y;c={id:e,name:z,iconName:y,label:this.label,cls:(this.hasArrow?"cke_button_expandable ":"")+(this.className||""),state:c,ariaDisabled:"disabled"==c?"true":"false",title:this.title+(r?" ("+r.display+")":""),ariaShortcut:r?a.lang.common.keyboardShortcut+" "+r.aria:"",titleJs:d.gecko&&!d.hc?"":(this.title|| +"").replace("'",""),hasArrow:"string"===typeof this.hasArrow&&this.hasArrow||(this.hasArrow?"true":"false"),keydownFn:q,focusFn:u,clickFn:w,style:CKEDITOR.skin.getIconStyle(v,"rtl"==a.lang.dir,x,this.iconOffset),arrowHtml:this.hasArrow?g.output():""};f.output(c,b);if(this.onRender)this.onRender();return p},setState:function(a){if(this._.state==a)return!1;this._.state=a;var b=CKEDITOR.document.getById(this._.id);return b?(b.setState(a,"cke_button"),b.setAttribute("aria-disabled",a==CKEDITOR.TRISTATE_DISABLED), +this.hasArrow?b.setAttribute("aria-expanded",a==CKEDITOR.TRISTATE_ON):a===CKEDITOR.TRISTATE_ON?b.setAttribute("aria-pressed",!0):b.removeAttribute("aria-pressed"),!0):!1},getState:function(){return this._.state},toFeature:function(a){if(this._.feature)return this._.feature;var b=this;this.allowedContent||this.requiredContent||!this.command||(b=a.getCommand(this.command)||b);return this._.feature=b}};CKEDITOR.ui.prototype.addButton=function(a,b){this.add(a,CKEDITOR.UI_BUTTON,b)}})();(function(){function b(a){function b(){for(var c= +f(),d=CKEDITOR.tools.clone(a.config.toolbarGroups)||h(a),e=0;ec.order?-1:0>a.order? +1:a.order]+data-cke-bookmark[^<]*?<\/span>/ig,"");f&&b(a,e)})}function B(){if("wysiwyg"==a.mode){var d=D("paste");a.getCommand("cut").setState(D("cut"));a.getCommand("copy").setState(D("copy"));a.getCommand("paste").setState(d);a.fire("pasteState",d)}}function D(d){var b= +a.getSelection(),b=b&&b.getRanges()[0];if((a.readOnly||b&&b.checkReadOnly())&&d in{paste:1,cut:1})return CKEDITOR.TRISTATE_DISABLED;if("paste"==d)return CKEDITOR.TRISTATE_OFF;d=a.getSelection();b=d.getRanges();return d.getType()==CKEDITOR.SELECTION_NONE||1==b.length&&b[0].collapsed?CKEDITOR.TRISTATE_DISABLED:CKEDITOR.TRISTATE_OFF}var A=CKEDITOR.plugins.clipboard,G=0,C=0;(function(){a.on("key",v);a.on("contentDom",d);a.on("selectionChange",B);if(a.contextMenu){a.contextMenu.addListener(function(){return{cut:D("cut"), +copy:D("copy"),paste:D("paste")}});var b=null;a.on("menuShow",function(){b&&(b.removeListener(),b=null);var d=a.contextMenu.findItemByCommandName("paste");d&&d.element&&(b=d.element.on("touchend",function(){a._.forcePasteDialog=!0}))})}if(a.ui.addButton)a.once("instanceReady",function(){a._.pasteButtons&&CKEDITOR.tools.array.forEach(a._.pasteButtons,function(d){if(d=a.ui.get(d))if(d=CKEDITOR.document.getById(d._.id))d.on("touchend",function(){a._.forcePasteDialog=!0})})})})();(function(){function d(b, +e,f,g,h){var k=a.lang.clipboard[e];a.addCommand(e,f);a.ui.addButton&&a.ui.addButton(b,{label:k,command:e,toolbar:"clipboard,"+g});a.addMenuItems&&a.addMenuItem(e,{label:k,command:e,group:"clipboard",order:h})}d("Cut","cut",e("cut"),10,1);d("Copy","copy",e("copy"),20,4);d("Paste","paste",f(),30,8);a._.pasteButtons||(a._.pasteButtons=[]);a._.pasteButtons.push("Paste")})();a.getClipboardData=function(d,b){function e(a){a.removeListener();a.cancel();b(a.data)}function f(a){a.removeListener();a.cancel(); +b({type:h,dataValue:a.data.dataValue,dataTransfer:a.data.dataTransfer,method:"paste"})}var g=!1,h="auto";b||(b=d,d=null);a.on("beforePaste",function(a){a.removeListener();g=!0;h=a.data.type},null,null,1E3);a.on("paste",e,null,null,0);!1===y()&&(a.removeListener("paste",e),a._.forcePasteDialog&&g&&a.fire("pasteDialog")?(a.on("pasteDialogCommit",f),a.on("dialogHide",function(a){a.removeListener();a.data.removeListener("pasteDialogCommit",f);a.data._.committed||b(null)})):b(null))}}function g(a){if(CKEDITOR.env.webkit){if(!a.match(/^[^<]*$/g)&& +!a.match(/^(
<\/div>|
[^<]*<\/div>)*$/gi))return"html"}else if(CKEDITOR.env.ie){if(!a.match(/^([^<]|)*$/gi)&&!a.match(/^(

([^<]|)*<\/p>|(\r\n))*$/gi))return"html"}else if(CKEDITOR.env.gecko){if(!a.match(/^([^<]|)*$/gi))return"html"}else return"html";return"htmlifiedtext"}function f(a,d){function b(a){return CKEDITOR.tools.repeat("\x3c/p\x3e\x3cp\x3e",~~(a/2))+(1==a%2?"\x3cbr\x3e":"")}d=d.replace(/(?!\u3000)\s+/g," ").replace(/> +/gi, +"\x3cbr\x3e");d=d.replace(/<\/?[A-Z]+>/g,function(a){return a.toLowerCase()});if(d.match(/^[^<]$/))return d;CKEDITOR.env.webkit&&-1(
|)<\/div>)(?!$|(

(
|)<\/div>))/g,"\x3cbr\x3e").replace(/^(
(
|)<\/div>){2}(?!$)/g,"\x3cdiv\x3e\x3c/div\x3e"),d.match(/
(
|)<\/div>/)&&(d="\x3cp\x3e"+d.replace(/(
(
|)<\/div>)+/g,function(a){return b(a.split("\x3c/div\x3e\x3cdiv\x3e").length+1)})+"\x3c/p\x3e"),d=d.replace(/<\/div>
/g,"\x3cbr\x3e"), +d=d.replace(/<\/?div>/g,""));CKEDITOR.env.gecko&&a.enterMode!=CKEDITOR.ENTER_BR&&(CKEDITOR.env.gecko&&(d=d.replace(/^

$/,"\x3cbr\x3e")),-1){2,}/g,function(a){return b(a.length/4)})+"\x3c/p\x3e"));return k(a,d)}function a(a){function d(){var a={},c;for(c in CKEDITOR.dtd)"$"!=c.charAt(0)&&"div"!=c&&"span"!=c&&(a[c]=1);return a}var b={};return{get:function(e){return"plain-text"==e?b.plainText||(b.plainText=new CKEDITOR.filter(a, +"br")):"semantic-content"==e?((e=b.semanticContent)||(e=new CKEDITOR.filter(a,{}),e.allow({$1:{elements:d(),attributes:!0,styles:!1,classes:!1}}),e=b.semanticContent=e),e):e?new CKEDITOR.filter(a,e):null}}}function m(a,d,b){d=CKEDITOR.htmlParser.fragment.fromHtml(d);var e=new CKEDITOR.htmlParser.basicWriter;b.applyTo(d,!0,!1,a.activeEnterMode);d.writeHtml(e);return e.getHtml()}function k(a,d){a.enterMode==CKEDITOR.ENTER_BR?d=d.replace(/(<\/p>

)+/g,function(a){return CKEDITOR.tools.repeat("\x3cbr\x3e", +a.length/7*2)}).replace(/<\/?p>/g,""):a.enterMode==CKEDITOR.ENTER_DIV&&(d=d.replace(/<(\/)?p>/g,"\x3c$1div\x3e"));return d}function l(a){a.data.preventDefault();a.data.$.dataTransfer.dropEffect="none"}function d(a){var d=CKEDITOR.plugins.clipboard;a.on("contentDom",function(){function e(d,f,g){f.select();b(a,{dataTransfer:g,method:"drop"},1);g.sourceEditor.fire("saveSnapshot");g.sourceEditor.editable().extractHtmlFromRange(d);g.sourceEditor.getSelection().selectRanges([d]);g.sourceEditor.fire("saveSnapshot")} +function f(e,g){e.select();b(a,{dataTransfer:g,method:"drop"},1);d.resetDragDataTransfer()}function g(d,b,e){var f={$:d.data.$,target:d.data.getTarget()};b&&(f.dragRange=b);e&&(f.dropRange=e);!1===a.fire(d.name,f)&&d.data.preventDefault()}function h(a){a.type!=CKEDITOR.NODE_ELEMENT&&(a=a.getParent());return a.getChildCount()}var k=a.editable(),l=CKEDITOR.plugins.clipboard.getDropTarget(a),m=a.ui.space("top"),y=a.ui.space("bottom");d.preventDefaultDropOnElement(m);d.preventDefaultDropOnElement(y); +k.attachListener(l,"dragstart",g);k.attachListener(a,"dragstart",d.resetDragDataTransfer,d,null,1);k.attachListener(a,"dragstart",function(b){d.initDragDataTransfer(b,a)},null,null,2);k.attachListener(a,"dragstart",function(){var b=d.dragRange=a.getSelection().getRanges()[0];CKEDITOR.env.ie&&10>CKEDITOR.env.version&&(d.dragStartContainerChildCount=b?h(b.startContainer):null,d.dragEndContainerChildCount=b?h(b.endContainer):null)},null,null,100);k.attachListener(l,"dragend",g);k.attachListener(a,"dragend", +d.initDragDataTransfer,d,null,1);k.attachListener(a,"dragend",d.resetDragDataTransfer,d,null,100);k.attachListener(l,"dragover",function(a){if(CKEDITOR.env.edge)a.data.preventDefault();else{var c=a.data.getTarget();c&&c.is&&c.is("html")?a.data.preventDefault():CKEDITOR.env.ie&&CKEDITOR.plugins.clipboard.isFileApiSupported&&a.data.$.dataTransfer.types.contains("Files")&&a.data.preventDefault()}});k.attachListener(l,"drop",function(b){if(!b.data.$.defaultPrevented&&(b.data.preventDefault(),!a.readOnly)){var e= +b.data.getTarget();if(!e.isReadOnly()||e.type==CKEDITOR.NODE_ELEMENT&&e.is("html")){var e=d.getRangeAtDropPosition(b,a),f=d.dragRange;e&&g(b,f,e)}}},null,null,9999);k.attachListener(a,"drop",d.initDragDataTransfer,d,null,1);k.attachListener(a,"drop",function(b){if(b=b.data){var g=b.dropRange,h=b.dragRange,k=b.dataTransfer;k.getTransferType(a)==CKEDITOR.DATA_TRANSFER_INTERNAL?setTimeout(function(){d.internalDrop(h,g,k,a)},0):k.getTransferType(a)==CKEDITOR.DATA_TRANSFER_CROSS_EDITORS?e(h,g,k):f(g,k)}}, +null,null,9999)})}var e;CKEDITOR.plugins.add("clipboard",{requires:"dialog,notification,toolbar",init:function(c){var b,e=a(c);c.config.forcePasteAsPlainText?b="plain-text":c.config.pasteFilter?b=c.config.pasteFilter:!CKEDITOR.env.webkit||"pasteFilter"in c.config||(b="semantic-content");c.pasteFilter=e.get(b);h(c);d(c);CKEDITOR.dialog.add("paste",CKEDITOR.getUrl(this.path+"dialogs/paste.js"));if(CKEDITOR.env.gecko){var k=["image/png","image/jpeg","image/gif"],l;c.on("paste",function(a){var d=a.data, +b=d.dataTransfer;if(!d.dataValue&&"paste"==d.method&&b&&1==b.getFilesCount()&&l!=b.id&&(b=b.getFile(0),-1!=CKEDITOR.tools.indexOf(k,b.type))){var e=new FileReader;e.addEventListener("load",function(){a.data.dataValue='\x3cimg src\x3d"'+e.result+'" /\x3e';c.fire("paste",a.data)},!1);e.addEventListener("abort",function(){c.fire("paste",a.data)},!1);e.addEventListener("error",function(){c.fire("paste",a.data)},!1);e.readAsDataURL(b);l=d.dataTransfer.id;a.stop()}},null,null,1)}c.on("paste",function(a){a.data.dataTransfer|| +(a.data.dataTransfer=new CKEDITOR.plugins.clipboard.dataTransfer);if(!a.data.dataValue){var d=a.data.dataTransfer,b=d.getData("text/html");if(b)a.data.dataValue=b,a.data.type="html";else if(b=d.getData("text/plain"))a.data.dataValue=c.editable().transformPlainTextToHtml(b),a.data.type="text"}},null,null,1);c.on("paste",function(a){var c=a.data.dataValue,d=CKEDITOR.dtd.$block;-1 <\/span>/gi," "),"html"!=a.data.type&&(c=c.replace(/]*>([^<]*)<\/span>/gi, +function(a,c){return c.replace(/\t/g,"\x26nbsp;\x26nbsp; \x26nbsp;")})),-1/,"")),c=c.replace(/(<[^>]+) class="Apple-[^"]*"/gi,"$1"));if(c.match(/^<[^<]+cke_(editable|contents)/i)){var b,e,f=new CKEDITOR.dom.element("div");for(f.setHtml(c);1==f.getChildCount()&&(b=f.getFirst())&&b.type==CKEDITOR.NODE_ELEMENT&&(b.hasClass("cke_editable")|| +b.hasClass("cke_contents"));)f=e=b;e&&(c=e.getHtml().replace(/
$/i,""))}CKEDITOR.env.ie?c=c.replace(/^ (?: |\r\n)?<(\w+)/g,function(c,b){return b.toLowerCase()in d?(a.data.preSniffing="html","\x3c"+b):c}):CKEDITOR.env.webkit?c=c.replace(/<\/(\w+)>


<\/div>$/,function(c,b){return b in d?(a.data.endsWithEOL=1,"\x3c/"+b+"\x3e"):c}):CKEDITOR.env.gecko&&(c=c.replace(/(\s)
$/,"$1"));a.data.dataValue=c},null,null,3);c.on("paste",function(a){a=a.data;var d=c._.nextPasteType||a.type,b=a.dataValue, +h,k=c.config.clipboard_defaultContentType||"html",l=a.dataTransfer.getTransferType(c)==CKEDITOR.DATA_TRANSFER_EXTERNAL,n=!0===c.config.forcePasteAsPlainText;h="html"==d||"html"==a.preSniffing?"html":g(b);delete c._.nextPasteType;"htmlifiedtext"==h&&(b=f(c.config,b));if("text"==d&&"html"==h)b=m(c,b,e.get("plain-text"));else if(l&&c.pasteFilter&&!a.dontFilter||n)b=m(c,b,c.pasteFilter);a.startsWithEOL&&(b='\x3cbr data-cke-eol\x3d"1"\x3e'+b);a.endsWithEOL&&(b+='\x3cbr data-cke-eol\x3d"1"\x3e');"auto"== +d&&(d="html"==h||"html"==k?"html":"text");a.type=d;a.dataValue=b;delete a.preSniffing;delete a.startsWithEOL;delete a.endsWithEOL},null,null,6);c.on("paste",function(a){a=a.data;a.dataValue&&(c.insertHtml(a.dataValue,a.type,a.range),setTimeout(function(){c.fire("afterPaste")},0))},null,null,1E3);c.on("pasteDialog",function(a){setTimeout(function(){c.openDialog("paste",a.data)},0)})}});CKEDITOR.plugins.clipboard={isCustomCopyCutSupported:CKEDITOR.env.ie&&16>CKEDITOR.env.version||CKEDITOR.env.iOS&& +605>CKEDITOR.env.version?!1:!0,isCustomDataTypesSupported:!CKEDITOR.env.ie||16<=CKEDITOR.env.version,isFileApiSupported:!CKEDITOR.env.ie||9CKEDITOR.env.version||d.isInline()?d:a.document},fixSplitNodesAfterDrop:function(a,d,b,e){function f(a,c,b){var e=a;e.type==CKEDITOR.NODE_TEXT&&(e=a.getParent());if(e.equals(c)&&b!=c.getChildCount())return a= +d.startContainer.getChild(d.startOffset-1),c=d.startContainer.getChild(d.startOffset),a&&a.type==CKEDITOR.NODE_TEXT&&c&&c.type==CKEDITOR.NODE_TEXT&&(b=a.getLength(),a.setText(a.getText()+c.getText()),c.remove(),d.setStart(a,b),d.collapse(!0)),!0}var g=d.startContainer;"number"==typeof e&&"number"==typeof b&&g.type==CKEDITOR.NODE_ELEMENT&&(f(a.startContainer,g,b)||f(a.endContainer,g,e))},isDropRangeAffectedByDragRange:function(a,d){var b=d.startContainer,e=d.endOffset;return a.endContainer.equals(b)&& +a.endOffset<=e||a.startContainer.getParent().equals(b)&&a.startContainer.getIndex()CKEDITOR.env.version&&this.fixSplitNodesAfterDrop(a,d,g.dragStartContainerChildCount,g.dragEndContainerChildCount);(l=this.isDropRangeAffectedByDragRange(a,d))||(k=a.createBookmark(!1)); +g=d.clone().createBookmark(!1);l&&(k=a.createBookmark(!1));a=k.startNode;d=k.endNode;l=g.startNode;d&&a.getPosition(l)&CKEDITOR.POSITION_PRECEDING&&d.getPosition(l)&CKEDITOR.POSITION_FOLLOWING&&l.insertBefore(a);a=f.createRange();a.moveToBookmark(k);h.extractHtmlFromRange(a,1);d=f.createRange();g.startNode.getCommonAncestor(h)||(g=f.getSelection().createBookmarks()[0]);d.moveToBookmark(g);b(f,{dataTransfer:e,method:"drop",range:d},1);f.fire("unlockSnapshot")},getRangeAtDropPosition:function(a,d){var b= +a.data.$,e=b.clientX,f=b.clientY,g=d.getSelection(!0).getRanges()[0],h=d.createRange();if(a.data.testRange)return a.data.testRange;if(document.caretRangeFromPoint&&d.document.$.caretRangeFromPoint(e,f))b=d.document.$.caretRangeFromPoint(e,f),h.setStart(CKEDITOR.dom.node(b.startContainer),b.startOffset),h.collapse(!0);else if(b.rangeParent)h.setStart(CKEDITOR.dom.node(b.rangeParent),b.rangeOffset),h.collapse(!0);else{if(CKEDITOR.env.ie&&8l&&!k;l++){if(!k)try{b.moveToPoint(e,f-l),k=!0}catch(m){}if(!k)try{b.moveToPoint(e,f+l),k=!0}catch(v){}}if(k){var x="cke-temp-"+(new Date).getTime();b.pasteHTML('\x3cspan id\x3d"'+x+'"\x3e​\x3c/span\x3e');var B=d.document.getById(x);h.moveToPosition(B,CKEDITOR.POSITION_BEFORE_START);B.remove()}else{var D=d.document.$.elementFromPoint(e,f),A=new CKEDITOR.dom.element(D),G;if(A.equals(d.editable())||"html"==A.getName())return g&&g.startContainer&& +!g.startContainer.equals(d.editable())?g:null;G=A.getClientRect();e/i,bodyRegExp:/([\s\S]*)<\/body>/i,fragmentRegExp:/\x3c!--(?:Start|End)Fragment--\x3e/g,data:{},files:[],nativeHtmlCache:"",normalizeType:function(a){a=a.toLowerCase();return"text"==a||"text/plain"==a?"Text":"url"==a?"URL":a}};this._.fallbackDataTransfer=new CKEDITOR.plugins.clipboard.fallbackDataTransfer(this); +this.id=this.getData(e);this.id||(this.id="Text"==e?"":"cke-"+CKEDITOR.tools.getUniqueId());d&&(this.sourceEditor=d,this.setData("text/html",d.getSelectedHtml(1)),"Text"==e||this.getData("text/plain")||this.setData("text/plain",d.getSelection().getSelectedText()))};CKEDITOR.DATA_TRANSFER_INTERNAL=1;CKEDITOR.DATA_TRANSFER_CROSS_EDITORS=2;CKEDITOR.DATA_TRANSFER_EXTERNAL=3;CKEDITOR.plugins.clipboard.dataTransfer.prototype={getData:function(a,d){a=this._.normalizeType(a);var b="text/html"==a&&d?this._.nativeHtmlCache: +this._.data[a];if(void 0===b||null===b||""===b){if(this._.fallbackDataTransfer.isRequired())b=this._.fallbackDataTransfer.getData(a,d);else try{b=this.$.getData(a)||""}catch(e){b=""}"text/html"!=a||d||(b=this._stripHtml(b))}"Text"==a&&CKEDITOR.env.gecko&&this.getFilesCount()&&"file://"==b.substring(0,7)&&(b="");if("string"===typeof b)var f=b.indexOf("\x3c/html\x3e"),b=-1!==f?b.substring(0,f+7):b;return b},setData:function(a,d){a=this._.normalizeType(a);"text/html"==a?(this._.data[a]=this._stripHtml(d), +this._.nativeHtmlCache=d):this._.data[a]=d;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported||"URL"==a||"Text"==a)if("Text"==e&&"Text"==a&&(this.id=d),this._.fallbackDataTransfer.isRequired())this._.fallbackDataTransfer.setData(a,d);else try{this.$.setData(a,d)}catch(b){}},storeId:function(){"Text"!==e&&this.setData(e,this.id)},getTransferType:function(a){return this.sourceEditor?this.sourceEditor==a?CKEDITOR.DATA_TRANSFER_INTERNAL:CKEDITOR.DATA_TRANSFER_CROSS_EDITORS:CKEDITOR.DATA_TRANSFER_EXTERNAL}, +cacheData:function(){function a(c){c=d._.normalizeType(c);var b=d.getData(c);"text/html"==c&&(d._.nativeHtmlCache=d.getData(c,!0),b=d._stripHtml(b));b&&(d._.data[c]=b)}if(this.$){var d=this,b,e;if(CKEDITOR.plugins.clipboard.isCustomDataTypesSupported){if(this.$.types)for(b=0;bb?q+b:c.width>b?q-a.left:q-a.right+c.width):gb?q-b:c.width>b?q-a.right+c.width:q-a.left);b=a.top;c.height-a.topf?u-f:c.height>f?u-a.bottom+c.height:u-a.top);CKEDITOR.env.ie&&!CKEDITOR.env.edge&&((c=a=n.$.offsetParent&&new CKEDITOR.dom.element(n.$.offsetParent))&&"html"==c.getName()&&(c=c.getDocument().getBody()),c&&"rtl"==c.getComputedStyle("direction")&& +(q=CKEDITOR.env.ie8Compat?q-2*n.getDocument().getDocumentElement().$.scrollLeft:q-(a.$.scrollWidth-a.$.clientWidth)));var a=n.getFirst(),k;(k=a.getCustomData("activePanel"))&&k.onHide&&k.onHide.call(this,1);a.setCustomData("activePanel",this);n.setStyles({top:u+"px",left:q+"px"});n.setOpacity(1);l&&l()},this);d.isLoaded?a():d.onLoad=a;CKEDITOR.tools.setTimeout(function(){var a=CKEDITOR.env.webkit&&CKEDITOR.document.getWindow().getScrollPosition().y;this.focus();e.element.focus();CKEDITOR.env.webkit&& +(CKEDITOR.document.getBody().$.scrollTop=a);this.allowBlur(!0);this._.markFirst&&(CKEDITOR.env.ie?CKEDITOR.tools.setTimeout(function(){e.markFirstDisplayed?e.markFirstDisplayed():e._.markFirstDisplayed()},0):e.markFirstDisplayed?e.markFirstDisplayed():e._.markFirstDisplayed());this._.editor.fire("panelShow",this)},0,this)},CKEDITOR.env.air?200:0,this);this.visible=1;this.onShow&&this.onShow.call(this)},reposition:function(){var b=this._.showBlockParams;this.visible&&this._.showBlockParams&&(this.hide(), +this.showBlock.apply(this,b))},focus:function(){if(CKEDITOR.env.webkit){var b=CKEDITOR.document.getActive();b&&!b.equals(this._.iframe)&&b.$.blur()}(this._.lastFocused||this._.iframe.getFrameDocument().getWindow()).focus()},blur:function(){var b=this._.iframe.getFrameDocument().getActive();b&&b.is("a")&&(this._.lastFocused=b)},hide:function(b){if(this.visible&&(!this.onHide||!0!==this.onHide.call(this))){this.hideChild();CKEDITOR.env.gecko&&this._.iframe.getFrameDocument().$.activeElement.blur(); +this.element.setStyle("display","none");this.visible=0;this.element.getFirst().removeCustomData("activePanel");if(b=b&&this._.returnFocus)CKEDITOR.env.webkit&&b.type&&b.getWindow().$.focus(),b.focus();delete this._.lastFocused;this._.showBlockParams=null;this._.editor.fire("panelHide",this)}},allowBlur:function(b){var f=this._.panel;void 0!==b&&(f.allowBlur=b);return f.allowBlur},showAsChild:function(b,f,a,h,k,l){if(this._.activeChild!=b||b._.panel._.offsetParentId!=a.getId())this.hideChild(),b.onHide= +CKEDITOR.tools.bind(function(){CKEDITOR.tools.setTimeout(function(){this._.focused||this.hide()},0,this)},this),this._.activeChild=b,this._.focused=!1,b.showBlock(f,a,h,k,l),this.blur(),(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)&&setTimeout(function(){b.element.getChild(0).$.style.cssText+=""},100)},hideChild:function(b){var f=this._.activeChild;f&&(delete f.onHide,delete this._.activeChild,f.hide(),b&&this.focus())}}});CKEDITOR.on("instanceDestroyed",function(){var b=CKEDITOR.tools.isEmpty(CKEDITOR.instances), +f;for(f in h){var a=h[f];b?a.destroy():a.element.hide()}b&&(h={})})})();CKEDITOR.plugins.add("menu",{requires:"floatpanel",beforeInit:function(b){for(var h=b.config.menu_groups.split(","),g=b._.menuGroups={},f=b._.menuItems={},a=0;ad.group?1:a.orderd.order?1:0})}var h='\x3cspan class\x3d"cke_menuitem"\x3e\x3ca id\x3d"{id}" class\x3d"cke_menubutton cke_menubutton__{name} cke_menubutton_{state} {cls}" href\x3d"{href}" title\x3d"{title}" tabindex\x3d"-1" _cke_focus\x3d1 hidefocus\x3d"true" role\x3d"{role}" aria-label\x3d"{label}" aria-describedby\x3d"{id}_description" aria-haspopup\x3d"{hasPopup}" aria-disabled\x3d"{disabled}" {ariaChecked} draggable\x3d"false"', +g="";CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(h+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(h+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;" ondragstart\x3d"return false;"');CKEDITOR.env.ie&&(g='return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26');var h=h+(' onmouseover\x3d"CKEDITOR.tools.callFunction({hoverFn},{index});" onmouseout\x3d"CKEDITOR.tools.callFunction({moveOutFn},{index});" onclick\x3d"'+g+'CKEDITOR.tools.callFunction({clickFn},{index}); return false;"\x3e')+ +'\x3cspan class\x3d"cke_menubutton_inner"\x3e\x3cspan class\x3d"cke_menubutton_icon"\x3e\x3cspan class\x3d"cke_button_icon cke_button__{iconName}_icon" style\x3d"{iconStyle}"\x3e\x3c/span\x3e\x3c/span\x3e\x3cspan class\x3d"cke_menubutton_label"\x3e{label}\x3c/span\x3e{shortcutHtml}{arrowHtml}\x3c/span\x3e\x3c/a\x3e\x3cspan id\x3d"{id}_description" class\x3d"cke_voice_label" aria-hidden\x3d"false"\x3e{ariaShortcut}\x3c/span\x3e\x3c/span\x3e',f=CKEDITOR.addTemplate("menuItem",h),a=CKEDITOR.addTemplate("menuArrow", +'\x3cspan class\x3d"cke_menuarrow"\x3e\x3cspan\x3e{label}\x3c/span\x3e\x3c/span\x3e'),m=CKEDITOR.addTemplate("menuShortcut",'\x3cspan class\x3d"cke_menubutton_label cke_menubutton_shortcut"\x3e{shortcut}\x3c/span\x3e');CKEDITOR.menu=CKEDITOR.tools.createClass({$:function(a,b){b=this._.definition=b||{};this.id=CKEDITOR.tools.getNextId();this.editor=a;this.items=[];this._.listeners=[];this._.level=b.level||1;var d=CKEDITOR.tools.extend({},b.panel,{css:[CKEDITOR.skin.getPath("editor")],level:this._.level- +1,block:{}}),e=d.block.attributes=d.attributes||{};!e.role&&(e.role="menu");this._.panelDefinition=d},_:{onShow:function(){var a=this.editor.getSelection(),b=a&&a.getStartElement(),d=this.editor.elementPath(),e=this._.listeners;this.removeAll();for(var c=0;cCKEDITOR.env.version?k.createText("\r"):k.createElement("br"),f.deleteContents(),f.insertNode(a),CKEDITOR.env.needsBrFiller? +(k.createText("").insertAfter(a),m&&(q||p.blockLimit).appendBogus(),a.getNext().$.nodeValue="",f.setStartAt(a.getNext(),CKEDITOR.POSITION_AFTER_START)):f.setStartAt(a,CKEDITOR.POSITION_AFTER_END)),f.collapse(!0),f.select(),f.scrollIntoView()):l(a,b,f,g)}}};m=CKEDITOR.plugins.enterkey;k=m.enterBr;l=m.enterBlock;d=/^h[1-6]$/})();(function(){function b(b,g){var f={},a=[],m={nbsp:" ",shy:"­",gt:"\x3e",lt:"\x3c",amp:"\x26",apos:"'",quot:'"'};b=b.replace(/\b(nbsp|shy|gt|lt|amp|apos|quot)(?:,|$)/g,function(b, +d){var h=g?"\x26"+d+";":m[d];f[h]=g?m[d]:"\x26"+d+";";a.push(h);return""});b=b.replace(/,$/,"");if(!g&&b){b=b.split(",");var k=document.createElement("div"),l;k.innerHTML="\x26"+b.join(";\x26")+";";l=k.innerHTML;k=null;for(k=0;kh&&(h=640);420>g&&(g=420);var a=parseInt((window.screen.height-g)/2,10),m=parseInt((window.screen.width- +h)/2,10);f=(f||"location\x3dno,menubar\x3dno,toolbar\x3dno,dependent\x3dyes,minimizable\x3dno,modal\x3dyes,alwaysRaised\x3dyes,resizable\x3dyes,scrollbars\x3dyes")+",width\x3d"+h+",height\x3d"+g+",top\x3d"+a+",left\x3d"+m;var k=window.open("",null,f,!0);if(!k)return!1;try{-1==navigator.userAgent.toLowerCase().indexOf(" chrome/")&&(k.moveTo(m,a),k.resizeTo(h,g)),k.focus(),k.location.href=b}catch(l){window.open(b,null,f,!0)}return!0}});"use strict";(function(){function b(a){this.editor=a;this.loaders= +[]}function h(a,b,f){var h=a.config.fileTools_defaultFileName;this.editor=a;this.lang=a.lang;"string"===typeof b?(this.data=b,this.file=g(this.data),this.loaded=this.total=this.file.size):(this.data=null,this.file=b,this.total=this.file.size,this.loaded=0);f?this.fileName=f:this.file.name?this.fileName=this.file.name:(a=this.file.type.split("/"),h&&(a[0]=h),this.fileName=a.join("."));this.uploaded=0;this.responseData=this.uploadTotal=null;this.status="created";this.abort=function(){this.changeStatus("abort")}} +function g(a){var b=a.match(f)[1];a=a.replace(f,"");a=atob(a);var g=[],h,d,e,c;for(h=0;hg.status||299u.height-q.bottom?e("pin"):e("bottom"),c=u.width/2,c=a.floatSpacePreferRight?"right":0p.width?"rtl"==a.contentsLangDirection?"right":"left":c-q.left>q.right-c?"left":"right",p.width>u.width?(c="left",n=0):(n="left"==c?0u.width&& +(c="left"==c?"right":"left",n=0)),d.setStyle(c,g(("pin"==l?B:v)+n+("pin"==l?0:"left"==c?y:-y)))):(l="pin",e("pin"),k(c))}}}();if(m){var l=new CKEDITOR.template('\x3cdiv id\x3d"cke_{name}" class\x3d"cke {id} cke_reset_all cke_chrome cke_editor_{name} cke_float cke_{langDir} '+CKEDITOR.env.cssClass+'" dir\x3d"{langDir}" title\x3d"'+(CKEDITOR.env.gecko?" ":"")+'" lang\x3d"{langCode}" role\x3d"application" style\x3d"{style}"'+(b.title?' aria-labelledby\x3d"cke_{name}_arialbl"':" ")+"\x3e"+(b.title?'\x3cspan id\x3d"cke_{name}_arialbl" class\x3d"cke_voice_label"\x3e{voiceLabel}\x3c/span\x3e': +" ")+'\x3cdiv class\x3d"cke_inner"\x3e\x3cdiv id\x3d"{topId}" class\x3d"cke_top" role\x3d"presentation"\x3e{content}\x3c/div\x3e\x3c/div\x3e\x3c/div\x3e'),d=CKEDITOR.document.getBody().append(CKEDITOR.dom.element.createFromHtml(l.output({content:m,id:b.id,langDir:b.lang.dir,langCode:b.langCode,name:b.name,style:"display:none;z-index:"+(a.baseFloatZIndex-1),topId:b.ui.spaceId("top"),voiceLabel:b.title}))),e=CKEDITOR.tools.eventsBuffer(500,k),c=CKEDITOR.tools.eventsBuffer(100,k);d.unselectable();d.on("mousedown", +function(a){a=a.data;a.getTarget().hasAscendant("a",1)||a.preventDefault()});b.on("focus",function(a){k(a);b.on("change",e.input);h.on("scroll",c.input);h.on("resize",c.input)});b.on("blur",function(){d.hide();b.removeListener("change",e.input);h.removeListener("scroll",c.input);h.removeListener("resize",c.input)});b.on("destroy",function(){h.removeListener("scroll",c.input);h.removeListener("resize",c.input);d.clearCustomData();d.remove()});b.focusManager.hasFocus&&d.show();b.focusManager.add(d, +1)}}var h=CKEDITOR.document.getWindow(),g=CKEDITOR.tools.cssLength;CKEDITOR.plugins.add("floatingspace",{init:function(f){f.on("loaded",function(){b(this)},null,null,20)}})})();CKEDITOR.plugins.add("listblock",{requires:"panel",onLoad:function(){var b=CKEDITOR.addTemplate("panel-list",'\x3cul role\x3d"presentation" class\x3d"cke_panel_list"\x3e{items}\x3c/ul\x3e'),h=CKEDITOR.addTemplate("panel-list-item",'\x3cli id\x3d"{id}" class\x3d"cke_panel_listItem" role\x3dpresentation\x3e\x3ca id\x3d"{id}_option" _cke_focus\x3d1 hidefocus\x3dtrue title\x3d"{title}" draggable\x3d"false" ondragstart\x3d"return false;" href\x3d"javascript:void(\'{val}\')" onclick\x3d"{onclick}CKEDITOR.tools.callFunction({clickFn},\'{val}\'); return false;" role\x3d"option"\x3e{text}\x3c/a\x3e\x3c/li\x3e'), +g=CKEDITOR.addTemplate("panel-list-group",'\x3ch1 id\x3d"{id}" draggable\x3d"false" ondragstart\x3d"return false;" class\x3d"cke_panel_grouptitle" role\x3d"presentation" \x3e{label}\x3c/h1\x3e'),f=/\'/g;CKEDITOR.ui.panel.prototype.addListBlock=function(a,b){return this.addBlock(a,new CKEDITOR.ui.listBlock(this.getHolderElement(),b))};CKEDITOR.ui.listBlock=CKEDITOR.tools.createClass({base:CKEDITOR.ui.panel.block,$:function(a,b){b=b||{};var f=b.attributes||(b.attributes={});(this.multiSelect=!!b.multiSelect)&& +(f["aria-multiselectable"]=!0);!f.role&&(f.role="listbox");this.base.apply(this,arguments);this.element.setAttribute("role",f.role);f=this.keys;f[40]="next";f[9]="next";f[38]="prev";f[CKEDITOR.SHIFT+9]="prev";f[32]=CKEDITOR.env.ie?"mouseup":"click";CKEDITOR.env.ie&&(f[13]="mouseup");this._.pendingHtml=[];this._.pendingList=[];this._.items={};this._.groups={}},_:{close:function(){if(this._.started){var a=b.output({items:this._.pendingList.join("")});this._.pendingList=[];this._.pendingHtml.push(a); +delete this._.started}},getClick:function(){this._.click||(this._.click=CKEDITOR.tools.addFunction(function(a){var b=this.toggle(a);if(this.onClick)this.onClick(a,b)},this));return this._.click}},proto:{add:function(a,b,g){var l=CKEDITOR.tools.getNextId();this._.started||(this._.started=1,this._.size=this._.size||0);this._.items[a]=l;var d;d=CKEDITOR.tools.htmlEncodeAttr(a).replace(f,"\\'");a={id:l,val:d,onclick:CKEDITOR.env.ie?'return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26': +"",clickFn:this._.getClick(),title:CKEDITOR.tools.htmlEncodeAttr(g||a),text:b||a};this._.pendingList.push(h.output(a))},startGroup:function(a){this._.close();var b=CKEDITOR.tools.getNextId();this._.groups[a]=b;this._.pendingHtml.push(g.output({id:b,label:a}))},commit:function(){this._.close();this.element.appendHtml(this._.pendingHtml.join(""));delete this._.size;this._.pendingHtml=[]},toggle:function(a){var b=this.isMarked(a);b?this.unmark(a):this.mark(a);return!b},hideGroup:function(a){var b=(a= +this.element.getDocument().getById(this._.groups[a]))&&a.getNext();a&&(a.setStyle("display","none"),b&&"ul"==b.getName()&&b.setStyle("display","none"))},hideItem:function(a){this.element.getDocument().getById(this._.items[a]).setStyle("display","none")},showAll:function(){var a=this._.items,b=this._.groups,f=this.element.getDocument(),g;for(g in a)f.getById(a[g]).setStyle("display","");for(var d in b)a=f.getById(b[d]),g=a.getNext(),a.setStyle("display",""),g&&"ul"==g.getName()&&g.setStyle("display", +"")},mark:function(a){this.multiSelect||this.unmarkAll();a=this._.items[a];var b=this.element.getDocument().getById(a);b.addClass("cke_selected");this.element.getDocument().getById(a+"_option").setAttribute("aria-selected",!0);this.onMark&&this.onMark(b)},markFirstDisplayed:function(){var a=this;this._.markFirstDisplayed(function(){a.multiSelect||a.unmarkAll()})},unmark:function(a){var b=this.element.getDocument();a=this._.items[a];var f=b.getById(a);f.removeClass("cke_selected");b.getById(a+"_option").removeAttribute("aria-selected"); +this.onUnmark&&this.onUnmark(f)},unmarkAll:function(){var a=this._.items,b=this.element.getDocument(),f;for(f in a){var g=a[f];b.getById(g).removeClass("cke_selected");b.getById(g+"_option").removeAttribute("aria-selected")}this.onUnmark&&this.onUnmark()},isMarked:function(a){return this.element.getDocument().getById(this._.items[a]).hasClass("cke_selected")},focus:function(a){this._.focusIndex=-1;var b=this.element.getElementsByTag("a"),f,g=-1;if(a)for(f=this.element.getDocument().getById(this._.items[a]).getFirst();a= +b.getItem(++g);){if(a.equals(f)){this._.focusIndex=g;break}}else this.element.focus();f&&setTimeout(function(){f.focus()},0)}}})}});CKEDITOR.plugins.add("richcombo",{requires:"floatpanel,listblock,button",beforeInit:function(b){b.ui.addHandler(CKEDITOR.UI_RICHCOMBO,CKEDITOR.ui.richCombo.handler)}});(function(){var b='\x3cspan id\x3d"{id}" class\x3d"cke_combo cke_combo__{name} {cls}" role\x3d"presentation"\x3e\x3cspan id\x3d"{id}_label" class\x3d"cke_combo_label"\x3e{label}\x3c/span\x3e\x3ca class\x3d"cke_combo_button" title\x3d"{title}" tabindex\x3d"-1"'+ +(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href\x3d\"javascript:void('{titleJs}')\"")+' hidefocus\x3d"true" role\x3d"button" aria-labelledby\x3d"{id}_label" aria-haspopup\x3d"listbox"',h="";CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(b+=' onkeypress\x3d"return false;"');CKEDITOR.env.gecko&&(b+=' onblur\x3d"this.style.cssText \x3d this.style.cssText;"');CKEDITOR.env.ie&&(h='return false;" onmouseup\x3d"CKEDITOR.tools.getMouseButton(event)\x3d\x3dCKEDITOR.MOUSE_BUTTON_LEFT\x26\x26');var b=b+(' onkeydown\x3d"return CKEDITOR.tools.callFunction({keydownFn},event,this);" onfocus\x3d"return CKEDITOR.tools.callFunction({focusFn},event);" onclick\x3d"'+ +h+'CKEDITOR.tools.callFunction({clickFn},this);return false;"\x3e\x3cspan id\x3d"{id}_text" class\x3d"cke_combo_text cke_combo_inlinelabel"\x3e{label}\x3c/span\x3e\x3cspan class\x3d"cke_combo_open"\x3e\x3cspan class\x3d"cke_combo_arrow"\x3e'+(CKEDITOR.env.hc?"\x26#9660;":CKEDITOR.env.air?"\x26nbsp;":"")+"\x3c/span\x3e\x3c/span\x3e\x3c/a\x3e\x3c/span\x3e"),g=CKEDITOR.addTemplate("combo",b);CKEDITOR.UI_RICHCOMBO="richcombo";CKEDITOR.ui.richCombo=CKEDITOR.tools.createClass({$:function(b){CKEDITOR.tools.extend(this, +b,{canGroup:!1,title:b.label,modes:{wysiwyg:1},editorFocus:1});b=this.panel||{};delete this.panel;this.id=CKEDITOR.tools.getNextNumber();this.document=b.parent&&b.parent.getDocument()||CKEDITOR.document;b.className="cke_combopanel";b.block={multiSelect:b.multiSelect,attributes:b.attributes};b.toolbarRelated=!0;this._={panelDefinition:b,items:{},listeners:[]}},proto:{renderHtml:function(b){var a=[];this.render(b,a);return a.join("")},render:function(b,a){function h(){if(this.getState()!=CKEDITOR.TRISTATE_ON){var a= +this.modes[b.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED;b.readOnly&&!this.readOnly&&(a=CKEDITOR.TRISTATE_DISABLED);this.setState(a);this.setValue("");a!=CKEDITOR.TRISTATE_DISABLED&&this.refresh&&this.refresh()}}var k=CKEDITOR.env,l="cke_"+this.id,d=CKEDITOR.tools.addFunction(function(a){z&&(b.unlockSelection(1),z=0);c.execute(a)},this),e=this,c={id:l,combo:this,focus:function(){CKEDITOR.document.getById(l).getChild(1).focus()},execute:function(a){var d=e._;if(d.state!=CKEDITOR.TRISTATE_DISABLED)if(e.createPanel(b), +d.on)d.panel.hide();else{e.commit();var c=e.getValue();c?d.list.mark(c):d.list.unmarkAll();d.panel.showBlock(e.id,new CKEDITOR.dom.element(a),4)}},clickFn:d};this._.listeners.push(b.on("activeFilterChange",h,this));this._.listeners.push(b.on("mode",h,this));this._.listeners.push(b.on("selectionChange",h,this));!this.readOnly&&this._.listeners.push(b.on("readOnly",h,this));var n=CKEDITOR.tools.addFunction(function(a,b){a=new CKEDITOR.dom.event(a);var e=a.getKeystroke();switch(e){case 13:case 32:case 40:CKEDITOR.tools.callFunction(d, +b);break;default:c.onkey(c,e)}a.preventDefault()}),w=CKEDITOR.tools.addFunction(function(){c.onfocus&&c.onfocus()}),z=0;c.keyDownFn=n;k={id:l,name:this.name||this.command,label:this.label,title:this.title,cls:this.className||"",titleJs:k.gecko&&!k.hc?"":(this.title||"").replace("'",""),keydownFn:n,focusFn:w,clickFn:d};g.output(k,a);if(this.onRender)this.onRender();return c},createPanel:function(b){if(!this._.panel){var a=this._.panelDefinition,g=this._.panelDefinition.block,h=a.parent||CKEDITOR.document.getBody(), +l="cke_combopanel__"+this.name,d=new CKEDITOR.ui.floatPanel(b,h,a),a=d.addListBlock(this.id,g),e=this;d.onShow=function(){this.element.addClass(l);e.setState(CKEDITOR.TRISTATE_ON);e._.on=1;e.editorFocus&&!b.focusManager.hasFocus&&b.focus();if(e.onOpen)e.onOpen()};d.onHide=function(a){this.element.removeClass(l);e.setState(e.modes&&e.modes[b.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);e._.on=0;if(!a&&e.onClose)e.onClose()};d.onEscape=function(){d.hide(1)};a.onClick=function(a,b){e.onClick&& +e.onClick.call(e,a,b);d.hide()};this._.panel=d;this._.list=a;d.getBlock(this.id).onHide=function(){e._.on=0;e.setState(CKEDITOR.TRISTATE_OFF)};this.init&&this.init()}},setValue:function(b,a){this._.value=b;var g=this.document.getById("cke_"+this.id+"_text");g&&(b||a?g.removeClass("cke_combo_inlinelabel"):(a=this.label,g.addClass("cke_combo_inlinelabel")),g.setText("undefined"!=typeof a?a:b))},getValue:function(){return this._.value||""},unmarkAll:function(){this._.list.unmarkAll()},mark:function(b){this._.list.mark(b)}, +hideItem:function(b){this._.list.hideItem(b)},hideGroup:function(b){this._.list.hideGroup(b)},showAll:function(){this._.list.showAll()},add:function(b,a,g){this._.items[b]=g||b;this._.list.add(b,a,g)},startGroup:function(b){this._.list.startGroup(b)},commit:function(){this._.committed||(this._.list.commit(),this._.committed=1,CKEDITOR.ui.fire("ready",this));this._.committed=1},setState:function(b){if(this._.state!=b){var a=this.document.getById("cke_"+this.id);a.setState(b,"cke_combo");b==CKEDITOR.TRISTATE_DISABLED? +a.setAttribute("aria-disabled",!0):a.removeAttribute("aria-disabled");this._.state=b}},getState:function(){return this._.state},enable:function(){this._.state==CKEDITOR.TRISTATE_DISABLED&&this.setState(this._.lastState)},disable:function(){this._.state!=CKEDITOR.TRISTATE_DISABLED&&(this._.lastState=this._.state,this.setState(CKEDITOR.TRISTATE_DISABLED))},destroy:function(){CKEDITOR.tools.array.forEach(this._.listeners,function(b){b.removeListener()});this._.listeners=[]}},statics:{handler:{create:function(b){return new CKEDITOR.ui.richCombo(b)}}}}); +CKEDITOR.ui.prototype.addRichCombo=function(b,a){this.add(b,CKEDITOR.UI_RICHCOMBO,a)}})();CKEDITOR.plugins.add("format",{requires:"richcombo",init:function(b){if(!b.blockless){for(var h=b.config,g=b.lang.format,f=h.format_tags.split(";"),a={},m=0,k=[],l=0;l=this.rect.right||a<=this.rect.top||a>=this.rect.bottom)&&this.hideVisible();(0>=b||b>=this.winTopPane.width||0>=a||a>=this.winTopPane.height)&&this.hideVisible()},this);c.attachListener(b,"resize",f);c.attachListener(b,"mode",h);b.on("destroy",h);this.lineTpl=(new CKEDITOR.template('\x3cdiv data-cke-lineutils-line\x3d"1" class\x3d"cke_reset_all" style\x3d"{lineStyle}"\x3e\x3cspan style\x3d"{tipLeftStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3cspan style\x3d"{tipRightStyle}"\x3e\x26nbsp;\x3c/span\x3e\x3c/div\x3e')).output({lineStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({}, +m,this.lineStyle,!0)),tipLeftStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},a,{left:"0px","border-left-color":"red","border-width":"6px 0 6px 6px"},this.tipCss,this.tipLeftStyle,!0)),tipRightStyle:CKEDITOR.tools.writeCssText(CKEDITOR.tools.extend({},a,{right:"0px","border-right-color":"red","border-width":"6px 6px 6px 0"},this.tipCss,this.tipRightStyle,!0))})}function f(a){var b;if(b=a&&a.type==CKEDITOR.NODE_ELEMENT)b=!(k[a.getComputedStyle("float")]||k[a.getAttribute("align")]);return b&& +!l[a.getComputedStyle("position")]}CKEDITOR.plugins.add("lineutils");CKEDITOR.LINEUTILS_BEFORE=1;CKEDITOR.LINEUTILS_AFTER=2;CKEDITOR.LINEUTILS_INSIDE=4;b.prototype={start:function(a){var b=this,c=this.editor,f=this.doc,g,h,k,l,q=CKEDITOR.tools.eventsBuffer(50,function(){c.readOnly||"wysiwyg"!=c.mode||(b.relations={},(h=f.$.elementFromPoint(k,l))&&h.nodeType&&(g=new CKEDITOR.dom.element(h),b.traverseSearch(g),isNaN(k+l)||b.pixelSearch(g,k,l),a&&a(b.relations,k,l)))});this.listener=this.editable.attachListener(this.target, +"mousemove",function(a){k=a.data.$.clientX;l=a.data.$.clientY;q.input()});this.editable.attachListener(this.inline?this.editable:this.frame,"mouseout",function(){q.reset()})},stop:function(){this.listener&&this.listener.removeListener()},getRange:function(){var a={};a[CKEDITOR.LINEUTILS_BEFORE]=CKEDITOR.POSITION_BEFORE_START;a[CKEDITOR.LINEUTILS_AFTER]=CKEDITOR.POSITION_AFTER_END;a[CKEDITOR.LINEUTILS_INSIDE]=CKEDITOR.POSITION_AFTER_START;return function(b){var c=this.editor.createRange();c.moveToPosition(this.relations[b.uid].element, +a[b.type]);return c}}(),store:function(){function a(b,d,f){var g=b.getUniqueId();g in f?f[g].type|=d:f[g]={element:b,type:d}}return function(b,c){var g;c&CKEDITOR.LINEUTILS_AFTER&&f(g=b.getNext())&&g.isVisible()&&(a(g,CKEDITOR.LINEUTILS_BEFORE,this.relations),c^=CKEDITOR.LINEUTILS_AFTER);c&CKEDITOR.LINEUTILS_INSIDE&&f(g=b.getFirst())&&g.isVisible()&&(a(g,CKEDITOR.LINEUTILS_BEFORE,this.relations),c^=CKEDITOR.LINEUTILS_INSIDE);a(b,c,this.relations)}}(),traverseSearch:function(a){var b,c,g;do if(g=a.$["data-cke-expando"], +!(g&&g in this.relations)){if(a.equals(this.editable))break;if(f(a))for(b in this.lookups)(c=this.lookups[b](a))&&this.store(a,c)}while((!a||a.type!=CKEDITOR.NODE_ELEMENT||"true"!=a.getAttribute("contenteditable"))&&(a=a.getParent()))},pixelSearch:function(){function a(d,g,h,k,l){for(var m=0,q;l(h);){h+=k;if(25==++m)break;if(q=this.doc.$.elementFromPoint(g,h))if(q==d)m=0;else if(b(d,q)&&(m=0,f(q=new CKEDITOR.dom.element(q))))return q}}var b=CKEDITOR.env.ie||CKEDITOR.env.webkit?function(a,b){return a.contains(b)}: +function(a,b){return!!(a.compareDocumentPosition(b)&16)};return function(b,e,g){var h=this.win.getViewPaneSize().height,k=a.call(this,b.$,e,g,-1,function(a){return 0this.rect.bottom)return!1;this.inline? +g.left=c.elementRect.left-this.rect.relativeX:(0[^<]*e});0>f&&(f=a._.upcasts.length);a._.upcasts.splice(f, +0,[CKEDITOR.tools.bind(b,d),d.name,e])}var e=b.upcast,f=b.upcastPriority||10;e&&("string"==typeof e?c(d,b,f):c(e,b,f))}function m(a,b){a.focused=null;if(b.isInited()){var d=b.editor.checkDirty();a.fire("widgetBlurred",{widget:b});b.setFocused(!1);!d&&b.editor.resetDirty()}}function k(a){a=a.data;if("wysiwyg"==this.editor.mode){var b=this.editor.editable(),d=this.instances,c,e,f,g;if(b){for(c in d)d[c].isReady()&&!b.contains(d[c].wrapper)&&this.destroy(d[c],!0);if(a&&a.initOnlyNew)d=this.initOnAll(); +else{var k=b.find(".cke_widget_wrapper"),d=[];c=0;for(e=k.count();ca.selected.length||M(d,"cut"===c.name)}var d=a.editor;d.on("contentDom",function(){var a=d.editable();a.attachListener(a, +"copy",b);a.attachListener(a,"cut",b)})}function D(a){var b=a.editor;b.on("selectionCheck",function(){a.fire("checkSelection")});a.on("checkSelection",a.checkSelection,a);b.on("selectionChange",function(d){var c=(d=h.getNestedEditable(b.editable(),d.data.selection.getStartElement()))&&a.getByElement(d),e=a.widgetHoldingFocusedEditable;e?e===c&&e.focusedEditable.equals(d)||(q(a,e,null),c&&d&&q(a,c,d)):c&&d&&q(a,c,d)});b.on("dataReady",function(){C(a).commit()});b.on("blur",function(){var b;(b=a.focused)&& +m(a,b);(b=a.widgetHoldingFocusedEditable)&&q(a,b,null)})}function A(a){var b=a.editor,c={};b.on("toDataFormat",function(b){var f=CKEDITOR.tools.getNextNumber(),g=[];b.data.downcastingSessionId=f;c[f]=g;b.data.dataValue.forEach(function(b){var c=b.attributes,f;if("data-cke-widget-white-space"in c){f=d(b);var k=e(b);f.parent.attributes["data-cke-white-space-first"]&&(f.value=f.value.replace(/^ /g," "));k.parent.attributes["data-cke-white-space-last"]&&(k.value=k.value.replace(/ $/g," "))}if("data-cke-widget-id"in +c){if(c=a.instances[c["data-cke-widget-id"]])f=b.getFirst(h.isParserWidgetElement),g.push({wrapper:b,element:f,widget:c,editables:{}}),"1"!=f.attributes["data-cke-widget-keep-attr"]&&delete f.attributes["data-widget"]}else if("data-cke-widget-editable"in c)return 0CKEDITOR.tools.indexOf(b,a)&&d.push(a);a=CKEDITOR.tools.indexOf(c,a);0<=a&&c.splice(a,1);return this},focus:function(a){e=a;return this},commit:function(){var f=a.focused!==e,g,h;a.editor.fire("lockSnapshot");for(f&&(g=a.focused)&&m(a, +g);g=c.pop();)b.splice(CKEDITOR.tools.indexOf(b,g),1),g.isInited()&&(h=g.editor.checkDirty(),g.setSelected(!1),!h&&g.editor.resetDirty());f&&e&&(h=a.editor.checkDirty(),a.focused=e,a.fire("widgetFocused",{widget:e}),e.setFocused(!0),!h&&a.editor.resetDirty());for(;g=d.pop();)b.push(g),g.setSelected(!0);a.editor.fire("unlockSnapshot")}}}function I(a){a&&a.addFilterRule(function(a){return a.replace(/\s*cke_widget_selected/g,"").replace(/\s*cke_widget_focused/g,"").replace(/]*cke_widget_drag_handler_container[^>]*.*?<\/span>/gmi, +"")})}function J(a,b,d){var c=0;b=H(b);var e=a.data.classes||{},f;if(b){for(e=CKEDITOR.tools.clone(e);f=b.pop();)d?e[f]||(c=e[f]=1):e[f]&&(delete e[f],c=1);c&&a.setData("classes",e)}}function F(a){a.cancel()}function M(a,b){function d(){var b=a.getSelectedHtml(!0);if(a.widgets.focused)return a.widgets.focused.getClipboardHtml();a.once("toDataFormat",function(a){a.data.widgetsCopy=!0},null,null,-1);return a.dataProcessor.toDataFormat(b)}var c=a.widgets.focused,e,f,g;U.hasCopyBin(a)||(f=new U(a,{beforeDestroy:function(){!b&& +c&&c.focus();g&&a.getSelection().selectBookmarks(g);e&&CKEDITOR.plugins.widgetselection.addFillers(a.editable())},afterDestroy:function(){b&&!a.readOnly&&(c?a.widgets.del(c):a.extractSelectedHtml(),a.fire("saveSnapshot"))}}),c||(e=CKEDITOR.env.webkit&&CKEDITOR.plugins.widgetselection.isWholeContentSelected(a.editable()),g=a.getSelection().createBookmarks(!0)),f.handle(d()))}function H(a){return(a=(a=a.getDefinition().attributes)&&a["class"])?a.split(/\s+/):null}function E(){var a=CKEDITOR.document.getActive(), +b=this.editor,c=b.editable();(c.isInline()?c:b.document.getWindow().getFrame()).equals(a)&&b.focusManager.focus(c)}function Q(){CKEDITOR.env.gecko&&this.editor.unlockSelection();CKEDITOR.env.webkit||(this.editor.forceNextSelectionCheck(),this.editor.selectionChange(1))}function O(a,b){P(a);K(a);X(a);S(a);L(a);Z(a);ca(a);if(CKEDITOR.env.ie&&9>CKEDITOR.env.version)a.wrapper.on("dragstart",function(b){var c=b.data.getTarget();h.getNestedEditable(a,c)||a.inline&&h.isDomDragHandler(c)||b.data.preventDefault()}); +a.wrapper.removeClass("cke_widget_new");a.element.addClass("cke_widget_element");a.on("key",function(b){b=b.data.keyCode;if(13==b)a.edit();else{if(b==CKEDITOR.CTRL+67||b==CKEDITOR.CTRL+88){M(a.editor,b==CKEDITOR.CTRL+88);return}if(b in R||CKEDITOR.CTRL&b||CKEDITOR.ALT&b)return}return!1},null,null,999);a.on("doubleclick",function(b){a.edit()&&b.cancel()});if(b.data)a.on("data",b.data);if(b.edit)a.on("edit",b.edit)}function P(a){(a.wrapper=a.element.getParent()).setAttribute("data-cke-widget-id",a.id)} +function K(a){if(a.parts){var b={},c,d;for(d in a.parts)c=a.wrapper.findOne(a.parts[d]),b[d]=c;a.parts=b}}function X(a){var b=a.editables,c,d;a.editables={};if(a.editables)for(c in b)d=b[c],a.initEditable(c,"string"==typeof d?{selector:d}:d)}function S(a){if(!0===a.mask)W(a);else if(a.mask){var b=new CKEDITOR.tools.buffers.throttle(250,ea,a),c=CKEDITOR.env.gecko?300:0,d,e;a.on("focus",function(){b.input();d=a.editor.on("change",b.input);e=a.on("blur",function(){d.removeListener();e.removeListener()})}); +a.editor.on("instanceReady",function(){setTimeout(function(){b.input()},c)});a.editor.on("mode",function(){setTimeout(function(){b.input()},c)});if(CKEDITOR.env.gecko){var f=a.element.find("img");CKEDITOR.tools.array.forEach(f.toArray(),function(a){a.on("load",function(){b.input()})})}for(var g in a.editables)a.editables[g].on("focus",function(){a.editor.on("change",b.input);e&&e.removeListener()}),a.editables[g].on("blur",function(){a.editor.removeListener("change",b.input)});b.input()}}function W(a){var b= +a.wrapper.findOne(".cke_widget_mask");b||(b=new CKEDITOR.dom.element("img",a.editor.document),b.setAttributes({src:CKEDITOR.tools.transparentImageData,"class":"cke_reset cke_widget_mask"}),a.wrapper.append(b));a.mask=b}function ea(){if(this.wrapper){this.maskPart=this.maskPart||this.mask;var a=this.parts[this.maskPart],b;if(a){b=this.wrapper.findOne(".cke_widget_partial_mask");b||(b=new CKEDITOR.dom.element("img",this.editor.document),b.setAttributes({src:CKEDITOR.tools.transparentImageData,"class":"cke_reset cke_widget_partial_mask"}), +this.wrapper.append(b));this.mask=b;var c=b.$,d=a.$,e=!(c.offsetTop==d.offsetTop&&c.offsetLeft==d.offsetLeft);if(c.offsetWidth!=d.offsetWidth||c.offsetHeight!=d.offsetHeight||e)c=a.getParent(),d=CKEDITOR.plugins.widget.isDomWidget(c),b.setStyles({top:a.$.offsetTop+(d?0:c.$.offsetTop)+"px",left:a.$.offsetLeft+(d?0:c.$.offsetLeft)+"px",width:a.$.offsetWidth+"px",height:a.$.offsetHeight+"px"})}}}function L(a){if(a.draggable){var b=a.editor,c=a.wrapper.getLast(h.isDomDragHandlerContainer),d;c?d=c.findOne("img"): +(c=new CKEDITOR.dom.element("span",b.document),c.setAttributes({"class":"cke_reset cke_widget_drag_handler_container",style:"background:rgba(220,220,220,0.5);background-image:url("+b.plugins.widget.path+"images/handle.png);display:none;"}),d=new CKEDITOR.dom.element("img",b.document),d.setAttributes({"class":"cke_reset cke_widget_drag_handler","data-cke-widget-drag-handler":"1",src:CKEDITOR.tools.transparentImageData,width:15,title:b.lang.widget.move,height:15,role:"presentation"}),a.inline&&d.setAttribute("draggable", +"true"),c.append(d),a.wrapper.append(c));a.wrapper.on("dragover",function(a){a.data.preventDefault()});a.wrapper.on("mouseenter",a.updateDragHandlerPosition,a);setTimeout(function(){a.on("data",a.updateDragHandlerPosition,a)},50);if(!a.inline&&(d.on("mousedown",T,a),CKEDITOR.env.ie&&9>CKEDITOR.env.version))d.on("dragstart",function(a){a.data.preventDefault(!0)});a.dragHandlerContainer=c}}function T(a){function b(){var c;for(t.reset();c=h.pop();)c.removeListener();var d=k;c=a.sender;var e=this.repository.finder, +f=this.repository.liner,g=this.editor,l=this.editor.editable();CKEDITOR.tools.isEmpty(f.visible)||(d=e.getRange(d[0]),this.focus(),g.fire("drop",{dropRange:d,target:d.startContainer}));l.removeClass("cke_widget_dragging");f.hideVisible();g.fire("dragend",{target:c})}if(CKEDITOR.tools.getMouseButton(a)===CKEDITOR.MOUSE_BUTTON_LEFT){var c=this.repository.finder,d=this.repository.locator,e=this.repository.liner,f=this.editor,g=f.editable(),h=[],k=[],l,q;this.repository._.draggedWidget=this;var m=c.greedySearch(), +t=CKEDITOR.tools.eventsBuffer(50,function(){l=d.locate(m);k=d.sort(q,1);k.length&&(e.prepare(m,l),e.placeLine(k[0]),e.cleanup())});g.addClass("cke_widget_dragging");h.push(g.on("mousemove",function(a){q=a.data.$.clientY;t.input()}));f.fire("dragstart",{target:a.sender});h.push(f.document.once("mouseup",b,this));g.isInline()||h.push(CKEDITOR.document.once("mouseup",b,this))}}function Z(a){var b=null;a.on("data",function(){var a=this.data.classes,c;if(b!=a){for(c in b)a&&a[c]||this.removeClass(c);for(c in a)this.addClass(c); +b=a}})}function ca(a){a.on("data",function(){if(a.wrapper){var b=this.getLabel?this.getLabel():this.editor.lang.widget.label.replace(/%1/,this.pathName||this.element.getName());a.wrapper.setAttribute("role","region");a.wrapper.setAttribute("aria-label",b)}},null,null,9999)}function aa(a){a.element.data("cke-widget-data",encodeURIComponent(JSON.stringify(a.data)))}function da(){function a(){}function b(a,c,d){return d&&this.checkElement(a)?(a=d.widgets.getByElement(a,!0))&&a.checkStyleActive(this): +!1}function c(a){function b(a,c,d){for(var e=a.length,f=0;f)?(?:<(?:div|span)(?: style="[^"]+")?>)?]*data-cke-copybin-start="1"[^>]*>.?<\/span>([\s\S]+)]*data-cke-copybin-end="1"[^>]*>.?<\/span>(?:<\/(?:div|span)>)?(?:<\/(?:div|span)>)?$/i,R={37:1,38:1,39:1,40:1,8:1,46:1};R[CKEDITOR.SHIFT+121]= +1;var U=CKEDITOR.tools.createClass({$:function(a,b){this._.createCopyBin(a,b);this._.createListeners(b)},_:{createCopyBin:function(a){var b=a.document,c=CKEDITOR.env.edge&&16<=CKEDITOR.env.version,d=!a.blockless&&!CKEDITOR.env.ie||c?"div":"span",c=b.createElement(d),b=b.createElement(d);b.setAttributes({id:"cke_copybin","data-cke-temp":"1"});c.setStyles({position:"absolute",width:"1px",height:"1px",overflow:"hidden"});c.setStyle("ltr"==a.config.contentsLangDirection?"left":"right","-5000px");this.editor= +a;this.copyBin=c;this.container=b},createListeners:function(a){a&&(a.beforeDestroy&&(this.beforeDestroy=a.beforeDestroy),a.afterDestroy&&(this.afterDestroy=a.afterDestroy))}},proto:{handle:function(a){var b=this.copyBin,c=this.editor,d=this.container,e=CKEDITOR.env.ie&&9>CKEDITOR.env.version,f=c.document.getDocumentElement().$,g=c.createRange(),h=this,k=CKEDITOR.env.mac&&CKEDITOR.env.webkit,l=k?100:0,q=window.requestAnimationFrame&&!k?requestAnimationFrame:setTimeout,m,t,n;b.setHtml('\x3cspan data-cke-copybin-start\x3d"1"\x3e​\x3c/span\x3e'+ +a+'\x3cspan data-cke-copybin-end\x3d"1"\x3e​\x3c/span\x3e');c.fire("lockSnapshot");d.append(b);c.editable().append(d);m=c.on("selectionChange",F,null,null,0);t=c.widgets.on("checkSelection",F,null,null,0);e&&(n=f.scrollTop);g.selectNodeContents(b);g.select();e&&(f.scrollTop=n);return new CKEDITOR.tools.promise(function(a){q(function(){h.beforeDestroy&&h.beforeDestroy();d.remove();m.removeListener();t.removeListener();c.fire("unlockSnapshot");h.afterDestroy&&h.afterDestroy();a()},l)})}},statics:{hasCopyBin:function(a){return!!U.getCopyBin(a)}, +getCopyBin:function(a){return a.document.getById("cke_copybin")}}});CKEDITOR.plugins.widget=h;h.repository=b;h.nestedEditable=g})();"use strict";(function(){function b(a){function b(){this.deflated||(a.widgets.focused==this.widget&&(this.focused=!0),a.widgets.destroy(this.widget),this.deflated=!0)}function d(){var b=a.editable(),c=a.document;if(this.deflated)this.widget=a.widgets.initOn(this.element,"image",this.widget.data),this.widget.inline&&!(new CKEDITOR.dom.elementPath(this.widget.wrapper,b)).block&& +(b=c.createElement(a.activeEnterMode==CKEDITOR.ENTER_P?"p":"div"),b.replace(this.widget.wrapper),this.widget.wrapper.move(b)),this.focused&&(this.widget.focus(),delete this.focused),delete this.deflated;else{var e=this.widget,b=f,c=e.wrapper,g=e.data.align,e=e.data.hasCaption;if(b){for(var h=3;h--;)c.removeClass(b[h]);"center"==g?e&&c.addClass(b[1]):"none"!=g&&c.addClass(b[z[g]])}else"center"==g?(e?c.setStyle("text-align","center"):c.removeStyle("text-align"),c.removeStyle("float")):("none"==g?c.removeStyle("float"): +c.setStyle("float",g),c.removeStyle("text-align"))}}var f=a.config.image2_alignClasses,k=a.config.image2_captionedClass;return{allowedContent:e(a),requiredContent:"img[src,alt]",features:c(a),styleableElements:"img figure",contentTransformations:[["img[width]: sizeToAttribute"]],editables:{caption:{selector:"figcaption",allowedContent:"br em strong sub sup u s; a[!href,target]"}},parts:{image:"img",caption:"figcaption"},dialog:"image2",template:'\x3cimg alt\x3d"" src\x3d"" /\x3e',data:function(){var c= +this.features;this.data.hasCaption&&!a.filter.checkFeature(c.caption)&&(this.data.hasCaption=!1);"none"==this.data.align||a.filter.checkFeature(c.align)||(this.data.align="none");this.shiftState({widget:this,element:this.element,oldData:this.oldData,newData:this.data,deflate:b,inflate:d});this.data.link?this.parts.link||(this.parts.link=this.parts.image.getParent()):this.parts.link&&delete this.parts.link;this.parts.image.setAttributes({src:this.data.src,"data-cke-saved-src":this.data.src,alt:this.data.alt}); +if(this.oldData&&!this.oldData.hasCaption&&this.data.hasCaption)for(var e in this.data.classes)this.parts.image.removeClass(e);if(a.filter.checkFeature(c.dimension)){c=this.data;c={width:c.width,height:c.height};e=this.parts.image;for(var f in c)c[f]?e.setAttribute(f,c[f]):e.removeAttribute(f)}this.oldData=CKEDITOR.tools.extend({},this.data)},init:function(){var b=CKEDITOR.plugins.image2,c=this.parts.image,d={hasCaption:!!this.parts.caption,src:c.getAttribute("src"),alt:c.getAttribute("alt")||"", +width:c.getAttribute("width")||"",height:c.getAttribute("height")||"",lock:this.ready?b.checkHasNaturalRatio(c):!0},e=c.getAscendant("a");e&&this.wrapper.contains(e)&&(this.parts.link=e);d.align||(c=d.hasCaption?this.element:c,f?(c.hasClass(f[0])?d.align="left":c.hasClass(f[2])&&(d.align="right"),d.align?c.removeClass(f[z[d.align]]):d.align="none"):(d.align=c.getStyle("float")||"none",c.removeStyle("float")));a.plugins.link&&this.parts.link&&(d.link=b.getLinkAttributesParser()(a,this.parts.link), +(c=d.link.advanced)&&c.advCSSClasses&&(c.advCSSClasses=CKEDITOR.tools.trim(c.advCSSClasses.replace(/cke_\S+/,""))));this.wrapper[(d.hasCaption?"remove":"add")+"Class"]("cke_image_nocaption");this.setData(d);a.filter.checkFeature(this.features.dimension)&&!0!==a.config.image2_disableResizer&&m(this);this.shiftState=b.stateShifter(this.editor);this.on("contextMenu",function(a){a.data.image=CKEDITOR.TRISTATE_OFF;if(this.parts.link||this.wrapper.getAscendant("a"))a.data.link=a.data.unlink=CKEDITOR.TRISTATE_OFF})}, +addClass:function(a){n(this).addClass(a)},hasClass:function(a){return n(this).hasClass(a)},removeClass:function(a){n(this).removeClass(a)},getClasses:function(){var a=new RegExp("^("+[].concat(k,f).join("|")+")$");return function(){var b=this.repository.parseElementClasses(n(this).getAttribute("class")),c;for(c in b)a.test(c)&&delete b[c];return b}}(),upcast:h(a),downcast:g(a),getLabel:function(){return this.editor.lang.widget.label.replace(/%1/,(this.data.alt||"")+" "+this.pathName)}}}function h(b){var c= +f(b),d=b.config.image2_captionedClass;return function(b,e){var f={width:1,height:1},g=b.name,h;if(!b.attributes["data-cke-realelement"]&&(c(b)?("div"==g&&(h=b.getFirst("figure"))&&(b.replaceWith(h),b=h),e.align="center",h=b.getFirst("img")||b.getFirst("a").getFirst("img")):"figure"==g&&b.hasClass(d)?h=b.find(function(a){return"img"===a.name&&-1!==CKEDITOR.tools.array.indexOf(["figure","a"],a.parent.name)},!0)[0]:a(b)&&(h="a"==b.name?b.children[0]:b),h)){for(var k in f)(f=h.attributes[k])&&f.match(r)&& +delete h.attributes[k];return b}}}function g(a){var b=a.config.image2_alignClasses;return function(a){var c="a"==a.name?a.getFirst():a,d=c.attributes,e=this.data.align;if(!this.inline){var f=a.getFirst("span");f&&f.replaceWith(f.getFirst({img:1,a:1}))}e&&"none"!=e&&(f=CKEDITOR.tools.parseCssText(d.style||""),"center"==e&&"figure"==a.name?a=a.wrapWith(new CKEDITOR.htmlParser.element("div",b?{"class":b[1]}:{style:"text-align:center"})):e in{left:1,right:1}&&(b?c.addClass(b[z[e]]):f["float"]=e),b||CKEDITOR.tools.isEmpty(f)|| +(d.style=CKEDITOR.tools.writeCssText(f)));return a}}function f(b){var c=b.config.image2_captionedClass,d=b.config.image2_alignClasses,e={figure:1,a:1,img:1};return function(f){if(!(f.name in{div:1,p:1}))return!1;var g=f.children;if(1!==g.length)return!1;g=g[0];if(!(g.name in e))return!1;if("p"==f.name){if(!a(g))return!1}else if("figure"==g.name){if(!g.hasClass(c))return!1}else if(b.enterMode==CKEDITOR.ENTER_P||!a(g))return!1;return(d?f.hasClass(d[1]):"center"==CKEDITOR.tools.parseCssText(f.attributes.style|| +"",!0)["text-align"])?!0:!1}}function a(a){return"img"==a.name?!0:"a"==a.name?1==a.children.length&&a.getFirst("img"):!1}function m(a){var b=a.editor,c=b.editable(),d=b.document,e=a.resizer=d.createElement("span");e.addClass("cke_image_resizer");e.setAttribute("title",b.lang.image2.resizer);e.append(new CKEDITOR.dom.text("​",d));if(a.inline)a.wrapper.append(e);else{var f=a.parts.link||a.parts.image,g=f.getParent(),h=d.createElement("span");h.addClass("cke_image_resizer_wrapper");h.append(f);h.append(e); +a.element.append(h,!0);g.is("span")&&g.remove()}e.on("mousedown",function(f){function g(a,b,c){var e=CKEDITOR.document,f=[];d.equals(e)||f.push(e.on(a,b));f.push(d.on(a,b));if(c)for(a=f.length;a--;)c.push(f.pop())}function h(){S=x+n*L;W=Math.round(S/r)}function k(){W=w-T;S=Math.round(W*r)}var l=a.parts.image,m=function(){var a=b.config.image2_maxSize,c;if(!a)return null;a=CKEDITOR.tools.copy(a);c=CKEDITOR.plugins.image2.getNatural(l);a.width=Math.max("natural"===a.width?c.width:a.width,15);a.height= +Math.max("natural"===a.height?c.height:a.height,15);return a}(),n="right"==a.data.align?-1:1,B=f.data.$.screenX,v=f.data.$.screenY,x=l.$.clientWidth,w=l.$.clientHeight,r=x/w,z=[],K="cke_image_s"+(~n?"e":"w"),X,S,W,ea,L,T,Z;b.fire("saveSnapshot");g("mousemove",function(a){X=a.data.$;L=X.screenX-B;T=v-X.screenY;Z=Math.abs(L/T);1==n?0>=L?0>=T?h():Z>=r?h():k():0>=T?Z>=r?k():h():k():0>=L?0>=T?Z>=r?k():h():k():0>=T?h():Z>=r?h():k();a=m&&(S>m.width||W>m.height);15>S||15>W||a||(ea={width:S,height:W},l.setAttributes(ea))}, +z);g("mouseup",function(){for(var d;d=z.pop();)d.removeListener();c.removeClass(K);e.removeClass("cke_image_resizing");ea&&(a.setData(ea),b.fire("saveSnapshot"));ea=!1},z);c.addClass(K);e.addClass("cke_image_resizing")});a.on("data",function(){e["right"==a.data.align?"addClass":"removeClass"]("cke_image_resizer_left")})}function k(a){var b=[],c;return function(e){var f=a.getCommand("justify"+e);if(f){b.push(function(){f.refresh(a,a.elementPath())});if(e in{right:1,left:1,center:1})f.on("exec",function(c){var f= +d(a);if(f){f.setData("align",e);for(f=b.length;f--;)b[f]();c.cancel()}});f.on("refresh",function(b){var f=d(a),g={right:1,left:1,center:1};f&&(void 0===c&&(c=a.filter.checkFeature(a.widgets.registered.image.features.align)),c?this.setState(f.data.align==e?CKEDITOR.TRISTATE_ON:e in g?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED):this.setState(CKEDITOR.TRISTATE_DISABLED),b.cancel())})}}}function l(a){if(a.plugins.link){var b=CKEDITOR.on("dialogDefinition",function(b){b=b.data;if("link"==b.name){b= +b.definition;var c=b.onShow,e=b.onOk;b.onShow=function(){var b=d(a),e=this.getContentElement("info","linkDisplayText").getElement().getParent().getParent();b&&(b.inline?!b.wrapper.getAscendant("a"):1)?(this.setupContent(b.data.link||{}),e.hide()):(e.show(),c.apply(this,arguments))};b.onOk=function(){var b=d(a);if(b&&(b.inline?!b.wrapper.getAscendant("a"):1)){var c={};this.commitContent(c);b.setData("link",c)}else e.apply(this,arguments)}}});a.on("destroy",function(){b.removeListener()});a.getCommand("unlink").on("exec", +function(b){var c=d(a);c&&c.parts.link&&(c.setData("link",null),this.refresh(a,a.elementPath()),b.cancel())});a.getCommand("unlink").on("refresh",function(b){var c=d(a);c&&(this.setState(c.data.link||c.wrapper.getAscendant("a")?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED),b.cancel())})}}function d(a){return(a=a.widgets.focused)&&"image"==a.name?a:null}function e(a){var b=a.config.image2_alignClasses;a={div:{match:f(a)},p:{match:f(a)},img:{attributes:"!src,alt,width,height"},figure:{classes:"!"+ +a.config.image2_captionedClass},figcaption:!0};b?(a.div.classes=b[1],a.p.classes=a.div.classes,a.img.classes=b[0]+","+b[2],a.figure.classes+=","+a.img.classes):(a.div.styles="text-align",a.p.styles="text-align",a.img.styles="float",a.figure.styles="float,display");return a}function c(a){a=a.config.image2_alignClasses;return{dimension:{requiredContent:"img[width,height]"},align:{requiredContent:"img"+(a?"("+a[0]+")":"{float}")},caption:{requiredContent:"figcaption"}}}function n(a){return a.data.hasCaption? +a.element:a.parts.image}var w=new CKEDITOR.template('\x3cfigure class\x3d"{captionedClass}"\x3e\x3cimg alt\x3d"" src\x3d"" /\x3e\x3cfigcaption\x3e{captionPlaceholder}\x3c/figcaption\x3e\x3c/figure\x3e'),z={left:0,center:1,right:2},r=/^\s*(\d+\%)\s*$/i;CKEDITOR.plugins.add("image2",{requires:"widget,dialog",icons:"image",hidpi:!0,onLoad:function(){CKEDITOR.addCss('.cke_image_nocaption{line-height:0}.cke_editable.cke_image_sw, .cke_editable.cke_image_sw *{cursor:sw-resize !important}.cke_editable.cke_image_se, .cke_editable.cke_image_se *{cursor:se-resize !important}.cke_image_resizer{display:none;position:absolute;width:10px;height:10px;bottom:-5px;right:-5px;background:#000;outline:1px solid #fff;line-height:0;cursor:se-resize;}.cke_image_resizer_wrapper{position:relative;display:inline-block;line-height:0;}.cke_image_resizer.cke_image_resizer_left{right:auto;left:-5px;cursor:sw-resize;}.cke_widget_wrapper:hover .cke_image_resizer,.cke_image_resizer.cke_image_resizing{display:block}.cke_editable[contenteditable\x3d"false"] .cke_image_resizer{display:none;}.cke_widget_wrapper\x3ea{display:inline-block}')}, +init:function(a){if(!a.plugins.detectConflict("image2",["easyimage"])){var c=a.config,d=a.lang.image2,e=b(a);c.filebrowserImage2BrowseUrl=c.filebrowserImageBrowseUrl;c.filebrowserImage2UploadUrl=c.filebrowserImageUploadUrl;e.pathName=d.pathName;e.editables.caption.pathName=d.pathNameCaption;a.widgets.add("image",e);a.ui.addButton&&a.ui.addButton("Image",{label:a.lang.common.image,command:"image",toolbar:"insert,10"});a.contextMenu&&(a.addMenuGroup("image",10),a.addMenuItem("image",{label:d.menu,command:"image", +group:"image"}));CKEDITOR.dialog.add("image2",this.path+"dialogs/image2.js")}},afterInit:function(a){var b={left:1,right:1,center:1,block:1},c=k(a),d;for(d in b)c(d);l(a)}});CKEDITOR.plugins.image2={stateShifter:function(a){function b(a,f){var g={};e?g.attributes={"class":e[1]}:g.styles={"text-align":"center"};g=d.createElement(a.activeEnterMode==CKEDITOR.ENTER_P?"p":"div",g);c(g,f);f.move(g);return g}function c(b,d){if(d.getParent()){var e=a.createRange();e.moveToPosition(d,CKEDITOR.POSITION_BEFORE_START); +d.remove();g.insertElementIntoRange(b,e)}else b.replace(d)}var d=a.document,e=a.config.image2_alignClasses,f=a.config.image2_captionedClass,g=a.editable(),h=["hasCaption","align","link"],k={align:function(c,d,f){var g=c.element;c.changed.align?c.newData.hasCaption||("center"==f&&(c.deflate(),c.element=b(a,g)),c.changed.hasCaption||"center"!=d||"center"==f||(c.deflate(),d=g.findOne("a,img"),d.replace(g),c.element=d)):"center"==f&&c.changed.hasCaption&&!c.newData.hasCaption&&(c.deflate(),c.element= +b(a,g));!e&&g.is("figure")&&("center"==f?g.setStyle("display","inline-block"):g.removeStyle("display"))},hasCaption:function(b,e,g){b.changed.hasCaption&&(e=b.element.is({img:1,a:1})?b.element:b.element.findOne("a,img"),b.deflate(),g?(g=CKEDITOR.dom.element.createFromHtml(w.output({captionedClass:f,captionPlaceholder:a.lang.image2.captionPlaceholder}),d),c(g,b.element),e.replace(g.findOne("img")),b.element=g):(e.replace(b.element),b.element=e))},link:function(b,c,e){if(b.changed.link){var f=b.element.is("img")? +b.element:b.element.findOne("img"),g=b.element.is("a")?b.element:b.element.findOne("a"),h=b.element.is("a")&&!e||b.element.is("img")&&e,k;h&&b.deflate();e?(c||(k=d.createElement("a",{attributes:{href:b.newData.link.url}}),k.replace(f),f.move(k)),e=CKEDITOR.plugins.image2.getLinkAttributesGetter()(a,e),CKEDITOR.tools.isEmpty(e.set)||(k||g).setAttributes(e.set),e.removed.length&&(k||g).removeAttributes(e.removed)):(e=g.findOne("img"),e.replace(g),k=e);h&&(b.element=k)}}};return function(a){var b,c; +a.changed={};for(c=0;cy.length)return!1;h=d.getParents(!0);for(m=0;mx;m++)v[m].indent+=h;h=CKEDITOR.plugins.list.arrayToList(v,c,null,a.config.enterMode,d.getDirection());if(!e.isIndent){var A;if((A=d.getParent())&&A.is("li"))for(var y=h.listNode.getChildren(),r=[],C,m=y.count()-1;0<=m;m--)(C=y.getItem(m))&& +C.is&&C.is("li")&&r.push(C)}h&&h.listNode.replace(d);if(r&&r.length)for(m=0;mf.length)){d=f[f.length-1].getNext();g=e.createElement(this.type);for(c.push(g);f.length;)c=f.shift(),a=e.createElement("li"),l=c,l.is("pre")||r.test(l.getName())||"false"==l.getAttribute("contenteditable")?c.appendTo(a):(c.copyAttributes(a),h&&c.getDirection()&&(a.removeStyle("direction"),a.removeAttribute("dir")),c.moveChildren(a), +c.remove()),a.appendTo(g);h&&k&&g.setAttribute("dir",h);d?g.insertBefore(d):g.appendTo(b)}}function g(a,b,c){function d(c){if(!(!(l=k[c?"getFirst":"getLast"]())||l.is&&l.isBlockBoundary()||!(m=b.root[c?"getPrevious":"getNext"](CKEDITOR.dom.walker.invisible(!0)))||m.is&&m.isBlockBoundary({br:1})))a.document.createElement("br")[c?"insertBefore":"insertAfter"](l)}for(var e=CKEDITOR.plugins.list.listToArray(b.root,c),f=[],g=0;ge[g-1].indent+1){f=e[g-1].indent+1-e[g].indent;for(h=e[g].indent;e[g]&&e[g].indent>=h;)e[g].indent+=f,g++;g--}var k=CKEDITOR.plugins.list.arrayToList(e,c,null,a.config.enterMode,b.root.getAttribute("dir")).listNode,l,m;d(!0);d();k.replace(b.root);a.fire("contentDomInvalidated")}function f(a,b){this.name= +a;this.context=this.type=b;this.allowedContent=b+" li";this.requiredContent=b}function a(a,b,c,d){for(var e,f;e=a[d?"getLast":"getFirst"](p);)(f=e.getDirection(1))!==b.getDirection(1)&&e.setAttribute("dir",f),e.remove(),c?e[d?"insertBefore":"insertAfter"](c):b.append(e,d),c=e}function m(b){function c(d){var e=b[d?"getPrevious":"getNext"](w);e&&e.type==CKEDITOR.NODE_ELEMENT&&e.is(b.getName())&&(a(b,e,null,!d),b.remove(),b=e)}c();c(1)}function k(a){return a.type==CKEDITOR.NODE_ELEMENT&&(a.getName()in +CKEDITOR.dtd.$block||a.getName()in CKEDITOR.dtd.$listItem)&&CKEDITOR.dtd[a.getName()]["#"]}function l(b,c,e){b.fire("saveSnapshot");e.enlarge(CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS);var f=e.extractContents();c.trim(!1,!0);var g=c.createBookmark(),h=new CKEDITOR.dom.elementPath(c.startContainer),k=h.block,h=h.lastElement.getAscendant("li",1)||k,l=new CKEDITOR.dom.elementPath(e.startContainer),n=l.contains(CKEDITOR.dtd.$listItem),l=l.contains(CKEDITOR.dtd.$list);k?(k=k.getBogus())&&k.remove():l&&(k=l.getPrevious(w))&& +z(k)&&k.remove();(k=f.getLast())&&k.type==CKEDITOR.NODE_ELEMENT&&k.is("br")&&k.remove();(k=c.startContainer.getChild(c.startOffset))?f.insertBefore(k):c.startContainer.append(f);n&&(f=d(n))&&(h.contains(n)?(a(f,n.getParent(),n),f.remove()):h.append(f));for(;e.checkStartOfBlock()&&e.checkEndOfBlock();){l=e.startPath();f=l.block;if(!f)break;f.is("li")&&(h=f.getParent(),f.equals(h.getLast(w))&&f.equals(h.getFirst(w))&&(f=h));e.moveToPosition(f,CKEDITOR.POSITION_BEFORE_START);f.remove()}e=e.clone();f= +b.editable();e.setEndAt(f,CKEDITOR.POSITION_BEFORE_END);e=new CKEDITOR.dom.walker(e);e.evaluator=function(a){return w(a)&&!z(a)};(e=e.next())&&e.type==CKEDITOR.NODE_ELEMENT&&e.getName()in CKEDITOR.dtd.$list&&m(e);c.moveToBookmark(g);c.select();b.fire("saveSnapshot")}function d(a){return(a=a.getLast(w))&&a.type==CKEDITOR.NODE_ELEMENT&&a.getName()in e?a:null}var e={ol:1,ul:1},c=CKEDITOR.dom.walker.whitespaces(),n=CKEDITOR.dom.walker.bookmark(),w=function(a){return!(c(a)||n(a))},z=CKEDITOR.dom.walker.bogus(); +CKEDITOR.plugins.list={listToArray:function(a,b,c,d,f){if(!e[a.getName()])return[];d||(d=0);c||(c=[]);for(var g=0,h=a.getChildCount();g= +h.$.documentMode&&r.append(h.createText(" ")),r.append(m.listNode),m=m.nextIndex;else if(-1==H.indent&&!c&&g){e[g.getName()]?(r=H.element.clone(!1,!0),z!=g.getDirection(1)&&r.setAttribute("dir",z)):r=new CKEDITOR.dom.documentFragment(h);var l=g.getDirection(1)!=z,E=H.element,Q=E.getAttribute("class"),O=E.getAttribute("style"),P=r.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT&&(d!=CKEDITOR.ENTER_BR||l||O||Q),K,X=H.contents.length,S;for(g=0;gb&&aI.version?" ":S,g=b.hotNode&&b.hotNode.getText()==f&&b.element.equals(b.hotNode)&&b.lastCmdDirection===!!c;d(b,function(d){g&& +b.hotNode&&b.hotNode.remove();d[c?"insertAfter":"insertBefore"](a);d.setAttributes({"data-cke-magicline-hot":1,"data-cke-magicline-dir":!!c});b.lastCmdDirection=!!c});I.ie||b.enterMode==CKEDITOR.ENTER_BR||b.hotNode.scrollIntoView();b.line.detach()}return function(d){d=d.getSelection().getStartElement();var f;d=d.getAscendant(T,1);if(!p(b,d)&&d&&!d.equals(b.editable)&&!d.contains(b.editable)){(f=k(d))&&"false"==f.getAttribute("contenteditable")&&(d=f);b.element=d;f=a(b,d,!c);var h;n(f)&&f.is(b.triggers)&& +f.is(L)&&(!a(b,f,!c)||(h=a(b,f,!c))&&n(h)&&h.is(b.triggers))?e(f):(h=g(b,d),n(h)&&(a(b,h,!c)?(d=a(b,h,!c))&&n(d)&&d.is(b.triggers)&&e(h):e(h)))}}}()}}function c(a,b){if(!b||b.type!=CKEDITOR.NODE_ELEMENT||!b.$)return!1;var c=a.line;return c.wrap.equals(b)||c.wrap.contains(b)}function n(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.$}function w(a){if(!n(a))return!1;var b;(b=z(a))||(n(a)?(b={left:1,right:1,center:1},b=!(!b[a.getComputedStyle("float")]&&!b[a.getAttribute("align")])):b=!1);return b}function z(a){return!!{absolute:1, +fixed:1}[a.getComputedStyle("position")]}function r(a,b){return n(b)?b.is(a.triggers):null}function p(a,b){if(!b)return!1;for(var c=b.getParents(1),d=c.length;d--;)for(var e=a.tabuList.length;e--;)if(c[d].hasAttribute(a.tabuList[e]))return!0;return!1}function q(a,b,c){b=b[c?"getLast":"getFirst"](function(b){return a.isRelevant(b)&&!b.is(ea)});if(!b)return!1;v(a,b);return c?b.size.top>a.mouse.y:b.size.bottom +(a.inInlineMode?e.editable.top+e.editable.height/2:Math.min(e.editable.height,e.pane.height)/2),b=b[g?"getLast":"getFirst"](function(a){return!(R(a)||U(a))});if(!b)return null;c(a,b)&&(b=a.line.wrap[g?"getPrevious":"getNext"](function(a){return!(R(a)||U(a))}));if(!n(b)||w(b)||!r(a,b))return null;v(a,b);return!g&&0<=b.size.top&&m(d.y,0,b.size.top+f)?(a=a.inInlineMode||0===e.scroll.y?P:X,new h([null,b,H,O,a])):g&&b.size.bottom<=e.pane.height&&m(d.y,b.size.bottom-f,e.pane.height)?(a=a.inInlineMode|| +m(b.size.bottom,e.pane.height-f,e.pane.height)?K:X,new h([b,null,E,O,a])):null}function t(b){var c=b.mouse,d=b.view,e=b.triggerOffset,f=g(b);if(!f)return null;v(b,f);var e=Math.min(e,0|f.size.outerHeight/2),k=[],l,t;if(m(c.y,f.size.top-1,f.size.top+e))t=!1;else if(m(c.y,f.size.bottom-e,f.size.bottom+1))t=!0;else return null;if(w(f)||q(b,f,t)||f.getParent().is(W))return null;var p=a(b,f,!t);if(p){if(p&&p.type==CKEDITOR.NODE_TEXT)return null;if(n(p)){if(w(p)||!r(b,p)||p.getParent().is(W))return null; +k=[p,f][t?"reverse":"concat"]().concat([Q,O])}}else f.equals(b.editable[t?"getLast":"getFirst"](b.isRelevant))?(x(b),t&&m(c.y,f.size.bottom-e,d.pane.height)&&m(f.size.bottom,d.pane.height-e,d.pane.height)?l=K:m(c.y,0,f.size.top+e)&&(l=P)):l=X,k=[null,f][t?"reverse":"concat"]().concat([t?E:H,O,l,f.equals(b.editable[t?"getLast":"getFirst"](b.isRelevant))?t?K:P:X]);return 0 in k?new h(k):null}function y(a,b,c,d){for(var e=b.getDocumentPosition(),f={},g={},h={},k={},l=V.length;l--;)f[V[l]]=parseInt(b.getComputedStyle.call(b, +"border-"+V[l]+"-width"),10)||0,h[V[l]]=parseInt(b.getComputedStyle.call(b,"padding-"+V[l]),10)||0,g[V[l]]=parseInt(b.getComputedStyle.call(b,"margin-"+V[l]),10)||0;c&&!d||B(a,d);k.top=e.y-(c?0:a.view.scroll.y);k.left=e.x-(c?0:a.view.scroll.x);k.outerWidth=b.$.offsetWidth;k.outerHeight=b.$.offsetHeight;k.height=k.outerHeight-(h.top+h.bottom+f.top+f.bottom);k.width=k.outerWidth-(h.left+h.right+f.left+f.right);k.bottom=k.top+k.outerHeight;k.right=k.left+k.outerWidth;a.inInlineMode&&(k.scroll={top:b.$.scrollTop, +left:b.$.scrollLeft});return A({border:f,padding:h,margin:g,ignoreScroll:c},k,!0)}function v(a,b,c){if(!n(b))return b.size=null;if(!b.size)b.size={};else if(b.size.ignoreScroll==c&&b.size.date>new Date-Z)return null;return A(b.size,y(a,b,c),{date:+new Date},!0)}function x(a,b){a.view.editable=y(a,a.editable,b,!0)}function B(a,b){a.view||(a.view={});var c=a.view;if(!(!b&&c&&c.date>new Date-Z)){var d=a.win,c=d.getScrollPosition(),d=d.getViewPaneSize();A(a.view,{scroll:{x:c.x,y:c.y,width:a.doc.$.documentElement.scrollWidth- +d.width,height:a.doc.$.documentElement.scrollHeight-d.height},pane:{width:d.width,height:d.height,bottom:d.height+c.y},date:+new Date},!0)}}function D(a,b,c,d){for(var e=d,f=d,g=0,k=!1,l=!1,m=a.view.pane.height,n=a.mouse;n.y+gd.left-e.x&&cd.top-e.y&&aCKEDITOR.env.version, +F=CKEDITOR.dtd,M={},H=128,E=64,Q=32,O=16,P=4,K=2,X=1,S=" ",W=F.$listItem,ea=F.$tableContent,L=A({},F.$nonEditable,F.$empty),T=F.$block,Z=100,ca="width:0px;height:0px;padding:0px;margin:0px;display:block;z-index:9999;color:#fff;position:absolute;font-size: 0px;line-height:0px;",aa=ca+"border-color:transparent;display:block;border-style:solid;",da="\x3cspan\x3e"+S+"\x3c/span\x3e";M[CKEDITOR.ENTER_BR]="br";M[CKEDITOR.ENTER_P]="p";M[CKEDITOR.ENTER_DIV]="div";h.prototype={set:function(a,b,c){this.properties= +a+b+(c||X);return this},is:function(a){return(this.properties&a)==a}};var ba=function(){function a(b,c){var d=b.$.elementFromPoint(c.x,c.y);return d&&d.nodeType?new CKEDITOR.dom.element(d):null}return function(b,d,e){if(!b.mouse)return null;var f=b.doc,g=b.line.wrap;e=e||b.mouse;var h=a(f,e);d&&c(b,h)&&(g.hide(),h=a(f,e),g.show());return!h||h.type!=CKEDITOR.NODE_ELEMENT||!h.$||I.ie&&9>I.version&&!b.boundary.equals(h)&&!b.boundary.contains(h)?null:h}}(),R=CKEDITOR.dom.walker.whitespaces(),U=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_COMMENT), +N=function(){function a(c){var e=c.element,g,h,k;if(!n(e)||e.contains(c.editable)||e.isReadOnly())return null;k=D(c,function(a,b){return!b.equals(a)},function(a,b){return ba(a,!0,b)},e);g=k.upper;h=k.lower;if(b(c,g,h))return k.set(Q,8);if(g&&e.contains(g))for(;!g.getParent().equals(e);)g=g.getParent();else g=e.getFirst(function(a){return d(c,a)});if(h&&e.contains(h))for(;!h.getParent().equals(e);)h=h.getParent();else h=e.getLast(function(a){return d(c,a)});if(!g||!h)return null;v(c,g);v(c,h);if(!m(c.mouse.y, +g.size.top,h.size.bottom))return null;for(var e=Number.MAX_VALUE,l,q,t,p;h&&!h.equals(g)&&(q=g.getNext(c.isRelevant));)l=Math.abs(f(c,g,q)-c.mouse.y),l|<\/font>)/,b=/CKEDITOR.env.version),d=l?":not([contenteditable\x3dfalse]):not(.cke_show_blocks_off)":"",e,c;for(g=f=a=m="";e=b.pop();)c=b.length?",":"",g+=".cke_show_blocks "+e+d+c,a+=".cke_show_blocks.cke_contents_ltr "+e+d+c,m+=".cke_show_blocks.cke_contents_rtl "+e+d+c,f+=".cke_show_blocks "+e+d+"{background-image:url("+CKEDITOR.getUrl(k+"images/block_"+e+".png")+")}"; +CKEDITOR.addCss((g+"{background-repeat:no-repeat;border:1px dotted gray;padding-top:8px}").concat(f,a+"{background-position:top left;padding-left:8px}",m+"{background-position:top right;padding-right:8px}"));l||CKEDITOR.addCss(".cke_show_blocks [contenteditable\x3dfalse],.cke_show_blocks .cke_show_blocks_off{border:none;padding-top:0;background-image:none}.cke_show_blocks.cke_contents_rtl [contenteditable\x3dfalse],.cke_show_blocks.cke_contents_rtl .cke_show_blocks_off{padding-right:0}.cke_show_blocks.cke_contents_ltr [contenteditable\x3dfalse],.cke_show_blocks.cke_contents_ltr .cke_show_blocks_off{padding-left:0}")}, +init:function(h){function g(){f.refresh(h)}if(!h.blockless){var f=h.addCommand("showblocks",b);f.canUndo=!1;h.config.startupOutlineBlocks&&f.setState(CKEDITOR.TRISTATE_ON);h.ui.addButton&&h.ui.addButton("ShowBlocks",{label:h.lang.showblocks.toolbar,command:"showblocks",toolbar:"tools,20"});h.on("mode",function(){f.state!=CKEDITOR.TRISTATE_DISABLED&&f.refresh(h)});h.elementMode==CKEDITOR.ELEMENT_MODE_INLINE&&(h.on("focus",g),h.on("blur",g));h.on("contentDom",function(){f.state!=CKEDITOR.TRISTATE_DISABLED&& +f.refresh(h)})}}})})();(function(){var b={preserveState:!0,editorFocus:!1,readOnly:1,exec:function(b){this.toggleState();this.refresh(b)},refresh:function(b){if(b.document){var g=this.state==CKEDITOR.TRISTATE_ON?"attachClass":"removeClass";b.editable()[g]("cke_show_borders")}}};CKEDITOR.plugins.add("showborders",{modes:{wysiwyg:1},onLoad:function(){var b;b=(CKEDITOR.env.ie6Compat?[".%1 table.%2,",".%1 table.%2 td, .%1 table.%2 th","{","border : #d3d3d3 1px dotted","}"]:".%1 table.%2,;.%1 table.%2 \x3e tr \x3e td, .%1 table.%2 \x3e tr \x3e th,;.%1 table.%2 \x3e tbody \x3e tr \x3e td, .%1 table.%2 \x3e tbody \x3e tr \x3e th,;.%1 table.%2 \x3e thead \x3e tr \x3e td, .%1 table.%2 \x3e thead \x3e tr \x3e th,;.%1 table.%2 \x3e tfoot \x3e tr \x3e td, .%1 table.%2 \x3e tfoot \x3e tr \x3e th;{;border : #d3d3d3 1px dotted;}".split(";")).join("").replace(/%2/g, +"cke_show_border").replace(/%1/g,"cke_show_borders ");CKEDITOR.addCss(b)},init:function(h){var g=h.addCommand("showborders",b);g.canUndo=!1;!1!==h.config.startupShowBorders&&g.setState(CKEDITOR.TRISTATE_ON);h.on("mode",function(){g.state!=CKEDITOR.TRISTATE_DISABLED&&g.refresh(h)},null,null,100);h.on("contentDom",function(){g.state!=CKEDITOR.TRISTATE_DISABLED&&g.refresh(h)});h.on("removeFormatCleanup",function(b){b=b.data;h.getCommand("showborders").state==CKEDITOR.TRISTATE_ON&&b.is("table")&&(!b.hasAttribute("border")|| +0>=parseInt(b.getAttribute("border"),10))&&b.addClass("cke_show_border")})},afterInit:function(b){var g=b.dataProcessor;b=g&&g.dataFilter;g=g&&g.htmlFilter;b&&b.addRules({elements:{table:function(b){b=b.attributes;var a=b["class"],g=parseInt(b.border,10);g&&!(0>=g)||a&&-1!=a.indexOf("cke_show_border")||(b["class"]=(a||"")+" cke_show_border")}}});g&&g.addRules({elements:{table:function(b){b=b.attributes;var a=b["class"];a&&(b["class"]=a.replace("cke_show_border","").replace(/\s{2}/," ").replace(/^\s+|\s+$/, +""))}}})}});CKEDITOR.on("dialogDefinition",function(b){var g=b.data.name;if("table"==g||"tableProperties"==g)if(b=b.data.definition,g=b.getContents("info").get("txtBorder"),g.commit=CKEDITOR.tools.override(g.commit,function(b){return function(a,g){b.apply(this,arguments);var h=parseInt(this.getValue(),10);g[!h||0>=h?"addClass":"removeClass"]("cke_show_border")}}),b=(b=b.getContents("advanced"))&&b.get("advCSSClasses"))b.setup=CKEDITOR.tools.override(b.setup,function(b){return function(){b.apply(this, +arguments);this.setValue(this.getValue().replace(/cke_show_border/,""))}}),b.commit=CKEDITOR.tools.override(b.commit,function(b){return function(a,g){b.apply(this,arguments);parseInt(g.getAttribute("border"),10)||g.addClass("cke_show_border")}})})})();(function(){CKEDITOR.plugins.add("sourcearea",{init:function(h){function g(){var b=a&&this.equals(CKEDITOR.document.getActive());this.hide();this.setStyle("height",this.getParent().$.clientHeight+"px");this.setStyle("width",this.getParent().$.clientWidth+ +"px");this.show();b&&this.focus()}if(h.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE){var f=CKEDITOR.plugins.sourcearea;h.addMode("source",function(a){var f=h.ui.space("contents").getDocument().createElement("textarea");f.setStyles(CKEDITOR.tools.extend({width:CKEDITOR.env.ie7Compat?"99%":"100%",height:"100%",resize:"none",outline:"none","text-align":"left"},CKEDITOR.tools.cssVendorPrefix("tab-size",h.config.sourceAreaTabSize||4)));f.setAttribute("dir","ltr");f.addClass("cke_source").addClass("cke_reset").addClass("cke_enable_context_menu"); +h.ui.space("contents").append(f);f=h.editable(new b(h,f));f.setData(h.getData(1));CKEDITOR.env.ie&&(f.attachListener(h,"resize",g,f),f.attachListener(CKEDITOR.document.getWindow(),"resize",g,f),CKEDITOR.tools.setTimeout(g,0,f));h.fire("ariaWidget",this);a()});h.addCommand("source",f.commands.source);h.ui.addButton&&h.ui.addButton("Source",{label:h.lang.sourcearea.toolbar,command:"source",toolbar:"mode,10"});h.on("mode",function(){h.getCommand("source").setState("source"==h.mode?CKEDITOR.TRISTATE_ON: +CKEDITOR.TRISTATE_OFF)});var a=CKEDITOR.env.ie&&9==CKEDITOR.env.version}}});var b=CKEDITOR.tools.createClass({base:CKEDITOR.editable,proto:{setData:function(b){this.setValue(b);this.status="ready";this.editor.fire("dataReady")},getData:function(){return this.getValue()},insertHtml:function(){},insertElement:function(){},insertText:function(){},setReadOnly:function(b){this[(b?"set":"remove")+"Attribute"]("readOnly","readonly")},detach:function(){b.baseProto.detach.call(this);this.clearCustomData(); +this.remove()}}})})();CKEDITOR.plugins.sourcearea={commands:{source:{modes:{wysiwyg:1,source:1},editorFocus:!1,readOnly:1,exec:function(b){"wysiwyg"==b.mode&&b.fire("saveSnapshot");b.getCommand("source").setState(CKEDITOR.TRISTATE_DISABLED);b.setMode("source"==b.mode?"wysiwyg":"source")},canUndo:!1}}};CKEDITOR.plugins.add("sourcedialog",{requires:"dialog",init:function(b){b.addCommand("sourcedialog",new CKEDITOR.dialogCommand("sourcedialog"));CKEDITOR.dialog.add("sourcedialog",this.path+"dialogs/sourcedialog.js"); +b.ui.addButton&&b.ui.addButton("Sourcedialog",{label:b.lang.sourcedialog.toolbar,command:"sourcedialog",toolbar:"mode,10"})}});CKEDITOR.plugins.add("specialchar",{availableLangs:{af:1,ar:1,az:1,bg:1,ca:1,cs:1,cy:1,da:1,de:1,"de-ch":1,el:1,en:1,"en-au":1,"en-ca":1,"en-gb":1,eo:1,es:1,"es-mx":1,et:1,eu:1,fa:1,fi:1,fr:1,"fr-ca":1,gl:1,he:1,hr:1,hu:1,id:1,it:1,ja:1,km:1,ko:1,ku:1,lt:1,lv:1,nb:1,nl:1,no:1,oc:1,pl:1,pt:1,"pt-br":1,ro:1,ru:1,si:1,sk:1,sl:1,sq:1,sr:1,"sr-latn":1,sv:1,th:1,tr:1,tt:1,ug:1, +uk:1,vi:1,zh:1,"zh-cn":1},requires:"dialog",init:function(b){var h=this;CKEDITOR.dialog.add("specialchar",this.path+"dialogs/specialchar.js");b.addCommand("specialchar",{exec:function(){var g=b.langCode,g=h.availableLangs[g]?g:h.availableLangs[g.replace(/-.*/,"")]?g.replace(/-.*/,""):"en";CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(h.path+"dialogs/lang/"+g+".js"),function(){CKEDITOR.tools.extend(b.lang.specialchar,h.langEntries[g]);b.openDialog("specialchar")})},modes:{wysiwyg:1},canUndo:!1});b.ui.addButton&& +b.ui.addButton("SpecialChar",{label:b.lang.specialchar.toolbar,command:"specialchar",toolbar:"insert,50"})}});CKEDITOR.config.specialChars="! \x26quot; # $ % \x26amp; ' ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 : ; \x26lt; \x3d \x26gt; ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ \x26euro; \x26lsquo; \x26rsquo; \x26ldquo; \x26rdquo; \x26ndash; \x26mdash; \x26iexcl; \x26cent; \x26pound; \x26curren; \x26yen; \x26brvbar; \x26sect; \x26uml; \x26copy; \x26ordf; \x26laquo; \x26not; \x26reg; \x26macr; \x26deg; \x26sup2; \x26sup3; \x26acute; \x26micro; \x26para; \x26middot; \x26cedil; \x26sup1; \x26ordm; \x26raquo; \x26frac14; \x26frac12; \x26frac34; \x26iquest; \x26Agrave; \x26Aacute; \x26Acirc; \x26Atilde; \x26Auml; \x26Aring; \x26AElig; \x26Ccedil; \x26Egrave; \x26Eacute; \x26Ecirc; \x26Euml; \x26Igrave; \x26Iacute; \x26Icirc; \x26Iuml; \x26ETH; \x26Ntilde; \x26Ograve; \x26Oacute; \x26Ocirc; \x26Otilde; \x26Ouml; \x26times; \x26Oslash; \x26Ugrave; \x26Uacute; \x26Ucirc; \x26Uuml; \x26Yacute; \x26THORN; \x26szlig; \x26agrave; \x26aacute; \x26acirc; \x26atilde; \x26auml; \x26aring; \x26aelig; \x26ccedil; \x26egrave; \x26eacute; \x26ecirc; \x26euml; \x26igrave; \x26iacute; \x26icirc; \x26iuml; \x26eth; \x26ntilde; \x26ograve; \x26oacute; \x26ocirc; \x26otilde; \x26ouml; \x26divide; \x26oslash; \x26ugrave; \x26uacute; \x26ucirc; \x26uuml; \x26yacute; \x26thorn; \x26yuml; \x26OElig; \x26oelig; \x26#372; \x26#374 \x26#373 \x26#375; \x26sbquo; \x26#8219; \x26bdquo; \x26hellip; \x26trade; \x26#9658; \x26bull; \x26rarr; \x26rArr; \x26hArr; \x26diams; \x26asymp;".split(" "); +(function(){CKEDITOR.plugins.add("stylescombo",{requires:"richcombo",init:function(b){var h=b.config,g=b.lang.stylescombo,f={},a=[],m=[];b.on("stylesSet",function(g){if(g=g.data.styles){for(var l,d,e,c=0,n=g.length;c=g)for(l=this.getNextSourceNode(b,CKEDITOR.NODE_ELEMENT);l;){if(l.isVisible()&&0===l.getTabIndex()){m=l;break}l=l.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT)}else for(l=this.getDocument().getBody().getFirst();l=l.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT);){if(!f)if(!a&&l.equals(this)){if(a=!0,b){if(!(l=l.getNextSourceNode(!0,CKEDITOR.NODE_ELEMENT)))break;f=1}}else a&&!this.contains(l)&&(f=1);if(l.isVisible()&&!(0>(d=l.getTabIndex()))){if(f&&d==g){m= +l;break}d>g&&(!m||!k||d(l=d.getTabIndex())))if(0>=g){if(f&&0===l){m=d;break}l>k&& +(m=d,k=l)}else{if(f&&l==g){m=d;break}lk)&&(m=d,k=l)}}m&&m.focus()};CKEDITOR.plugins.add("table",{requires:"dialog",init:function(b){function h(b){return CKEDITOR.tools.extend(b||{},{contextSensitive:1,refresh:function(a,b){this.setState(b.contains("table",1)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)}})}if(!b.blockless){var g=b.lang.table;b.addCommand("table",new CKEDITOR.dialogCommand("table",{context:"table",allowedContent:"table{width,height,border-collapse}[align,border,cellpadding,cellspacing,summary];caption tbody thead tfoot;th td tr[scope];td{border*,background-color,vertical-align,width,height}[colspan,rowspan];"+ +(b.plugins.dialogadvtab?"table"+b.plugins.dialogadvtab.allowedContent():""),requiredContent:"table",contentTransformations:[["table{width}: sizeToStyle","table[width]: sizeToAttribute"],["td: splitBorderShorthand"],[{element:"table",right:function(b){if(b.styles){var a;if(b.styles.border)a=CKEDITOR.tools.style.parse.border(b.styles.border);else if(CKEDITOR.env.ie&&8===CKEDITOR.env.version){var g=b.styles;g["border-left"]&&g["border-left"]===g["border-right"]&&g["border-right"]===g["border-top"]&& +g["border-top"]===g["border-bottom"]&&(a=CKEDITOR.tools.style.parse.border(g["border-top"]))}a&&a.style&&"solid"===a.style&&a.width&&0!==parseFloat(a.width)&&(b.attributes.border=1);"collapse"==b.styles["border-collapse"]&&(b.attributes.cellspacing=0)}}}]]}));b.addCommand("tableProperties",new CKEDITOR.dialogCommand("tableProperties",h()));b.addCommand("tableDelete",h({exec:function(b){var a=b.elementPath().contains("table",1);if(a){var g=a.getParent(),h=b.editable();1!=g.getChildCount()||g.is("td", +"th")||g.equals(h)||(a=g);b=b.createRange();b.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START);a.remove();b.select()}}}));b.ui.addButton&&b.ui.addButton("Table",{label:g.toolbar,command:"table",toolbar:"insert,30"});CKEDITOR.dialog.add("table",this.path+"dialogs/table.js");CKEDITOR.dialog.add("tableProperties",this.path+"dialogs/table.js");b.addMenuItems&&b.addMenuItems({table:{label:g.menu,command:"tableProperties",group:"table",order:5},tabledelete:{label:g.deleteTable,command:"tableDelete",group:"table", +order:1}});b.on("doubleclick",function(b){b.data.element.is("table")&&(b.data.dialog="tableProperties")});b.contextMenu&&b.contextMenu.addListener(function(){return{tabledelete:CKEDITOR.TRISTATE_OFF,table:CKEDITOR.TRISTATE_OFF}})}}});(function(){function b(a,b){function c(a){return b?b.contains(a)&&a.getAscendant("table",!0).equals(b):!0}function d(a){0c)c=e}return c}function m(c,d){for(var e=p(c)?c:b(c),f=e[0].getAscendant("table"),g=a(e,1),e=a(e),h=d?g:e,k=CKEDITOR.tools.buildTableMap(f),f=[],g=[],e=[],l=k.length,m=0;ml?new CKEDITOR.dom.element(h[0][l+1]):k&&-1!==h[0][k-1].cellIndex?new CKEDITOR.dom.element(h[0][k-1]):new CKEDITOR.dom.element(e.$.parentNode);m.length==a&&(d[0].moveToPosition(e,CKEDITOR.POSITION_AFTER_END),d[0].select(),e.remove());return k}function l(a,b){var c=a.getStartElement().getAscendant({td:1,th:1},!0);if(c){var d=c.clone();d.appendBogus();b?d.insertBefore(c):d.insertAfter(c)}}function d(a){if(a instanceof CKEDITOR.dom.selection){var c= +a.getRanges(),f=b(a),g=f[0]&&f[0].getAscendant("table"),h;a:{var k=0;h=f.length-1;for(var l={},m,n;m=f[k++];)CKEDITOR.dom.element.setMarker(l,m,"delete_cell",!0);for(k=0;m=f[k++];)if((n=m.getPrevious())&&!n.getCustomData("delete_cell")||(n=m.getNext())&&!n.getCustomData("delete_cell")){CKEDITOR.dom.element.clearAllMarkers(l);h=n;break a}CKEDITOR.dom.element.clearAllMarkers(l);k=f[0].getParent();(k=k.getPrevious())?h=k.getLast():(k=f[h].getParent(),h=(k=k.getNext())?k.getChild(0):null)}a.reset();for(a= +f.length-1;0<=a;a--)d(f[a]);h?e(h,!0):g&&(c[0].moveToPosition(g,CKEDITOR.POSITION_BEFORE_START),c[0].select(),g.remove())}else a instanceof CKEDITOR.dom.element&&(c=a.getParent(),1==c.getChildCount()?c.remove():a.remove())}function e(a,b){var c=a.getDocument(),d=CKEDITOR.document;CKEDITOR.env.ie&&10==CKEDITOR.env.version&&(d.focus(),c.focus());c=new CKEDITOR.dom.range(c);c["moveToElementEdit"+(b?"End":"Start")](a)||(c.selectNodeContents(a),c.collapse(b?!1:!0));c.select(!0)}function c(a,b,c){a=a[b]; +if("undefined"==typeof c)return a;for(b=0;a&&bf.length)||(g=a.getCommonAncestor())&&g.type==CKEDITOR.NODE_ELEMENT&&g.is("table"))return!1;var h;a=f[0];g=a.getAscendant("table");var k=CKEDITOR.tools.buildTableMap(g),l=k.length,m=k[0].length,n=a.getParent().$.rowIndex,p=c(k,n,a);if(d){var r;try{var w=parseInt(a.getAttribute("rowspan"),10)||1; +h=parseInt(a.getAttribute("colspan"),10)||1;r=k["up"==d?n-w:"down"==d?n+w:n]["left"==d?p-h:"right"==d?p+h:p]}catch(z){return!1}if(!r||a.$==r)return!1;f["up"==d||"left"==d?"unshift":"push"](new CKEDITOR.dom.element(r))}d=a.getDocument();var M=n,w=r=0,H=!e&&new CKEDITOR.dom.documentFragment(d),E=0;for(d=0;d=m?a.removeAttribute("rowSpan"):a.$.rowSpan=r;r>=l?a.removeAttribute("colSpan"):a.$.colSpan=w;e=new CKEDITOR.dom.nodeList(g.$.rows);f=e.count();for(d=f-1;0<=d;d--)g=e.getItem(d),g.$.cells.length||(g.remove(),f++);return a} +function w(a,d){var e=b(a);if(1l){f.insertBefore(new CKEDITOR.dom.element(p));break}else p=null;p||g.append(f)}else for(m=n=1,g=f.clone(),g.insertAfter(f),g.append(f=e.clone()), +p=c(h,k),l=0;lh);n++){k[l+n]||(k[l+n]=[]);for(var w=0;w=b)break}}return k},function(){function e(b){return CKEDITOR.env.ie?b.$.clientWidth:parseInt(b.getComputedStyle("width"),10)}function f(b, -d){var a=b.getComputedStyle("border-"+d+"-width"),c={thin:"0px",medium:"1px",thick:"2px"};0>a.indexOf("px")&&(a=a in c&&"none"!=b.getComputedStyle("border-style")?c[a]:0);return parseInt(a,10)}function c(b){var d=[],a={},c="rtl"==b.getComputedStyle("direction");CKEDITOR.tools.array.forEach(b.$.rows,function(e,h){var k=-1,l=0,r=null;e?(l=new CKEDITOR.dom.element(e),r={height:l.$.offsetHeight,position:l.getDocumentPosition()}):r=void 0;for(var l=r.height,r=r.position,v=0,q=e.cells.length;v=t.x&&a<=t.x+t.width&&b>=t.y&&b<=t.y+t.height))return t=null,v=B=0,p.removeListener("mouseup",w),r.removeListener("mousedown", -l),r.removeListener("mousemove",u),p.getBody().setStyle("cursor","auto"),d?r.remove():r.hide(),0;var c=a-Math.round(r.$.offsetWidth/2);if(v){if(c==D||c==z)return 1;c=Math.max(c,D);c=Math.min(c,z);B=c-q}r.setStyle("left",k(c));return 1}}function l(b){var d=b.data.getTarget();if("mouseout"==b.name){if(!d.is("table"))return;for(var a=new CKEDITOR.dom.element(b.data.$.relatedTarget||b.data.$.toElement);a&&a.$&&!a.equals(d)&&!a.is("body");)a=a.getParent();if(!a||a.equals(d))return}d.getAscendant("table", -1).removeCustomData("_cke_table_pillars");b.removeListener()}var k=CKEDITOR.tools.cssLength,d=CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks);CKEDITOR.plugins.add("tableresize",{requires:"tabletools",init:function(d){d.on("contentDom",function(){var e,a=d.editable();a.attachListener(a.isInline()?a:d.document,"mousemove",function(a){a=a.data;var f=a.getTarget();if(f.type==CKEDITOR.NODE_ELEMENT){var k=a.getPageOffset().x,t=a.getPageOffset().y;if(e&&e.move(k,t))h(a);else if(f.is("table")|| -f.getAscendant({thead:1,tbody:1,tfoot:1},1))if(a=f.getAscendant("table",1),d.editable().contains(a)){(f=a.getCustomData("_cke_table_pillars"))||(a.setCustomData("_cke_table_pillars",f=c(a)),a.on("mouseout",l),a.on("mousedown",l));a:{a=f;for(var f=0,p=a.length;f=r.x&&k<=r.x+r.width&&t>=r.y&&t<=r.y+r.height){k=r;break a}}k=null}k&&(!e&&(e=new b(d)),e.attachTo(k))}}})})}})}(),"use strict",function(){var e=[CKEDITOR.CTRL+90,CKEDITOR.CTRL+89,CKEDITOR.CTRL+CKEDITOR.SHIFT+90],f={8:1, -46:1};CKEDITOR.plugins.add("undo",{init:function(b){function g(b){a.enabled&&!1!==b.data.command.canUndo&&a.save()}function f(){a.enabled=b.readOnly?!1:"wysiwyg"==b.mode;a.onChange()}var a=b.undoManager=new c(b),h=a.editingHandler=new l(a),k=b.addCommand("undo",{exec:function(){a.undo()&&(b.selectionChange(),this.fire("afterUndo"))},startDisabled:!0,canUndo:!1}),u=b.addCommand("redo",{exec:function(){a.redo()&&(b.selectionChange(),this.fire("afterRedo"))},startDisabled:!0,canUndo:!1});b.setKeystroke([[e[0], -"undo"],[e[1],"redo"],[e[2],"redo"]]);a.onChange=function(){k.setState(a.undoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);u.setState(a.redoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)};b.on("beforeCommandExec",g);b.on("afterCommandExec",g);b.on("saveSnapshot",function(b){a.save(b.data&&b.data.contentOnly)});b.on("contentDom",h.attachListeners,h);b.on("instanceReady",function(){b.fire("saveSnapshot")});b.on("beforeModeUnload",function(){"wysiwyg"==b.mode&&a.save(!0)});b.on("mode", -f);b.on("readOnly",f);b.ui.addButton&&(b.ui.addButton("Undo",{label:b.lang.undo.undo,command:"undo",toolbar:"undo,10"}),b.ui.addButton("Redo",{label:b.lang.undo.redo,command:"redo",toolbar:"undo,20"}));b.resetUndo=function(){a.reset();b.fire("saveSnapshot")};b.on("updateSnapshot",function(){a.currentImage&&a.update()});b.on("lockSnapshot",function(b){b=b.data;a.lock(b&&b.dontUpdate,b&&b.forceUpdate)});b.on("unlockSnapshot",a.unlock,a)}});CKEDITOR.plugins.undo={};var c=CKEDITOR.plugins.undo.UndoManager= -function(b){this.strokesRecorded=[0,0];this.locked=null;this.previousKeyGroup=-1;this.limit=b.config.undoStackSize||20;this.strokesLimit=25;this.editor=b;this.reset()};c.prototype={type:function(b,e){var f=c.getKeyGroup(b),a=this.strokesRecorded[f]+1;e=e||a>=this.strokesLimit;this.typing||(this.hasUndo=this.typing=!0,this.hasRedo=!1,this.onChange());e?(a=0,this.editor.fire("saveSnapshot")):this.editor.fire("change");this.strokesRecorded[f]=a;this.previousKeyGroup=f},keyGroupChanged:function(b){return c.getKeyGroup(b)!= -this.previousKeyGroup},reset:function(){this.snapshots=[];this.index=-1;this.currentImage=null;this.hasRedo=this.hasUndo=!1;this.locked=null;this.resetType()},resetType:function(){this.strokesRecorded=[0,0];this.typing=!1;this.previousKeyGroup=-1},refreshState:function(){this.hasUndo=!!this.getNextImage(!0);this.hasRedo=!!this.getNextImage(!1);this.resetType();this.onChange()},save:function(b,c,e){var a=this.editor;if(this.locked||"ready"!=a.status||"wysiwyg"!=a.mode)return!1;var f=a.editable();if(!f|| -"ready"!=f.status)return!1;f=this.snapshots;c||(c=new h(a));if(!1===c.contents)return!1;if(this.currentImage)if(c.equalsContent(this.currentImage)){if(b||c.equalsSelection(this.currentImage))return!1}else!1!==e&&a.fire("change");f.splice(this.index+1,f.length-this.index-1);f.length==this.limit&&f.shift();this.index=f.push(c)-1;this.currentImage=c;!1!==e&&this.refreshState();return!0},restoreImage:function(b){var c=this.editor,e;b.bookmarks&&(c.focus(),e=c.getSelection());this.locked={level:999};this.editor.loadSnapshot(b.contents); -b.bookmarks?e.selectBookmarks(b.bookmarks):CKEDITOR.env.ie&&(e=this.editor.document.getBody().$.createTextRange(),e.collapse(!0),e.select());this.locked=null;this.index=b.index;this.currentImage=this.snapshots[this.index];this.update();this.refreshState();c.fire("change")},getNextImage:function(b){var c=this.snapshots,e=this.currentImage,a;if(e)if(b)for(a=this.index-1;0<=a;a--){if(b=c[a],!e.equalsContent(b))return b.index=a,b}else for(a=this.index+1;a=this.undoManager.strokesLimit&&(this.undoManager.type(b.keyCode,!0),this.keyEventsStack.resetInputs())}},onKeyup:function(b){var e=this.undoManager;b=b.data.getKey();var f=this.keyEventsStack.getTotalInputs();this.keyEventsStack.remove(b); -if(!(c.ieFunctionalKeysBug(b)&&this.lastKeydownImage&&this.lastKeydownImage.equalsContent(new h(e.editor,!0))))if(0CKEDITOR.env.version&&e.enterMode!=CKEDITOR.ENTER_DIV&&c("div");if(CKEDITOR.env.webkit||CKEDITOR.env.ie&&10this.$.offsetHeight){var c=e.createRange();c[33==b?"moveToElementEditStart":"moveToElementEditEnd"](this);c.select();a.data.preventDefault()}});CKEDITOR.env.ie&&this.attachListener(d,"blur",function(){try{d.$.selection.empty()}catch(a){}});CKEDITOR.env.iOS&&this.attachListener(d,"touchend",function(){b.focus()});g=e.document.getElementsByTag("title").getItem(0); -g.data("cke-title",g.getText());CKEDITOR.env.ie&&(e.document.$.title=this._.docTitle);CKEDITOR.tools.setTimeout(function(){"unloaded"==this.status&&(this.status="ready");e.fire("contentDom");this._.isPendingFocus&&(e.focus(),this._.isPendingFocus=!1);setTimeout(function(){e.fire("dataReady")},0)},0,this)}function f(b){function c(){var d;b.editable().attachListener(b,"selectionChange",function(){var a=b.getSelection().getSelectedElement();a&&(d&&(d.detachEvent("onresizestart",e),d=null),a.$.attachEvent("onresizestart", -e),d=a.$)})}function e(b){b.returnValue=!1}if(CKEDITOR.env.gecko)try{var d=b.document.$;d.execCommand("enableObjectResizing",!1,!b.config.disableObjectResizing);d.execCommand("enableInlineTableEditing",!1,!b.config.disableNativeTableHandles)}catch(f){}else CKEDITOR.env.ie&&11>CKEDITOR.env.version&&b.config.disableObjectResizing&&c(b)}function c(){var b=[];if(8<=CKEDITOR.document.$.documentMode){b.push("html.CSS1Compat [contenteditable\x3dfalse]{min-height:0 !important}");var c=[],e;for(e in CKEDITOR.dtd.$removeEmpty)c.push("html.CSS1Compat "+ -e+"[contenteditable\x3dfalse]");b.push(c.join(",")+"{display:inline-block}")}else CKEDITOR.env.gecko&&(b.push("html{height:100% !important}"),b.push("img:-moz-broken{-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}"));b.push("html{cursor:text;*cursor:auto}");b.push("img,input,textarea{cursor:default}");return b.join("\n")}var h;CKEDITOR.plugins.add("wysiwygarea",{init:function(b){b.config.fullPage&&b.addFeature({allowedContent:"html head title; style [media,type]; body (*)[id]; meta link [*]", -requiredContent:"body"});b.addMode("wysiwyg",function(c){function e(a){a&&a.removeListener();b.editable(new h(b,f.$.contentWindow.document.body));b.setData(b.getData(1),c)}var d="document.open();"+(CKEDITOR.env.ie?"("+CKEDITOR.tools.fixDomain+")();":"")+"document.close();",d=CKEDITOR.env.air?"javascript:void(0)":CKEDITOR.env.ie&&!CKEDITOR.env.edge?"javascript:void(function(){"+encodeURIComponent(d)+"}())":"",f=CKEDITOR.dom.element.createFromHtml('\x3ciframe src\x3d"'+d+'" frameBorder\x3d"0"\x3e\x3c/iframe\x3e'); -f.setStyles({width:"100%",height:"100%"});f.addClass("cke_wysiwyg_frame").addClass("cke_reset");d=b.ui.space("contents");d.append(f);var m=CKEDITOR.env.ie&&!CKEDITOR.env.edge||CKEDITOR.env.gecko;if(m)f.on("load",e);var a=b.title,n=b.fire("ariaEditorHelpLabel",{}).label;a&&(CKEDITOR.env.ie&&n&&(a+=", "+n),f.setAttribute("title",a));if(n){var a=CKEDITOR.tools.getNextId(),w=CKEDITOR.dom.element.createFromHtml('\x3cspan id\x3d"'+a+'" class\x3d"cke_voice_label"\x3e'+n+"\x3c/span\x3e");d.append(w,1);f.setAttribute("aria-describedby", -a)}b.on("beforeModeUnload",function(a){a.removeListener();w&&w.remove()});f.setAttributes({tabIndex:b.tabIndex,allowTransparency:"true"});!m&&e();b.fire("ariaWidget",f)})}});CKEDITOR.editor.prototype.addContentsCss=function(b){var c=this.config,e=c.contentsCss;CKEDITOR.tools.isArray(e)||(c.contentsCss=e?[e]:[]);c.contentsCss.push(b)};h=CKEDITOR.tools.createClass({$:function(){this.base.apply(this,arguments);this._.frameLoadedHandler=CKEDITOR.tools.addFunction(function(b){CKEDITOR.tools.setTimeout(e, -0,this,b)},this);this._.docTitle=this.getWindow().getFrame().getAttribute("title")},base:CKEDITOR.editable,proto:{setData:function(b,e){var f=this.editor;if(e)this.setHtml(b),this.fixInitialSelection(),f.fire("dataReady");else{this._.isLoadingData=!0;f._.dataStore={id:1};var d=f.config,g=d.fullPage,h=d.docType,a=CKEDITOR.tools.buildStyleHtml(c()).replace(/

', - format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '/* '/*

', - format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '

', - format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '

', - format_string('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// '// assertEquals('This field has been disabled because you do not have sufficient permissions to edit it.', $element['#value']); + } + +} diff --git a/core/modules/filter/tests/src/Kernel/FilterSettingsTest.php b/core/modules/filter/tests/src/Kernel/FilterSettingsTest.php deleted file mode 100644 index f77efac85..000000000 --- a/core/modules/filter/tests/src/Kernel/FilterSettingsTest.php +++ /dev/null @@ -1,62 +0,0 @@ -container->get('plugin.manager.filter')->getDefinitions(); - - // Create text format using filter default settings. - $filter_defaults_format = FilterFormat::create([ - 'format' => 'filter_defaults', - 'name' => 'Filter defaults', - ]); - $filter_defaults_format->save(); - - // Verify that default weights defined in hook_filter_info() were applied. - $saved_settings = []; - foreach ($filter_defaults_format->filters() as $name => $filter) { - $expected_weight = $filter_info[$name]['weight']; - $this->assertEqual($filter->weight, $expected_weight, format_string('@name filter weight %saved equals %default', [ - '@name' => $name, - '%saved' => $filter->weight, - '%default' => $expected_weight, - ])); - $saved_settings[$name]['weight'] = $expected_weight; - } - - // Re-save the text format. - $filter_defaults_format->save(); - // Reload it from scratch. - filter_formats_reset(); - - // Verify that saved filter settings have not been changed. - foreach ($filter_defaults_format->filters() as $name => $filter) { - $this->assertEqual($filter->weight, $saved_settings[$name]['weight'], format_string('@name filter weight %saved equals %previous', [ - '@name' => $name, - '%saved' => $filter->weight, - '%previous' => $saved_settings[$name]['weight'], - ])); - } - } - -} diff --git a/core/modules/filter/tests/src/Kernel/Migrate/d6/FilterFormatPermissionTest.php b/core/modules/filter/tests/src/Kernel/Migrate/d6/FilterFormatPermissionTest.php index 80b0c7d81..e1fec476c 100644 --- a/core/modules/filter/tests/src/Kernel/Migrate/d6/FilterFormatPermissionTest.php +++ b/core/modules/filter/tests/src/Kernel/Migrate/d6/FilterFormatPermissionTest.php @@ -3,7 +3,9 @@ namespace Drupal\Tests\filter\Kernel\Migrate\d6; use Drupal\filter\Plugin\migrate\process\d6\FilterFormatPermission; +use Drupal\migrate\Plugin\MigrateProcessInterface; use Drupal\migrate\Plugin\Migration; +use Drupal\migrate\Plugin\MigrationInterface; use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase; /** @@ -23,10 +25,21 @@ class FilterFormatPermissionTest extends MigrateDrupalTestBase { public function testConfigurableFilterFormat() { $migration = Migration::create($this->container, [], 'custom_migration', []); $filterFormatPermissionMigration = FilterFormatPermission::create($this->container, ['migration' => 'custom_filter_format'], 'custom_filter_format', [], $migration); - $migrationPlugin = $this->readAttribute($filterFormatPermissionMigration, 'migrationPlugin'); - $config = $this->readAttribute($migrationPlugin, 'configuration'); - + $config = $this->readAttribute($filterFormatPermissionMigration, 'configuration'); $this->assertEquals($config['migration'], 'custom_filter_format'); } + /** + * Tests legacy plugin usage. + * + * @group legacy + * + * @expectedDeprecation Passing a migration process plugin as the fourth argument to Drupal\filter\Plugin\migrate\process\d6\FilterFormatPermission::__construct is deprecated in drupal:8.8.0 and will throw an error in drupal:9.0.0. Pass the migrate.lookup service instead. See https://www.drupal.org/node/3047268 + */ + public function testLegacyConstruct() { + $process_plugin = $this->prophesize(MigrateProcessInterface::class)->reveal(); + $plugin = new FilterFormatPermission([], '', [], $this->prophesize(MigrationInterface::class)->reveal(), $process_plugin); + $this->assertSame($process_plugin, $this->readAttribute($plugin, 'migrationPlugin')); + } + } diff --git a/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php b/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php index 0f54245c1..7588d2378 100644 --- a/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php +++ b/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php @@ -38,8 +38,10 @@ protected function setUp() { * The expected filters in the format, keyed by ID with weight as values. * @param int $weight * The weight of the filter. + * @param bool $status + * The status of the filter. */ - protected function assertEntity($id, $label, array $enabled_filters, $weight) { + protected function assertEntity($id, $label, array $enabled_filters, $weight, $status) { /** @var \Drupal\filter\FilterFormatInterface $entity */ $entity = FilterFormat::load($id); $this->assertInstanceOf(FilterFormatInterface::class, $entity); @@ -47,6 +49,7 @@ protected function assertEntity($id, $label, array $enabled_filters, $weight) { // get('filters') will return enabled filters only, not all of them. $this->assertSame(array_keys($enabled_filters), array_keys($entity->get('filters'))); $this->assertSame($weight, $entity->get('weight')); + $this->assertSame($status, $entity->status()); foreach ($entity->get('filters') as $filter_id => $filter) { $this->assertSame($filter['weight'], $enabled_filters[$filter_id]); } @@ -56,10 +59,10 @@ protected function assertEntity($id, $label, array $enabled_filters, $weight) { * Tests the Drupal 7 filter format to Drupal 8 migration. */ public function testFilterFormat() { - $this->assertEntity('custom_text_format', 'Custom Text format', ['filter_autop' => 0, 'filter_html' => -10], 0); - $this->assertEntity('filtered_html', 'Filtered HTML', ['filter_autop' => 2, 'filter_html' => 1, 'filter_htmlcorrector' => 10, 'filter_url' => 0], 0); - $this->assertEntity('full_html', 'Full HTML', ['filter_autop' => 1, 'filter_htmlcorrector' => 10, 'filter_url' => 0], 1); - $this->assertEntity('plain_text', 'Plain text', ['filter_html_escape' => 0, 'filter_url' => 1, 'filter_autop' => 2], 10); + $this->assertEntity('custom_text_format', 'Custom Text format', ['filter_autop' => 0, 'filter_html' => -10], 0, TRUE); + $this->assertEntity('filtered_html', 'Filtered HTML', ['filter_autop' => 2, 'filter_html' => 1, 'filter_htmlcorrector' => 10, 'filter_url' => 0], 0, TRUE); + $this->assertEntity('full_html', 'Full HTML', ['filter_autop' => 1, 'filter_htmlcorrector' => 10, 'filter_url' => 0], 1, TRUE); + $this->assertEntity('plain_text', 'Plain text', ['filter_html_escape' => 0, 'filter_url' => 1, 'filter_autop' => 2], 10, TRUE); // This assertion covers issue #2555089. Drupal 7 formats are identified // by machine names, so migrated formats should be merged into existing // ones. @@ -74,11 +77,9 @@ public function testFilterFormat() { $config = $format->filters('filter_url')->getConfiguration(); $this->assertSame(128, $config['settings']['filter_url_length']); - // The php_code format gets migrated, but the php_code filter is changed to - // filter_null. - $format = FilterFormat::load('php_code'); - $this->assertInstanceOf(FilterFormatInterface::class, $format); - $this->assertArrayHasKey('filter_null', $format->get('filters')); + // The disabled php_code format gets migrated, but the php_code filter is + // changed to filter_null. + $this->assertEntity('php_code', 'PHP code', ['filter_null' => 0], 11, FALSE); } } diff --git a/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php index f633debad..02cc83769 100644 --- a/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php +++ b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php @@ -24,7 +24,7 @@ class FilterIdTest extends KernelTestBase { /** * The mocked MigrateExecutable. * - * @var \Drupal\migrate\MigrateExecutableInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\MigrateExecutableInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $executable; @@ -33,7 +33,7 @@ class FilterIdTest extends KernelTestBase { */ protected function setUp() { parent::setUp(); - $this->executable = $this->getMock(MigrateExecutableInterface::class); + $this->executable = $this->createMock(MigrateExecutableInterface::class); } /** diff --git a/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterSettingsTest.php b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterSettingsTest.php index 425021107..842ab1204 100644 --- a/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterSettingsTest.php +++ b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterSettingsTest.php @@ -23,10 +23,10 @@ class FilterSettingsTest extends MigrateTestCase { * @covers ::transform */ public function testTransform($value, $destination_id, $expected_value) { - $migration = $this->getMock(MigrationInterface::class); + $migration = $this->createMock(MigrationInterface::class); $plugin = new FilterSettings([], 'filter_settings', [], $migration); - $executable = $this->getMock(MigrateExecutableInterface::class); + $executable = $this->createMock(MigrateExecutableInterface::class); $row = $this->getMockBuilder(Row::class) ->disableOriginalConstructor() ->getMock(); diff --git a/core/modules/filter/tests/src/Unit/FilterHtmlTest.php b/core/modules/filter/tests/src/Unit/FilterHtmlTest.php index f8285ec5a..0e4340501 100644 --- a/core/modules/filter/tests/src/Unit/FilterHtmlTest.php +++ b/core/modules/filter/tests/src/Unit/FilterHtmlTest.php @@ -37,7 +37,7 @@ protected function setUp() { * * @param string $html * Input HTML. - * @param array $expected + * @param string $expected * The expected output string. */ public function testfilterAttributes($html, $expected) { diff --git a/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php b/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php index 966f30712..9e5b75219 100644 --- a/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php +++ b/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php @@ -14,7 +14,7 @@ class FilterUninstallValidatorTest extends UnitTestCase { use AssertHelperTrait; /** - * @var \Drupal\filter\FilterUninstallValidator|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\filter\FilterUninstallValidator|\PHPUnit\Framework\MockObject\MockObject */ protected $filterUninstallValidator; @@ -117,7 +117,7 @@ public function testValidateNoMatchingFormats() { ['test_filter_plugin4', $filter_plugin_enabled], ]); - $filter_format1 = $this->getMock('Drupal\filter\FilterFormatInterface'); + $filter_format1 = $this->createMock('Drupal\filter\FilterFormatInterface'); $filter_format1->expects($this->once()) ->method('filters') ->willReturn($filter_plugin_collection1); @@ -142,7 +142,7 @@ public function testValidateNoMatchingFormats() { ->with('test_filter_plugin4') ->willReturn($filter_plugin_enabled); - $filter_format2 = $this->getMock('Drupal\filter\FilterFormatInterface'); + $filter_format2 = $this->createMock('Drupal\filter\FilterFormatInterface'); $filter_format2->expects($this->once()) ->method('filters') ->willReturn($filter_plugin_collection2); diff --git a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml index 965a691c9..2f87d23d5 100644 --- a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml +++ b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml @@ -22,6 +22,7 @@ content: rows: 9 summary_rows: 3 placeholder: '' + show_summary: false third_party_settings: { } comment_forum: type: comment_default @@ -76,6 +77,7 @@ content: region: content settings: match_operator: CONTAINS + match_limit: 10 size: 60 placeholder: '' third_party_settings: { } diff --git a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml index 50df98ac1..51fa06906 100644 --- a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml +++ b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml @@ -25,5 +25,12 @@ content: size: 60 placeholder: '' third_party_settings: { } + status: + type: boolean_checkbox + settings: + display_label: true + weight: 100 + region: content + third_party_settings: { } hidden: forum_container: true diff --git a/core/modules/forum/config/optional/field.field.node.forum.body.yml b/core/modules/forum/config/optional/field.field.node.forum.body.yml index af6f7ad12..4289cdb2b 100644 --- a/core/modules/forum/config/optional/field.field.node.forum.body.yml +++ b/core/modules/forum/config/optional/field.field.node.forum.body.yml @@ -18,4 +18,5 @@ default_value: { } default_value_callback: '' settings: display_summary: true + required_summary: false field_type: text_with_summary diff --git a/core/modules/forum/forum.info.yml b/core/modules/forum/forum.info.yml index 4b7eb4c56..d275dfccb 100644 --- a/core/modules/forum/forum.info.yml +++ b/core/modules/forum/forum.info.yml @@ -8,12 +8,6 @@ dependencies: - drupal:comment - drupal:options package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: forum.overview - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index bddc7e36c..a505c3f1d 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -9,6 +9,7 @@ use Drupal\comment\CommentInterface; use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; @@ -335,8 +336,6 @@ function forum_form_node_form_alter(&$form, FormStateInterface $form_state, $for if (isset($form['taxonomy_forums'])) { $widget =& $form['taxonomy_forums']['widget']; - // Make the vocabulary required for 'real' forum-nodes. - $widget['#required'] = TRUE; $widget['#multiple'] = FALSE; if (empty($widget['#default_value'])) { // If there is no default forum already selected, try to get the forum @@ -448,11 +447,11 @@ function template_preprocess_forums(&$variables) { if ($variables['tid'] != $topic->forum_tid) { $variables['topics'][$id]->moved = TRUE; $variables['topics'][$id]->title = $topic->getTitle(); - $variables['topics'][$id]->message = \Drupal::l(t('This topic has been moved'), new Url('forum.page', ['taxonomy_term' => $topic->forum_tid])); + $variables['topics'][$id]->message = Link::fromTextAndUrl(t('This topic has been moved'), Url::fromRoute('forum.page', ['taxonomy_term' => $topic->forum_tid]))->toString(); } else { $variables['topics'][$id]->moved = FALSE; - $variables['topics'][$id]->title_link = \Drupal::l($topic->getTitle(), $topic->toUrl()); + $variables['topics'][$id]->title_link = Link::fromTextAndUrl($topic->getTitle(), $topic->toUrl())->toString(); $variables['topics'][$id]->message = ''; } $forum_submitted = [ @@ -474,7 +473,7 @@ function template_preprocess_forums(&$variables) { $variables['topics'][$id]->new_url = ''; if ($topic->new_replies) { - $page_number = \Drupal::entityManager()->getStorage('comment') + $page_number = \Drupal::entityTypeManager()->getStorage('comment') ->getNewCommentPageNumber($topic->comment_count, $topic->new_replies, $topic, 'comment_forum'); $query = $page_number ? ['page' => $page_number] : NULL; $variables['topics'][$id]->new_text = \Drupal::translation()->formatPlural($topic->new_replies, '1 new post in topic %title', '@count new posts in topic %title', ['%title' => $variables['topics'][$id]->label()]); diff --git a/core/modules/forum/migrations/state/forum.migrate_drupal.yml b/core/modules/forum/migrations/state/forum.migrate_drupal.yml new file mode 100644 index 000000000..5b9175454 --- /dev/null +++ b/core/modules/forum/migrations/state/forum.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + forum: forum + 7: + forum: forum diff --git a/core/modules/forum/src/Controller/ForumController.php b/core/modules/forum/src/Controller/ForumController.php index 2348872b4..347013b81 100644 --- a/core/modules/forum/src/Controller/ForumController.php +++ b/core/modules/forum/src/Controller/ForumController.php @@ -125,20 +125,20 @@ public function __construct(ForumManagerInterface $forum_manager, VocabularyStor * {@inheritdoc} */ public static function create(ContainerInterface $container) { - /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ - $entity_manager = $container->get('entity.manager'); + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ + $entity_type_manager = $container->get('entity_type.manager'); return new static( $container->get('forum_manager'), - $entity_manager->getStorage('taxonomy_vocabulary'), - $entity_manager->getStorage('taxonomy_term'), + $entity_type_manager->getStorage('taxonomy_vocabulary'), + $entity_type_manager->getStorage('taxonomy_term'), $container->get('current_user'), - $entity_manager->getAccessControlHandler('node'), - $entity_manager->getFieldMap(), - $entity_manager->getStorage('node_type'), + $entity_type_manager->getAccessControlHandler('node'), + $container->get('entity_field.manager')->getFieldMap(), + $entity_type_manager->getStorage('node_type'), $container->get('renderer'), - $entity_manager->getDefinition('node'), - $entity_manager->getDefinition('comment') + $entity_type_manager->getDefinition('node'), + $entity_type_manager->getDefinition('comment') ); } @@ -154,7 +154,7 @@ public static function create(ContainerInterface $container) { public function forumPage(TermInterface $taxonomy_term) { // Get forum details. $taxonomy_term->forums = $this->forumManager->getChildren($this->config('forum.settings')->get('vocabulary'), $taxonomy_term->id()); - $taxonomy_term->parents = $this->forumManager->getParents($taxonomy_term->id()); + $taxonomy_term->parents = $this->termStorage->loadAllParents($taxonomy_term->id()); if (empty($taxonomy_term->forum_container->value)) { $build = $this->forumManager->getTopics($taxonomy_term->id(), $this->currentUser()); diff --git a/core/modules/forum/src/Form/ForumForm.php b/core/modules/forum/src/Form/ForumForm.php index 73f5e8f1b..75264cc4d 100644 --- a/core/modules/forum/src/Form/ForumForm.php +++ b/core/modules/forum/src/Form/ForumForm.php @@ -3,6 +3,7 @@ namespace Drupal\forum\Form; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\taxonomy\TermForm; @@ -78,7 +79,7 @@ public function save(array $form, FormStateInterface $form_state) { $route_name = $this->urlStub == 'container' ? 'entity.taxonomy_term.forum_edit_container_form' : 'entity.taxonomy_term.forum_edit_form'; $route_parameters = ['taxonomy_term' => $term->id()]; - $link = $this->l($this->t('Edit'), new Url($route_name, $route_parameters)); + $link = Link::fromTextAndUrl($this->t('Edit'), new Url($route_name, $route_parameters))->toString(); $view_link = $term->toLink($term->getName())->toString(); switch ($status) { case SAVED_NEW: diff --git a/core/modules/forum/src/Form/Overview.php b/core/modules/forum/src/Form/Overview.php index b3017968c..2158170da 100644 --- a/core/modules/forum/src/Form/Overview.php +++ b/core/modules/forum/src/Form/Overview.php @@ -71,8 +71,8 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular // Use the existing taxonomy overview submit handler. $form['terms']['#empty'] = $this->t('No containers or forums available. Add container or Add forum.', [ - ':container' => $this->url('forum.add_container'), - ':forum' => $this->url('forum.add_forum'), + ':container' => Url::fromRoute('forum.add_container')->toString(), + ':forum' => Url::fromRoute('forum.add_forum')->toString(), ]); return $form; } diff --git a/core/modules/forum/src/ForumManager.php b/core/modules/forum/src/ForumManager.php index 6e95ff044..e8be2869e 100644 --- a/core/modules/forum/src/ForumManager.php +++ b/core/modules/forum/src/ForumManager.php @@ -347,7 +347,7 @@ protected function lastVisit($nid, AccountInterface $account) { * @param int $tid * The forum tid. * - * @return \stdClass + * @return object * The last post for the given forum. */ protected function getLastPost($tid) { @@ -388,7 +388,7 @@ protected function getLastPost($tid) { * @param int $tid * The forum tid. * - * @return \stdClass|null + * @return object|null * Statistics for the given forum if statistics exist, else NULL. */ protected function getForumStatistics($tid) { @@ -480,6 +480,7 @@ public function resetCache() { * {@inheritdoc} */ public function getParents($tid) { + @trigger_error(__NAMESPACE__ . '\ForumManager::getParents() is deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Call loadAllParents() on taxonomy term storage directly. See https://www.drupal.org/node/3069599', E_USER_DEPRECATED); return $this->entityTypeManager->getStorage('taxonomy_term')->loadAllParents($tid); } diff --git a/core/modules/forum/src/ForumManagerInterface.php b/core/modules/forum/src/ForumManagerInterface.php index 9e8833ebc..f1f7079ec 100644 --- a/core/modules/forum/src/ForumManagerInterface.php +++ b/core/modules/forum/src/ForumManagerInterface.php @@ -60,8 +60,10 @@ public function resetCache(); * @return array * Array of parent terms. * - * @deprecated Scheduled to be removed in 9.0.x, see - * https://www.drupal.org/node/2371593. + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Call + * loadAllParents() on taxonomy term storage directly. + * + * @see https://www.drupal.org/node/3069599 */ public function getParents($tid); diff --git a/core/modules/forum/tests/modules/forum_test_views/forum_test_views.info.yml b/core/modules/forum/tests/modules/forum_test_views/forum_test_views.info.yml index c5efa95b7..6debc15a7 100644 --- a/core/modules/forum/tests/modules/forum_test_views/forum_test_views.info.yml +++ b/core/modules/forum/tests/modules/forum_test_views/forum_test_views.info.yml @@ -2,14 +2,8 @@ name: 'Forum test views' type: module description: 'Provides default views for views forum tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:forum - drupal:views - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml b/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml index a874401a7..3331a3a76 100644 --- a/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml +++ b/core/modules/forum/tests/modules/forum_test_views/test_views/views.view.test_forum_index.yml @@ -8,7 +8,6 @@ description: '' tag: '' base_table: forum_index base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/forum/tests/src/Functional/ForumBlockTest.php b/core/modules/forum/tests/src/Functional/ForumBlockTest.php index b231f6d0e..a5043fe26 100644 --- a/core/modules/forum/tests/src/Functional/ForumBlockTest.php +++ b/core/modules/forum/tests/src/Functional/ForumBlockTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\forum\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\comment\Entity\Comment; use Drupal\Tests\BrowserTestBase; @@ -20,6 +21,11 @@ class ForumBlockTest extends BrowserTestBase { */ public static $modules = ['forum', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with various administrative privileges. */ @@ -57,7 +63,7 @@ public function testNewForumTopicsBlock() { // We expect all 5 forum topics to appear in the "New forum topics" block. foreach ($topics as $topic) { - $this->assertLink($topic, 0, format_string('Forum topic @topic found in the "New forum topics" block.', ['@topic' => $topic])); + $this->assertLink($topic, 0, new FormattableMarkup('Forum topic @topic found in the "New forum topics" block.', ['@topic' => $topic])); } // Configure the new forum topics block to only show 2 topics. @@ -69,10 +75,10 @@ public function testNewForumTopicsBlock() { // topics" block. for ($index = 0; $index < 5; $index++) { if (in_array($index, [3, 4])) { - $this->assertLink($topics[$index], 0, format_string('Forum topic @topic found in the "New forum topics" block.', ['@topic' => $topics[$index]])); + $this->assertLink($topics[$index], 0, new FormattableMarkup('Forum topic @topic found in the "New forum topics" block.', ['@topic' => $topics[$index]])); } else { - $this->assertNoText($topics[$index], format_string('Forum topic @topic not found in the "New forum topics" block.', ['@topic' => $topics[$index]])); + $this->assertNoText($topics[$index], new FormattableMarkup('Forum topic @topic not found in the "New forum topics" block.', ['@topic' => $topics[$index]])); } } } @@ -115,10 +121,10 @@ public function testActiveForumTopicsBlock() { $this->drupalGet(''); for ($index = 0; $index < 10; $index++) { if ($index < 5) { - $this->assertLink($topics[$index], 0, format_string('Forum topic @topic found in the "Active forum topics" block.', ['@topic' => $topics[$index]])); + $this->assertLink($topics[$index], 0, new FormattableMarkup('Forum topic @topic found in the "Active forum topics" block.', ['@topic' => $topics[$index]])); } else { - $this->assertNoText($topics[$index], format_string('Forum topic @topic not found in the "Active forum topics" block.', ['@topic' => $topics[$index]])); + $this->assertNoText($topics[$index], new FormattableMarkup('Forum topic @topic not found in the "Active forum topics" block.', ['@topic' => $topics[$index]])); } } diff --git a/core/modules/forum/tests/src/Functional/ForumIndexTest.php b/core/modules/forum/tests/src/Functional/ForumIndexTest.php index 5d0ccdff3..3bb803a97 100644 --- a/core/modules/forum/tests/src/Functional/ForumIndexTest.php +++ b/core/modules/forum/tests/src/Functional/ForumIndexTest.php @@ -18,6 +18,11 @@ class ForumIndexTest extends BrowserTestBase { */ public static $modules = ['taxonomy', 'comment', 'forum']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/forum/tests/src/Functional/ForumNodeAccessTest.php b/core/modules/forum/tests/src/Functional/ForumNodeAccessTest.php index 9849084c6..212bbdd2b 100644 --- a/core/modules/forum/tests/src/Functional/ForumNodeAccessTest.php +++ b/core/modules/forum/tests/src/Functional/ForumNodeAccessTest.php @@ -19,6 +19,11 @@ class ForumNodeAccessTest extends BrowserTestBase { */ public static $modules = ['node', 'comment', 'forum', 'taxonomy', 'tracker', 'node_access_test', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); node_access_rebuild(); diff --git a/core/modules/forum/tests/src/Functional/ForumTest.php b/core/modules/forum/tests/src/Functional/ForumTest.php index af331916b..d906baf27 100644 --- a/core/modules/forum/tests/src/Functional/ForumTest.php +++ b/core/modules/forum/tests/src/Functional/ForumTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\forum\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Entity\EntityInterface; @@ -27,6 +28,11 @@ class ForumTest extends BrowserTestBase { */ public static $modules = ['taxonomy', 'comment', 'forum', 'node', 'block', 'menu_ui', 'help']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A user with various administrative privileges. */ @@ -257,7 +263,9 @@ public function testAddOrphanTopic() { $tids = \Drupal::entityQuery('taxonomy_term') ->condition('vid', $vid) ->execute(); - entity_delete_multiple('taxonomy_term', $tids); + $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $terms = $term_storage->loadMultiple($tids); + $term_storage->delete($terms); // Create an orphan forum item. $edit = []; @@ -425,7 +433,7 @@ public function createForum($type, $parent = 0) { 'Created new @type @term.', ['@term' => $name, '@type' => t($type)] ), - format_string('@type was created', ['@type' => ucfirst($type)]) + new FormattableMarkup('@type was created', ['@type' => ucfirst($type)]) ); // Verify that the creation message contains a link to a term. @@ -576,7 +584,7 @@ public function createForumTopic($forum, $container = FALSE) { // Retrieve node object, ensure that the topic was created and in the proper forum. $node = $this->drupalGetNodeByTitle($title); - $this->assertTrue($node != NULL, format_string('Node @title was loaded', ['@title' => $title])); + $this->assertTrue($node != NULL, new FormattableMarkup('Node @title was loaded', ['@title' => $title])); $this->assertEqual($node->taxonomy_forums->target_id, $tid, 'Saved forum topic was in the expected forum'); // View forum topic. diff --git a/core/modules/forum/tests/src/Functional/ForumUninstallTest.php b/core/modules/forum/tests/src/Functional/ForumUninstallTest.php index c2d3848ff..84c81c893 100644 --- a/core/modules/forum/tests/src/Functional/ForumUninstallTest.php +++ b/core/modules/forum/tests/src/Functional/ForumUninstallTest.php @@ -23,6 +23,11 @@ class ForumUninstallTest extends BrowserTestBase { */ public static $modules = ['forum']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests if forum module uninstallation properly deletes the field. */ @@ -80,10 +85,9 @@ public function testForumUninstallWithField() { // Delete any forum terms. $vid = $this->config('forum.settings')->get('vocabulary'); - $terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]); - foreach ($terms as $term) { - $term->delete(); - } + $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $terms = $storage->loadByProperties(['vid' => $vid]); + $storage->delete($terms); // Ensure that the forum node type can not be deleted. $this->drupalGet('admin/structure/types/manage/forum'); @@ -137,7 +141,7 @@ public function testForumUninstallWithoutFieldStorage() { // Delete all terms in the Forums vocabulary. Uninstalling the forum module // will fail unless this is done. - $terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => 'forums']); + $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['vid' => 'forums']); foreach ($terms as $term) { $term->delete(); } diff --git a/core/modules/forum/tests/src/Functional/Views/ForumIntegrationTest.php b/core/modules/forum/tests/src/Functional/Views/ForumIntegrationTest.php index 992500e1f..eda9006f9 100644 --- a/core/modules/forum/tests/src/Functional/Views/ForumIntegrationTest.php +++ b/core/modules/forum/tests/src/Functional/Views/ForumIntegrationTest.php @@ -21,6 +21,11 @@ class ForumIntegrationTest extends ViewTestBase { */ public static $modules = ['forum_test_views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * @@ -39,11 +44,11 @@ protected function setUp($import_test_views = TRUE) { */ public function testForumIntegration() { // Create a forum. - $entity_manager = $this->container->get('entity.manager'); - $term = $entity_manager->getStorage('taxonomy_term')->create(['vid' => 'forums', 'name' => $this->randomMachineName()]); + $entity_type_manager = $this->container->get('entity_type.manager'); + $term = $entity_type_manager->getStorage('taxonomy_term')->create(['vid' => 'forums', 'name' => $this->randomMachineName()]); $term->save(); - $comment_storage = $entity_manager->getStorage('comment'); + $comment_storage = $entity_type_manager->getStorage('comment'); // Create some nodes which are part of this forum with some comments. $nodes = []; diff --git a/core/modules/forum/tests/src/Kernel/LegacyForumTest.php b/core/modules/forum/tests/src/Kernel/LegacyForumTest.php new file mode 100644 index 000000000..fdd610aa2 --- /dev/null +++ b/core/modules/forum/tests/src/Kernel/LegacyForumTest.php @@ -0,0 +1,69 @@ +installEntitySchema('taxonomy_term'); + $this->installConfig('forum'); + $this->termStorage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + } + + /** + * Tests the getParents() method. + * + * @expectedDeprecation Drupal\forum\ForumManager::getParents() is deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Call loadAllParents() on taxonomy term storage directly. See https://www.drupal.org/node/3069599 + */ + public function testGetParents() { + // Add a forum. + $forum = $this->termStorage->create([ + 'name' => 'Forum', + 'vid' => 'forums', + 'forum_container' => 1, + ]); + $forum->save(); + + // Add a container. + $subforum = $this->termStorage->create([ + 'name' => 'Subforum', + 'vid' => 'forums', + 'forum_container' => 0, + 'parent' => $forum->id(), + ]); + $subforum->save(); + + $legacy_parents = \Drupal::service('forum_manager')->getParents($subforum->id()); + $parents = $this->termStorage->loadAllParents($subforum->id()); + $this->assertSame($parents, $legacy_parents); + } + +} diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php index ca4fe59d5..6a86a180b 100644 --- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php +++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php @@ -42,8 +42,8 @@ public function testConstructor() { 'forum.settings' => ['IAmATestKey' => 'IAmATestValue'], ] ); - $forum_manager = $this->getMock('Drupal\forum\ForumManagerInterface'); - $translation_manager = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'); + $forum_manager = $this->createMock('Drupal\forum\ForumManagerInterface'); + $translation_manager = $this->createMock('Drupal\Core\StringTranslation\TranslationInterface'); // Make an object to test. $builder = $this->getMockForAbstractClass( @@ -101,7 +101,7 @@ public function testBuild() { $prophecy->getCacheContexts()->willReturn([]); $prophecy->getCacheMaxAge()->willReturn(Cache::PERMANENT); - $vocab_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $vocab_storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $vocab_storage->expects($this->any()) ->method('load') ->will($this->returnValueMap([ @@ -140,7 +140,7 @@ public function testBuild() { $breadcrumb_builder->setStringTranslation($translation_manager); // Our empty data set. - $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); // Expected result set. $expected = [ diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php index 47a0113d1..962e5f0ea 100644 --- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php +++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Link; +use Drupal\forum\Breadcrumb\ForumListingBreadcrumbBuilder; use Drupal\taxonomy\TermStorageInterface; use Drupal\Tests\UnitTestCase; use Symfony\Component\DependencyInjection\Container; @@ -44,10 +45,10 @@ protected function setUp() { */ public function testApplies($expected, $route_name = NULL, $parameter_map = []) { // Make some test doubles. - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $entity_manager = $this->createMock('Drupal\Core\Entity\EntityManagerInterface'); $config_factory = $this->getConfigFactoryStub([]); - $forum_manager = $this->getMock('Drupal\forum\ForumManagerInterface'); - $translation_manager = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'); + $forum_manager = $this->createMock('Drupal\forum\ForumManagerInterface'); + $translation_manager = $this->createMock('Drupal\Core\StringTranslation\TranslationInterface'); // Make an object to test. $builder = $this->getMockBuilder('Drupal\forum\Breadcrumb\ForumListingBreadcrumbBuilder') @@ -60,7 +61,7 @@ public function testApplies($expected, $route_name = NULL, $parameter_map = []) ->setMethods(NULL) ->getMock(); - $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); $route_match->expects($this->once()) ->method('getRouteName') ->will($this->returnValue($route_name)); @@ -154,7 +155,7 @@ public function testBuild() { $prophecy->getCacheTags()->willReturn(['taxonomy_vocabulary:5']); $prophecy->getCacheContexts()->willReturn([]); $prophecy->getCacheMaxAge()->willReturn(Cache::PERMANENT); - $vocab_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $vocab_storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $vocab_storage->expects($this->any()) ->method('load') ->will($this->returnValueMap([ @@ -179,17 +180,10 @@ public function testBuild() { ] ); - $forum_manager = $this->getMock('Drupal\forum\ForumManagerInterface'); + $forum_manager = $this->createMock('Drupal\forum\ForumManagerInterface'); // Build a breadcrumb builder to test. - $breadcrumb_builder = $this->getMock( - 'Drupal\forum\Breadcrumb\ForumListingBreadcrumbBuilder', NULL, [ - $entity_manager, - $config_factory, - $forum_manager, - $translation_manager, - ] - ); + $breadcrumb_builder = new ForumListingBreadcrumbBuilder($entity_manager, $config_factory, $forum_manager, $translation_manager); // Add a translation manager for t(). $translation_manager = $this->getStringTranslationStub(); @@ -205,7 +199,7 @@ public function testBuild() { $forum_listing = $prophecy->reveal(); // Our data set. - $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); $route_match->expects($this->exactly(2)) ->method('getParameter') ->with('taxonomy_term') diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php index 33acddff3..a194ecc68 100644 --- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php +++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Link; +use Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder; use Drupal\taxonomy\TermStorageInterface; use Drupal\Tests\UnitTestCase; use Symfony\Component\DependencyInjection\Container; @@ -44,15 +45,15 @@ protected function setUp() { */ public function testApplies($expected, $route_name = NULL, $parameter_map = []) { // Make some test doubles. - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $entity_manager = $this->createMock('Drupal\Core\Entity\EntityManagerInterface'); $config_factory = $this->getConfigFactoryStub([]); - $forum_manager = $this->getMock('Drupal\forum\ForumManagerInterface'); + $forum_manager = $this->createMock('Drupal\forum\ForumManagerInterface'); $forum_manager->expects($this->any()) ->method('checkNodeType') ->will($this->returnValue(TRUE)); - $translation_manager = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'); + $translation_manager = $this->createMock('Drupal\Core\StringTranslation\TranslationInterface'); // Make an object to test. $builder = $this->getMockBuilder('Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder') @@ -67,7 +68,7 @@ public function testApplies($expected, $route_name = NULL, $parameter_map = []) ->setMethods(NULL) ->getMock(); - $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); $route_match->expects($this->once()) ->method('getRouteName') ->will($this->returnValue($route_name)); @@ -164,7 +165,7 @@ public function testBuild() { $prophecy->getCacheTags()->willReturn(['taxonomy_vocabulary:5']); $prophecy->getCacheContexts()->willReturn([]); $prophecy->getCacheMaxAge()->willReturn(Cache::PERMANENT); - $vocab_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $vocab_storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $vocab_storage->expects($this->any()) ->method('load') ->will($this->returnValueMap([ @@ -190,20 +191,14 @@ public function testBuild() { ); // Build a breadcrumb builder to test. - $breadcrumb_builder = $this->getMock( - 'Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder', NULL, [ - $entity_manager, - $config_factory, - $forum_manager, - $translation_manager, - ] - ); + $breadcrumb_builder = new ForumNodeBreadcrumbBuilder($entity_manager, + $config_factory, + $forum_manager, + $translation_manager); // Add a translation manager for t(). $translation_manager = $this->getStringTranslationStub(); - $property = new \ReflectionProperty('Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder', 'stringTranslation'); - $property->setAccessible(TRUE); - $property->setValue($breadcrumb_builder, $translation_manager); + $breadcrumb_builder->setStringTranslation($translation_manager); // The forum node we need a breadcrumb back from. $forum_node = $this->getMockBuilder('Drupal\node\Entity\Node') @@ -211,7 +206,7 @@ public function testBuild() { ->getMock(); // Our data set. - $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); $route_match->expects($this->exactly(2)) ->method('getParameter') ->with('node') diff --git a/core/modules/forum/tests/src/Unit/ForumManagerTest.php b/core/modules/forum/tests/src/Unit/ForumManagerTest.php index 6a3297fd0..969bb7121 100644 --- a/core/modules/forum/tests/src/Unit/ForumManagerTest.php +++ b/core/modules/forum/tests/src/Unit/ForumManagerTest.php @@ -23,7 +23,7 @@ public function testGetIndex() { ->disableOriginalConstructor() ->getMock(); - $config_factory = $this->getMock('\Drupal\Core\Config\ConfigFactoryInterface'); + $config_factory = $this->createMock('\Drupal\Core\Config\ConfigFactoryInterface'); $config = $this->getMockBuilder('\Drupal\Core\Config\Config') ->disableOriginalConstructor() @@ -60,14 +60,17 @@ public function testGetIndex() { ->disableOriginalConstructor() ->getMock(); - $manager = $this->getMock('\Drupal\forum\ForumManager', ['getChildren'], [ - $config_factory, - $entity_type_manager, - $connection, - $translation_manager, - $comment_manager, - $entity_field_manager, - ]); + $manager = $this->getMockBuilder('\Drupal\forum\ForumManager') + ->setMethods(['getChildren']) + ->setConstructorArgs([ + $config_factory, + $entity_type_manager, + $connection, + $translation_manager, + $comment_manager, + $entity_field_manager, + ]) + ->getMock(); $manager->expects($this->once()) ->method('getChildren') diff --git a/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php b/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php index 37c3bfac6..6166ea91c 100644 --- a/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php +++ b/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php @@ -15,7 +15,7 @@ class ForumUninstallValidatorTest extends UnitTestCase { use AssertHelperTrait; /** - * @var \Drupal\forum\ForumUninstallValidator|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\forum\ForumUninstallValidator|\PHPUnit\Framework\MockObject\MockObject */ protected $forumUninstallValidator; @@ -56,7 +56,7 @@ public function testValidate() { ->method('hasForumNodes') ->willReturn(FALSE); - $vocabulary = $this->getMock('Drupal\taxonomy\VocabularyInterface'); + $vocabulary = $this->createMock('Drupal\taxonomy\VocabularyInterface'); $this->forumUninstallValidator->expects($this->once()) ->method('getForumVocabulary') ->willReturn($vocabulary); @@ -79,7 +79,7 @@ public function testValidateHasForumNodes() { ->method('hasForumNodes') ->willReturn(TRUE); - $vocabulary = $this->getMock('Drupal\taxonomy\VocabularyInterface'); + $vocabulary = $this->createMock('Drupal\taxonomy\VocabularyInterface'); $this->forumUninstallValidator->expects($this->once()) ->method('getForumVocabulary') ->willReturn($vocabulary); @@ -107,7 +107,7 @@ public function testValidateHasTermsForVocabularyWithNodesAccess() { $url = $this->prophesize(Url::class); $url->toString()->willReturn('/path/to/vocabulary/overview'); - $vocabulary = $this->getMock('Drupal\taxonomy\VocabularyInterface'); + $vocabulary = $this->createMock('Drupal\taxonomy\VocabularyInterface'); $vocabulary->expects($this->once()) ->method('label') ->willReturn('Vocabulary label'); @@ -142,7 +142,7 @@ public function testValidateHasTermsForVocabularyWithNodesNoAccess() { ->method('hasForumNodes') ->willReturn(TRUE); - $vocabulary = $this->getMock('Drupal\taxonomy\VocabularyInterface'); + $vocabulary = $this->createMock('Drupal\taxonomy\VocabularyInterface'); $vocabulary->expects($this->once()) ->method('label') ->willReturn('Vocabulary label'); @@ -179,7 +179,7 @@ public function testValidateHasTermsForVocabularyAccess() { $url = $this->prophesize(Url::class); $url->toString()->willReturn('/path/to/vocabulary/overview'); - $vocabulary = $this->getMock('Drupal\taxonomy\VocabularyInterface'); + $vocabulary = $this->createMock('Drupal\taxonomy\VocabularyInterface'); $vocabulary->expects($this->once()) ->method('toUrl') ->willReturn($url->reveal()); @@ -213,7 +213,7 @@ public function testValidateHasTermsForVocabularyNoAccess() { ->method('hasForumNodes') ->willReturn(FALSE); - $vocabulary = $this->getMock('Drupal\taxonomy\VocabularyInterface'); + $vocabulary = $this->createMock('Drupal\taxonomy\VocabularyInterface'); $vocabulary->expects($this->once()) ->method('label') ->willReturn('Vocabulary label'); diff --git a/core/modules/hal/hal.info.yml b/core/modules/hal/hal.info.yml index 0c5925afa..283994325 100644 --- a/core/modules/hal/hal.info.yml +++ b/core/modules/hal/hal.info.yml @@ -2,13 +2,7 @@ name: 'HAL' type: module description: 'Serializes entities using Hypertext Application Language.' package: Web services -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:serialization - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php index 39daf1cf1..aec86c912 100644 --- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php +++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php @@ -123,7 +123,7 @@ public function normalize($entity, $format = NULL, array $context = []) { * @param array $data * Entity data to restore. * @param string $class - * Unused, entity_create() is used to instantiate entity objects. + * Unused parameter. * @param string $format * Format the given data was extracted from. * @param array $context diff --git a/core/modules/hal/src/Normalizer/FileEntityNormalizer.php b/core/modules/hal/src/Normalizer/FileEntityNormalizer.php index e5ae786d7..ac219015f 100644 --- a/core/modules/hal/src/Normalizer/FileEntityNormalizer.php +++ b/core/modules/hal/src/Normalizer/FileEntityNormalizer.php @@ -14,7 +14,7 @@ /** * Converts the Drupal entity object structure to a HAL array structure. * - * @deprecated in Drupal 8.5.0, to be removed before Drupal 9.0.0. + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. */ class FileEntityNormalizer extends ContentEntityNormalizer { diff --git a/core/modules/hal/tests/modules/hal_test/hal_test.info.yml b/core/modules/hal/tests/modules/hal_test/hal_test.info.yml index db9d87b4b..8f7d7ad0b 100644 --- a/core/modules/hal/tests/modules/hal_test/hal_test.info.yml +++ b/core/modules/hal/tests/modules/hal_test/hal_test.info.yml @@ -2,11 +2,5 @@ name: HAL test module type: module description: "Support module for HAL tests." package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php index 648ab6deb..d647a8296 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Comment/CommentHalJsonTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\comment\Functional\Hal\CommentHalJsonTestBase as CommentHalJsonTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\comment\Functional\Hal\CommentHalJsonTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Feed/FeedHalJsonTestBase.php b/core/modules/hal/tests/src/Functional/EntityResource/Feed/FeedHalJsonTestBase.php index 364e1072b..0bf1173c9 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Feed/FeedHalJsonTestBase.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Feed/FeedHalJsonTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\aggregator\Functional\Hal\FeedHalJsonTestBase as FeedHalJsonTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\aggregator\Functional\Hal\FeedHalJsonTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php b/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php index 6663d28b8..8d61811d7 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\file\Functional\Hal\FileUploadHalJsonTestBase as FileUploadHalJsonTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\file\Functional\Hal\FileUploadHalJsonTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Item/ItemHalJsonTestBase.php b/core/modules/hal/tests/src/Functional/EntityResource/Item/ItemHalJsonTestBase.php index 6febd3d6c..f7f354878 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/Item/ItemHalJsonTestBase.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/Item/ItemHalJsonTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\aggregator\Functional\Hal\ItemHalJsonTestBase as ItemHalJsonTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\aggregator\Functional\Hal\ItemHalJsonTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/hal/tests/src/Kernel/DenormalizeTest.php b/core/modules/hal/tests/src/Kernel/DenormalizeTest.php index 6c6fb731c..320ab65a5 100644 --- a/core/modules/hal/tests/src/Kernel/DenormalizeTest.php +++ b/core/modules/hal/tests/src/Kernel/DenormalizeTest.php @@ -86,7 +86,7 @@ public function testTypeHandlingWithInvalidType() { ], ]; - $this->setExpectedException(UnexpectedValueException::class); + $this->expectException(UnexpectedValueException::class); $this->serializer->denormalize($data_with_invalid_type, $this->entityClass, $this->format); } @@ -100,7 +100,7 @@ public function testTypeHandlingWithNoTypes() { ], ]; - $this->setExpectedException(UnexpectedValueException::class); + $this->expectException(UnexpectedValueException::class); $this->serializer->denormalize($data_with_no_types, $this->entityClass, $this->format); } @@ -146,7 +146,8 @@ public function testMarkFieldForDeletion() { public function testDenormalizeSerializedItem() { $entity = EntitySerializedField::create(['serialized' => 'boo']); $normalized = $this->serializer->normalize($entity, $this->format); - $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedItem).'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedItem).'); $this->serializer->denormalize($normalized, EntitySerializedField::class, $this->format); } @@ -159,7 +160,8 @@ public function testDenormalizeInvalidCustomSerializedField() { $this->assertEquals($normalized['serialized_long'][0]['value'], ['Hello world!']); $normalized['serialized_long'][0]['value'] = 'boo'; - $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); $this->serializer->denormalize($normalized, EntitySerializedField::class); } diff --git a/core/modules/hal/tests/src/Unit/FieldItemNormalizerDenormalizeExceptionsUnitTest.php b/core/modules/hal/tests/src/Unit/FieldItemNormalizerDenormalizeExceptionsUnitTest.php index 4af4a3cc6..33fa4dc33 100644 --- a/core/modules/hal/tests/src/Unit/FieldItemNormalizerDenormalizeExceptionsUnitTest.php +++ b/core/modules/hal/tests/src/Unit/FieldItemNormalizerDenormalizeExceptionsUnitTest.php @@ -23,7 +23,7 @@ public function testFieldItemNormalizerDenormalizeExceptions($context) { $field_item_normalizer = new FieldItemNormalizer(); $data = []; $class = []; - $this->setExpectedException(InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $field_item_normalizer->denormalize($data, $class, NULL, $context); } diff --git a/core/modules/hal/tests/src/Unit/FieldNormalizerDenormalizeExceptionsUnitTest.php b/core/modules/hal/tests/src/Unit/FieldNormalizerDenormalizeExceptionsUnitTest.php index 792e6333a..2ac34ebbb 100644 --- a/core/modules/hal/tests/src/Unit/FieldNormalizerDenormalizeExceptionsUnitTest.php +++ b/core/modules/hal/tests/src/Unit/FieldNormalizerDenormalizeExceptionsUnitTest.php @@ -23,7 +23,7 @@ public function testFieldNormalizerDenormalizeExceptions($context) { $field_item_normalizer = new FieldNormalizer(); $data = []; $class = []; - $this->setExpectedException(InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $field_item_normalizer->denormalize($data, $class, NULL, $context); } diff --git a/core/modules/hal/tests/src/Unit/NormalizerDenormalizeExceptionsUnitTestBase.php b/core/modules/hal/tests/src/Unit/NormalizerDenormalizeExceptionsUnitTestBase.php index d1a3b9b1d..43bbfb4b5 100644 --- a/core/modules/hal/tests/src/Unit/NormalizerDenormalizeExceptionsUnitTestBase.php +++ b/core/modules/hal/tests/src/Unit/NormalizerDenormalizeExceptionsUnitTestBase.php @@ -18,7 +18,9 @@ abstract class NormalizerDenormalizeExceptionsUnitTestBase extends UnitTestCase * @return array Test data. */ public function providerNormalizerDenormalizeExceptions() { - $mock = $this->getMock('\Drupal\Core\Field\Plugin\DataType\FieldItem', ['getParent']); + $mock = $this->getMockBuilder('\Drupal\Core\Field\Plugin\DataType\FieldItem') + ->setMethods(['getParent']) + ->getMock(); $mock->expects($this->any()) ->method('getParent') ->will($this->returnValue(NULL)); diff --git a/core/modules/help/help.api.php b/core/modules/help/help.api.php index 6306f14b0..7345ba51a 100644 --- a/core/modules/help/help.api.php +++ b/core/modules/help/help.api.php @@ -72,9 +72,11 @@ function hook_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_ * @see \Drupal\help\Annotation\HelpSection * @see \Drupal\help\HelpSectionManager */ -function hook_help_section_info_alter(&$info) { +function hook_help_section_info_alter(array &$info) { // Alter the header for the module overviews section. - $info['hook_help']['header'] = t('Overviews of modules'); + $info['hook_help']['title'] = t('Overviews of modules'); + // Move the module overviews section to the end. + $info['hook_help']['weight'] = 500; } /** diff --git a/core/modules/help/help.info.yml b/core/modules/help/help.info.yml index 0746b1ba6..ee4c6433e 100644 --- a/core/modules/help/help.info.yml +++ b/core/modules/help/help.info.yml @@ -2,11 +2,5 @@ name: Help type: module description: 'Manages the display of online help.' package: Core -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/help/src/Annotation/HelpSection.php b/core/modules/help/src/Annotation/HelpSection.php index b56c9899b..0958d30ef 100644 --- a/core/modules/help/src/Annotation/HelpSection.php +++ b/core/modules/help/src/Annotation/HelpSection.php @@ -57,4 +57,13 @@ class HelpSection extends Plugin { */ public $permission = ''; + /** + * An optional weight for the help section. + * + * The sections will be ordered by this weight on the help page. + * + * @var int + */ + public $weight = 0; + } diff --git a/core/modules/help/src/Controller/HelpController.php b/core/modules/help/src/Controller/HelpController.php index 0ccf354e7..92d12d6ce 100644 --- a/core/modules/help/src/Controller/HelpController.php +++ b/core/modules/help/src/Controller/HelpController.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\help\HelpSectionManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -28,6 +29,13 @@ class HelpController extends ControllerBase { */ protected $helpManager; + /** + * The module extension list. + * + * @var \Drupal\Core\Extension\ModuleExtensionList + */ + protected $moduleExtensionList; + /** * Creates a new HelpController. * @@ -35,10 +43,18 @@ class HelpController extends ControllerBase { * The current route match. * @param \Drupal\help\HelpSectionManager $help_manager * The help section manager. + * @param \Drupal\Core\Extension\ModuleExtensionList|null $module_extension_list + * The module extension list. This is left optional for BC reasons, but the + * optional usage is deprecated and will become required in Drupal 9.0.0. */ - public function __construct(RouteMatchInterface $route_match, HelpSectionManager $help_manager) { + public function __construct(RouteMatchInterface $route_match, HelpSectionManager $help_manager, ModuleExtensionList $module_extension_list = NULL) { $this->routeMatch = $route_match; $this->helpManager = $help_manager; + if ($module_extension_list === NULL) { + @trigger_error('Calling HelpController::__construct() with the $module_extension_list argument is supported in drupal:8.8.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2709919.', E_USER_DEPRECATED); + $module_extension_list = \Drupal::service('extension.list.module'); + } + $this->moduleExtensionList = $module_extension_list; } /** @@ -47,7 +63,8 @@ public function __construct(RouteMatchInterface $route_match, HelpSectionManager public static function create(ContainerInterface $container) { return new static( $container->get('current_route_match'), - $container->get('plugin.manager.help_section') + $container->get('plugin.manager.help_section'), + $container->get('extension.list.module') ); } @@ -85,6 +102,7 @@ public function helpMain() { '#description' => $plugin->getDescription(), '#empty' => $this->t('There is currently nothing in this section.'), '#links' => [], + '#weight' => $plugin_definition['weight'], ]; $links = $plugin->listTopics(); @@ -118,7 +136,7 @@ public function helpPage($name) { $module_name = $this->moduleHandler()->getName($name); $build['#title'] = $module_name; - $info = system_get_info('module', $name); + $info = $this->moduleExtensionList->getExtensionInfo($name); if ($info['package'] === 'Core (Experimental)') { $this->messenger()->addWarning($this->t('This module is experimental. Experimental modules are provided for testing purposes only. Use at your own risk.', [':url' => 'https://www.drupal.org/core/experimental'])); } @@ -136,7 +154,7 @@ public function helpPage($name) { // Only print list of administration pages if the module in question has // any such pages associated with it. - $admin_tasks = system_get_module_admin_tasks($name, system_get_info('module', $name)); + $admin_tasks = system_get_module_admin_tasks($name, $info); if (!empty($admin_tasks)) { $links = []; foreach ($admin_tasks as $task) { diff --git a/core/modules/help/tests/modules/help_page_test/help_page_test.info.yml b/core/modules/help/tests/modules/help_page_test/help_page_test.info.yml index 793358579..7de2a0d06 100644 --- a/core/modules/help/tests/modules/help_page_test/help_page_test.info.yml +++ b/core/modules/help/tests/modules/help_page_test/help_page_test.info.yml @@ -2,12 +2,6 @@ name: 'Help Page Test' type: module description: 'Module to test the help page.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x hidden: true - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/help/tests/modules/help_test/help_test.info.yml b/core/modules/help/tests/modules/help_test/help_test.info.yml index 8e4e6fdf3..668ae38ba 100644 --- a/core/modules/help/tests/modules/help_test/help_test.info.yml +++ b/core/modules/help/tests/modules/help_test/help_test.info.yml @@ -1,12 +1,6 @@ name: help_test type: module -# core: 8.x +core: 8.x package: Testing dependencies: - drupal:help - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.info.yml b/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.info.yml index 18b20d369..cc15ce4b3 100644 --- a/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.info.yml +++ b/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.info.yml @@ -2,12 +2,6 @@ name: 'More Help Page Test' type: module description: 'Module to test the help page.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x hidden: true - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.module b/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.module index 4bc939751..bbd68a9b1 100644 --- a/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.module +++ b/core/modules/help/tests/modules/more_help_page_test/more_help_page_test.module @@ -18,3 +18,10 @@ function more_help_page_test_help($route_name, RouteMatchInterface $route_match) return ['#markup' => 'Help text from more_help_page_test_help module.']; } } + +/** + * Implements hook_help_section_info_alter(). + */ +function more_help_page_test_help_section_info_alter(array &$info) { + $info['hook_help']['weight'] = 500; +} diff --git a/core/modules/help/tests/src/Functional/ExperimentalHelpTest.php b/core/modules/help/tests/src/Functional/ExperimentalHelpTest.php index 0eabbda56..ab8d2aca2 100644 --- a/core/modules/help/tests/src/Functional/ExperimentalHelpTest.php +++ b/core/modules/help/tests/src/Functional/ExperimentalHelpTest.php @@ -21,6 +21,11 @@ class ExperimentalHelpTest extends BrowserTestBase { */ public static $modules = ['help', 'experimental_module_test', 'help_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The admin user. * diff --git a/core/modules/help/tests/src/Functional/HelpBlockTest.php b/core/modules/help/tests/src/Functional/HelpBlockTest.php index 81c4fc153..f4f8db5b1 100644 --- a/core/modules/help/tests/src/Functional/HelpBlockTest.php +++ b/core/modules/help/tests/src/Functional/HelpBlockTest.php @@ -16,6 +16,11 @@ class HelpBlockTest extends BrowserTestBase { */ public static $modules = ['help', 'help_page_test', 'block', 'more_help_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The help block instance. * diff --git a/core/modules/help/tests/src/Functional/HelpPageOrderTest.php b/core/modules/help/tests/src/Functional/HelpPageOrderTest.php new file mode 100644 index 000000000..0015ff26f --- /dev/null +++ b/core/modules/help/tests/src/Functional/HelpPageOrderTest.php @@ -0,0 +1,64 @@ +drupalCreateUser([ + 'access administration pages', + 'view the administration theme', + 'administer permissions', + 'access tour', + ]); + $this->drupalLogin($account); + } + + /** + * Tests the order of the help page. + */ + public function testHelp() { + $pos = 0; + $this->drupalGet('admin/help'); + $page_text = $this->getTextContent(); + foreach ($this->stringOrder as $item) { + $new_pos = strpos($page_text, $item, $pos); + $this->assertTrue($new_pos > $pos, 'Order of ' . $item . ' is correct on help page'); + $pos = $new_pos; + } + } + +} diff --git a/core/modules/help/tests/src/Functional/HelpPageReverseOrderTest.php b/core/modules/help/tests/src/Functional/HelpPageReverseOrderTest.php new file mode 100644 index 000000000..d31471da9 --- /dev/null +++ b/core/modules/help/tests/src/Functional/HelpPageReverseOrderTest.php @@ -0,0 +1,34 @@ +getModuleList() as $module => $name) { - $this->assertLink($name, 0, format_string('Link properly added to @name (admin/help/@module)', ['@module' => $module, '@name' => $name])); + $this->assertLink($name, 0, new FormattableMarkup('Link properly added to @name (admin/help/@module)', ['@module' => $module, '@name' => $name])); } // Ensure that module which does not provide an module overview page is @@ -121,9 +127,10 @@ protected function verifyHelp($response = 200) { $this->drupalGet('admin/help/' . $module); $this->assertResponse($response); if ($response == 200) { - $this->assertTitle($name . ' | Drupal', format_string('%module title was displayed', ['%module' => $module])); + $this->assertTitle($name . ' | Drupal', new FormattableMarkup('%module title was displayed', ['%module' => $module])); $this->assertEquals($name, $this->cssSelect('h1.page-title')[0]->getText(), "$module heading was displayed"); - $admin_tasks = system_get_module_admin_tasks($module, system_get_info('module', $module)); + $info = \Drupal::service('extension.list.module')->getExtensionInfo($module); + $admin_tasks = system_get_module_admin_tasks($module, $info); if (!empty($admin_tasks)) { $this->assertText(t('@module administration pages', ['@module' => $name])); } @@ -152,7 +159,7 @@ protected function verifyHelp($response = 200) { */ protected function getModuleList() { $modules = []; - $module_data = system_rebuild_module_data(); + $module_data = $this->container->get('extension.list.module')->getList(); foreach (\Drupal::moduleHandler()->getImplementations('help') as $module) { $modules[$module] = $module_data[$module]->info['name']; } diff --git a/core/modules/help/tests/src/Functional/NoHelpTest.php b/core/modules/help/tests/src/Functional/NoHelpTest.php index 5df945e35..0b68c0d22 100644 --- a/core/modules/help/tests/src/Functional/NoHelpTest.php +++ b/core/modules/help/tests/src/Functional/NoHelpTest.php @@ -20,6 +20,11 @@ class NoHelpTest extends BrowserTestBase { */ public static $modules = ['help', 'menu_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The user who will be created. */ diff --git a/core/modules/help/tests/src/Kernel/HelpEmptyPageTest.php b/core/modules/help/tests/src/Kernel/HelpEmptyPageTest.php index 070a95451..7010fc876 100644 --- a/core/modules/help/tests/src/Kernel/HelpEmptyPageTest.php +++ b/core/modules/help/tests/src/Kernel/HelpEmptyPageTest.php @@ -32,7 +32,7 @@ public function register(ContainerBuilder $container) { * Ensures that no URL generator is called on a page without hook_help(). */ public function testEmptyHookHelp() { - $all_modules = system_rebuild_module_data(); + $all_modules = \Drupal::service('extension.list.module')->getList(); $all_modules = array_filter($all_modules, function ($module) { // Filter contrib, hidden, already enabled modules and modules in the // Testing package. diff --git a/core/modules/help_topics/config/optional/block.block.seven_help_search.yml b/core/modules/help_topics/config/optional/block.block.seven_help_search.yml new file mode 100644 index 000000000..d44236ce9 --- /dev/null +++ b/core/modules/help_topics/config/optional/block.block.seven_help_search.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + enforced: + config: + - search.page.help_search + module: + - search + - system + theme: + - seven +id: seven_help_search +theme: seven +region: help +weight: -4 +provider: null +plugin: search_form_block +settings: + id: search_form_block + label: 'Search help' + provider: search + label_display: visible + page_id: help_search +visibility: + request_path: + id: request_path + pages: /admin/help + negate: false + context_mapping: { } diff --git a/core/modules/help_topics/config/optional/search.page.help_search.yml b/core/modules/help_topics/config/optional/search.page.help_search.yml new file mode 100644 index 000000000..5ae158e5e --- /dev/null +++ b/core/modules/help_topics/config/optional/search.page.help_search.yml @@ -0,0 +1,11 @@ +langcode: en +status: true +dependencies: + module: + - help_topics +id: help_search +label: Help +path: help +weight: 0 +plugin: help_search +configuration: { } diff --git a/core/modules/help_topics/config/schema/help_topics.schema.yml b/core/modules/help_topics/config/schema/help_topics.schema.yml new file mode 100644 index 000000000..77f1e80a0 --- /dev/null +++ b/core/modules/help_topics/config/schema/help_topics.schema.yml @@ -0,0 +1,3 @@ +search.plugin.help_search: + type: sequence + label: 'Help search' diff --git a/core/modules/help_topics/help_topics.api.php b/core/modules/help_topics/help_topics.api.php new file mode 100644 index 000000000..8e7fe6bad --- /dev/null +++ b/core/modules/help_topics/help_topics.api.php @@ -0,0 +1,31 @@ + 'Stores information about indexed help search items', + 'fields' => [ + 'sid' => [ + 'description' => 'Numeric index of this item in the search index', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + 'section_plugin_id' => [ + 'description' => 'The help section the item comes from', + 'type' => 'varchar_ascii', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ], + 'permission' => [ + 'description' => 'The permission needed to view this item', + 'type' => 'varchar_ascii', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ], + 'topic_id' => [ + 'description' => 'The topic ID of the item', + 'type' => 'varchar_ascii', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ], + ], + 'primary key' => ['sid'], + 'indexes' => [ + 'section_plugin_id' => ['section_plugin_id'], + 'topic_id' => ['topic_id'], + ], + ]; + + return $schema; +} diff --git a/core/modules/help_topics/help_topics.module b/core/modules/help_topics/help_topics.module new file mode 100644 index 000000000..f051873bd --- /dev/null +++ b/core/modules/help_topics/help_topics.module @@ -0,0 +1,84 @@ +toString(); + $module_handler = \Drupal::moduleHandler(); + $locale_help = ($module_handler->moduleExists('locale')) ? Url::fromRoute('help.page', ['name' => 'locale'])->toString() : '#'; + $search_help = ($module_handler->moduleExists('search')) ? Url::fromRoute('help.page', ['name' => 'search'])->toString() : '#'; + $output = ''; + $output .= '

' . t('About') . '

'; + $output .= '

' . t('The Help Topics module adds module- and theme-provided help topics to the module overviews from the core Help module. If the core Search module is enabled, these topics are also searchable. For more information, see the online documentation for the Help Topics module.', [':online' => 'https://www.drupal.org/modules/help_topics']) . '

'; + $output .= '

' . t('Uses') . '

'; + $output .= '
'; + $output .= '
' . t('Viewing help topics') . '
'; + $output .= '
' . t('The top-level help topics are listed on the main Help page. Links to other topics, including non-top-level help topics, can be found under the "Related" heading when viewing a topic page.', [':help_page' => $help_home]) . '
'; + $output .= '
' . t('Providing help topics') . '
'; + $output .= '
' . t("Modules and themes can provide help topics as Twig-file-based plugins in a project sub-directory called help_topics; plugin meta-data is provided in YAML front matter within each Twig file. Plugin-based help topics provided by modules and themes will automatically be updated when a module or theme is updated. Use the plugins in core/modules/help_topics/help_topics as a guide when writing and formatting a help topic plugin for your theme or module.") . '
'; + $output .= '
' . t('Translating help topics') . '
'; + $output .= '
' . t('The title and body text of help topics provided by contributed modules and themes are translatable using the Interface Translation module. Topics provided by custom modules and themes are also translatable if they have been viewed at least once in a non-English language, which triggers putting their translatable text into the translation database.', [':locale_help' => $locale_help]) . '
'; + $output .= '
' . t('Configuring help search') . '
'; + $output .= '
' . t('To search help, you will need to install the core Search module, configure a search page, and add a search block to the Help page or another administrative page. (A search page is provided automatically, and if you use the core Seven administrative theme, a help search block is shown on the main Help page.) Then users with search permissions, and permission to view help, will be able to search help. See the Search module help page for more information.', [':search_help' => $search_help]) . '
'; + $output .= '
'; + return ['#markup' => $output]; + + case 'help.help_topic': + $help_home = Url::fromRoute('help.main')->toString(); + return '

' . t('See the Help page for more topics.', [ + ':help_page' => $help_home, + ]) . '

'; + } +} + +/** + * Implements hook_theme(). + */ +function help_topics_theme() { + return [ + 'help_topic' => [ + 'variables' => [ + 'body' => [], + 'related' => [], + ], + ], + ]; +} + +/** + * Implements hook_modules_uninstalled(). + */ +function help_topics_modules_uninstalled(array $modules) { + // Early return if search is not installed or if we're uninstalling this + // module. + if (!\Drupal::hasService('plugin.manager.search') || + in_array('help_topics', $modules)) { + return; + } + $search_plugin_manager = \Drupal::service('plugin.manager.search'); + if ($search_plugin_manager->hasDefinition('help_search')) { + // Ensure that topics for extensions that have been uninstalled are removed. + $help_search = $search_plugin_manager->createInstance('help_search'); + $help_search->updateTopicList(); + } +} + +/** + * Implements hook_themes_uninstalled(). + */ +function help_topics_themes_uninstalled(array $themes) { + // Use the same code as module uninstall to ensure that theme help is removed + // when a theme is uninstalled. + help_topics_modules_uninstalled([]); +} diff --git a/core/modules/help_topics/help_topics.routing.yml b/core/modules/help_topics/help_topics.routing.yml new file mode 100644 index 000000000..4bf760ef7 --- /dev/null +++ b/core/modules/help_topics/help_topics.routing.yml @@ -0,0 +1,6 @@ +help.help_topic: + path: '/admin/help/topic/{id}' + defaults: + _controller: '\Drupal\help_topics\Controller\HelpTopicPluginController::viewHelpTopic' + requirements: + _permission: 'access administration pages' diff --git a/core/modules/help_topics/help_topics.services.yml b/core/modules/help_topics/help_topics.services.yml new file mode 100644 index 000000000..ed6a59df0 --- /dev/null +++ b/core/modules/help_topics/help_topics.services.yml @@ -0,0 +1,25 @@ +services: + help.breadcrumb: + class: Drupal\help_topics\HelpBreadcrumbBuilder + arguments: ['@string_translation'] + tags: + - { name: breadcrumb_builder, priority: 900 } + public: false + plugin.manager.help_topic: + class: Drupal\help_topics\HelpTopicPluginManager + arguments: ['@module_handler', '@theme_handler', '@cache.discovery', '@app.root'] + help.twig.loader: + class: Drupal\help_topics\HelpTopicTwigLoader + arguments: ['@app.root', '@module_handler', '@theme_handler'] + # Lowest core priority because loading help topics is not the usual case. + tags: + - { name: twig.loader, priority: -200 } + public: false + plugin.manager.help_section_topics: + class: Drupal\help_topics\HelpSectionManager + decorates: plugin.manager.help_section + parent: plugin.manager.help_section + calls: + - [setSearchManager, ['@?plugin.manager.search']] + tags: + - { name: plugin_manager_cache_clear } diff --git a/core/modules/help_topics/help_topics/block.configure.html.twig b/core/modules/help_topics/help_topics/block.configure.html.twig new file mode 100644 index 000000000..6d5e832e0 --- /dev/null +++ b/core/modules/help_topics/help_topics/block.configure.html.twig @@ -0,0 +1,25 @@ +--- +label: 'Configuring a previously-placed block' +related: + - block.overview +--- +{% set layout_url = render_var(url('block.admin_display')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Configure the settings of a block that was previously placed in a region of a theme.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}In the Manage administrative menu, navigate to Structure > Block layout.{% endtrans %}
  2. +
  3. {% trans %}Click the name of the theme that contains the block.{% endtrans %}
  4. +
  5. {% trans %}Optionally, click Demonstrate block regions to see the regions of the theme.{% endtrans %}
  6. +
  7. {% trans %}If you only want to change the region where a block is located, or the ordering of blocks within a region, drag blocks to their desired positions and click Save blocks.{% endtrans %}
  8. +
  9. {% trans %}If you want to change additional settings, find the region where the block you want to update is currently located, and click Configure in the line of the block description.{% endtrans %}
  10. +
  11. {% trans %}Edit the block's settings. The available settings vary depending on the module that provides the block, but for all blocks you can change:{% endtrans %} +
      +
    • {% trans %}Block title: The heading for the block on your site -- for some blocks, you will need to check the Override title checkbox in order to enter a title{% endtrans %}
    • +
    • {% trans %}Display title: Check the box if you want the title displayed{% endtrans %}
    • +
    • {% trans %}Visibility: Add conditions for when the block should be displayed{% endtrans %}
    • +
    • {% trans %}Region: Change the theme region the block is displayed in{% endtrans %}
    • +
    +
  12. +
  13. {% trans %}Click Save block.{% endtrans %}
  14. +
diff --git a/core/modules/help_topics/help_topics/block.overview.html.twig b/core/modules/help_topics/help_topics/block.overview.html.twig new file mode 100644 index 000000000..055ee6c37 --- /dev/null +++ b/core/modules/help_topics/help_topics/block.overview.html.twig @@ -0,0 +1,18 @@ +--- +label: 'Managing blocks' +top_level: true +--- +

{% trans %}What are blocks?{% endtrans %}

+

{% trans %}Blocks are boxes of content rendered into an area, or region, of a web page of your site. Blocks are placed and configured specifically for each theme.{% endtrans %}

+

{% trans %}What are custom blocks?{% endtrans %}

+

{% trans %}Custom blocks are blocks whose content you can edit. You can define one or more custom block types, and attach fields to each custom block type. Custom blocks can be placed just like blocks provided by other modules.{% endtrans %}

+

{% trans %}What is the block description?{% endtrans %}

+

{% trans %}The block description is an identification name for a block, which is shown in the administrative interface. It is not displayed on the site.{% endtrans %}

+

{% trans %}What is the block title?{% endtrans %}

+

{% trans %}The block title is the heading that is optionally shown to site visitors when the block is placed in a region.{% endtrans %}

+

{% trans %}Managing blocks overview{% endtrans %}

+

{% trans %}The Block module allows you to place blocks in regions of your installed themes, and configure block settings. The Custom Block module allows you to custom block types and custom blocks. See the related topics listed below for specific tasks.{% endtrans %}

+

{% trans %}Additional resources{% endtrans %}

+ diff --git a/core/modules/help_topics/help_topics/block.place.html.twig b/core/modules/help_topics/help_topics/block.place.html.twig new file mode 100644 index 000000000..6349eb299 --- /dev/null +++ b/core/modules/help_topics/help_topics/block.place.html.twig @@ -0,0 +1,18 @@ +--- +label: 'Placing a block' +related: + - block.overview + - block.configure +--- +{% set layout_url = render_var(url('block.admin_display')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Place a block into a theme's region. {% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}In the Manage administrative menu, navigate to Structure > Block layout.{% endtrans %}
  2. +
  3. {% trans %}Click the name of the theme that you want to place the block in.{% endtrans %}
  4. +
  5. {% trans %}Optionally, click Demonstrate block regions to see the regions of the theme.{% endtrans %}
  6. +
  7. {% trans %}Find the region where you want the block, and click Place block in that region. A modal dialog will pop up.{% endtrans %}
  8. +
  9. {% trans %}Find the block you want to place and click Place block. A Configure block modal dialog will pop up.{% endtrans %}
  10. +
  11. {% trans %}Configure the block and click Save block (see related topic for configuration details).{% endtrans %}
  12. +
diff --git a/core/modules/help_topics/help_topics/block_content.add.html.twig b/core/modules/help_topics/help_topics/block_content.add.html.twig new file mode 100644 index 000000000..19f3133c5 --- /dev/null +++ b/core/modules/help_topics/help_topics/block_content.add.html.twig @@ -0,0 +1,18 @@ +--- +label: 'Creating a custom block' +related: + - block.overview + - block.configure + - block.place + - block_content.type +--- +{% set content_url = render_var(url('entity.block_content.collection')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Create a custom block, which can later be placed on the site.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}In the Manage administrative menu, navigate to Structure > Block layout > Custom block library.{% endtrans %}
  2. +
  3. {% trans %}Click Add custom block. If you have more than one custom block type defined on your site, click the name of the type you want to create.{% endtrans %}
  4. +
  5. {% trans %}Enter a description of your block (to be shown to administrators) and the body text for your block.{% endtrans %}
  6. +
  7. {% trans %}Click Save.{% endtrans %}
  8. +
diff --git a/core/modules/help_topics/help_topics/block_content.type.html.twig b/core/modules/help_topics/help_topics/block_content.type.html.twig new file mode 100644 index 000000000..7ef57a1d6 --- /dev/null +++ b/core/modules/help_topics/help_topics/block_content.type.html.twig @@ -0,0 +1,20 @@ +--- +label: 'Defining a custom block type' +related: + - block.overview + - block.configure + - block.place + - block_content.add +--- +{% set types_url = render_var(url('entity.block_content_type.collection')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Define a custom block type and its fields.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}In the Manage administrative menu, navigate to Structure > Block layout > Custom block library > Block types.{% endtrans %}
  2. +
  3. {% trans %}Click Add custom block type.{% endtrans %}
  4. +
  5. {% trans %}Enter a label for this block type (shown in the administrative interface). Optionally, edit the automatically-generated machine name or the description.{% endtrans %}
  6. +
  7. {% trans %}Click Save. You will be returned to the Block types page.{% endtrans %}
  8. +
  9. {% trans %}Click Manage fields in the row of your new block type, and add the desired fields to your block type.{% endtrans %}
  10. +
  11. {% trans %}Optionally, click Manage form display or Manage display to change the editing form or field display for your block type.{% endtrans %}
  12. +
diff --git a/core/modules/help_topics/help_topics/contextual.overview.html.twig b/core/modules/help_topics/help_topics/contextual.overview.html.twig new file mode 100644 index 000000000..fa7604899 --- /dev/null +++ b/core/modules/help_topics/help_topics/contextual.overview.html.twig @@ -0,0 +1,20 @@ +--- +label: 'Using contextual links' +related: + - core.ui_components + - block.overview +--- +

{% trans %}Goal{% endtrans %}

+

{% trans %}Use contextual links to access administrative tasks without navigating the administrative menu.{% + endtrans %}

+

{% trans %}What are contextual links?{% endtrans %}

+

{% trans %}Contextual links give users with the Use contextual links permission quick access to administrative tasks related to areas of non-administrative pages. For example, if a page on your site displays a block, the block would have a contextual link that would allow users with permission to configure the block. If the block contains a menu or a view, it would also have a contextual link for editing the menu links or the view. Clicking a contextual link takes you to the related administrative page directly, without needing to navigate through the administrative menu system.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}Make sure that the core Contextual Links module is installed, and that you have a role with the Use contextual links permission. Optionally, make sure that a toolbar module is installed (either the core Toolbar module or a contributed module replacement).{% endtrans %}
  2. +
  3. {% trans %}Visit a non-administrative page on your site, such as the home page.{% endtrans %}
  4. +
  5. {% trans %}Locate a block or another area on the page that you want to edit or configure.{% endtrans %}
  6. +
  7. {% trans %}Make the contextual links button visible by hovering your mouse over that area in the page. In most themes, this button looks like a pencil and is placed in the upper right corner of the page area (upper left for right-to-left languages), and hovering will also temporarily outline the affected area. Alternatively, click the contextual links toggle button on the right end of the toolbar (left end for right-to-left languages), which will make all contextual link buttons on the page visible until it is clicked again.{% endtrans %}
  8. +
  9. {% trans %}While the contextual links button for the area of interest is visible, click the button to display the list of links for that area. Click a link in the list to visit the corresponding administrative page.{% endtrans %}
  10. +
  11. {% trans %}Complete your administrative task and save your settings, or cancel the action. You should be returned to the page you started from.{% endtrans %}
  12. +
diff --git a/core/modules/help_topics/help_topics/core.security.html.twig b/core/modules/help_topics/help_topics/core.security.html.twig new file mode 100644 index 000000000..25ef43cf2 --- /dev/null +++ b/core/modules/help_topics/help_topics/core.security.html.twig @@ -0,0 +1,12 @@ +--- +label: 'Making your site secure' +top_level: true +--- +

{% trans %}What are security updates?{% endtrans %}

+

{% trans %}Any software occasionally has bugs, and sometimes these bugs have security implications. When security bugs are fixed in the core software, modules, or themes that your site uses, they are released in a security update. You will need to apply security updates in order to keep your site secure.{% endtrans %}

+

{% trans %}Security tasks{% endtrans %}

+

{% trans %}Keeping track of updates, updating the core software, and updating contributed modules and/or themes are all part of keeping your site secure. See the related topics listed below for specific tasks.{% endtrans %}

+

{% trans %}Additional resources{% endtrans %}

+ diff --git a/core/modules/help_topics/help_topics/core.ui_accessibility.html.twig b/core/modules/help_topics/help_topics/core.ui_accessibility.html.twig new file mode 100644 index 000000000..083454fd2 --- /dev/null +++ b/core/modules/help_topics/help_topics/core.ui_accessibility.html.twig @@ -0,0 +1,11 @@ +--- +label: 'Accessibility of the administrative interface' +related: + - core.ui_components +--- +

{% trans %}Overview of accessibility{% endtrans %}

+

{% trans %}The core administrative interface has built-in compliance with many accessibility standards, so that most pages are accessible to most users in their default state. However, certain pages become more accessible to some users through the use of a non-default interface. These replacement interfaces include:{% endtrans %}

+
+
{% trans %}Disabling drag-and-drop functionality{% endtrans %}
+
{% trans %}The default drag-and-drop user interface for ordering tables in the administrative interface presents a challenge for some users, including keyboard-only users and users of screen readers and other assistive technology. The drag-and-drop interface can be disabled in a table by clicking a link labeled Show row weights above the table. The replacement interface allows users to order the table by choosing numerical weights (with increasing numbers) instead of dragging table rows.{% endtrans %}
+
diff --git a/core/modules/help_topics/help_topics/core.ui_components.html.twig b/core/modules/help_topics/help_topics/core.ui_components.html.twig new file mode 100644 index 000000000..f8aab0dd4 --- /dev/null +++ b/core/modules/help_topics/help_topics/core.ui_components.html.twig @@ -0,0 +1,18 @@ +--- +label: 'Using the administrative interface' +top_level: true +related: + - block.overview +--- +

{% trans %}Administrative interface overview{% endtrans %}

+

{% trans %}The administrative interface has several components:{% endtrans %}

+
    +
  • {% trans %}Accessibility features, to enable all users to perform administrative tasks.{% endtrans %}
  • +
  • {% trans %}A menu system, which you can navigate to find pages for administrative tasks. The core Toolbar module displays this menu on the top or left side of the page (right side in right-to-left languages). There are also contributed module replacements for the core Toolbar module, with additional features, such as the Admin Toolbar module.{% endtrans %}
  • +
  • {% trans %}The core Shortcuts module enhances the toolbar with a configurable list of links to commonly-used tasks.{% endtrans %}
  • +
  • {% trans %}If you install the core Contextual Links module, non-administrative pages will contain links leading to related administrative tasks.{% endtrans %}
  • +
  • {% trans %}The core Help module displays help topics, and provides a Help block that can be placed on administrative pages to provide an overview of their functionality.{% endtrans %}
  • +
  • {% trans %}The core Tour module allows modules to provide interactive tours of administrative pages for more detailed help.{% endtrans %}
  • +
+ +

{% trans %}See the related topics listed below for specific tasks.{% endtrans %}

diff --git a/core/modules/help_topics/help_topics/help.help_topic_search.html.twig b/core/modules/help_topics/help_topics/help.help_topic_search.html.twig new file mode 100644 index 000000000..48461da97 --- /dev/null +++ b/core/modules/help_topics/help_topics/help.help_topic_search.html.twig @@ -0,0 +1,21 @@ +--- +label: 'Configuring help search' +top_level: true +related: + - block.place +--- +{% set extend_url = render_var(url('system.modules_list')) %} +{% set help_url = render_var(url('help.main')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Set up your site so that users can search for help.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}In the Manage administrative menu, navigate to Extend. Verify that the Search, Help, Help Topics, and Block modules are installed (or install them if they are not already installed).{% endtrans %}
  2. +
  3. {% trans %}In the Manage administrative menu, navigate to Configuration > Search and metadata > Search pages.{% endtrans %}
  4. +
  5. {% trans %}Verify that a Help search page is listed in the Search pages section. If not, add a new page of type Help.{% endtrans %}
  6. +
  7. {% trans %}Check the indexing status of the Help search page. If it is not fully indexed, run Cron until indexing is complete.{% endtrans %}
  8. +
  9. {% trans %}In the future, you can click Rebuild search index on this page, or clear the site cache, in order to force help topic text to be reindexed for searching. This should be done whenever a module, theme, language, or string translation is updated.{% endtrans %}
  10. +
  11. {% trans %}In the Manage administrative menu, navigate to Structure > Block layout.{% endtrans %}
  12. +
  13. {% trans %}Click the link for your administrative theme (such as the core Seven theme), near the top of the page, and verify that there is already a search block for help located in the Help region. If not, follow the steps in the related topic to place the Search form block in the Help region. When configuring the block, choose Help as the search page, and in the Pages tab under Visibility, enter /admin/help to make the search form only visible on the main Help page.{% endtrans %}
  14. +
  15. {% trans %}In the Manage administrative menu, navigate to Help. Verify that the search block is visible, and try a search.{% endtrans %}
  16. +
diff --git a/core/modules/help_topics/help_topics/shortcut.overview.html.twig b/core/modules/help_topics/help_topics/shortcut.overview.html.twig new file mode 100644 index 000000000..683bdee62 --- /dev/null +++ b/core/modules/help_topics/help_topics/shortcut.overview.html.twig @@ -0,0 +1,19 @@ +--- +label: 'Creating and using shortcut administrative links' +related: + - core.ui_components +--- +

{% trans %}Goal{% endtrans %}

+

{% trans %}Create, view, and use a set of shortcuts to access administrative pages.{% endtrans %}

+

{% trans %}What are shortcuts?{% endtrans %}

+

{% trans %}Shortcuts are quick links to administrative pages; they are managed by the core Shortcut module. A site can have one or more shortcut sets, which can be shared by one or more users (by default, there is only one set shared by all users); each set contains a limited number of shortcuts. Users need Use shortcuts permission to view shortcuts; Edit current shortcut set permission to add, delete, or edit the shortcuts in the set assigned to them; and Select any shortcut set permission to select a different shortcut set when editing their user profile. There is also an Administer shortcuts permission, which allows an administrator to do any of these actions, as well as select shortcut sets for other users.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}Make sure that the core Shortcut module is enabled, and that you have a role with Edit current shortcut set or Administer shortcuts permission. Also, make sure that a toolbar module is installed (either the core Toolbar module or a contributed module replacement).{% endtrans %}
  2. +
  3. {% trans %}Navigate to an administrative page that you want in your shortcut list.{% endtrans %}
  4. +
  5. {% trans %}Click the shortcut link to add the page to your shortcut list -- in the core Seven administrative theme, the link looks like a star, and is displayed next to the page title. However, if the page is already in your shortcut set, clicking the shortcut link will remove it from your shortcut set.{% endtrans %}
  6. +
  7. {% trans %}Repeat until all the desired links have been added to your shortcut set.{% endtrans %}
  8. +
  9. {% trans %}Click Shortcuts in the toolbar to display your shortcuts, and verify that the list is complete.{% endtrans %}
  10. +
  11. {% trans %}Optionally, click Edit shortcuts at the right end of the shortcut list (left end in right-to-left languages), to remove links or change their order.{% endtrans %}
  12. +
  13. {% trans %}Click any link in the shortcut bar to go directly to the administrative page.{% endtrans %}
  14. +
diff --git a/core/modules/help_topics/help_topics/system.config_basic.html.twig b/core/modules/help_topics/help_topics/system.config_basic.html.twig new file mode 100644 index 000000000..c1253a2a6 --- /dev/null +++ b/core/modules/help_topics/help_topics/system.config_basic.html.twig @@ -0,0 +1,27 @@ +--- +label: 'Changing basic site settings' +top_level: true +related: + - user.security_account_settings +--- +{% set regional_url = render_var(url('system.regional_settings')) %} +{% set information_url = render_var(url('system.site_information_settings')) %} +{% set datetime_url = render_var(url('entity.date_format.collection')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Configure the basic settings of your site, including the site name, slogan, main email address, default time zone, default country, and the date formats to use.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}In the Manage administrative menu, navigate to Configuration > System > Basic site settings.{% endtrans %}
  2. +
  3. {% trans %}Enter the site name, slogan, and main email address for your site. {% endtrans %}
  4. +
  5. {% trans %}Click Save configuration. You should see a message indicating that the settings were saved.{% endtrans %}
  6. +
  7. {% trans %}In the Manage administrative menu, navigate to Configuration > Regional and language > Regional settings.{% endtrans %}
  8. +
  9. {% trans %}Select the default country and default time zone for your site.{% endtrans %}
  10. +
  11. {% trans %}Click Save configuration. You should see a message indicating that the settings were saved.{% endtrans %}
  12. +
  13. {% trans %}In the Manage administrative menu, navigate to Configuration > Regional and language > Date and time formats.{% endtrans %}
  14. +
  15. {% trans %}Look at the Patterns for the Default long, medium, and short date formats. If any of them does not match the date format you want to use on your site, click Edit in that row to edit the format.{% endtrans %}
  16. +
  17. {% trans %}Adjust the Format string until the Displayed format matches what you want. (Date format strings are composed of PHP date format codes.){% endtrans %}
  18. +
  19. {% trans %}Click Save format. You should see a message indicating that the format was saved.{% endtrans %}
  20. +
  21. {% trans %}Repeat the previous three steps for any other date formats that need to be changed.{% endtrans %}
  22. +
+

{% trans %}Additional resources{% endtrans %}

+

{% trans %}PHP date format codes reference{% endtrans %}

diff --git a/core/modules/help_topics/help_topics/system.config_error.html.twig b/core/modules/help_topics/help_topics/system.config_error.html.twig new file mode 100644 index 000000000..c4bed1488 --- /dev/null +++ b/core/modules/help_topics/help_topics/system.config_error.html.twig @@ -0,0 +1,24 @@ +--- +label: 'Configuring error responses, including 403/404 pages' +related: + - system.config_basic +--- +{% set log_settings_url = render_var(url('system.logging_settings')) %} +{% set information_url = render_var(url('system.site_information_settings')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Set up your site to respond appropriately to site errors, including 403 and 404 page responses.{% endtrans %}

+

{% trans %}What are 403 and 404 responses?{% endtrans %}

+

{% trans %}When a user visits a web page, the web server sends a response code in addition to the page content. A normal, non-error response has code 200. If the page does not exist on the site, the response code is 404. If the page exists, but the user is not authorized to visit the page, the response code is 403. The core software provides default responses for both 403 and 404 codes, but if you prefer, you can create your own pages for each.{% endtrans %}

+

{% trans %}What other errors can occur?{% endtrans %}

+

{% trans %}Under some situations, your site can generate error messages. These can be due to user errors (such as entering invalid values in a form, or incorrect configuration), PHP runtime errors, or software bugs. Some errors may result in a white screen of death (a totally blank web page response); less drastic errors will generate error messages. You can configure what happens when an error message is generated.{% endtrans %}

+

{% trans %}Steps {% endtrans %}

+
    +
  1. {% trans %}If desired, create pages to use for 403 and 404 responses. Note the URLs for these pages.{% endtrans %}
  2. +
  3. {% trans %}In the Manage administrative menu, navigate to Configuration > System > Basic site settings.{% endtrans %}
  4. +
  5. {% trans %}In the Error pages section, enter the URL for your 403/403 pages, starting after the site home page URL. For example, if your site URL is https://example.com and your 404 page is https://example.com/not-found, you would enter /not-found.{% endtrans %}
  6. +
  7. {% trans %}Click Save configuration. You should see a message indicating that the settings were saved.{% endtrans %}
  8. +
  9. {% trans %}In the Manage administrative menu, navigate to Configuration > Development > Logging and errors.{% endtrans %}
  10. +
  11. {% trans %}For a production site, select None under Error messages to display. For a site that is in development, select one of the other options, so that you are more aware of the errors the site is generating.{% endtrans %}
  12. +
  13. {% trans %}Click Save configuration. You should see a message indicating that the settings were saved.{% endtrans %}
  14. +
  15. {% trans %}If you have the Database Logging module installed, in the Manage administrative menu, navigate to Reports > Recent log messages to see a report of the error and informational messages your site has generated.{% endtrans %}
  16. +
diff --git a/core/modules/help_topics/help_topics/tour.overview.html.twig b/core/modules/help_topics/help_topics/tour.overview.html.twig new file mode 100644 index 000000000..af2a19760 --- /dev/null +++ b/core/modules/help_topics/help_topics/tour.overview.html.twig @@ -0,0 +1,16 @@ +--- +label: 'Taking tours of administrative pages' +related: + - core.ui_components +--- +

{% trans %}Goal{% endtrans %}

+

{% trans %}Take a tour of an administrative page.{% endtrans %}

+

{% trans %}What are tours?{% endtrans %}

+

{% trans %}The core Tour module provides users with tours, which are guided tours of the administrative interface. Each tour starts on a particular administrative page, and consists of one or more tips that highlight elements of the page, guide you through a workflow, or explain key concepts. Users need Access tour permission to view tours, and JavaScript must be enabled in their browsers.{% endtrans %}

+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}Make sure that the core Tour module is installed, and that you have a role with the Access tour permission. Also, make sure that a toolbar module is installed (either the core Toolbar module or a contributed module replacement).{% endtrans %}
  2. +
  3. {% trans %}Visit an administrative page that has a tour, such as the edit view page provided by the core Views UI module.{% endtrans %}
  4. +
  5. {% trans %}Click the Tour button at the right end of the toolbar (left end for right-to-left languages). The first tip of the tour should appear.{% endtrans %}
  6. +
  7. {% trans %}Click the Next button to advance to the next tip, and End tour at the end to close the tour.{% endtrans %}
  8. +
diff --git a/core/modules/help_topics/help_topics/user.security_account_settings.html.twig b/core/modules/help_topics/help_topics/user.security_account_settings.html.twig new file mode 100644 index 000000000..3898cc9d1 --- /dev/null +++ b/core/modules/help_topics/help_topics/user.security_account_settings.html.twig @@ -0,0 +1,34 @@ +--- +label: 'Configuring how user accounts are created and deleted' +related: + - core.security +--- +{% set account_settings_url = render_var(url('entity.user.admin_form')) %} +

{% trans %}Goal{% endtrans %}

+

{% trans %}Configure settings related to how user accounts are created and deleted.{% endtrans %}

+

{% trans %}What are the settings related to user account creation and deletion?{% endtrans %}

+
    +
  • {% trans %}You can make it possible for new users to register themselves for accounts, with or without email verification or administrative approval. Or, you can make it so only administrators with Administer users permission can register new users.{% endtrans %}
  • +
  • {% trans %}You can configure what happens to content that a user created, if their account is canceled (deleted).{% endtrans %}
  • +
  • {% trans %}You can edit the email messages that are sent to users when their accounts are pending, approved, created, blocked, or canceled, or when they request a password reset.{% endtrans %}
  • +
+

{% trans %}What are variables in email message text?{% endtrans %}

+

{% trans %}Variables are short text strings, enclosed in square brackets [], that you can insert into configured email message text. When an individual message is generated, data from your site is substituted for the variables. Some commonly-used variables are:{% endtrans %}

+
    +
  • {% trans %}[site:name]: The name of your web site.{% endtrans %}
  • +
  • {% trans %}[site:url]: The URL of your web site.{% endtrans %}
  • +
  • {% trans %}[site:login-url]: The URL where users can log in to your site.{% endtrans %}
  • +
  • {% trans %}[user:display-name]: The user's displayed name.{% endtrans %}
  • +
  • {% trans %}[user:account-name]: The users's account name.{% endtrans %}
  • +
  • {% trans %}[user:mail]: The user's email alias.{% endtrans %}
  • +
  • {% trans %}[user:one-time-login-url]: An expiring URL that a user can use to log in once, if they need to reset their password.{% endtrans %}
  • +
+

{% trans %}Steps{% endtrans %}

+
    +
  1. {% trans %}In the Manage administrative menu, navigate to Configuration > People > Account settings.{% endtrans %}
  2. +
  3. {% trans %}Select the method you want to use for creating user accounts, and check or uncheck the box that requires email verification, to match the settings you want for your site.{% endtrans %}
  4. +
  5. {% trans %}Select the desired option for what happens to content that a user created if their account is canceled.{% endtrans %}
  6. +
  7. {% trans %}Optionally, edit the text of email messages related to user accounts.{% endtrans %}
  8. +
  9. {% trans %}Verify that the other settings are correct.{% endtrans %}
  10. +
  11. {% trans %}Click Save configuration. You should see a message indicating that the settings were saved.{% endtrans %}
  12. +
diff --git a/core/modules/help_topics/src/Controller/HelpTopicPluginController.php b/core/modules/help_topics/src/Controller/HelpTopicPluginController.php new file mode 100644 index 000000000..256acd389 --- /dev/null +++ b/core/modules/help_topics/src/Controller/HelpTopicPluginController.php @@ -0,0 +1,117 @@ +helpTopicPluginManager = $help_topic_plugin_manager; + $this->renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.help_topic'), + $container->get('renderer') + ); + } + + /** + * Displays a help topic page. + * + * @param string $id + * The plugin ID. Maps to the {id} placeholder in the + * help.help_topic route. + * + * @return array + * A render array with the contents of a help topic page. + */ + public function viewHelpTopic($id) { + $build = []; + + if (!$this->helpTopicPluginManager->hasDefinition($id)) { + throw new NotFoundHttpException(); + } + /* @var \Drupal\help_topics\HelpTopicPluginInterface $help_topic */ + $help_topic = $this->helpTopicPluginManager->createInstance($id); + + $build['#body'] = $help_topic->getBody(); + + $this->renderer->addCacheableDependency($build, $help_topic); + + // Build the related topics section, starting with the list this topic + // says are related. + $links = []; + + $related = $help_topic->getRelated(); + foreach ($related as $other_id) { + if ($other_id !== $id) { + /** @var \Drupal\help_topics\HelpTopicPluginInterface $topic */ + $topic = $this->helpTopicPluginManager->createInstance($other_id); + $links[$other_id] = [ + 'title' => $topic->getLabel(), + 'url' => Url::fromRoute('help.help_topic', ['id' => $other_id]), + ]; + $this->renderer->addCacheableDependency($build, $topic); + } + } + + if (count($links)) { + uasort($links, [SortArray::class, 'sortByTitleElement']); + $build['#related'] = [ + '#theme' => 'links__related', + '#heading' => [ + 'text' => $this->t('Related topics'), + 'level' => 'h2', + ], + '#links' => $links, + ]; + } + + $build['#theme'] = 'help_topic'; + $build['#title'] = $help_topic->getLabel(); + return $build; + } + +} diff --git a/core/modules/help_topics/src/FrontMatter.php b/core/modules/help_topics/src/FrontMatter.php new file mode 100644 index 000000000..4d95f268e --- /dev/null +++ b/core/modules/help_topics/src/FrontMatter.php @@ -0,0 +1,173 @@ +serializer = $serializer; + $this->source = $source; + } + + /** + * Creates a new FrontMatter instance. + * + * @param string $source + * A string source. + * @param string $serializer + * A class that implements + * \Drupal\Component\Serialization\SerializationInterface. + * + * @return static + */ + public static function load($source, $serializer = '\Drupal\Component\Serialization\Yaml') { + return new static($source, $serializer); + } + + /** + * Parses the source. + * + * @return array + * An associative array containing: + * - code: The real source code. + * - data: The front matter data extracted and decoded. + * - line: The line number where the real source code starts. + * + * @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException + * Exception thrown when the Front Matter cannot be parsed. + */ + private function parse() { + if (!$this->parsed) { + $this->parsed = [ + 'code' => $this->source, + 'data' => [], + 'line' => 1, + ]; + + // Check for front matter data. + $len = strlen(static::FRONT_MATTER_SEPARATOR); + $matches = []; + if (substr($this->parsed['code'], 0, $len + 1) === static::FRONT_MATTER_SEPARATOR . "\n" || substr($this->parsed['code'], 0, $len + 2) === static::FRONT_MATTER_SEPARATOR . "\r\n") { + preg_match(static::FRONT_MATTER_REGEXP, $this->parsed['code'], $matches); + $matches = array_map('trim', $matches); + } + + // Immediately return if the code doesn't contain front matter data. + if (empty($matches)) { + return $this->parsed; + } + + // Set the extracted source code. + $this->parsed['code'] = $matches[2]; + + // Set the extracted front matter data. Do not catch any exceptions here + // as doing so would only obfuscate any errors found in the front matter + // data. Typecast to an array to ensure top level scalars are in an array. + if ($matches[1]) { + $this->parsed['data'] = (array) $this->serializer::decode($matches[1]); + } + + // Determine the real source line by counting newlines from the data and + // then adding 2 to account for the front matter separator (---) wrappers + // and then adding 1 more for the actual line number after the data. + $this->parsed['line'] = count(preg_split('/\r\n|\n/', $matches[1])) + 3; + } + return $this->parsed; + } + + /** + * Retrieves the extracted source code. + * + * @return string + * The extracted source code. + * + * @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException + * Exception thrown when the Front Matter cannot be parsed. + */ + public function getCode() { + return $this->parse()['code']; + } + + /** + * Retrieves the extracted front matter data. + * + * @return array + * The extracted front matter data. + * + * @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException + * Exception thrown when the Front Matter cannot be parsed. + */ + public function getData() { + return $this->parse()['data']; + } + + /** + * Retrieves the line where the source code starts, after any data. + * + * @return int + * The source code line. + * + * @throws \Drupal\Component\Serialization\Exception\InvalidDataTypeException + * Exception thrown when the Front Matter cannot be parsed. + */ + public function getLine() { + return $this->parse()['line']; + } + +} diff --git a/core/modules/help_topics/src/HelpBreadcrumbBuilder.php b/core/modules/help_topics/src/HelpBreadcrumbBuilder.php new file mode 100644 index 000000000..1387b4fa2 --- /dev/null +++ b/core/modules/help_topics/src/HelpBreadcrumbBuilder.php @@ -0,0 +1,54 @@ +stringTranslation = $string_translation; + } + + /** + * {@inheritdoc} + */ + public function applies(RouteMatchInterface $route_match) { + return $route_match->getRouteName() == 'help.help_topic'; + } + + /** + * {@inheritdoc} + */ + public function build(RouteMatchInterface $route_match) { + $breadcrumb = new Breadcrumb(); + $breadcrumb->addCacheContexts(['url.path.parent']); + $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Administration'), 'system.admin')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Help'), 'help.main')); + + return $breadcrumb; + } + +} diff --git a/core/modules/help_topics/src/HelpSectionManager.php b/core/modules/help_topics/src/HelpSectionManager.php new file mode 100644 index 000000000..92e9b83e4 --- /dev/null +++ b/core/modules/help_topics/src/HelpSectionManager.php @@ -0,0 +1,49 @@ +searchManager = $search_manager; + } + + /** + * {@inheritdoc} + */ + public function clearCachedDefinitions() { + parent::clearCachedDefinitions(); + if ($this->searchManager && $this->searchManager->hasDefinition('help_search') && $this->moduleHandler->moduleExists('help_topics')) { + // Rebuild the index on cache clear so that new help topics are indexed + // and any changes due to help topics edits or translation changes are + // picked up. + $help_search = $this->searchManager->createInstance('help_search'); + $help_search->markForReindex(); + } + } + +} diff --git a/core/modules/help_topics/src/HelpTopicDiscovery.php b/core/modules/help_topics/src/HelpTopicDiscovery.php new file mode 100644 index 000000000..dd7ae1629 --- /dev/null +++ b/core/modules/help_topics/src/HelpTopicDiscovery.php @@ -0,0 +1,185 @@ +directories = $directories; + } + + /** + * {@inheritdoc} + */ + public function getDefinitions() { + $plugins = $this->findAll(); + + // Flatten definitions into what's expected from plugins. + $definitions = []; + foreach ($plugins as $list) { + foreach ($list as $id => $definition) { + $definitions[$id] = $definition; + } + } + + return $definitions; + } + + /** + * Returns an array of discoverable items. + * + * @return array + * An array of discovered data keyed by provider. + * + * @throws \Drupal\Component\Discovery\DiscoveryException + * Exception thrown if there is a problem during discovery. + */ + public function findAll() { + $all = []; + + $files = $this->findFiles(); + + $file_cache = FileCacheFactory::get('help_topic_discovery:help_topics'); + + // Try to load from the file cache first. + foreach ($file_cache->getMultiple(array_keys($files)) as $file => $data) { + $all[$files[$file]][$data['id']] = $data; + unset($files[$file]); + } + + // If there are files left that were not returned from the cache, load and + // parse them now. This list was flipped above and is keyed by filename. + if ($files) { + foreach ($files as $file => $provider) { + $plugin_id = substr(basename($file), 0, -10); + // The plugin ID begins with provider. + list($file_name_provider,) = explode('.', $plugin_id, 2); + // Only the Help Topics module can provide help for other extensions. + // @todo https://www.drupal.org/project/drupal/issues/3072312 Remove + // help_topics special case once Help Topics is stable and core + // modules can provide their own help topics. + if ($provider !== 'help_topics' && $provider !== $file_name_provider) { + throw new DiscoveryException("$file file name should begin with '$provider'"); + } + $data = [ + // The plugin ID is derived from the filename. The extension + // '.html.twig' is removed. + 'id' => $plugin_id, + 'provider' => $file_name_provider, + 'class' => HelpTopicTwig::class, + static::FILE_KEY => $file, + ]; + + // Get the rest of the plugin definition from front matter contained in + // the help topic Twig file. + try { + $front_matter = FrontMatter::load(file_get_contents($file), Yaml::class)->getData(); + } + catch (InvalidDataTypeException $e) { + throw new DiscoveryException(sprintf('Malformed YAML in help topic "%s": %s.', $file, $e->getMessage())); + } + foreach ($front_matter as $key => $value) { + switch ($key) { + case 'related': + if (!is_array($value)) { + throw new DiscoveryException("$file contains invalid value for 'related' key, the value must be an array of strings"); + } + $data[$key] = $value; + break; + + case 'top_level': + if (!is_bool($value)) { + throw new DiscoveryException("$file contains invalid value for 'top_level' key, the value must be a Boolean"); + } + $data[$key] = $value; + break; + + case 'label': + $data[$key] = new TranslatableMarkup($value); + break; + + default: + throw new DiscoveryException("$file contains invalid key='$key'"); + } + } + if (!isset($data['label'])) { + throw new DiscoveryException("$file does not contain the required key with name='label'"); + } + + $all[$provider][$data['id']] = $data; + $file_cache->set($file, $data); + } + } + + return $all; + } + + /** + * Returns an array of providers keyed by file path. + * + * @return array + * An array of providers keyed by file path. + */ + protected function findFiles() { + $file_list = []; + foreach ($this->directories as $provider => $directories) { + $directories = (array) $directories; + foreach ($directories as $directory) { + if (is_dir($directory)) { + /** @var \SplFileInfo $fileInfo */ + $iterator = new RegexDirectoryIterator($directory, '/\.html\.twig$/i'); + foreach ($iterator as $fileInfo) { + $file_list[$fileInfo->getPathname()] = $provider; + } + } + } + } + return $file_list; + } + +} diff --git a/core/modules/help_topics/src/HelpTopicPluginBase.php b/core/modules/help_topics/src/HelpTopicPluginBase.php new file mode 100644 index 000000000..5a0e90505 --- /dev/null +++ b/core/modules/help_topics/src/HelpTopicPluginBase.php @@ -0,0 +1,64 @@ +pluginDefinition['provider']; + } + + /** + * {@inheritdoc} + */ + public function getLabel() { + return $this->pluginDefinition['label']; + } + + /** + * {@inheritdoc} + */ + public function isTopLevel() { + return $this->pluginDefinition['top_level']; + } + + /** + * {@inheritdoc} + */ + public function getRelated() { + return $this->pluginDefinition['related']; + } + + /** + * {@inheritdoc} + */ + public function toUrl(array $options = []) { + return Url::fromRoute('help.help_topic', ['id' => $this->getPluginId()], $options); + } + + /** + * {@inheritdoc} + */ + public function toLink($text = NULL, array $options = []) { + if (!$text) { + $text = $this->getLabel(); + } + return Link::createFromRoute($text, 'help.help_topic', ['id' => $this->getPluginId()], $options); + } + +} diff --git a/core/modules/help_topics/src/HelpTopicPluginInterface.php b/core/modules/help_topics/src/HelpTopicPluginInterface.php new file mode 100644 index 000000000..50eb42c7a --- /dev/null +++ b/core/modules/help_topics/src/HelpTopicPluginInterface.php @@ -0,0 +1,83 @@ + '', + // The title of the help topic plugin. + 'label' => '', + // Whether or not the topic should appear on the help topics list. + 'top_level' => '', + // List of related topic machine names. + 'related' => [], + // The class used to instantiate the plugin. + 'class' => '', + ]; + + /** + * The theme handler. + * + * @var \Drupal\Core\Extension\ThemeHandlerInterface + */ + protected $themeHandler; + + /** + * The app root. + * + * @var string + */ + protected $root; + + /** + * Constructs a new HelpTopicManager object. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler + * The theme handler. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param string $root + * The app root. + */ + public function __construct(ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache_backend, $root) { + // Note that the parent construct is not called because this not use + // annotated class discovery. + $this->moduleHandler = $module_handler; + $this->themeHandler = $theme_handler; + $this->alterInfo('help_topics_info'); + // Use the 'config:core.extension' cache tag so the plugin cache is + // invalidated on theme install and uninstall. + $this->setCacheBackend($cache_backend, 'help_topics', ['config:core.extension']); + $this->root = (string) $root; + } + + /** + * {@inheritdoc} + */ + protected function getDiscovery() { + if (!isset($this->discovery)) { + $module_directories = $this->moduleHandler->getModuleDirectories(); + $all_directories = array_merge( + ['core' => $this->root . '/core'], + $module_directories, + $this->themeHandler->getThemeDirectories() + ); + + // Search for Twig help topics in subdirectory help_topics, under + // modules/profiles, themes, and the core directory. + $all_directories = array_map(function ($dir) { + return [$dir . '/help_topics']; + }, $all_directories); + $discovery = new HelpTopicDiscovery($all_directories); + + // Also allow modules/profiles to extend help topic discovery to their + // own plugins and derivers, in mymodule.help_topics.yml files. + $discovery = new YamlDiscoveryDecorator($discovery, 'help_topics', $module_directories); + $discovery = new ContainerDerivativeDiscoveryDecorator($discovery); + $this->discovery = $discovery; + } + return $this->discovery; + } + + /** + * {@inheritdoc} + */ + protected function providerExists($provider) { + return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider); + } + + /** + * {@inheritdoc} + */ + protected function findDefinitions() { + $definitions = parent::findDefinitions(); + + // At this point the plugin list only contains valid plugins. Ensure all + // related plugins exist and the relationship is bi-directional. This + // ensures topics are listed on their related topics. + foreach ($definitions as $plugin_id => $plugin_definition) { + foreach ($plugin_definition['related'] as $key => $related_id) { + // If the related help topic does not exist it might be for a module + // that is not installed. Remove it. + // @todo Discuss this more as this could cause silent errors but it + // offers useful functionality to relate to help topic provided by + // extensions that are yet to be installed. + if (!isset($definitions[$related_id])) { + unset($definitions[$plugin_id]['related'][$key]); + continue; + } + // Make the related relationship bi-directional. + if (isset($definitions[$related_id]) && !in_array($plugin_id, $definitions[$related_id]['related'], TRUE)) { + $definitions[$related_id]['related'][] = $plugin_id; + } + } + } + return $definitions; + } + +} diff --git a/core/modules/help_topics/src/HelpTopicPluginManagerInterface.php b/core/modules/help_topics/src/HelpTopicPluginManagerInterface.php new file mode 100644 index 000000000..95e7ad665 --- /dev/null +++ b/core/modules/help_topics/src/HelpTopicPluginManagerInterface.php @@ -0,0 +1,16 @@ +twig = $twig; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('twig') + ); + } + + /** + * {@inheritdoc} + */ + public function getBody() { + return [ + '#markup' => $this->twig->load('@help_topics/' . $this->getPluginId() . '.html.twig')->render(), + ]; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return ['core.extension']; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + +} diff --git a/core/modules/help_topics/src/HelpTopicTwigLoader.php b/core/modules/help_topics/src/HelpTopicTwigLoader.php new file mode 100644 index 000000000..b785736f1 --- /dev/null +++ b/core/modules/help_topics/src/HelpTopicTwigLoader.php @@ -0,0 +1,96 @@ +addExtension($root_path . '/core'); + array_map([$this, 'addExtension'], $module_handler->getModuleDirectories()); + array_map([$this, 'addExtension'], $theme_handler->getThemeDirectories()); + } + + /** + * Adds an extensions help_topics directory to the Twig loader. + * + * @param $path + * The path to the extension. + */ + protected function addExtension($path) { + $path .= DIRECTORY_SEPARATOR . 'help_topics'; + if (is_dir($path)) { + $this->cache = $this->errorCache = []; + $this->paths[self::MAIN_NAMESPACE][] = rtrim($path, '/\\'); + } + } + + /** + * {@inheritdoc} + */ + public function getSourceContext($name) { + $path = $this->findTemplate($name); + + $contents = file_get_contents($path); + try { + // Note: always use \Drupal\Core\Serialization\Yaml here instead of the + // "serializer.yaml" service. This allows the core serializer to utilize + // core related functionality which isn't available as the standalone + // component based serializer. + $front_matter = FrontMatter::load($contents, Yaml::class); + + // Reconstruct the content if there is front matter data detected. Prepend + // the source with {% line \d+ %} to inform Twig that the source code + // actually starts on a different line past the front matter data. This is + // particularly useful when used in error reporting. + if ($front_matter->getData() && ($line = $front_matter->getLine())) { + $contents = "{% line $line %}" . $front_matter->getCode(); + } + } + catch (InvalidDataTypeException $e) { + throw new LoaderError(sprintf('Malformed YAML in help topic "%s": %s.', $path, $e->getMessage())); + } + + return new Source($contents, $name, $path); + } + +} diff --git a/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php new file mode 100644 index 000000000..c604d650a --- /dev/null +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php @@ -0,0 +1,283 @@ +pluginManager = $plugin_manager; + $this->renderer = $renderer; + $this->defaultLanguage = $default_language; + $this->languageManager = $language_manager; + $this->translationManager = $translation_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('plugin.manager.help_topic'), + $container->get('renderer'), + $container->get('language.default'), + $container->get('language_manager'), + $container->get('string_translation') + ); + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return $this->getCacheMetadata()->getCacheTags(); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return $this->getCacheMetadata()->getCacheContexts(); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return $this->getCacheMetadata()->getCacheMaxAge(); + } + + /** + * {@inheritdoc} + */ + public function listTopics() { + // Map the top level help topic plugins to a list of topic links. + return array_map(function (HelpTopicPluginInterface $topic) { + return $topic->toLink(); + }, $this->getPlugins()); + } + + /** + * Gets the top level help topic plugins. + * + * @return \Drupal\help_topics\HelpTopicPluginInterface[] + * The top level help topic plugins. + */ + protected function getPlugins() { + if (!isset($this->topLevelPlugins)) { + $definitions = $this->pluginManager->getDefinitions(); + + // Get all the top level topics and merge their list cache tags. + foreach ($definitions as $definition) { + if ($definition['top_level']) { + $this->topLevelPlugins[$definition['id']] = $this->pluginManager->createInstance($definition['id']); + } + } + + // Sort the top level topics by label and, if the labels match, then by + // plugin ID. + usort($this->topLevelPlugins, function (HelpTopicPluginInterface $a, HelpTopicPluginInterface $b) { + $a_label = (string) $a->getLabel(); + $b_label = (string) $b->getLabel(); + if ($a_label === $b_label) { + return $a->getPluginId() < $b->getPluginId() ? -1 : 1; + } + return strnatcasecmp($a_label, $b_label); + }); + } + return $this->topLevelPlugins; + } + + /** + * {@inheritdoc} + */ + public function listSearchableTopics() { + $definitions = $this->pluginManager->getDefinitions(); + return array_column($definitions, 'id'); + } + + /** + * {@inheritdoc} + */ + public function renderTopicForSearch($topic_id, LanguageInterface $language) { + $plugin = $this->pluginManager->createInstance($topic_id); + if (!$plugin) { + return []; + } + + // We are rendering this topic for search indexing or search results, + // possibly in a different language than the current language. The topic + // title and body come from translatable things in the Twig template, so we + // need to set the default language to the desired language, render them, + // then restore the default language so we do not affect other cron + // processes. Also, just in case there is an exception, wrap the whole + // thing in a try/finally block, and reset the language in the finally part. + $old_language = $this->defaultLanguage->get(); + try { + if ($old_language->getId() !== $language->getId()) { + $this->defaultLanguage->set($language); + $this->translationManager->setDefaultLangcode($language->getId()); + $this->languageManager->reset(); + } + $topic = []; + + // Render the title in this language. + $title_build = [ + 'title' => [ + '#type' => '#markup', + '#markup' => $plugin->getLabel(), + ], + ]; + $topic['title'] = $this->renderer->renderPlain($title_build); + $cacheable_metadata = CacheableMetadata::createFromRenderArray($title_build); + + // Render the body in this language. For this, we need to set up a render + // context, because the Twig plugins that provide the body assumes one + // is present. + $context = new RenderContext(); + $build = [ + 'body' => $this->renderer->executeInRenderContext($context, [$plugin, 'getBody']), + ]; + $topic['text'] = $this->renderer->renderPlain($build); + $cacheable_metadata->addCacheableDependency(CacheableMetadata::createFromRenderArray($build)); + $cacheable_metadata->addCacheableDependency($plugin); + if (!$context->isEmpty()) { + $cacheable_metadata->addCacheableDependency($context->pop()); + } + + // Add the other information. + $topic['url'] = $plugin->toUrl(); + $topic['cacheable_metadata'] = $cacheable_metadata; + } + finally { + // Restore the original language. + if ($old_language->getId() !== $language->getId()) { + $this->defaultLanguage->set($old_language); + $this->translationManager->setDefaultLangcode($old_language->getId()); + $this->languageManager->reset(); + } + } + + return $topic; + } + + /** + * Gets the merged CacheableMetadata for all the top level help topic plugins. + * + * @return \Drupal\Core\Cache\CacheableMetadata + * The merged CacheableMetadata for all the top level help topic plugins. + */ + protected function getCacheMetadata() { + if (!isset($this->cacheableMetadata)) { + $this->cacheableMetadata = new CacheableMetadata(); + foreach ($this->getPlugins() as $plugin) { + $this->cacheableMetadata->addCacheableDependency($plugin); + } + } + return $this->cacheableMetadata; + } + +} diff --git a/core/modules/help_topics/src/Plugin/Search/HelpSearch.php b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php new file mode 100644 index 000000000..458dc4b8a --- /dev/null +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php @@ -0,0 +1,507 @@ +get('database'), + $container->get('config.factory')->get('search.settings'), + $container->get('language_manager'), + $container->get('messenger'), + $container->get('current_user'), + $container->get('state'), + $container->get('plugin.manager.help_section'), + $container->get('search.index') + ); + } + + /** + * Constructs a \Drupal\help_search\Plugin\Search\HelpSearch object. + * + * @param array $configuration + * Configuration for the plugin. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Database\Connection $database + * The current database connection. + * @param \Drupal\Core\Config\Config $search_settings + * A config object for 'search.settings'. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + * @param \Drupal\Core\Session\AccountInterface $account + * The $account object to use for checking for access to view help. + * @param \Drupal\Core\State\StateInterface $state + * The state object. + * @param \Drupal\help\HelpSectionManager $help_section_manager + * The help section manager. + * @param \Drupal\search\SearchIndexInterface $search_index + * The search index. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, Config $search_settings, LanguageManagerInterface $language_manager, MessengerInterface $messenger, AccountInterface $account, StateInterface $state, HelpSectionManager $help_section_manager, SearchIndexInterface $search_index) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->database = $database; + $this->searchSettings = $search_settings; + $this->languageManager = $language_manager; + $this->messenger = $messenger; + $this->account = $account; + $this->state = $state; + $this->helpSectionManager = $help_section_manager; + $this->searchIndex = $search_index; + } + + /** + * {@inheritdoc} + */ + public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) { + $result = AccessResult::allowedIfHasPermission($account, 'access administration pages'); + return $return_as_object ? $result : $result->isAllowed(); + } + + /** + * {@inheritdoc} + */ + public function getType() { + return $this->getPluginId(); + } + + /** + * {@inheritdoc} + */ + public function execute() { + if ($this->isSearchExecutable()) { + $results = $this->findResults(); + + if ($results) { + return $this->prepareResults($results); + } + } + + return []; + } + + /** + * Finds the search results. + * + * @return \Drupal\Core\Database\StatementInterface|null + * Results from search query execute() method, or NULL if the search + * failed. + */ + protected function findResults() { + // We need to check access for the current user to see the topics that + // could be returned by search. Each entry in the help_search_items + // database has an optional permission that comes from the HelpSection + // plugin, in addition to the generic 'access administration pages' + // permission. In order to enforce these permissions so only topics that + // the current user has permission to view are selected by the query, make + // a list of the permission strings and pre-check those permissions. + $this->addCacheContexts(['user.permissions']); + if (!$this->account->hasPermission('access administration pages')) { + return NULL; + } + $permissions = $this->database + ->select('help_search_items', 'hsi') + ->distinct() + ->fields('hsi', ['permission']) + ->condition('permission', '', '<>') + ->execute() + ->fetchCol(); + $denied_permissions = array_filter($permissions, function ($permission) { + return !$this->account->hasPermission($permission); + }); + + $query = $this->database + ->select('search_index', 'i') + // Restrict the search to the current interface language. + ->condition('i.langcode', $this->languageManager->getCurrentLanguage()->getId()) + ->extend(SearchQuery::class) + ->extend(PagerSelectExtender::class); + $query->innerJoin('help_search_items', 'hsi', 'i.sid = hsi.sid AND i.type = :type', [':type' => $this->getType()]); + if ($denied_permissions) { + $query->condition('hsi.permission', $denied_permissions, 'NOT IN'); + } + $query->searchExpression($this->getKeywords(), $this->getType()); + + $find = $query + ->fields('i', ['langcode']) + ->fields('hsi', ['section_plugin_id', 'topic_id']) + // Since SearchQuery makes these into GROUP BY queries, if we add + // a field, for PostgreSQL we also need to make it an aggregate or a + // GROUP BY. In this case, we want GROUP BY. + ->groupBy('i.langcode') + ->groupBy('hsi.section_plugin_id') + ->groupBy('hsi.topic_id') + ->limit(10) + ->execute(); + + // Check query status and set messages if needed. + $status = $query->getStatus(); + + if ($status & SearchQuery::EXPRESSIONS_IGNORED) { + $this->messenger->addWarning($this->t('Your search used too many AND/OR expressions. Only the first @count terms were included in this search.', ['@count' => $this->searchSettings->get('and_or_limit')])); + } + + if ($status & SearchQuery::LOWER_CASE_OR) { + $this->messenger->addWarning($this->t('Search for either of the two terms with uppercase OR. For example, cats OR dogs.')); + } + + if ($status & SearchQuery::NO_POSITIVE_KEYWORDS) { + $this->messenger->addWarning($this->formatPlural($this->searchSettings->get('index.minimum_word_size'), 'You must include at least one keyword to match in the content, and punctuation is ignored.', 'You must include at least one keyword to match in the content. Keywords must be at least @count characters, and punctuation is ignored.')); + } + + return $find; + } + + /** + * Prepares search results for display. + * + * @param \Drupal\Core\Database\StatementInterface $found + * Results found from a successful search query execute() method. + * + * @return array + * List of search result render arrays, with links, snippets, etc. + */ + protected function prepareResults(StatementInterface $found) { + $results = []; + $plugins = []; + $languages = []; + $keys = $this->getKeywords(); + foreach ($found as $item) { + $section_plugin_id = $item->section_plugin_id; + if (!isset($plugins[$section_plugin_id])) { + $plugins[$section_plugin_id] = $this->getSectionPlugin($section_plugin_id); + } + if ($plugins[$section_plugin_id]) { + $langcode = $item->langcode; + if (!isset($languages[$langcode])) { + $languages[$langcode] = $this->languageManager->getLanguage($item->langcode); + } + $topic = $plugins[$section_plugin_id]->renderTopicForSearch($item->topic_id, $languages[$langcode]); + if ($topic) { + if (isset($topic['cacheable_metadata'])) { + $this->addCacheableDependency($topic['cacheable_metadata']); + } + $results[] = [ + 'title' => $topic['title'], + 'link' => $topic['url']->toString(), + 'snippet' => search_excerpt($keys, $topic['title'] . ' ' . $topic['text'], $item->langcode), + 'langcode' => $item->langcode, + ]; + } + } + } + + return $results; + } + + /** + * {@inheritdoc} + */ + public function updateIndex() { + // Update the list of items to be indexed. + $this->updateTopicList(); + + // Find some items that need to be updated. Start with ones that have + // never been indexed. + $limit = (int) $this->searchSettings->get('index.cron_limit'); + + $query = $this->database->select('help_search_items', 'hsi'); + $query->fields('hsi', ['sid', 'section_plugin_id', 'topic_id']); + $query->leftJoin('search_dataset', 'sd', 'sd.sid = hsi.sid AND sd.type = :type', [':type' => $this->getType()]); + $query->where('sd.sid IS NULL'); + $query->groupBy('hsi.sid') + ->groupBy('hsi.section_plugin_id') + ->groupBy('hsi.topic_id') + ->range(0, $limit); + $items = $query->execute()->fetchAll(); + + // If there is still space in the indexing limit, index items that have + // been indexed before, but are currently marked as needing a re-index. + if (count($items) < $limit) { + $query = $this->database->select('help_search_items', 'hsi'); + $query->fields('hsi', ['sid', 'section_plugin_id', 'topic_id']); + $query->leftJoin('search_dataset', 'sd', 'sd.sid = hsi.sid AND sd.type = :type', [':type' => $this->getType()]); + $query->condition('sd.reindex', 0, '<>'); + $query->groupBy('hsi.sid') + ->groupBy('hsi.section_plugin_id') + ->groupBy('hsi.topic_id') + ->range(0, $limit - count($items)); + $items = $items + $query->execute()->fetchAll(); + } + + // Index the items we have chosen, in all available languages. + $language_list = $this->languageManager->getLanguages(LanguageInterface::STATE_CONFIGURABLE); + $section_plugins = []; + + $words = []; + try { + foreach ($items as $item) { + $section_plugin_id = $item->section_plugin_id; + if (!isset($section_plugins[$section_plugin_id])) { + $section_plugins[$section_plugin_id] = $this->getSectionPlugin($section_plugin_id); + } + + if (!$section_plugins[$section_plugin_id]) { + $this->removeItemsFromIndex($item->sid); + continue; + } + + $section_plugin = $section_plugins[$section_plugin_id]; + $this->searchIndex->clear($this->getType(), $item->sid); + foreach ($language_list as $langcode => $language) { + $topic = $section_plugin->renderTopicForSearch($item->topic_id, $language); + if ($topic) { + // Index the title plus body text. + $text = '

' . $topic['title'] . '

' . "\n" . $topic['text']; + $words += $this->searchIndex->index($this->getType(), $item->sid, $langcode, $text, FALSE); + } + } + } + } + finally { + $this->searchIndex->updateWordWeights($words); + } + } + + /** + * {@inheritdoc} + */ + public function indexClear() { + $this->searchIndex->clear($this->getType()); + } + + /** + * Rebuilds the database table containing topics to be indexed. + */ + public function updateTopicList() { + // Start by fetching the existing list, so we can remove items not found + // at the end. + $old_list = $this->database->select('help_search_items', 'hsi') + ->fields('hsi', ['sid', 'topic_id', 'section_plugin_id', 'permission']) + ->execute(); + $old_list_ordered = []; + $sids_to_remove = []; + foreach ($old_list as $item) { + $old_list_ordered[$item->section_plugin_id][$item->topic_id] = $item; + $sids_to_remove[$item->sid] = $item->sid; + } + + $section_plugins = $this->helpSectionManager->getDefinitions(); + foreach ($section_plugins as $section_plugin_id => $section_plugin_definition) { + $plugin = $this->getSectionPlugin($section_plugin_id); + if (!$plugin) { + continue; + } + $permission = $section_plugin_definition['permission'] ?? ''; + foreach ($plugin->listSearchableTopics() as $topic_id) { + if (isset($old_list_ordered[$section_plugin_id][$topic_id])) { + $old_item = $old_list_ordered[$section_plugin_id][$topic_id]; + if ($old_item->permission == $permission) { + // Record has not changed. + unset($sids_to_remove[$old_item->sid]); + continue; + } + + // Permission has changed, update record. + $this->database->update('help_search_items') + ->condition('sid', $old_item->sid) + ->fields(['permission' => $permission]) + ->execute(); + unset($sids_to_remove[$old_item->sid]); + continue; + } + + // New record, create it. + $this->database->insert('help_search_items') + ->fields([ + 'section_plugin_id' => $section_plugin_id, + 'permission' => $permission, + 'topic_id' => $topic_id, + ]) + ->execute(); + } + } + + // Remove remaining items from the index. + $this->removeItemsFromIndex($sids_to_remove); + } + + /** + * {@inheritdoc} + */ + public function markForReindex() { + $this->updateTopicList(); + $this->searchIndex->markForReindex($this->getType()); + } + + /** + * {@inheritdoc} + */ + public function indexStatus() { + $this->updateTopicList(); + $total = $this->database->select('help_search_items', 'hsi') + ->countQuery() + ->execute() + ->fetchField(); + + $query = $this->database->select('help_search_items', 'hsi'); + $query->addExpression('COUNT(DISTINCT(hsi.sid))'); + $query->leftJoin('search_dataset', 'sd', 'hsi.sid = sd.sid AND sd.type = :type', [':type' => $this->getType()]); + $condition = new Condition('OR'); + $condition->condition('sd.reindex', 0, '<>') + ->isNull('sd.sid'); + $query->condition($condition); + $remaining = $query->execute()->fetchField(); + + return [ + 'remaining' => $remaining, + 'total' => $total, + ]; + } + + /** + * Removes an item or items from the search index. + * + * @param int|int[] $sids + * Search ID (sid) of item or items to remove. + */ + protected function removeItemsFromIndex($sids) { + $sids = (array) $sids; + + // Remove items from our table in batches of 100, to avoid problems + // with having too many placeholders in database queries. + foreach (array_chunk($sids, 100) as $this_list) { + $this->database->delete('help_search_items') + ->condition('sid', $this_list, 'IN') + ->execute(); + } + // Remove items from the search tables individually, as there is no bulk + // function to delete items from the search index. + foreach ($sids as $sid) { + $this->searchIndex->clear($this->getType(), $sid); + } + } + + /** + * Instantiates a help section plugin and verifies it is searchable. + * + * @param string $section_plugin_id + * Type of plugin to instantiate. + * + * @return \Drupal\help_topics\SearchableHelpInterface|false + * Plugin object, or FALSE if it is not searchable. + */ + protected function getSectionPlugin($section_plugin_id) { + /** @var \Drupal\help\HelpSectionPluginInterface $section_plugin */ + $section_plugin = $this->helpSectionManager->createInstance($section_plugin_id); + // Intentionally return boolean to allow caching of results. + return $section_plugin instanceof SearchableHelpInterface ? $section_plugin : FALSE; + } + +} diff --git a/core/modules/help_topics/src/SearchableHelpInterface.php b/core/modules/help_topics/src/SearchableHelpInterface.php new file mode 100644 index 000000000..8b6b8ebc3 --- /dev/null +++ b/core/modules/help_topics/src/SearchableHelpInterface.php @@ -0,0 +1,46 @@ + + {{ body }} + {{ related }} + diff --git a/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.bad_html.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.bad_html.html.twig new file mode 100644 index 000000000..897f2d5ac --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.bad_html.html.twig @@ -0,0 +1,5 @@ +--- +label: 'Help topic with bad HTML syntax' +top_level: true +--- +

{% trans %}Body goes here{% endtrans %} diff --git a/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.empty.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.empty.html.twig new file mode 100644 index 000000000..0023b35ab --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.empty.html.twig @@ -0,0 +1,4 @@ +--- +label: 'Help topic containing no body' +top_level: true +--- diff --git a/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.h1.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.h1.html.twig new file mode 100644 index 000000000..cd47ddd8b --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.h1.html.twig @@ -0,0 +1,5 @@ +--- +label: 'Help topic with H1 header' +top_level: true +--- +

{% trans %}Body goes here{% endtrans %}

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.hierarchy.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.hierarchy.html.twig new file mode 100644 index 000000000..d2b046dd4 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.hierarchy.html.twig @@ -0,0 +1,5 @@ +--- +label: 'Help topic with h3 without an h2' +top_level: true +--- +

{% trans %}Body goes here{% endtrans %}

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.related.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.related.html.twig new file mode 100644 index 000000000..96f46670d --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.related.html.twig @@ -0,0 +1,7 @@ +--- +label: 'Help topic related to nonexistent topic' +top_level: true +related: + - this.is.not.a.valid.help_topic.id +--- +

{% trans %}Body goes here{% trans %}

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.top_level.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.top_level.html.twig new file mode 100644 index 000000000..ae19252f8 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.top_level.html.twig @@ -0,0 +1,4 @@ +--- +label: 'Help topic not top level or related to top level' +--- +

{% trans %}Body goes here{% endtrans %}

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.translated.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.translated.html.twig new file mode 100644 index 000000000..5b618acde --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/bad_help_topics/syntax/bad_help_topics.translated.html.twig @@ -0,0 +1,5 @@ +--- +label: 'Help topic with untranslated text' +top_level: true +--- +

Body goes here

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.additional.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.additional.html.twig new file mode 100644 index 000000000..cb70357ed --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.additional.html.twig @@ -0,0 +1,6 @@ +--- +label: 'Additional topic' +related: + - help_topics_test.test +--- +

{% trans %}This topic should get listed automatically on the Help test topic.{% endtrans %}

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.linked.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.linked.html.twig new file mode 100644 index 000000000..a101a1c15 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.linked.html.twig @@ -0,0 +1,4 @@ +--- +label: 'Linked topic' +--- +

{% trans %}This topic is not supposed to be top-level.{% endtrans %}

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.test.html.twig b/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.test.html.twig new file mode 100644 index 000000000..d8126215d --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/help_topics/help_topics_test.test.html.twig @@ -0,0 +1,11 @@ +--- +label: "ABC Help Test module" +top_level: true +related: + - help_topics_test.linked + - does_not_exist.and_no_error +--- +{% set help_topic_url = render_var(url('help.help_topic', {id: 'help_topics_test.additional'})) %} +

{% trans %}This is a test. It should link to the additional topic. Also there should be a related topic link below to the Help module topic page and the linked topic.{% endtrans %}

+

{% trans %}Nonworditem totranslate.{% endtrans %}

+

{% trans %}Test translation.{% endtrans %}

diff --git a/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.help_topics.yml b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.help_topics.yml new file mode 100644 index 000000000..d7a44ebca --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.help_topics.yml @@ -0,0 +1,9 @@ +help_topics_test_direct_yml: + class: 'Drupal\help_topics_test\Plugin\HelpTopic\TestHelpTopicPlugin' + top_level: true + related: {} + label: "Test direct yaml topic label" + body: "Test direct yaml body" + +help_topics_derivatives: + deriver: 'Drupal\help_topics_test\Plugin\Deriver\TestHelpTopicDeriver' diff --git a/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.info.yml b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.info.yml new file mode 100644 index 000000000..155c43d56 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.info.yml @@ -0,0 +1,7 @@ +# The name of this module is deliberately different from its machine +# name to test the presented order of help topics. +name: 'ABC Help Test' +type: module +description: 'Support module for help testing.' +package: Testing +core: 8.x diff --git a/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.module b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.module new file mode 100644 index 000000000..6bbecbac3 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.module @@ -0,0 +1,25 @@ +get('help_topics_test.test:top_level', TRUE); +} diff --git a/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.permissions.yml b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.permissions.yml new file mode 100644 index 000000000..4147e3051 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/help_topics_test.permissions.yml @@ -0,0 +1,2 @@ +access test help: + title: 'Access the test help section' diff --git a/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/Deriver/TestHelpTopicDeriver.php b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/Deriver/TestHelpTopicDeriver.php new file mode 100644 index 000000000..957e6cda9 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/Deriver/TestHelpTopicDeriver.php @@ -0,0 +1,39 @@ + $plugin_id, + 'id' => $plugin_id, + 'class' => 'Drupal\\help_topics_test\\Plugin\\HelpTopic\\TestHelpTopicPlugin', + 'label' => 'Label for ' . $id, + 'body' => 'Body for ' . $id, + 'top_level' => TRUE, + 'related' => [], + 'provider' => 'help_topics_test', + ]; + return $definitions; + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinition($derivative_id, $base_plugin_definition) { + return $base_plugin_definition; + } + +} diff --git a/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpSection/TestHelpSection.php b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpSection/TestHelpSection.php new file mode 100644 index 000000000..f4d2a741e --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpSection/TestHelpSection.php @@ -0,0 +1,79 @@ +getId() == 'en') { + return [ + 'title' => 'Foo in English title wcsrefsdf', + 'text' => 'Something about foo body notawordenglish sqruct', + 'url' => Url::fromUri('https://foo.com'), + ]; + } + return [ + 'title' => 'Foomm Foreign heading', + 'text' => 'Fake foreign foo text notawordgerman asdrsad', + 'url' => Url::fromUri('https://mm.foo.com'), + ]; + + case 'bar': + if ($language->getId() == 'en') { + return [ + 'title' => 'Bar in English', + 'text' => 'Something about bar anotherwordenglish asdrsad', + 'url' => Url::fromUri('https://bar.com'), + ]; + } + return [ + 'title' => \Drupal::state()->get('help_topics_test:translated_title', 'Barmm Foreign sdeeeee'), + 'text' => 'Fake foreign barmm anotherwordgerman sqruct', + 'url' => Url::fromUri('https://mm.bar.com'), + ]; + + default: + throw new \InvalidArgumentException('Unexpected ID encountered'); + } + } + +} diff --git a/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpTopic/TestHelpTopicPlugin.php b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpTopic/TestHelpTopicPlugin.php new file mode 100644 index 000000000..421c26321 --- /dev/null +++ b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpTopic/TestHelpTopicPlugin.php @@ -0,0 +1,44 @@ + 'markup', + '#markup' => $this->pluginDefinition['body'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return ['foobar']; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + +} diff --git a/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php new file mode 100644 index 000000000..2f6765c15 --- /dev/null +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php @@ -0,0 +1,266 @@ +drupalLogin($this->createUser([ + 'access administration pages', + 'administer site configuration', + 'view the administration theme', + 'administer permissions', + 'administer languages', + 'administer search', + 'access test help', + 'search content', + ])); + + // Add English language and set to default. + $this->drupalPostForm('admin/config/regional/language/add', [ + 'predefined_langcode' => 'en', + ], 'Add language'); + $this->drupalPostForm('admin/config/regional/language', [ + 'site_default_language' => 'en', + ], 'Save configuration'); + // When default language is changed, the container is rebuilt in the child + // site, so a rebuild in the main site is required to use the new container + // here. + $this->rebuildContainer(); + + // Before running cron, verify that a search returns no results. + $this->drupalPostForm('search/help', ['keys' => 'notawordenglish'], 'Search'); + $this->assertSearchResultsCount(0); + + // Run cron until the topics are fully indexed, with a limit of 100 runs + // to avoid infinite loops. + $num_runs = 100; + $plugin = HelpSearch::create($this->container, [], 'help_search', []); + do { + $this->cronRun(); + $remaining = $plugin->indexStatus()['remaining']; + } while (--$num_runs && $remaining); + $this->assertNotEmpty($num_runs); + $this->assertEmpty($remaining); + + // Visit the Search settings page and verify it says 100% indexed. + $this->drupalGet('admin/config/search/pages'); + $this->assertSession()->pageTextContains('100% of the site has been indexed'); + } + + /** + * Tests help topic search. + */ + public function testHelpSearch() { + $german = \Drupal::languageManager()->getLanguage('de'); + $session = $this->assertSession(); + + // Verify that when we search in English for a word that is only in + // English text, we find the topic. Note that these "words" are provided + // by the topics that come from + // \Drupal\help_topics_test\Plugin\HelpSection\TestHelpSection. + $this->drupalPostForm('search/help', ['keys' => 'notawordenglish'], 'Search'); + $this->assertSearchResultsCount(1); + $session->linkExists('Foo in English title wcsrefsdf'); + + // Same for German. + $this->drupalPostForm('search/help', ['keys' => 'notawordgerman'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(1); + $session->linkExists('Foomm Foreign heading'); + + // Verify when we search in English for a word that only exists in German, + // we get no results. + $this->drupalPostForm('search/help', ['keys' => 'notawordgerman'], 'Search'); + $this->assertSearchResultsCount(0); + $session->pageTextContains('no results'); + + // Same for German. + $this->drupalPostForm('search/help', ['keys' => 'notawordenglish'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(0); + $session->pageTextContains('no results'); + + // Verify when we search in English for a word that exists in one topic + // in English and a different topic in German, we only get the one English + // topic. + $this->drupalPostForm('search/help', ['keys' => 'sqruct'], 'Search'); + $this->assertSearchResultsCount(1); + $session->linkExists('Foo in English title wcsrefsdf'); + + // Same for German. + $this->drupalPostForm('search/help', ['keys' => 'asdrsad'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(1); + $session->linkExists('Foomm Foreign heading'); + + // All of the above tests used the TestHelpSection plugin. Also verify + // that we can search for translated regular help topics, in both English + // and German. + $this->drupalPostForm('search/help', ['keys' => 'nonworditem'], 'Search'); + $this->assertSearchResultsCount(1); + $session->linkExists('ABC Help Test module'); + // Click the link and verify we ended up on the topic page. + $this->clickLink('ABC Help Test module'); + $session->pageTextContains('This is a test'); + + $this->drupalPostForm('search/help', ['keys' => 'nonwordgerman'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(1); + $session->linkExists('ABC-Hilfetestmodul'); + $this->clickLink('ABC-Hilfetestmodul'); + $session->pageTextContains('Übersetzung testen.'); + + // Verify that we can search from the admin/help page. + $this->drupalGet('admin/help'); + $session->pageTextContains('Search help'); + $this->drupalPostForm(NULL, ['keys' => 'nonworditem'], 'Search'); + $this->assertSearchResultsCount(1); + $session->linkExists('ABC Help Test module'); + + // Same for German. + $this->drupalPostForm('admin/help', ['keys' => 'nonwordgerman'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(1); + $session->linkExists('ABC-Hilfetestmodul'); + + // Verify we can search for title text (other searches used text + // that was part of the body). + $this->drupalPostForm('search/help', ['keys' => 'wcsrefsdf'], 'Search'); + $this->assertSearchResultsCount(1); + $session->linkExists('Foo in English title wcsrefsdf'); + + $this->drupalPostForm('admin/help', ['keys' => 'sdeeeee'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(1); + $session->linkExists('Barmm Foreign sdeeeee'); + + // Just changing the title and running cron is not enough to reindex so + // 'sdeeeee' still hits a match. The content is updated because the help + // topic is rendered each time. + \Drupal::state()->set('help_topics_test:translated_title', 'Updated translated title'); + $this->cronRun(); + $this->drupalPostForm('admin/help', ['keys' => 'sdeeeee'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(1); + $session->linkExists('Updated translated title'); + // Searching for the updated test shouldn't produce a match. + $this->drupalPostForm('admin/help', ['keys' => 'translated title'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(0); + + // Clear the caches and re-run cron - this should re-index the help. + $this->rebuildAll(); + $this->cronRun(); + $this->drupalPostForm('admin/help', ['keys' => 'sdeeeee'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(0); + $this->drupalPostForm('admin/help', ['keys' => 'translated title'], 'Search', [ + 'language' => $german, + ]); + $this->assertSearchResultsCount(1); + $session->linkExists('Updated translated title'); + + // Verify the cache tags and contexts. + $session->responseHeaderContains('X-Drupal-Cache-Tags', 'config:search.page.help_search'); + $session->responseHeaderContains('X-Drupal-Cache-Tags', 'search_index:help_search'); + $session->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions'); + $session->responseHeaderContains('X-Drupal-Cache-Contexts', 'languages:language_interface'); + + // Log in as a user that does not have permission to see TestHelpSection + // items, and verify they can still search for help topics but not see these + // items. + $this->drupalLogin($this->createUser([ + 'access administration pages', + 'administer site configuration', + 'view the administration theme', + 'administer permissions', + 'administer languages', + 'administer search', + 'search content', + ])); + + $this->drupalGet('admin/help'); + $session->pageTextContains('Search help'); + + $this->drupalPostForm('search/help', ['keys' => 'nonworditem'], 'Search'); + $this->assertSearchResultsCount(1); + $session->linkExists('ABC Help Test module'); + + $this->drupalPostForm('search/help', ['keys' => 'notawordenglish'], 'Search'); + $this->assertSearchResultsCount(0); + $session->pageTextContains('no results'); + + // Uninstall the test module and verify its topics are immediately not + // searchable. + \Drupal::service('module_installer')->uninstall(['help_topics_test']); + $this->drupalPostForm('search/help', ['keys' => 'nonworditem'], 'Search'); + $this->assertSearchResultsCount(0); + } + + /** + * Tests uninstalling the help_topics module. + */ + public function testUninstall() { + // Ensure we can uninstall help_topics and use the help system without + // breaking. + $this->drupalLogin($this->rootUser); + $edit = []; + $edit['uninstall[help_topics]'] = TRUE; + $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall')); + $this->drupalPostForm(NULL, NULL, t('Uninstall')); + $this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.'); + $this->drupalGet('admin/help'); + $this->assertResponse(200); + } + + /** + * Asserts that help search returned the expected number of results. + * + * @param int $count + * The expected number of search results. + */ + protected function assertSearchResultsCount($count) { + $this->assertSession()->elementsCount('css', '.help_search-results > li', $count); + } + +} diff --git a/core/modules/help_topics/tests/src/Functional/HelpTopicTest.php b/core/modules/help_topics/tests/src/Functional/HelpTopicTest.php new file mode 100644 index 000000000..3dcbbd21c --- /dev/null +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicTest.php @@ -0,0 +1,253 @@ +install(['seven', 'help_topics_test_theme']); + \Drupal::service('config.factory')->getEditable('system.theme')->set('admin', 'seven')->save(); + + // Place various blocks. + $settings = [ + 'theme' => 'seven', + 'region' => 'help', + ]; + $this->placeBlock('help_block', $settings); + $this->placeBlock('local_tasks_block', $settings); + $this->placeBlock('local_actions_block', $settings); + $this->placeBlock('page_title_block', $settings); + $this->placeBlock('system_breadcrumb_block', $settings); + + // Create users. + $this->adminUser = $this->createUser([ + 'access administration pages', + 'view the administration theme', + 'administer permissions', + 'administer site configuration', + ]); + $this->anyUser = $this->createUser([]); + } + + /** + * Tests the main help page and individual pages for topics. + */ + public function testHelp() { + $session = $this->assertSession(); + + // Log in the regular user. + $this->drupalLogin($this->anyUser); + $this->verifyHelp(403); + + // Log in the admin user. + $this->drupalLogin($this->adminUser); + $this->verifyHelp(); + $this->verifyHelpLinks(); + $this->verifyBreadCrumb(); + + // Verify that help topics text appears on admin/help, and cache tags. + $this->drupalGet('admin/help'); + $session->responseContains('

Topics

'); + $session->pageTextContains('Topics can be provided by modules or themes'); + $session->responseHeaderContains('X-Drupal-Cache-Tags', 'core.extension'); + + // Verify links for for help topics and order. + $page_text = $this->getTextContent(); + $start = strpos($page_text, 'Topics can be provided'); + $pos = $start; + foreach ($this->getTopicList() as $info) { + $name = $info['name']; + $session->linkExists($name); + $new_pos = strpos($page_text, $name, $start); + $this->assertTrue($new_pos > $pos, 'Order of ' . $name . ' is correct on page'); + $pos = $new_pos; + } + + // Ensure the plugin manager alter hook works as expected. + $session->linkExists('ABC Help Test module'); + \Drupal::state()->set('help_topics_test.test:top_level', FALSE); + \Drupal::service('plugin.manager.help_topic')->clearCachedDefinitions(); + $this->drupalGet('admin/help'); + $session->linkNotExists('ABC Help Test module'); + \Drupal::state()->set('help_topics_test.test:top_level', TRUE); + \Drupal::service('plugin.manager.help_topic')->clearCachedDefinitions(); + $this->drupalGet('admin/help'); + + // Ensure all the expected links are present before uninstalling. + $session->linkExists('ABC Help Test module'); + $session->linkExists('ABC Help Test'); + $session->linkExists('XYZ Help Test theme'); + + // Uninstall the test module and verify the topics are gone, after + // reloading page. + $this->container->get('module_installer')->uninstall(['help_topics_test']); + $this->drupalGet('admin/help'); + $session->linkNotExists('ABC Help Test module'); + $session->linkNotExists('ABC Help Test'); + $session->linkExists('XYZ Help Test theme'); + + // Uninstall the test theme and verify the topic is gone. + $this->container->get('theme_installer')->uninstall(['help_topics_test_theme']); + $this->drupalGet('admin/help'); + $session->linkNotExists('XYZ Help Test theme'); + } + + /** + * Verifies the logged in user has access to various help links and pages. + * + * @param int $response + * (optional) The HTTP response code to test for. If it's 200 (default), + * the test verifies the user sees the help; if it's not, it verifies they + * are denied access. + */ + protected function verifyHelp($response = 200) { + // Verify access to help topic pages. + foreach ($this->getTopicList() as $topic => $info) { + // View help topic page. + $this->drupalGet('admin/help/topic/' . $topic); + $session = $this->assertSession(); + $session->statusCodeEquals($response); + if ($response == 200) { + // Verify page information. + $name = $info['name']; + $session->titleEquals($name . ' | Drupal'); + $session->responseContains('

' . $name . '

'); + foreach ($info['tags'] as $tag) { + $session->responseHeaderContains('X-Drupal-Cache-Tags', $tag); + } + } + } + } + + /** + * Verifies links on the test help topic page and other pages. + * + * Assumes an admin user is logged in. + */ + protected function verifyHelpLinks() { + $session = $this->assertSession(); + // Verify links on the test top-level page. + $page = 'admin/help/topic/help_topics_test.test'; + $links = [ + 'link to the additional topic' => 'Additional topic', + 'Linked topic' => 'This topic is not supposed to be top-level', + 'Additional topic' => 'This topic should get listed automatically', + ]; + foreach ($links as $link_text => $page_text) { + $this->drupalGet($page); + $this->clickLink($link_text); + $session->pageTextContains($page_text); + } + + // Verify theme provided help topics work and can be related. + $this->drupalGet('admin/help/topic/help_topics_test_theme.test'); + $session->pageTextContains('This is a theme provided topic.'); + $this->assertContains('This is a theme provided topic.', $session->elementExists('css', 'article')->getText()); + $this->clickLink('Additional topic'); + $session->linkExists('XYZ Help Test theme'); + + // Verify that the non-top-level topics do not appear on the Help page. + $this->drupalGet('admin/help'); + $session->linkNotExists('Linked topic'); + $session->linkNotExists('Additional topic'); + } + + /** + * Gets a list of topic IDs to test. + * + * @return array + * A list of topics to test, in the order in which they should appear. The + * keys are the machine names of the topics. The values are arrays with the + * following elements: + * - name: Displayed name. + * - tags: Cache tags to test for. + */ + protected function getTopicList() { + return [ + 'help_topics_test.test' => [ + 'name' => 'ABC Help Test module', + 'tags' => ['core.extension'], + ], + 'help_topics_derivatives:test_derived_topic' => [ + 'name' => 'Label for test_derived_topic', + 'tags' => ['foobar'], + ], + 'help_topics_test_direct_yml' => [ + 'name' => 'Test direct yaml topic label', + 'tags' => ['foobar'], + ], + ]; + } + + /** + * Tests breadcrumb on a help topic page. + */ + public function verifyBreadCrumb() { + // Verify Help Topics administration breadcrumbs. + $trail = [ + '' => 'Home', + 'admin' => 'Administration', + 'admin/help' => 'Help', + ]; + $this->assertBreadcrumb('admin/help/topic/help_topics_test.test', $trail); + // Ensure we are on the expected help topic page. + $this->assertSession()->pageTextContains('Also there should be a related topic link below to the Help module topic page and the linked topic.'); + + // Verify that another page does not have the help breadcrumb. + $trail = [ + '' => 'Home', + 'admin' => 'Administration', + 'admin/config' => 'Configuration', + 'admin/config/system' => 'System', + ]; + $this->assertBreadcrumb('admin/config/system/site-information', $trail); + } + +} diff --git a/core/modules/help_topics/tests/src/Functional/HelpTopicTranslatedTestBase.php b/core/modules/help_topics/tests/src/Functional/HelpTopicTranslatedTestBase.php new file mode 100644 index 000000000..c2f1158aa --- /dev/null +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicTranslatedTestBase.php @@ -0,0 +1,86 @@ +install(['seven']); + \Drupal::configFactory()->getEditable('system.theme') + ->set('admin', 'seven') + ->save(TRUE); + + // Place various blocks. + $settings = [ + 'theme' => 'seven', + 'region' => 'help', + ]; + $this->placeBlock('help_block', $settings); + $this->placeBlock('local_tasks_block', $settings); + $this->placeBlock('local_actions_block', $settings); + $this->placeBlock('page_title_block', $settings); + + // Create user. + $this->drupalLogin($this->createUser([ + 'access administration pages', + 'view the administration theme', + 'administer permissions', + ])); + } + + /** + * {@inheritdoc} + */ + protected function installParameters() { + $parameters = parent::installParameters(); + // Install in German. This will ensure the language and locale modules are + // installed. + $parameters['parameters']['langcode'] = 'de'; + // Create a po file so we don't attempt to download one from + // localize.drupal.org and to have a test translation that will not change. + \Drupal::service('file_system')->mkdir($this->publicFilesDirectory . '/translations', NULL, TRUE); + $contents = <<root . '/core/includes/install.core.inc'; + $version = _install_get_version_info(\Drupal::VERSION)['major'] . '.0.0'; + file_put_contents($this->publicFilesDirectory . "/translations/drupal-{$version}.de.po", $contents); + return $parameters; + } + +} diff --git a/core/modules/help_topics/tests/src/Functional/HelpTopicTranslationTest.php b/core/modules/help_topics/tests/src/Functional/HelpTopicTranslationTest.php new file mode 100644 index 000000000..5a45c8c65 --- /dev/null +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicTranslationTest.php @@ -0,0 +1,50 @@ +drupalLogin($this->createUser([ + 'access administration pages', + 'view the administration theme', + 'administer permissions', + ])); + } + + /** + * Tests help topic translations. + */ + public function testHelpTopicTranslations() { + $session = $this->assertSession(); + + // Verify that help topic link is translated on admin/help. + $this->drupalGet('admin/help'); + $session->linkExists('ABC-Hilfetestmodul'); + // Verify that the language cache tag appears on admin/help. + $session->responseHeaderContains('X-Drupal-Cache-Contexts', 'languages:language_interface'); + // Verify that help topic is translated. + $this->drupalGet('admin/help/topic/help_topics_test.test'); + $session->pageTextContains('ABC-Hilfetestmodul'); + $session->pageTextContains('Übersetzung testen.'); + // Verify that the language cache tag appears on a topic page. + $session->responseHeaderContains('X-Drupal-Cache-Contexts', 'languages:language_interface'); + } + +} diff --git a/core/modules/help_topics/tests/src/Functional/HelpTopicsSyntaxTest.php b/core/modules/help_topics/tests/src/Functional/HelpTopicsSyntaxTest.php new file mode 100644 index 000000000..6127dd4e8 --- /dev/null +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicsSyntaxTest.php @@ -0,0 +1,262 @@ +drupalLogin($this->rootUser); + + // Enable all modules and themes, so that all routes mentioned in topics + // will be defined. + $module_directories = $this->listDirectories('module'); + $modules_to_install = array_keys($module_directories); + $modules_to_install = $this->removeDeprecatedModules($modules_to_install); + \Drupal::service('module_installer')->install($modules_to_install); + $theme_directories = $this->listDirectories('theme'); + \Drupal::service('theme_installer')->install(array_keys($theme_directories)); + + $directories = $module_directories + $theme_directories + + $this->listDirectories('profile'); + $directories['core'] = \Drupal::service('app.root') . '/core/help_topics'; + $directories['bad_help_topics'] = \Drupal::service('extension.list.module')->getPath('help_topics_test') . '/bad_help_topics/syntax/'; + + // Filter out directories outside of core. If you want to run this test + // on a contrib/custom module, remove the next line. + $directories = array_filter($directories, function ($directory) { + return strpos($directory, 'core') === 0; + }); + + // Verify that a few key modules, themes, and profiles are listed, so that + // we can be certain our directory list is complete and we will be testing + // all existing help topics. If these lines in the test fail in the future, + // it is probably because something we chose to list here is being removed. + // Substitute another item of the same type that still exists, so that this + // test can continue. + $this->assertArrayHasKey('system', $directories, 'System module is being scanned'); + $this->assertArrayHasKey('help', $directories, 'Help module is being scanned'); + $this->assertArrayHasKey('seven', $directories, 'Seven theme is being scanned'); + $this->assertArrayHasKey('standard', $directories, 'Standard profile is being scanned'); + + $definitions = (new HelpTopicDiscovery($directories))->getDefinitions(); + $this->assertGreaterThan(0, count($definitions), 'At least 1 topic was found'); + + // Test each topic for compliance with standards, or for failing in the + // right way. + foreach (array_keys($definitions) as $id) { + if (strpos($id, 'bad_help_topics.') === 0) { + $this->verifyBadTopic($id, $definitions); + } + else { + $this->verifyTopic($id, $definitions); + } + } + } + + /** + * Verifies rendering and standards compliance of one help topic. + * + * @param string $id + * ID of the topic to verify. + * @param array $definitions + * Array of all topic definitions, keyed by ID. + * @param int $response + * Expected response from visiting the page for the topic. + */ + protected function verifyTopic($id, $definitions, $response = 200) { + $definition = $definitions[$id]; + + // Visit the URL for the topic. + $this->drupalGet('admin/help/topic/' . $id); + + // Verify the title and response. + $session = $this->assertSession(); + $session->statusCodeEquals($response); + if ($response == 200) { + $session->titleEquals($definition['label'] . ' | Drupal'); + } + + // Verify that all the related topics exist. Also check to see if any of + // them are top-level (we will need that in the next section). + $has_top_level_related = FALSE; + if (isset($definition['related'])) { + foreach ($definition['related'] as $related_id) { + $this->assertArrayHasKey($related_id, $definitions, 'Topic ' . $id . ' is only related to topics that exist (' . $related_id . ')'); + $has_top_level_related = $has_top_level_related || !empty($definitions[$related_id]['top_level']); + } + } + + // Verify this is either top-level or related to a top-level topic. + $this->assertTrue(!empty($definition['top_level']) || $has_top_level_related, 'Topic ' . $id . ' is either top-level or related to at least one other top-level topic'); + + // Verify that the label is not empty. + $this->assertNotEmpty($definition['label'], 'Topic ' . $id . ' has a non-empty label'); + + // Read in the file so we can run some tests on that. + $body = file_get_contents($definition[HelpTopicDiscovery::FILE_KEY]); + $this->assertNotEmpty($body, 'Topic ' . $id . ' has a non-empty Twig file'); + + // Remove the front matter data (already tested above), and Twig set and + // variable printouts from the file. + $body = preg_replace('|---.*---|sU', '', $body); + $body = preg_replace('|\{\{.*\}\}|sU', '', $body); + $body = preg_replace('|\{\% set.*\%\}|sU', '', $body); + $body = trim($body); + $this->assertNotEmpty($body, 'Topic ' . $id . ' Twig file contains some text outside of front matter'); + + // Verify that if we remove all the translated text, whitespace, and + // HTML tags, there is nothing left (that is, all text is translated). + $text = preg_replace('|\{\% trans \%\}.*\{\% endtrans \%\}|sU', '', $body); + $text = strip_tags($text); + $text = preg_replace('|\s+|', '', $text); + $this->assertEmpty($text, 'Topic ' . $id . ' Twig file has all of its text translated'); + + // Load the topic body as HTML and verify that it parses. + $doc = new \DOMDocument(); + $doc->strictErrorChecking = TRUE; + $doc->validateOnParse = TRUE; + libxml_use_internal_errors(TRUE); + if (!$doc->loadHTML($body)) { + foreach (libxml_get_errors() as $error) { + $this->fail($error->message); + } + + libxml_clear_errors(); + } + + // Check for headings hierarchy. + $levels = [1, 2, 3, 4, 5, 6]; + foreach ($levels as $level) { + $num_headings[$level] = $doc->getElementsByTagName('h' . $level)->length; + if ($level == 1) { + $this->assertSame(0, $num_headings[1], 'Topic ' . $id . ' has no H1 tag'); + // Set num_headings to 1 for this level, so the rest of the hierarchy + // can be tested using simpler code. + $num_headings[1] = 1; + } + else { + // We should either not have this heading, or if we do have one at this + // level, we should also have the next-smaller level. That is, if we + // have an h3, we should have also had an h2. + $this->assertTrue($num_headings[$level - 1] > 0 || $num_headings[$level] == 0, + 'Topic ' . $id . ' has the correct H2-H6 heading hierarchy'); + } + } + } + + /** + * Verifies that a bad topic fails in the expected way. + * + * @param string $id + * ID of the topic to verify. It should start with "bad_help_topics.". + * @param array $definitions + * Array of all topic definitions, keyed by ID. + */ + protected function verifyBadTopic($id, $definitions) { + $bad_topic_type = substr($id, 16); + // Topics should fail verifyTopic() in specific ways. + try { + $this->verifyTopic($id, $definitions, 404); + } + catch (ExpectationFailedException $e) { + $message = $e->getMessage(); + switch ($bad_topic_type) { + case 'related': + $this->assertContains('only related to topics that exist', $message); + break; + + case 'bad_html': + $this->assertContains('Unexpected end tag', $message); + break; + + case 'top_level': + $this->assertContains('is either top-level or related to at least one other top-level topic', $message); + break; + + case 'empty': + $this->assertContains('contains some text outside of front matter', $message); + break; + + case 'translated': + $this->assertContains('Twig file has all of its text translated', $message); + break; + + case 'h1': + $this->assertContains('has no H1 tag', $message); + break; + + case 'hierarchy': + $this->assertContains('has the correct H2-H6 heading hierarchy', $message); + break; + + default: + // This was an unexpected error. + throw $e; + } + } + } + + /** + * Lists the extension help topic directories of a certain type. + * + * @param string $type + * The type of extension to list: module, theme, or profile. + * + * @return string[] + * An array of all of the help topic directories for this type of + * extension, keyed by extension short name. + */ + protected function listDirectories($type) { + $directories = []; + + // Find the extensions of this type, even if they are not installed, but + // excluding test ones. + $lister = \Drupal::service('extension.list.' . $type); + foreach (array_keys($lister->getAllAvailableInfo()) as $name) { + $path = $lister->getPath($name); + // You can tell test modules because they are in package 'Testing', but + // test themes are only known by being found in test directories. So... + // exclude things in test directories. + if ((strpos($path, '/tests') === FALSE) && + (strpos($path, '/testing') === FALSE)) { + $directories[$name] = $path . '/help_topics'; + } + } + return $directories; + } + +} diff --git a/core/modules/help_topics/tests/src/Functional/LegacyHelpTopicsSyntaxTest.php b/core/modules/help_topics/tests/src/Functional/LegacyHelpTopicsSyntaxTest.php new file mode 100644 index 000000000..024544a99 --- /dev/null +++ b/core/modules/help_topics/tests/src/Functional/LegacyHelpTopicsSyntaxTest.php @@ -0,0 +1,20 @@ + [ + 'foo' => [ + 'help_topics' => [ + // The content of the help topic does not matter. + 'test.topic.html.twig' => '', + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['foo' => vfsStream::url('root/modules/foo/help_topics')]); + + $this->expectException(DiscoveryException::class); + $this->expectExceptionMessage("vfs://root/modules/foo/help_topics/test.topic.html.twig file name should begin with 'foo'"); + $discovery->getDefinitions(); + } + + /** + * @covers ::findAll + */ + public function testDiscoveryExceptionMissingLabel() { + vfsStream::setup('root'); + + vfsStream::create([ + 'modules' => [ + 'test' => [ + 'help_topics' => [ + // The content of the help topic does not matter. + 'test.topic.html.twig' => '', + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]); + + $this->expectException(DiscoveryException::class); + $this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig does not contain the required key with name='label'"); + $discovery->getDefinitions(); + } + + /** + * @covers ::findAll + */ + public function testDiscoveryExceptionInvalidYamlKey() { + vfsStream::setup('root'); + $topic_content = << [ + 'test' => [ + 'help_topics' => [ + 'test.topic.html.twig' => $topic_content, + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]); + + $this->expectException(DiscoveryException::class); + $this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig contains invalid key='foo'"); + $discovery->getDefinitions(); + } + + /** + * @covers ::findAll + */ + public function testDiscoveryExceptionInvalidTopLevel() { + vfsStream::setup('root'); + $topic_content = << [ + 'test' => [ + 'help_topics' => [ + 'test.topic.html.twig' => $topic_content, + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]); + + $this->expectException(DiscoveryException::class); + $this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig contains invalid value for 'top_level' key, the value must be a Boolean"); + $discovery->getDefinitions(); + } + + /** + * @covers ::findAll + */ + public function testDiscoveryExceptionInvalidRelated() { + vfsStream::setup('root'); + $topic_content = << [ + 'test' => [ + 'help_topics' => [ + 'test.topic.html.twig' => $topic_content, + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]); + + $this->expectException(DiscoveryException::class); + $this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig contains invalid value for 'related' key, the value must be an array of strings"); + $discovery->getDefinitions(); + } + + /** + * @covers ::findAll + */ + public function testHelpTopicsExtensionProviderSpecialCase() { + vfsStream::setup('root'); + $topic_content = <<Test +EOF; + + vfsStream::create([ + 'modules' => [ + 'help_topics' => [ + 'help_topics' => [ + 'core.topic.html.twig' => $topic_content, + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['help_topics' => vfsStream::url('root/modules/help_topics/help_topics')]); + $this->assertArrayHasKey('core.topic', $discovery->getDefinitions()); + } + + /** + * @covers ::findAll + */ + public function testHelpTopicsInCore() { + vfsStream::setup('root'); + $topic_content = <<Test +EOF; + + vfsStream::create([ + 'core' => [ + 'help_topics' => [ + 'core.topic.html.twig' => $topic_content, + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['core' => vfsStream::url('root/core/help_topics')]); + $this->assertArrayHasKey('core.topic', $discovery->getDefinitions()); + } + + /** + * @covers ::findAll + */ + public function testHelpTopicsBrokenYaml() { + vfsStream::setup('root'); + $topic_content = <<Test +EOF; + + vfsStream::create([ + 'modules' => [ + 'help_topics' => [ + 'help_topics' => [ + 'core.topic.html.twig' => $topic_content, + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['help_topics' => vfsStream::url('root/modules/help_topics/help_topics')]); + $this->expectException(DiscoveryException::class); + $this->expectExceptionMessage("Malformed YAML in help topic \"vfs://root/modules/help_topics/help_topics/core.topic.html.twig\":"); + $discovery->getDefinitions(); + } + + /** + * @covers ::findAll + */ + public function testHelpTopicsDefinition() { + $container = new ContainerBuilder(); + $container->set('string_translation', $this->getStringTranslationStub()); + \Drupal::setContainer($container); + + vfsStream::setup('root'); + $topic_content = <<Test +EOF; + + vfsStream::create([ + 'modules' => [ + 'foo' => [ + 'help_topics' => [ + 'foo.topic.html.twig' => $topic_content, + ], + ], + ], + ]); + $discovery = new HelpTopicDiscovery(['foo' => vfsStream::url('root/modules/foo/help_topics')]); + $definition = $discovery->getDefinitions()['foo.topic']; + $this->assertEquals('Test', $definition['label']); + $this->assertInstanceOf(TranslatableMarkup::class, $definition['label']); + $this->assertSame(TRUE, $definition['top_level']); + // Each related plugin ID should be trimmed. + $this->assertSame(['one', 'two', 'three'], $definition['related']); + $this->assertSame('foo', $definition['provider']); + $this->assertSame(HelpTopicTwig::class, $definition['class']); + $this->assertSame(vfsStream::url('root/modules/foo/help_topics/foo.topic.html.twig'), $definition['_discovered_file_path']); + $this->assertSame('foo.topic', $definition['id']); + } + +} diff --git a/core/modules/help_topics/tests/src/Unit/HelpTopicTwigLoaderTest.php b/core/modules/help_topics/tests/src/Unit/HelpTopicTwigLoaderTest.php new file mode 100644 index 000000000..2f5258d05 --- /dev/null +++ b/core/modules/help_topics/tests/src/Unit/HelpTopicTwigLoaderTest.php @@ -0,0 +1,150 @@ +setUpVfs(); + $this->helpLoader = new HelpTopicTwigLoader('\fake\root\path', + $this->getHandlerMock('module'), + $this->getHandlerMock('theme') + ); + } + + /** + * @covers ::__construct + */ + public function testConstructor() { + // Verify that the module/theme directories were added in the constructor, + // and non-existent directories were omitted. + $paths = $this->helpLoader->getPaths(HelpTopicTwigLoader::MAIN_NAMESPACE); + $this->assertEquals(count($paths), 2); + $this->assertTrue(in_array($this->directories['module']['test'] . '/help_topics', $paths)); + $this->assertTrue(in_array($this->directories['theme']['test'] . '/help_topics', $paths)); + } + + /** + * @covers ::getSourceContext + */ + public function testGetSourceContext() { + $source = $this->helpLoader->getSourceContext('@' . HelpTopicTwigLoader::MAIN_NAMESPACE . '/test.topic.html.twig'); + $this->assertEquals('{% line 4 %}

Test

', $source->getCode()); + } + + /** + * @covers ::getSourceContext + */ + public function testGetSourceContextException() { + $this->expectException(LoaderError::class); + $this->expectExceptionMessage("Malformed YAML in help topic \"vfs://root/modules/test/help_topics/test.invalid_yaml.html.twig\":"); + + $source = $this->helpLoader->getSourceContext('@' . HelpTopicTwigLoader::MAIN_NAMESPACE . '/test.invalid_yaml.html.twig'); + } + + /** + * Creates a mock module or theme handler class for the test. + * + * @param string $type + * Type of handler to return: 'module' or 'theme'. + * + * @return \PHPUnit\Framework\MockObject\MockObject + * The mock of module or theme handler. + */ + protected function getHandlerMock($type) { + if ($type == 'module') { + $class = 'Drupal\Core\Extension\ModuleHandlerInterface'; + $method = 'getModuleDirectories'; + } + else { + $class = 'Drupal\Core\Extension\ThemeHandlerInterface'; + $method = 'getThemeDirectories'; + } + + $handler = $this + ->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + + $handler + ->method($method) + ->willReturn($this->directories[$type]); + + return $handler; + } + + /** + * Sets up the virtual file system. + */ + protected function setUpVfs() { + $content = <<Test +EOF; + $invalid_content = <<Test +EOF; + $help_topics_dir = [ + 'help_topics' => [ + 'test.topic.html.twig' => $content, + 'test.invalid_yaml.html.twig' => $invalid_content, + ], + ]; + + vfsStream::setup('root'); + vfsStream::create([ + 'modules' => [ + 'test' => $help_topics_dir, + ], + 'themes' => [ + 'test' => $help_topics_dir, + ], + ]); + + $this->directories = [ + 'root' => vfsStream::url('root'), + 'module' => [ + 'test' => vfsStream::url('root/modules/test'), + 'not_a_dir' => vfsStream::url('root/modules/not_a_dir'), + ], + 'theme' => [ + 'test' => vfsStream::url('root/themes/test'), + 'not_a_dir' => vfsStream::url('root/themes/not_a_dir'), + ], + ]; + } + +} diff --git a/core/modules/help_topics/tests/src/Unit/HelpTopicTwigTest.php b/core/modules/help_topics/tests/src/Unit/HelpTopicTwigTest.php new file mode 100644 index 000000000..201b73b70 --- /dev/null +++ b/core/modules/help_topics/tests/src/Unit/HelpTopicTwigTest.php @@ -0,0 +1,141 @@ + 'test.topic', + 'provider' => 'test', + 'label' => 'This is the topic label', + 'top_level' => TRUE, + 'related' => ['something'], + 'body' => '

This is the topic body

', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->helpTopic = new HelpTopicTwig([], + self::PLUGIN_INFORMATION['id'], + self::PLUGIN_INFORMATION, + $this->getTwigMock()); + } + + /** + * @covers ::getBody + * @covers ::getLabel + */ + public function testText() { + $this->assertEquals($this->helpTopic->getBody(), + ['#markup' => self::PLUGIN_INFORMATION['body']]); + $this->assertEquals($this->helpTopic->getLabel(), + self::PLUGIN_INFORMATION['label']); + } + + /** + * @covers ::getProvider + * @covers ::isTopLevel + * @covers ::getRelated + */ + public function testDefinition() { + $this->assertEquals($this->helpTopic->getProvider(), + self::PLUGIN_INFORMATION['provider']); + $this->assertEquals($this->helpTopic->isTopLevel(), + self::PLUGIN_INFORMATION['top_level']); + $this->assertEquals($this->helpTopic->getRelated(), + self::PLUGIN_INFORMATION['related']); + } + + /** + * @covers ::getCacheContexts + * @covers ::getCacheTags + * @covers ::getCacheMaxAge + */ + public function testCacheInfo() { + $this->assertEquals($this->helpTopic->getCacheContexts(), []); + $this->assertEquals($this->helpTopic->getCacheTags(), ['core.extension']); + $this->assertEquals($this->helpTopic->getCacheMaxAge(), Cache::PERMANENT); + } + + /** + * Creates a mock Twig loader class for the test. + */ + protected function getTwigMock() { + $twig = $this + ->getMockBuilder('Drupal\Core\Template\TwigEnvironment') + ->disableOriginalConstructor() + ->getMock(); + + $twig + ->method('load') + ->willReturn(new FakeTemplateWrapper(self::PLUGIN_INFORMATION['body'])); + + return $twig; + } + +} + +/** + * Defines a fake template class to mock \Twig_TemplateWrapper. + * + * We cannot use getMockBuilder() for this, because the Twig TemplateWrapper + * class is declared "final" and cannot be mocked. + */ +class FakeTemplateWrapper { + + /** + * Body text to return from the render() method. + * + * @var string + */ + protected $body; + + /** + * Constructor. + * + * @param string $body + * Body text to return from the render() method. + */ + public function __construct($body) { + $this->body = $body; + } + + /** + * Mocks the \Twig_TemplateWrapper render() method. + * + * @param array $context + * (optional) Render context. + */ + public function render(array $context = []) { + return $this->body; + } + +} diff --git a/core/modules/help_topics/tests/themes/help_topics_test_theme/help_topics/help_topics_test_theme.test.html.twig b/core/modules/help_topics/tests/themes/help_topics_test_theme/help_topics/help_topics_test_theme.test.html.twig new file mode 100644 index 000000000..79c85eed4 --- /dev/null +++ b/core/modules/help_topics/tests/themes/help_topics_test_theme/help_topics/help_topics_test_theme.test.html.twig @@ -0,0 +1,7 @@ +--- +label: 'XYZ Help Test theme' +top_level: true +related: + - help_topics_test.additional +--- +

{% trans %}This is a theme provided topic.{% endtrans %}

diff --git a/core/modules/help_topics/tests/themes/help_topics_test_theme/help_topics_test_theme.info.yml b/core/modules/help_topics/tests/themes/help_topics_test_theme/help_topics_test_theme.info.yml new file mode 100644 index 000000000..d7d3bfe10 --- /dev/null +++ b/core/modules/help_topics/tests/themes/help_topics_test_theme/help_topics_test_theme.info.yml @@ -0,0 +1,6 @@ +name: Test Help Topics +type: theme +base theme: stable +description: A theme to test help topics. +version: VERSION +core: 8.x diff --git a/core/modules/history/history.info.yml b/core/modules/history/history.info.yml index 3041c830a..006da6297 100644 --- a/core/modules/history/history.info.yml +++ b/core/modules/history/history.info.yml @@ -2,13 +2,7 @@ name: History type: module description: 'Records which user has read which content.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:node - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/history/history.module b/core/modules/history/history.module index 59f0e7fa9..11a45307b 100644 --- a/core/modules/history/history.module +++ b/core/modules/history/history.module @@ -13,6 +13,7 @@ use Drupal\Core\Url; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\history\HistoryRenderCallback; use Drupal\user\UserInterface; /** @@ -81,7 +82,7 @@ function history_read_multiple($nids) { return $return; } - $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', [ + $result = \Drupal::database()->query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', [ ':uid' => \Drupal::currentUser()->id(), ':nids[]' => array_keys($nodes_to_read), ]); @@ -188,9 +189,13 @@ function history_user_delete($account) { * * @return array * A renderable array containing the last read timestamp. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\history\HistoryRenderCallback::lazyBuilder() instead. + * + * @see https://www.drupal.org/node/2966725 */ function history_attach_timestamp($node_id) { - $element = []; - $element['#attached']['drupalSettings']['history']['lastReadTimestamps'][$node_id] = (int) history_read($node_id); - return $element; + @trigger_error('history_attach_timestamp() is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Use \Drupal\history\HistoryRenderCallback::lazyBuilder() instead. See https://www.drupal.org/node/2966725', E_USER_DEPRECATED); + return HistoryRenderCallback::lazyBuilder($node_id); } diff --git a/core/modules/history/src/HistoryRenderCallback.php b/core/modules/history/src/HistoryRenderCallback.php new file mode 100644 index 000000000..c6a92eda6 --- /dev/null +++ b/core/modules/history/src/HistoryRenderCallback.php @@ -0,0 +1,27 @@ +installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installSchema('history', ['history']); + $this->installSchema('system', ['sequences']); + + } + + /** + * Tests history_attach_timestamp() deprecation. + * + * @expectedDeprecation history_attach_timestamp() is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Use \Drupal\history\HistoryRenderCallback::lazyBuilder() instead. See https://www.drupal.org/node/2966725 + */ + public function testHistoryAttachTimestamp() { + $node = Node::create([ + 'title' => 'n1', + 'type' => 'default', + ]); + $node->save(); + + $user1 = User::create([ + 'name' => 'user1', + 'mail' => 'user1@example.com', + ]); + $user1->save(); + + \Drupal::currentUser()->setAccount($user1); + history_write(1); + + $render = history_attach_timestamp(1); + $this->assertEquals(REQUEST_TIME, $render['#attached']['drupalSettings']['history']['lastReadTimestamps'][1]); + } + +} diff --git a/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php b/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php index f66ce5247..9baeff7b2 100644 --- a/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php +++ b/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php @@ -42,7 +42,7 @@ protected function setUp($import_test_views = TRUE) { $this->installSchema('history', ['history']); // Use classy theme because its marker is wrapped in a span so it can be // easily targeted with xpath. - \Drupal::service('theme_handler')->install(['classy']); + \Drupal::service('theme_installer')->install(['classy']); \Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->initTheme('classy')); } diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc index bf69b9044..1afdde2fd 100644 --- a/core/modules/image/image.field.inc +++ b/core/modules/image/image.field.inc @@ -21,11 +21,6 @@ function template_preprocess_image_widget(&$variables) { $variables['attributes'] = ['class' => ['image-widget', 'js-form-managed-file', 'form-managed-file', 'clearfix']]; - if (!empty($element['fids']['#value'])) { - $file = reset($element['#files']); - $element['file_' . $file->id()]['filename']['#suffix'] = ' (' . format_size($file->getSize()) . ') '; - } - $variables['data'] = []; foreach (Element::children($element) as $child) { $variables['data'][$child] = $element[$child]; diff --git a/core/modules/image/image.info.yml b/core/modules/image/image.info.yml index 441928def..c17c7e09a 100644 --- a/core/modules/image/image.info.yml +++ b/core/modules/image/image.info.yml @@ -2,14 +2,8 @@ name: Image type: module description: 'Defines an image field type and provides image manipulation tools.' package: Field types -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:file configure: entity.image_style.collection - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/image/image.install b/core/modules/image/image.install index 0342d512f..ad29b977b 100644 --- a/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -13,7 +13,7 @@ use Drupal\Core\File\FileSystemInterface; */ function image_install() { // Create the styles directory and ensure it's writable. - $directory = file_default_scheme() . '://styles'; + $directory = \Drupal::config('system.file')->get('default_scheme') . '://styles'; \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); } @@ -25,7 +25,7 @@ function image_uninstall() { /** @var \Drupal\Core\File\FileSystemInterface $file_system */ $file_system = \Drupal::service('file_system'); try { - $file_system->deleteRecursive(file_default_scheme() . '://styles'); + $file_system->deleteRecursive(\Drupal::config('system.file')->get('default_scheme') . '://styles'); } catch (FileException $e) { // Ignore failed deletes. diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 4c0e8cca9..d7bb236c3 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -5,19 +5,20 @@ * Exposes global functionality for creating image styles. */ -use Drupal\Core\Url; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\file\FileInterface; -use Drupal\field\FieldStorageConfigInterface; +use Drupal\Core\StreamWrapper\StreamWrapperManager; +use Drupal\Core\Url; use Drupal\field\FieldConfigInterface; +use Drupal\field\FieldStorageConfigInterface; +use Drupal\file\FileInterface; use Drupal\image\Entity\ImageStyle; /** * Image style constant for user presets in the database. * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. * * @see https://www.drupal.org/node/1820974 */ @@ -26,7 +27,7 @@ const IMAGE_STORAGE_NORMAL = 1; /** * Image style constant for user presets that override module-defined presets. * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. * * @see https://www.drupal.org/node/1820974 */ @@ -35,7 +36,7 @@ const IMAGE_STORAGE_OVERRIDE = 2; /** * Image style constant for module-defined presets in code. * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. * * @see https://www.drupal.org/node/1820974 */ @@ -44,7 +45,7 @@ const IMAGE_STORAGE_DEFAULT = 4; /** * Image style constant to represent an editable preset. * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. * * @see https://www.drupal.org/node/1820974 */ @@ -53,7 +54,7 @@ define('IMAGE_STORAGE_EDITABLE', IMAGE_STORAGE_NORMAL | IMAGE_STORAGE_OVERRIDE); /** * Image style constant to represent any module-based preset. * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. * * @see https://www.drupal.org/node/1820974 */ @@ -179,7 +180,8 @@ function image_theme() { * Control the access to files underneath the styles directory. */ function image_file_download($uri) { - $path = file_uri_target($uri); + + $path = StreamWrapperManager::getTarget($uri); // Private file access for image style derivatives. if (strpos($path, 'styles/') === 0) { @@ -189,7 +191,7 @@ function image_file_download($uri) { $args = array_slice($args, 3); // Then the remaining parts are the path to the image. - $original_uri = file_uri_scheme($uri) . '://' . implode('/', $args); + $original_uri = StreamWrapperManager::getScheme($uri) . '://' . implode('/', $args); // Check that the file exists and is an image. $image = \Drupal::service('image.factory')->get($uri); @@ -275,7 +277,6 @@ function image_style_options($include_empty = TRUE) { * - width: The width of the image. * - height: The height of the image. * - style_name: The name of the image style to be applied. - * - attributes: Additional attributes to apply to the image. * - uri: URI of the source image before styling. * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0 * always require an alt attribute. The HTML 5 draft allows the alt @@ -289,7 +290,8 @@ function image_style_options($include_empty = TRUE) { * - http://dev.w3.org/html5/spec/Overview.html#alt * - title: The title text is displayed when the image is hovered in some * popular browsers. - * - attributes: Associative array of attributes to be placed in the img tag. + * - attributes: Associative array of additional attributes to be placed in + * the img tag. */ function template_preprocess_image_style(&$variables) { $style = ImageStyle::load($variables['style_name']); @@ -434,7 +436,7 @@ function image_field_storage_config_update(FieldStorageConfigInterface $field_st } // If the upload destination changed, then move the file. - if ($file_new && (file_uri_scheme($file_new->getFileUri()) != $field_storage->getSetting('uri_scheme'))) { + if ($file_new && (StreamWrapperManager::getScheme($file_new->getFileUri()) != $field_storage->getSetting('uri_scheme'))) { $directory = $field_storage->getSetting('uri_scheme') . '://default_images/'; \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY); file_move($file_new, $directory . $file_new->getFilename()); @@ -472,7 +474,7 @@ function image_field_config_update(FieldConfigInterface $field) { } // If the upload destination changed, then move the file. - if ($file_new && (file_uri_scheme($file_new->getFileUri()) != $field_storage->getSetting('uri_scheme'))) { + if ($file_new && (StreamWrapperManager::getScheme($file_new->getFileUri()) != $field_storage->getSetting('uri_scheme'))) { $directory = $field_storage->getSetting('uri_scheme') . '://default_images/'; \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY); file_move($file_new, $directory . $file_new->getFilename()); diff --git a/core/modules/image/image.views.inc b/core/modules/image/image.views.inc index cca0e3d90..483f989fa 100644 --- a/core/modules/image/image.views.inc +++ b/core/modules/image/image.views.inc @@ -39,11 +39,11 @@ function image_field_views_data(FieldStorageConfigInterface $field_storage) { function image_field_views_data_views_data_alter(array &$data, FieldStorageConfigInterface $field_storage) { $entity_type_id = $field_storage->getTargetEntityTypeId(); $field_name = $field_storage->getName(); - $entity_manager = \Drupal::entityManager(); - $entity_type = $entity_manager->getDefinition($entity_type_id); + $entity_type_manager = \Drupal::entityTypeManager(); + $entity_type = $entity_type_manager->getDefinition($entity_type_id); $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type_id; /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ - $table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping(); + $table_mapping = $entity_type_manager->getStorage($entity_type_id)->getTableMapping(); list($label) = views_entity_field_label($entity_type_id, $field_name); diff --git a/core/modules/image/migrations/d6_imagecache_presets.yml b/core/modules/image/migrations/d6_imagecache_presets.yml index d34da7452..f6c9b5815 100644 --- a/core/modules/image/migrations/d6_imagecache_presets.yml +++ b/core/modules/image/migrations/d6_imagecache_presets.yml @@ -14,7 +14,7 @@ process: plugin: make_unique_entity_field entity_type: image_style field: name - length: 32 + length: 30 label: presetname effects: plugin: d6_imagecache_actions diff --git a/core/modules/image/migrations/state/image.migrate_drupal.yml b/core/modules/image/migrations/state/image.migrate_drupal.yml new file mode 100644 index 000000000..b87b9c7b1 --- /dev/null +++ b/core/modules/image/migrations/state/image.migrate_drupal.yml @@ -0,0 +1,6 @@ +finished: + 6: + imagecache: image + imagefield: image + 7: + image: image diff --git a/core/modules/image/src/Controller/ImageStyleDownloadController.php b/core/modules/image/src/Controller/ImageStyleDownloadController.php index 3d4ee1cc8..2913b9fe5 100644 --- a/core/modules/image/src/Controller/ImageStyleDownloadController.php +++ b/core/modules/image/src/Controller/ImageStyleDownloadController.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Core\Image\ImageFactory; use Drupal\Core\Lock\LockBackendInterface; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Drupal\image\ImageStyleInterface; use Drupal\system\FileDownloadController; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -48,8 +49,11 @@ class ImageStyleDownloadController extends FileDownloadController { * The lock backend. * @param \Drupal\Core\Image\ImageFactory $image_factory * The image factory. + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager + * The stream wrapper manager. */ - public function __construct(LockBackendInterface $lock, ImageFactory $image_factory) { + public function __construct(LockBackendInterface $lock, ImageFactory $image_factory, StreamWrapperManagerInterface $stream_wrapper_manager = NULL) { + parent::__construct($stream_wrapper_manager); $this->lock = $lock; $this->imageFactory = $image_factory; $this->logger = $this->getLogger('image'); @@ -61,7 +65,8 @@ public function __construct(LockBackendInterface $lock, ImageFactory $image_fact public static function create(ContainerInterface $container) { return new static( $container->get('lock'), - $container->get('image.factory') + $container->get('image.factory'), + $container->get('stream_wrapper_manager') ); } @@ -102,9 +107,9 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st // The $target variable for a derivative of a style has // styles//... as structure, so we check if the $target variable // starts with styles/. - $valid = !empty($image_style) && file_stream_wrapper_valid_scheme($scheme); + $valid = !empty($image_style) && $this->streamWrapperManager->isValidScheme($scheme); if (!$this->config('image.settings')->get('allow_insecure_derivatives') || strpos(ltrim($target, '\/'), 'styles/') === 0) { - $valid &= $request->query->get(IMAGE_DERIVATIVE_TOKEN) === $image_style->getPathToken($image_uri); + $valid &= hash_equals($image_style->getPathToken($image_uri), $request->query->get(IMAGE_DERIVATIVE_TOKEN, '')); } if (!$valid) { // Return a 404 (Page Not Found) rather than a 403 (Access Denied) as the diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index 3baded7ce..222c4c697 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -11,6 +11,7 @@ use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Routing\RequestHelper; use Drupal\Core\Site\Settings; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\Core\Url; use Drupal\image\ImageEffectPluginCollection; use Drupal\image\ImageEffectInterface; @@ -173,16 +174,19 @@ protected static function replaceImageStyle(ImageStyleInterface $style) { * {@inheritdoc} */ public function buildUri($uri) { - $source_scheme = $scheme = $this->fileUriScheme($uri); + $source_scheme = $scheme = StreamWrapperManager::getScheme($uri); $default_scheme = $this->fileDefaultScheme(); if ($source_scheme) { - $path = $this->fileUriTarget($uri); + $path = StreamWrapperManager::getTarget($uri); // The scheme of derivative image files only needs to be computed for // source files not stored in the default scheme. if ($source_scheme != $default_scheme) { $class = $this->getStreamWrapperManager()->getClass($source_scheme); - $is_writable = $class::getType() & StreamWrapperInterface::WRITE; + $is_writable = NULL; + if ($class) { + $is_writable = $class::getType() & StreamWrapperInterface::WRITE; + } // Compute the derivative URI scheme. Derivatives created from writable // source stream wrappers will inherit the scheme. Derivatives created @@ -202,6 +206,10 @@ public function buildUri($uri) { */ public function buildUrl($path, $clean_urls = NULL) { $uri = $this->buildUri($path); + + /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */ + $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager'); + // The token query is added even if the // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so // that the emitted links remain valid if it is changed back to the default @@ -214,7 +222,7 @@ public function buildUrl($path, $clean_urls = NULL) { $token_query = []; if (!\Drupal::config('image.settings')->get('suppress_itok_output')) { // The passed $path variable can be either a relative path or a full URI. - $original_uri = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path); + $original_uri = $stream_wrapper_manager::getScheme($path) ? $stream_wrapper_manager->normalizeUri($path) : file_build_uri($path); $token_query = [IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($original_uri)]; } @@ -234,9 +242,9 @@ public function buildUrl($path, $clean_urls = NULL) { // ensure that it is included. Once the file exists it's fine to fall back // to the actual file path, this avoids bootstrapping PHP once the files are // built. - if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { - $directory_path = $this->getStreamWrapperManager()->getViaUri($uri)->getDirectoryPath(); - return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($uri), ['absolute' => TRUE, 'query' => $token_query])->toString(); + if ($clean_urls === FALSE && $stream_wrapper_manager::getScheme($uri) == 'public' && !file_exists($uri)) { + $directory_path = $stream_wrapper_manager->getViaUri($uri)->getDirectoryPath(); + return Url::fromUri('base:' . $directory_path . '/' . $stream_wrapper_manager::getTarget($uri), ['absolute' => TRUE, 'query' => $token_query])->toString(); } $file_url = file_create_url($uri); @@ -505,16 +513,18 @@ protected function addExtension($path) { * @param string $uri * A stream, referenced as "scheme://target" or "data:target". * - * @see file_uri_target() - * - * @todo: Remove when https://www.drupal.org/node/2050759 is in. - * * @return string * A string containing the name of the scheme, or FALSE if none. For * example, the URI "public://example.txt" would return "public". + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\StreamWrapper\StreamWrapperManager::getTarget() instead. + * + * @see https://www.drupal.org/node/3035273 */ protected function fileUriScheme($uri) { - return file_uri_scheme($uri); + @trigger_error('fileUriTarget() is deprecated in drupal:8.8.0. It will be removed from drupal:9.0.0. See https://www.drupal.org/node/3035273', E_USER_DEPRECATED); + return StreamWrapperManager::getScheme($uri); } /** @@ -525,31 +535,31 @@ protected function fileUriScheme($uri) { * @param string $uri * A stream, referenced as "scheme://target" or "data:target". * - * @see file_uri_scheme() - * - * @todo: Convert file_uri_target() into a proper injectable service. - * * @return string|bool * A string containing the target (path), or FALSE if none. * For example, the URI "public://sample/test.txt" would return * "sample/test.txt". + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\StreamWrapper\StreamWrapperManager::getUriTarget() instead. + * + * @see https://www.drupal.org/node/3035273 */ protected function fileUriTarget($uri) { - return file_uri_target($uri); + @trigger_error('fileUriTarget() is deprecated in drupal:8.8.0. It will be removed from drupal:9.0.0. See https://www.drupal.org/node/3035273', E_USER_DEPRECATED); + return StreamWrapperManager::getTarget($uri); } /** - * Provides a wrapper for file_default_scheme() to allow unit testing. + * Provides a wrapper to allow unit testing. * * Gets the default file stream implementation. * - * @todo: Convert file_default_scheme() into a proper injectable service. - * * @return string * 'public', 'private' or any other file scheme defined as the default. */ protected function fileDefaultScheme() { - return file_default_scheme(); + return \Drupal::config('system.file')->get('default_scheme'); } /** diff --git a/core/modules/image/src/Form/ImageStyleEditForm.php b/core/modules/image/src/Form/ImageStyleEditForm.php index c166bb8fb..f33c20b87 100644 --- a/core/modules/image/src/Form/ImageStyleEditForm.php +++ b/core/modules/image/src/Form/ImageStyleEditForm.php @@ -42,7 +42,7 @@ public function __construct(EntityStorageInterface $image_style_storage, ImageEf */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager')->getStorage('image_style'), + $container->get('entity_type.manager')->getStorage('image_style'), $container->get('plugin.manager.image.effect') ); } diff --git a/core/modules/image/src/Form/ImageStyleFormBase.php b/core/modules/image/src/Form/ImageStyleFormBase.php index e446f69af..166faa8f6 100644 --- a/core/modules/image/src/Form/ImageStyleFormBase.php +++ b/core/modules/image/src/Form/ImageStyleFormBase.php @@ -41,7 +41,7 @@ public function __construct(EntityStorageInterface $image_style_storage) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager')->getStorage('image_style') + $container->get('entity_type.manager')->getStorage('image_style') ); } diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php index 8430fdc2f..5250be51d 100644 --- a/core/modules/image/src/ImageStyleInterface.php +++ b/core/modules/image/src/ImageStyleInterface.php @@ -16,7 +16,7 @@ interface ImageStyleInterface extends ConfigEntityInterface { * The replacement image style ID or NULL if no replacement has been * selected. * - * @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal\image\ImageStyleStorageInterface::getReplacementId() instead. * * @see \Drupal\image\ImageStyleStorageInterface::getReplacementId() @@ -37,7 +37,7 @@ public function getName(); * @param string $name * The name of the image style. * - * @return \Drupal\image\ImageStyleInterface + * @return $this * The class instance this method is called on. */ public function setName($name); diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index b48f0d14a..ee99b0f87 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -85,7 +85,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration['view_mode'], $configuration['third_party_settings'], $container->get('current_user'), - $container->get('entity.manager')->getStorage('image_style') + $container->get('entity_type.manager')->getStorage('image_style') ); } diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php index ba4677d7e..ba86a3b92 100644 --- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php +++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php @@ -209,8 +209,6 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#title' => t('Maximum image resolution'), '#element_validate' => [[get_class($this), 'validateResolution']], '#weight' => 4.1, - '#field_prefix' => '
', - '#field_suffix' => '
', '#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of EXIF data in the image.'), ]; $element['max_resolution']['x'] = [ @@ -220,6 +218,7 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#default_value' => $max_resolution[0], '#min' => 1, '#field_suffix' => ' × ', + '#prefix' => '
', ]; $element['max_resolution']['y'] = [ '#type' => 'number', @@ -228,6 +227,7 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#default_value' => $max_resolution[1], '#min' => 1, '#field_suffix' => ' ' . t('pixels'), + '#suffix' => '
', ]; $min_resolution = explode('x', $settings['min_resolution']) + ['', '']; @@ -236,8 +236,6 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#title' => t('Minimum image resolution'), '#element_validate' => [[get_class($this), 'validateResolution']], '#weight' => 4.2, - '#field_prefix' => '
', - '#field_suffix' => '
', '#description' => t('The minimum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'), ]; $element['min_resolution']['x'] = [ @@ -247,6 +245,7 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#default_value' => $min_resolution[0], '#min' => 1, '#field_suffix' => ' × ', + '#prefix' => '
', ]; $element['min_resolution']['y'] = [ '#type' => 'number', @@ -255,6 +254,7 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#default_value' => $min_resolution[1], '#min' => 1, '#field_suffix' => ' ' . t('pixels'), + '#suffix' => '
', ]; // Remove the description option. @@ -481,7 +481,7 @@ public static function validateDefaultImageForm(array &$element, FormStateInterf if (isset($element['fids']['#value'][0])) { $value = $element['fids']['#value'][0]; // Convert the file ID to a uuid. - if ($file = \Drupal::entityManager()->getStorage('file')->load($value)) { + if ($file = \Drupal::entityTypeManager()->getStorage('file')->load($value)) { $value = $file->uuid(); } } @@ -503,8 +503,17 @@ public function isDisplayed() { * Gets the entity manager. * * @return \Drupal\Core\Entity\EntityManagerInterface + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal::entityTypeManager() instead in most cases. If the needed method + * is not on \Drupal\Core\Entity\EntityTypeManagerInterface, see the + * deprecated \Drupal\Core\Entity\EntityManager to find the correct + * interface or service. + * + * @see https://www.drupal.org/node/2549139 */ protected function getEntityManager() { + @trigger_error(__METHOD__ . ' is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal::entityTypeManager() instead in most cases. If the needed method is not on \Drupal\Core\Entity\EntityTypeManagerInterface, see the deprecated \Drupal\Core\Entity\EntityManager to find the correct interface or service. See https://www.drupal.org/node/2549139', E_USER_DEPRECATED); if (!isset($this->entityManager)) { $this->entityManager = \Drupal::entityManager(); } diff --git a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php index 2dde55278..47aa51fa1 100644 --- a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php +++ b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php @@ -326,7 +326,7 @@ public function onDependencyRemoval(array $dependencies) { if ($style_id && $style = ImageStyle::load($style_id)) { if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) { /** @var \Drupal\image\ImageStyleStorageInterface $storage */ - $storage = \Drupal::entityManager()->getStorage($style->getEntityTypeId()); + $storage = \Drupal::entityTypeManager()->getStorage($style->getEntityTypeId()); $replacement_id = $storage->getReplacementId($style_id); // If a valid replacement has been provided in the storage, replace the // preview image style with the replacement. diff --git a/core/modules/image/src/Plugin/migrate/cckfield/d7/ImageField.php b/core/modules/image/src/Plugin/migrate/cckfield/d7/ImageField.php index 081ff055b..52d442adf 100644 --- a/core/modules/image/src/Plugin/migrate/cckfield/d7/ImageField.php +++ b/core/modules/image/src/Plugin/migrate/cckfield/d7/ImageField.php @@ -15,7 +15,7 @@ * destination_module = "file" * ) * - * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. Use * \Drupal\image\Plugin\migrate\field\d7\ImageField instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/image/src/Plugin/migrate/process/d6/ImageCacheActions.php b/core/modules/image/src/Plugin/migrate/process/d6/ImageCacheActions.php index 0d40769ae..c677ca378 100644 --- a/core/modules/image/src/Plugin/migrate/process/d6/ImageCacheActions.php +++ b/core/modules/image/src/Plugin/migrate/process/d6/ImageCacheActions.php @@ -20,6 +20,9 @@ public function transform($value, MigrateExecutableInterface $migrate_executable $effects = []; foreach ($row->getSourceProperty('actions') as $action) { + if (empty($action['action'])) { + continue; + } $id = preg_replace('/^imagecache/', 'image', $action['action']); if ($id === 'image_crop') { diff --git a/core/modules/image/src/Tests/ImageFieldTestBase.php b/core/modules/image/src/Tests/ImageFieldTestBase.php index 980b9e619..d4095be99 100644 --- a/core/modules/image/src/Tests/ImageFieldTestBase.php +++ b/core/modules/image/src/Tests/ImageFieldTestBase.php @@ -24,7 +24,7 @@ /** * This class provides methods specifically for testing Image's field handling. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\image\Functional\ImageFieldTestBase instead. */ abstract class ImageFieldTestBase extends WebTestBase { diff --git a/core/modules/image/templates/image-formatter.html.twig b/core/modules/image/templates/image-formatter.html.twig index 63ca3c6fd..b0d1aa2bd 100644 --- a/core/modules/image/templates/image-formatter.html.twig +++ b/core/modules/image/templates/image-formatter.html.twig @@ -14,7 +14,7 @@ */ #} {% if url %} - {{ image }} + {{ link(image, url) }} {% else %} {{ image }} {% endif %} diff --git a/core/modules/image/tests/modules/image_access_test_hidden/image_access_test_hidden.info.yml b/core/modules/image/tests/modules/image_access_test_hidden/image_access_test_hidden.info.yml index 7efd3b161..293265b09 100644 --- a/core/modules/image/tests/modules/image_access_test_hidden/image_access_test_hidden.info.yml +++ b/core/modules/image/tests/modules/image_access_test_hidden/image_access_test_hidden.info.yml @@ -2,11 +2,5 @@ name: 'Image access test for hidden fields' type: module description: 'Provides an entity field access hook implementation to set an image field as hidden.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/image/tests/modules/image_module_test/image_module_test.info.yml b/core/modules/image/tests/modules/image_module_test/image_module_test.info.yml index c46d77ad6..7c4d3ace4 100644 --- a/core/modules/image/tests/modules/image_module_test/image_module_test.info.yml +++ b/core/modules/image/tests/modules/image_module_test/image_module_test.info.yml @@ -2,11 +2,5 @@ name: 'Image test' type: module description: 'Provides hook implementations for testing Image module functionality.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/image/tests/modules/image_test_views/image_test_views.info.yml b/core/modules/image/tests/modules/image_test_views/image_test_views.info.yml index 22ebb329d..eb9cc3a26 100644 --- a/core/modules/image/tests/modules/image_test_views/image_test_views.info.yml +++ b/core/modules/image/tests/modules/image_test_views/image_test_views.info.yml @@ -2,14 +2,8 @@ name: 'Image test views' type: module description: 'Provides default views for views image tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:image - drupal:views - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml b/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml index 8ea166b9b..31011264d 100644 --- a/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml +++ b/core/modules/image/tests/modules/image_test_views/test_views/views.view.test_image_user_image_data.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: users_field_data base_field: uid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/image/tests/src/Functional/FileMoveTest.php b/core/modules/image/tests/src/Functional/FileMoveTest.php index fd2536358..c2a3a1bbe 100644 --- a/core/modules/image/tests/src/Functional/FileMoveTest.php +++ b/core/modules/image/tests/src/Functional/FileMoveTest.php @@ -27,6 +27,11 @@ class FileMoveTest extends BrowserTestBase { */ public static $modules = ['image']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests moving a randomly generated image. */ diff --git a/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonAnonTest.php b/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonAnonTest.php index 61da51f7d..a010f68f0 100644 --- a/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonAnonTest.php +++ b/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonAnonTest.php @@ -17,6 +17,11 @@ class ImageStyleHalJsonAnonTest extends ImageStyleResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonBasicAuthTest.php b/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonBasicAuthTest.php index a6ff4941a..74a351663 100644 --- a/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonBasicAuthTest.php +++ b/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class ImageStyleHalJsonBasicAuthTest extends ImageStyleResourceTestBase { */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonCookieTest.php b/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonCookieTest.php index 731e356c9..e899b6d13 100644 --- a/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonCookieTest.php +++ b/core/modules/image/tests/src/Functional/Hal/ImageStyleHalJsonCookieTest.php @@ -17,6 +17,11 @@ class ImageStyleHalJsonCookieTest extends ImageStyleResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php index ca2729398..46c96ca24 100644 --- a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php +++ b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php @@ -23,6 +23,11 @@ class ImageAdminStylesTest extends ImageFieldTestBase { compareFiles as drupalCompareFiles; } + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Given an image style, generate an image. */ @@ -44,7 +49,11 @@ public function createSampleImage(ImageStyleInterface $style) { * Count the number of images currently create for a style. */ public function getImageCount(ImageStyleInterface $style) { - return count(file_scan_directory('public://styles/' . $style->id(), '/.*/')); + $count = 0; + if (is_dir('public://styles/' . $style->id())) { + $count = count(\Drupal::service('file_system')->scanDirectory('public://styles/' . $style->id(), '/.*/')); + } + return $count; } /** @@ -61,7 +70,7 @@ public function testNumericStyleName() { $this->drupalPostForm('admin/config/media/image-styles/add', $edit, t('Create new style')); $this->assertRaw(t('Style %name was created.', ['%name' => $style_label])); $options = image_style_options(); - $this->assertTrue(array_key_exists($style_name, $options), format_string('Array key %key exists.', ['%key' => $style_name])); + $this->assertTrue(array_key_exists($style_name, $options), new FormattableMarkup('Array key %key exists.', ['%key' => $style_name])); } /** @@ -159,7 +168,7 @@ public function testStyle() { // Assert that every effect was saved. foreach (array_keys($effect_edits) as $effect_name) { - $this->assertTrue(isset($uuids[$effect_name]), format_string( + $this->assertTrue(isset($uuids[$effect_name]), new FormattableMarkup( 'A %effect_name effect was saved with ID %uuid', [ '%effect_name' => $effect_name, @@ -198,7 +207,7 @@ public function testStyle() { // Create an image to make sure it gets flushed after saving. $image_path = $this->createSampleImage($style); - $this->assertEqual($this->getImageCount($style), 1, format_string('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); + $this->assertEqual($this->getImageCount($style), 1, new FormattableMarkup('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); $this->drupalPostForm($style_path, $edit, t('Save')); @@ -208,7 +217,7 @@ public function testStyle() { // Check that the URL was updated. $this->drupalGet($style_path); $this->assertTitle(t('Edit style @name | Drupal', ['@name' => $style_label])); - $this->assertResponse(200, format_string('Image style %original renamed to %new', ['%original' => $style->id(), '%new' => $style_name])); + $this->assertResponse(200, new FormattableMarkup('Image style %original renamed to %new', ['%original' => $style->id(), '%new' => $style_name])); // Check that the available image effects are properly sorted. $option = $this->xpath('//select[@id=:id]//option', [':id' => 'edit-new--2']); @@ -217,7 +226,7 @@ public function testStyle() { // Check that the image was flushed after updating the style. // This is especially important when renaming the style. Make sure that // the old image directory has been deleted. - $this->assertEqual($this->getImageCount($style), 0, format_string('Image style %style was flushed after renaming the style and updating the order of effects.', ['%style' => $style->label()])); + $this->assertEqual($this->getImageCount($style), 0, new FormattableMarkup('Image style %style was flushed after renaming the style and updating the order of effects.', ['%style' => $style->label()])); // Load the style by the new name with the new weights. $style = ImageStyle::load($style_name); @@ -238,7 +247,7 @@ public function testStyle() { // Create an image to make sure it gets flushed after deleting an effect. $image_path = $this->createSampleImage($style); - $this->assertEqual($this->getImageCount($style), 1, format_string('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); + $this->assertEqual($this->getImageCount($style), 1, new FormattableMarkup('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); // Delete the 'image_crop' effect from the style. $this->drupalPostForm($style_path . '/effects/' . $uuids['image_crop'] . '/delete', [], t('Delete')); @@ -252,7 +261,7 @@ public function testStyle() { // actually deleted. $entity_type_manager = $this->container->get('entity_type.manager'); $style = $entity_type_manager->getStorage('image_style')->loadUnchanged($style->id()); - $this->assertFalse($style->getEffects()->has($uuids['image_crop']), format_string( + $this->assertFalse($style->getEffects()->has($uuids['image_crop']), new FormattableMarkup( 'Effect with ID %uuid no longer found on image style %style', [ '%uuid' => $uuids['image_crop'], @@ -277,10 +286,10 @@ public function testStyle() { $this->drupalPostForm($style_path . '/delete', [], t('Delete')); // Confirm the style directory has been removed. - $directory = file_default_scheme() . '://styles/' . $style_name; - $this->assertFalse(is_dir($directory), format_string('Image style %style directory removed on style deletion.', ['%style' => $style->label()])); + $directory = 'public://styles/' . $style_name; + $this->assertFalse(is_dir($directory), new FormattableMarkup('Image style %style directory removed on style deletion.', ['%style' => $style->label()])); - $this->assertFalse(ImageStyle::load($style_name), format_string('Image style %style successfully deleted.', ['%style' => $style->label()])); + $this->assertNull(ImageStyle::load($style_name), new FormattableMarkup('Image style %style successfully deleted.', ['%style' => $style->label()])); // Test empty text when there are no image styles. @@ -311,7 +320,8 @@ public function testStyleReplacement() { // Create an image field that uses the new style. $field_name = strtolower($this->randomMachineName(10)); $this->createImageField($field_name, 'article'); - entity_get_display('node', 'article', 'default') + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article') ->setComponent($field_name, [ 'type' => 'image', 'settings' => ['image_style' => $style_name], @@ -329,7 +339,7 @@ public function testStyleReplacement() { // Test that image is displayed using newly created style. $this->drupalGet('node/' . $nid); - $this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), format_string('Image displayed using style @style.', ['@style' => $style_name])); + $this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), new FormattableMarkup('Image displayed using style @style.', ['@style' => $style_name])); // Rename the style and make sure the image field is updated. $new_style_name = strtolower($this->randomMachineName(10)); @@ -339,7 +349,7 @@ public function testStyleReplacement() { 'label' => $new_style_label, ]; $this->drupalPostForm($style_path . $style_name, $edit, t('Save')); - $this->assertText(t('Changes to the style have been saved.'), format_string('Style %name was renamed to %new_name.', ['%name' => $style_name, '%new_name' => $new_style_name])); + $this->assertText(t('Changes to the style have been saved.'), new FormattableMarkup('Style %name was renamed to %new_name.', ['%name' => $style_name, '%new_name' => $new_style_name])); $this->drupalGet('node/' . $nid); // Reload the image style using the new name. @@ -456,7 +466,8 @@ public function testConfigImport() { // Create an image field that uses the new style. $field_name = strtolower($this->randomMachineName(10)); $this->createImageField($field_name, 'article'); - entity_get_display('node', 'article', 'default') + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article') ->setComponent($field_name, [ 'type' => 'image', 'settings' => ['image_style' => $style_name], @@ -474,7 +485,7 @@ public function testConfigImport() { // Test that image is displayed using newly created style. $this->drupalGet('node/' . $nid); - $this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), format_string('Image displayed using style @style.', ['@style' => $style_name])); + $this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), new FormattableMarkup('Image displayed using style @style.', ['@style' => $style_name])); // Copy config to sync, and delete the image style. $sync = $this->container->get('config.storage.sync'); @@ -488,7 +499,7 @@ public function testConfigImport() { $sync->delete('image.style.' . $style_name); $this->configImporter()->import(); - $this->assertFalse(ImageStyle::load($style_name), 'Style deleted after config import.'); + $this->assertNull(ImageStyle::load($style_name), 'Style deleted after config import.'); $this->assertEqual($this->getImageCount($style), 0, 'Image style was flushed after being deleted by config import.'); } diff --git a/core/modules/image/tests/src/Functional/ImageDimensionsTest.php b/core/modules/image/tests/src/Functional/ImageDimensionsTest.php index 4c80bb177..6a969e572 100644 --- a/core/modules/image/tests/src/Functional/ImageDimensionsTest.php +++ b/core/modules/image/tests/src/Functional/ImageDimensionsTest.php @@ -26,6 +26,11 @@ class ImageDimensionsTest extends BrowserTestBase { */ public static $modules = ['image', 'image_module_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected $profile = 'testing'; /** diff --git a/core/modules/image/tests/src/Functional/ImageEffectsTest.php b/core/modules/image/tests/src/Functional/ImageEffectsTest.php index f6b4ab36e..08713f005 100644 --- a/core/modules/image/tests/src/Functional/ImageEffectsTest.php +++ b/core/modules/image/tests/src/Functional/ImageEffectsTest.php @@ -19,6 +19,11 @@ class ImageEffectsTest extends ToolkitTestBase { */ public static $modules = ['image', 'image_test', 'image_module_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The image effect manager. * diff --git a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php index 3998b350e..f08198268 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php @@ -2,7 +2,9 @@ namespace Drupal\Tests\image\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\File\FileSystemInterface; use Drupal\field\Entity\FieldConfig; use Drupal\file\Entity\File; use Drupal\field\Entity\FieldStorageConfig; @@ -31,18 +33,23 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase { */ public static $modules = ['field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests CRUD for fields and field storages with default images. */ public function testDefaultImages() { - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); // Create files to use as the default images. $files = $this->drupalGetTestFiles('image'); // Create 10 files so the default image fids are not a single value. for ($i = 1; $i <= 10; $i++) { $filename = $this->randomMachineName() . "$i"; $desired_filepath = 'public://' . $filename; - \Drupal::service('file_system')->copy($files[0]->uri, $desired_filepath, FILE_EXISTS_ERROR); + \Drupal::service('file_system')->copy($files[0]->uri, $desired_filepath, FileSystemInterface::EXISTS_ERROR); $file = File::create(['uri' => $desired_filepath, 'filename' => $filename, 'name' => $filename]); $file->save(); } @@ -107,11 +114,14 @@ public function testDefaultImages() { ]); $field2->save(); - $widget_settings = entity_get_form_display('node', $field->getTargetBundle(), 'default')->getComponent($field_name); - entity_get_form_display('node', 'page', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + + $widget_settings = $display_repository->getFormDisplay('node', $field->getTargetBundle())->getComponent($field_name); + $display_repository->getFormDisplay('node', 'page') ->setComponent($field_name, $widget_settings) ->save(); - entity_get_display('node', 'page', 'default') + $display_repository->getViewDisplay('node', 'page') ->setComponent($field_name) ->save(); @@ -122,7 +132,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field_storage']->id(), - format_string( + new FormattableMarkup( 'Article image field storage default equals expected file ID of @fid.', ['@fid' => $default_images['field_storage']->id()] ) @@ -132,7 +142,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field']->id(), - format_string( + new FormattableMarkup( 'Article image field default equals expected file ID of @fid.', ['@fid' => $default_images['field']->id()] ) @@ -143,7 +153,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field_storage']->id(), - format_string( + new FormattableMarkup( 'Page image field storage default equals expected file ID of @fid.', ['@fid' => $default_images['field_storage']->id()] ) @@ -154,7 +164,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field2']->id(), - format_string( + new FormattableMarkup( 'Page image field default equals expected file ID of @fid.', ['@fid' => $default_images['field2']->id()] ) @@ -166,7 +176,7 @@ public function testDefaultImages() { $this->assertEqual( $article_built[$field_name][0]['#item']->target_id, $default_images['field']->id(), - format_string( + new FormattableMarkup( 'A new article node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field']->id()] ) @@ -185,7 +195,7 @@ public function testDefaultImages() { $this->assertEqual( $page_built[$field_name][0]['#item']->target_id, $default_images['field2']->id(), - format_string( + new FormattableMarkup( 'A new page node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field2']->id()] ) @@ -203,7 +213,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field_storage_new']->id(), - format_string( + new FormattableMarkup( 'Updated image field storage default equals expected file ID of @fid.', ['@fid' => $default_images['field_storage_new']->id()] ) @@ -216,7 +226,7 @@ public function testDefaultImages() { $this->assertEqual( $article_built[$field_name][0]['#item']->target_id, $default_images['field']->id(), - format_string( + new FormattableMarkup( 'An existing article node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field']->id()] ) @@ -224,7 +234,7 @@ public function testDefaultImages() { $this->assertEqual( $page_built[$field_name][0]['#item']->target_id, $default_images['field2']->id(), - format_string( + new FormattableMarkup( 'An existing page node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field2']->id()] ) @@ -241,7 +251,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field_new']->id(), - format_string( + new FormattableMarkup( 'Updated article image field default equals expected file ID of @fid.', ['@fid' => $default_images['field_new']->id()] ) @@ -256,7 +266,7 @@ public function testDefaultImages() { $this->assertEqual( $article_built[$field_name][0]['#item']->target_id, $default_images['field_new']->id(), - format_string( + new FormattableMarkup( 'An existing article node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field_new']->id()] ) @@ -265,7 +275,7 @@ public function testDefaultImages() { $this->assertEqual( $page_built[$field_name][0]['#item']->target_id, $default_images['field2']->id(), - format_string( + new FormattableMarkup( 'An existing page node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field2']->id()] ) @@ -298,7 +308,7 @@ public function testDefaultImages() { $this->assertEqual( $article_built[$field_name][0]['#item']->target_id, $default_images['field_storage_new']->id(), - format_string( + new FormattableMarkup( 'An existing article node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field_storage_new']->id()] ) @@ -307,7 +317,7 @@ public function testDefaultImages() { $this->assertEqual( $page_built[$field_name][0]['#item']->target_id, $default_images['field2']->id(), - format_string( + new FormattableMarkup( 'An existing page node without an image has the expected default image file ID of @fid.', ['@fid' => $default_images['field2']->id()] ) @@ -337,7 +347,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field_storage_private']->id(), - format_string( + new FormattableMarkup( 'Updated image field storage default equals expected file ID of @fid.', ['@fid' => $default_images['field_storage_private']->id()] ) @@ -356,7 +366,7 @@ public function testDefaultImages() { $this->assertFieldByXpath( '//input[@name="settings[default_image][uuid][fids]"]', $default_images['field_private']->id(), - format_string( + new FormattableMarkup( 'Updated article image field default equals expected file ID of @fid.', ['@fid' => $default_images['field_private']->id()] ) diff --git a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php index b1355fe05..96baad54a 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php @@ -2,13 +2,15 @@ namespace Drupal\Tests\image\Functional; -use Drupal\Core\Url; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\StreamWrapper\StreamWrapperManager; +use Drupal\Core\Url; use Drupal\field\Entity\FieldStorageConfig; -use Drupal\Tests\TestFileCreationTrait; +use Drupal\image\Entity\ImageStyle; use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait; +use Drupal\Tests\TestFileCreationTrait; use Drupal\user\RoleInterface; -use Drupal\image\Entity\ImageStyle; /** * Tests the display of image fields. @@ -32,6 +34,11 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { */ public static $modules = ['field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Test image formatters on node display for public files. */ @@ -54,7 +61,7 @@ public function testImageFieldFormattersPrivate() { public function _testImageFieldFormatters($scheme) { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $field_name = strtolower($this->randomMachineName()); $field_settings = ['alt_field_required' => 0]; $instance = $this->createImageField($field_name, 'article', ['uri_scheme' => $scheme], $field_settings); @@ -117,7 +124,8 @@ public function _testImageFieldFormatters($scheme) { 'type' => 'image', 'settings' => ['image_link' => 'file'], ]; - $display = entity_get_display('node', $node->getType(), 'default'); + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', $node->getType()); $display->setComponent($field_name, $display_options) ->save(); @@ -229,7 +237,7 @@ public function _testImageFieldFormatters($scheme) { public function testImageFieldSettings() { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $test_image = current($this->drupalGetTestFiles('image')); list(, $test_image_extension) = explode('.', $test_image->filename); $field_name = strtolower($this->randomMachineName()); @@ -279,7 +287,7 @@ public function testImageFieldSettings() { $file = $node->{$field_name}->entity; $url = file_url_transform_relative(ImageStyle::load('medium')->buildUrl($file->getFileUri())); - $this->assertTrue($this->cssSelect('img[width=40][height=20][class=image-style-medium][src="' . $url . '"]')); + $this->assertSession()->elementExists('css', 'img[width=40][height=20][class=image-style-medium][src="' . $url . '"]'); // Add alt/title fields to the image and verify that they are displayed. $image = [ @@ -329,7 +337,7 @@ public function testImageFieldSettings() { $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); // Add the required alt text. $this->drupalPostForm(NULL, [$field_name . '[1][alt]' => $alt], t('Save')); - $this->assertText(format_string('Article @title has been updated.', ['@title' => $node->getTitle()])); + $this->assertText(new FormattableMarkup('Article @title has been updated.', ['@title' => $node->getTitle()])); // Assert ImageWidget::process() calls FieldWidget::process(). $this->drupalGet('node/' . $node->id() . '/edit'); @@ -348,7 +356,7 @@ public function testImageFieldDefaultImage() { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); // Create a new image field. $field_name = strtolower($this->randomMachineName()); $this->createImageField($field_name, 'article'); @@ -375,7 +383,7 @@ public function testImageFieldDefaultImage() { ]; $this->drupalPostForm("admin/structure/types/manage/article/fields/node.article.$field_name/storage", $edit, t('Save field settings')); // Clear field definition cache so the new default image is detected. - \Drupal::entityManager()->clearCachedFieldDefinitions(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); $field_storage = FieldStorageConfig::loadByName('node', $field_name); $default_image = $field_storage->getSetting('default_image'); $file = \Drupal::service('entity.repository')->loadEntityByUuid('file', $default_image['uuid']); @@ -428,10 +436,10 @@ public function testImageFieldDefaultImage() { $this->getSession()->getPage()->pressButton(t('Save field settings')); // Clear field definition cache so the new default image is detected. - \Drupal::entityManager()->clearCachedFieldDefinitions(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); $field_storage = FieldStorageConfig::loadByName('node', $field_name); $default_image = $field_storage->getSetting('default_image'); - $this->assertFalse($default_image['uuid'], 'Default image removed from field.'); + $this->assertEmpty($default_image['uuid'], 'Default image removed from field.'); // Create an image field that uses the private:// scheme and test that the // default image works as expected. $private_field_name = strtolower($this->randomMachineName()); @@ -445,12 +453,13 @@ public function testImageFieldDefaultImage() { ]; $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.' . $private_field_name . '/storage', $edit, t('Save field settings')); // Clear field definition cache so the new default image is detected. - \Drupal::entityManager()->clearCachedFieldDefinitions(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); $private_field_storage = FieldStorageConfig::loadByName('node', $private_field_name); $default_image = $private_field_storage->getSetting('default_image'); $file = \Drupal::service('entity.repository')->loadEntityByUuid('file', $default_image['uuid']); - $this->assertEqual('private', file_uri_scheme($file->getFileUri()), 'Default image uses private:// scheme.'); + + $this->assertEqual('private', StreamWrapperManager::getScheme($file->getFileUri()), 'Default image uses private:// scheme.'); $this->assertTrue($file->isPermanent(), 'The default image status is permanent.'); // Create a new node with no image attached and ensure that default private // image is displayed. diff --git a/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php index 86ad32d57..59627521a 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php @@ -17,6 +17,11 @@ class ImageFieldValidateTest extends ImageFieldTestBase { compareFiles as drupalCompareFiles; } + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test image validity. */ @@ -40,7 +45,11 @@ public function testValid() { $this->drupalPostForm(NULL, [], t('Save')); // Get invalid image test files from simpletest. - $files = file_scan_directory(drupal_get_path('module', 'simpletest') . '/files', '/invalid-img-.*/'); + $dir = 'core/tests/fixtures/files'; + $files = []; + if (is_dir($dir)) { + $files = $file_system->scanDirectory($dir, '/invalid-img-.*/'); + } $invalid_image_files = []; foreach ($files as $file) { $invalid_image_files[$file->filename] = $file; diff --git a/core/modules/image/tests/src/Functional/ImageFieldWidgetTest.php b/core/modules/image/tests/src/Functional/ImageFieldWidgetTest.php index cdec5b905..655e7ab8a 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldWidgetTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldWidgetTest.php @@ -11,6 +11,11 @@ */ class ImageFieldWidgetTest extends ImageFieldTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests file widget element. */ diff --git a/core/modules/image/tests/src/Functional/ImageOnTranslatedEntityTest.php b/core/modules/image/tests/src/Functional/ImageOnTranslatedEntityTest.php index 15ca75828..c0f01f271 100644 --- a/core/modules/image/tests/src/Functional/ImageOnTranslatedEntityTest.php +++ b/core/modules/image/tests/src/Functional/ImageOnTranslatedEntityTest.php @@ -22,6 +22,11 @@ class ImageOnTranslatedEntityTest extends ImageFieldTestBase { */ public static $modules = ['language', 'content_translation', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The name of the image field used in the test. * @@ -92,7 +97,7 @@ public function testSyncedImages() { // Verify that the image field on the "Basic basic" node type is // translatable. - $definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'basicpage'); + $definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'basicpage'); $this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node image field is translatable.'); // Create a default language node. diff --git a/core/modules/image/tests/src/Functional/ImageStyleDeleteTest.php b/core/modules/image/tests/src/Functional/ImageStyleDeleteTest.php index fff27601c..71554333b 100644 --- a/core/modules/image/tests/src/Functional/ImageStyleDeleteTest.php +++ b/core/modules/image/tests/src/Functional/ImageStyleDeleteTest.php @@ -12,6 +12,11 @@ */ class ImageStyleDeleteTest extends ImageFieldTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php b/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php index 00b9615db..a5036eec8 100644 --- a/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php +++ b/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\image\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\image\Entity\ImageStyle; use Drupal\Tests\TestFileCreationTrait; @@ -17,6 +18,11 @@ class ImageStyleFlushTest extends ImageFieldTestBase { compareFiles as drupalCompareFiles; } + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Given an image style and a wrapper, generate an image. */ @@ -41,7 +47,11 @@ public function createSampleImage($style, $wrapper) { * Count the number of images currently created for a style in a wrapper. */ public function getImageCount($style, $wrapper) { - return count(file_scan_directory($wrapper . '://styles/' . $style->id(), '/.*/')); + $count = 0; + if (is_dir($wrapper . '://styles/' . $style->id())) { + $count = count(\Drupal::service('file_system')->scanDirectory($wrapper . '://styles/' . $style->id(), '/.*/')); + } + return $count; } /** @@ -88,11 +98,11 @@ public function testFlush() { $image_path = $this->createSampleImage($style, 'public'); // Expecting to find 2 images, one is the sample.png image shown in // image style preview. - $this->assertEqual($this->getImageCount($style, 'public'), 2, format_string('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); + $this->assertEqual($this->getImageCount($style, 'public'), 2, new FormattableMarkup('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); // Create an image for the 'private' wrapper. $image_path = $this->createSampleImage($style, 'private'); - $this->assertEqual($this->getImageCount($style, 'private'), 1, format_string('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); + $this->assertEqual($this->getImageCount($style, 'private'), 1, new FormattableMarkup('Image style %style image %file successfully generated.', ['%style' => $style->label(), '%file' => $image_path])); // Remove the 'image_scale' effect and updates the style, which in turn // forces an image style flush. @@ -107,10 +117,10 @@ public function testFlush() { $this->assertResponse(200); // Post flush, expected 1 image in the 'public' wrapper (sample.png). - $this->assertEqual($this->getImageCount($style, 'public'), 1, format_string('Image style %style flushed correctly for %wrapper wrapper.', ['%style' => $style->label(), '%wrapper' => 'public'])); + $this->assertEqual($this->getImageCount($style, 'public'), 1, new FormattableMarkup('Image style %style flushed correctly for %wrapper wrapper.', ['%style' => $style->label(), '%wrapper' => 'public'])); // Post flush, expected no image in the 'private' wrapper. - $this->assertEqual($this->getImageCount($style, 'private'), 0, format_string('Image style %style flushed correctly for %wrapper wrapper.', ['%style' => $style->label(), '%wrapper' => 'private'])); + $this->assertEqual($this->getImageCount($style, 'private'), 0, new FormattableMarkup('Image style %style flushed correctly for %wrapper wrapper.', ['%style' => $style->label(), '%wrapper' => 'private'])); } } diff --git a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php index 4a9e57d83..194f6e57a 100644 --- a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php +++ b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\image\Functional; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\image\Entity\ImageStyle; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Tests\BrowserTestBase; @@ -27,6 +28,11 @@ class ImageStylesPathAndUrlTest extends BrowserTestBase { */ public static $modules = ['image', 'image_module_test', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The image style. * @@ -189,7 +195,7 @@ public function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_s // match the desired scheme before testing this, then switch it back to the // "temporary" scheme used throughout this test afterwards. $this->config('system.file')->set('default_scheme', $scheme)->save(); - $relative_path = file_uri_target($original_uri); + $relative_path = StreamWrapperManager::getTarget($original_uri); $generate_url_from_relative_path = $this->style->buildUrl($relative_path, $clean_url); $this->assertEqual($generate_url, $generate_url_from_relative_path); $this->config('system.file')->set('default_scheme', 'temporary')->save(); diff --git a/core/modules/image/tests/src/Functional/QuickEditImageControllerTest.php b/core/modules/image/tests/src/Functional/QuickEditImageControllerTest.php index a037d4808..853a030b8 100644 --- a/core/modules/image/tests/src/Functional/QuickEditImageControllerTest.php +++ b/core/modules/image/tests/src/Functional/QuickEditImageControllerTest.php @@ -24,6 +24,11 @@ class QuickEditImageControllerTest extends BrowserTestBase { */ public static $modules = ['node', 'image', 'quickedit']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The machine name of our image field. * @@ -126,7 +131,7 @@ public function testValidImageUpload() { break; } } - $this->assertTrue($valid_image); + $this->assertNotFalse($valid_image); $this->drupalLogin($this->contentAuthorUser); $this->uploadImage($valid_image, $node->id(), $this->fieldName, $node->language()->getId()); @@ -155,7 +160,7 @@ public function testInvalidUpload() { break; } } - $this->assertTrue($invalid_image); + $this->assertNotFalse($invalid_image); $this->drupalLogin($this->contentAuthorUser); $this->uploadImage($invalid_image, $node->id(), $this->fieldName, $node->language()->getId()); diff --git a/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonAnonTest.php b/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonAnonTest.php index e367fb6e6..a6b62aa34 100644 --- a/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonAnonTest.php +++ b/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonAnonTest.php @@ -21,4 +21,9 @@ class ImageStyleJsonAnonTest extends ImageStyleResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonBasicAuthTest.php b/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonBasicAuthTest.php index ab6ae57d5..5d4ba8648 100644 --- a/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonBasicAuthTest.php +++ b/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ImageStyleJsonBasicAuthTest extends ImageStyleResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonCookieTest.php b/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonCookieTest.php index 502123366..0d0bb88e8 100644 --- a/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonCookieTest.php +++ b/core/modules/image/tests/src/Functional/Rest/ImageStyleJsonCookieTest.php @@ -26,4 +26,9 @@ class ImageStyleJsonCookieTest extends ImageStyleResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlAnonTest.php b/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlAnonTest.php index 427641aa4..8f976f308 100644 --- a/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlAnonTest.php +++ b/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlAnonTest.php @@ -23,6 +23,11 @@ class ImageStyleXmlAnonTest extends ImageStyleResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlBasicAuthTest.php b/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlBasicAuthTest.php index cd376a314..052a50e97 100644 --- a/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlBasicAuthTest.php +++ b/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ImageStyleXmlBasicAuthTest extends ImageStyleResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlCookieTest.php b/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlCookieTest.php index 7ca459a0a..75554393e 100644 --- a/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlCookieTest.php +++ b/core/modules/image/tests/src/Functional/Rest/ImageStyleXmlCookieTest.php @@ -28,6 +28,11 @@ class ImageStyleXmlCookieTest extends ImageStyleResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/image/tests/src/FunctionalJavascript/ImageAdminStylesTest.php b/core/modules/image/tests/src/FunctionalJavascript/ImageAdminStylesTest.php index e573c89a8..acc4bdc5b 100644 --- a/core/modules/image/tests/src/FunctionalJavascript/ImageAdminStylesTest.php +++ b/core/modules/image/tests/src/FunctionalJavascript/ImageAdminStylesTest.php @@ -11,6 +11,11 @@ */ class ImageAdminStylesTest extends ImageFieldTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests editing Ajax-enabled image effect forms. */ @@ -54,7 +59,7 @@ public function testAjaxEnabledEffectForm() { $this->getSession()->getPage()->pressButton('Ajax refresh'); $this->assertTrue($page->waitFor(10, function ($page) { $ajax_value = $page->find('css', '#ajax-value')->getText(); - return preg_match('/^Ajax value [0-9.]+ [0-9.]+$/', $ajax_value); + return (bool) preg_match('/^Ajax value [0-9.]+ [0-9.]+$/', $ajax_value); })); $page->pressButton('Update effect'); $assert->pageTextContains('The image effect was successfully applied.'); diff --git a/core/modules/image/tests/src/FunctionalJavascript/ImageFieldValidateTest.php b/core/modules/image/tests/src/FunctionalJavascript/ImageFieldValidateTest.php index 02ca72d19..a8907d74c 100644 --- a/core/modules/image/tests/src/FunctionalJavascript/ImageFieldValidateTest.php +++ b/core/modules/image/tests/src/FunctionalJavascript/ImageFieldValidateTest.php @@ -12,6 +12,11 @@ */ class ImageFieldValidateTest extends ImageFieldTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test the validation message is displayed only once for ajax uploads. */ diff --git a/core/modules/image/tests/src/FunctionalJavascript/QuickEditImageTest.php b/core/modules/image/tests/src/FunctionalJavascript/QuickEditImageTest.php index b4444eff2..2b1dd79db 100644 --- a/core/modules/image/tests/src/FunctionalJavascript/QuickEditImageTest.php +++ b/core/modules/image/tests/src/FunctionalJavascript/QuickEditImageTest.php @@ -22,6 +22,11 @@ class QuickEditImageTest extends QuickEditJavascriptTestBase { */ public static $modules = ['node', 'image', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permissions to edit Articles and use Quick Edit. * diff --git a/core/modules/image/tests/src/Kernel/ImageFieldCreationTrait.php b/core/modules/image/tests/src/Kernel/ImageFieldCreationTrait.php index 851b424bf..a528dfe47 100644 --- a/core/modules/image/tests/src/Kernel/ImageFieldCreationTrait.php +++ b/core/modules/image/tests/src/Kernel/ImageFieldCreationTrait.php @@ -50,14 +50,16 @@ protected function createImageField($name, $type_name, $storage_settings = [], $ ]); $field_config->save(); - entity_get_form_display('node', $type_name, 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $display_repository->getFormDisplay('node', $type_name) ->setComponent($name, [ 'type' => 'image_image', 'settings' => $widget_settings, ]) ->save(); - entity_get_display('node', $type_name, 'default') + $display_repository->getViewDisplay('node', $type_name) ->setComponent($name, [ 'type' => 'image', 'settings' => $formatter_settings, diff --git a/core/modules/image/tests/src/Kernel/ImageFormatterTest.php b/core/modules/image/tests/src/Kernel/ImageFormatterTest.php index 1baa83019..5d97cdf77 100644 --- a/core/modules/image/tests/src/Kernel/ImageFormatterTest.php +++ b/core/modules/image/tests/src/Kernel/ImageFormatterTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\image\Kernel; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -74,7 +75,8 @@ protected function setUp() { ], ])->save(); - $this->display = entity_get_display($this->entityType, $this->bundle, 'default') + $this->display = \Drupal::service('entity_display.repository') + ->getViewDisplay($this->entityType, $this->bundle) ->setComponent($this->fieldName, [ 'type' => 'image', 'label' => 'hidden', @@ -172,6 +174,31 @@ public function testImageFormatterSvg() { $this->assertFalse(strpos($build[$this->fieldName][1]['#markup'], 'height')); } + /** + * Tests Image Formatter URL options handling. + */ + public function testImageFormatterUrlOptions() { + $this->display->setComponent($this->fieldName, ['settings' => ['image_link' => 'content']]); + + // Create a test entity with the image field set. + $entity = EntityTest::create([ + 'name' => $this->randomMachineName(), + ]); + $entity->{$this->fieldName}->generateSampleItems(2); + $entity->save(); + + // Generate the render array to verify URL options are as expected. + $build = $this->display->build($entity); + $this->assertInstanceOf(Url::class, $build[$this->fieldName][0]['#url']); + $build[$this->fieldName][0]['#url']->setOption('attributes', ['data-attributes-test' => 'test123']); + + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = $this->container->get('renderer'); + + $output = $renderer->renderRoot($build[$this->fieldName][0]); + $this->assertContains('fail('Exception did not fail'); } catch (EntityStorageException $exception) { - $this->assertInstanceOf(\PHPUnit_Framework_Error_Warning::class, $exception->getPrevious()); + $this->assertInstanceOf(Warning::class, $exception->getPrevious()); $this->assertEquals($exception->getMessage(), 'Missing file with ID 9999.'); $this->assertEmpty($entity->image_test->width); $this->assertEmpty($entity->image_test->height); diff --git a/core/modules/image/tests/src/Kernel/ImageStyleCustomStreamWrappersTest.php b/core/modules/image/tests/src/Kernel/ImageStyleCustomStreamWrappersTest.php index 6a45a9868..3e0b345af 100644 --- a/core/modules/image/tests/src/Kernel/ImageStyleCustomStreamWrappersTest.php +++ b/core/modules/image/tests/src/Kernel/ImageStyleCustomStreamWrappersTest.php @@ -5,6 +5,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\StreamWrapper\PrivateStream; use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper; use Drupal\file_test\StreamWrapper\DummyRemoteReadOnlyStreamWrapper; use Drupal\file_test\StreamWrapper\DummyStreamWrapper; @@ -75,7 +76,7 @@ public function register(ContainerBuilder $container) { */ public function testCustomStreamWrappers($source_scheme, $expected_scheme) { $derivative_uri = $this->imageStyle->buildUri("$source_scheme://some/path/image.png"); - $derivative_scheme = $this->fileSystem->uriScheme($derivative_uri); + $derivative_scheme = StreamWrapperManager::getScheme($derivative_uri); // Check that the derivative scheme is the expected scheme. $this->assertSame($expected_scheme, $derivative_scheme); diff --git a/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php b/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php index 74fa6431b..a37fa3235 100644 --- a/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php +++ b/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php @@ -88,7 +88,7 @@ public function testEntityDisplayDependency() { // of selecting a replacement style by setting the replacement image style // ID in the image style storage. /** @var \Drupal\image\ImageStyleStorageInterface $storage */ - $storage = $this->container->get('entity.manager')->getStorage($style->getEntityTypeId()); + $storage = $this->container->get('entity_type.manager')->getStorage($style->getEntityTypeId()); $storage->setReplacementId('main_style', 'replacement_style'); $style->delete(); diff --git a/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php b/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php index 8a1c2c6e7..0d2196942 100644 --- a/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php +++ b/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php @@ -30,7 +30,7 @@ class ImageThemeFunctionTest extends KernelTestBase { * * @var array */ - public static $modules = ['entity_test', 'field', 'file', 'image', 'system', 'simpletest', 'user']; + public static $modules = ['entity_test', 'field', 'file', 'image', 'system', 'user']; /** * Created file entity. diff --git a/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php b/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php index f609cd7da..de7a27605 100644 --- a/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php +++ b/core/modules/image/tests/src/Kernel/Migrate/d6/MigrateImageCacheTest.php @@ -105,15 +105,10 @@ public function testMissingEffectPlugin() { $this->startCollectingMessages(); $this->executeMigration('d6_imagecache_presets'); - $messages = $this->migration->getIdMap()->getMessageIterator(); - $count = 0; - foreach ($messages as $message) { - $count++; - $this->assertContains('The "image_deprecated_scale" plugin does not exist.', $message->message); - $this->assertEqual($message->level, MigrationInterface::MESSAGE_ERROR); - } - // There should be only the one message. - $this->assertEqual($count, 1); + $messages = iterator_to_array($this->migration->getIdMap()->getMessages()); + $this->assertCount(1, $messages); + $this->assertContains('The "image_deprecated_scale" plugin does not exist.', $messages[0]->message); + $this->assertEqual($messages[0]->level, MigrationInterface::MESSAGE_ERROR); } /** diff --git a/core/modules/image/tests/src/Kernel/Views/RelationshipUserImageDataTest.php b/core/modules/image/tests/src/Kernel/Views/RelationshipUserImageDataTest.php index e0f605e92..36e12972c 100644 --- a/core/modules/image/tests/src/Kernel/Views/RelationshipUserImageDataTest.php +++ b/core/modules/image/tests/src/Kernel/Views/RelationshipUserImageDataTest.php @@ -72,7 +72,7 @@ public function testViewsHandlerRelationshipUserImageData() { 'status' => FILE_STATUS_PERMANENT, ]); $file->enforceIsNew(); - file_put_contents($file->getFileUri(), file_get_contents('core/modules/simpletest/files/image-1.png')); + file_put_contents($file->getFileUri(), file_get_contents('core/tests/fixtures/files/image-1.png')); $file->save(); $account = User::create([ diff --git a/core/modules/image/tests/src/Unit/ImageStyleTest.php b/core/modules/image/tests/src/Unit/ImageStyleTest.php index bc954b3cc..f02e3eeff 100644 --- a/core/modules/image/tests/src/Unit/ImageStyleTest.php +++ b/core/modules/image/tests/src/Unit/ImageStyleTest.php @@ -2,8 +2,8 @@ namespace Drupal\Tests\image\Unit; -use Drupal\Tests\UnitTestCase; use Drupal\Component\Utility\Crypt; +use Drupal\Tests\UnitTestCase; /** * @coversDefaultClass \Drupal\image\Entity\ImageStyle @@ -15,16 +15,16 @@ class ImageStyleTest extends UnitTestCase { /** * The entity type used for testing. * - * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityType; /** - * The entity manager used for testing. + * The entity type manager used for testing. * - * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $entityManager; + protected $entityTypeManager; /** * The ID of the type of the entity under test. @@ -38,7 +38,7 @@ class ImageStyleTest extends UnitTestCase { * * @param string $image_effect_id * The image effect ID. - * @param \Drupal\image\ImageEffectInterface|\PHPUnit_Framework_MockObject_MockObject $image_effect + * @param \Drupal\image\ImageEffectInterface|\PHPUnit\Framework\MockObject\MockObject $image_effect * The image effect used for testing. * * @return \Drupal\image\ImageStyleInterface @@ -69,12 +69,6 @@ protected function getImageStyleMock($image_effect_id, $image_effect, $stubs = [ $image_style->expects($this->any()) ->method('getImageEffectPluginManager') ->will($this->returnValue($effectManager)); - $image_style->expects($this->any()) - ->method('fileUriScheme') - ->will($this->returnCallback([$this, 'fileUriScheme'])); - $image_style->expects($this->any()) - ->method('fileUriTarget') - ->will($this->returnCallback([$this, 'fileUriTarget'])); $image_style->expects($this->any()) ->method('fileDefaultScheme') ->will($this->returnCallback([$this, 'fileDefaultScheme'])); @@ -88,12 +82,12 @@ protected function getImageStyleMock($image_effect_id, $image_effect, $stubs = [ protected function setUp() { $this->entityTypeId = $this->randomMachineName(); $this->provider = $this->randomMachineName(); - $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $this->entityType = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); $this->entityType->expects($this->any()) ->method('getProvider') ->will($this->returnValue($this->provider)); - $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); - $this->entityManager->expects($this->any()) + $this->entityTypeManager = $this->createMock('\Drupal\Core\Entity\EntityTypeManagerInterface'); + $this->entityTypeManager->expects($this->any()) ->method('getDefinition') ->with($this->entityTypeId) ->will($this->returnValue($this->entityType)); @@ -203,30 +197,6 @@ public function testGetPathToken() { $this->assertEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); } - /** - * Mock function for ImageStyle::fileUriScheme(). - */ - public function fileUriScheme($uri) { - if (preg_match('/^([\w\-]+):\/\/|^(data):/', $uri, $matches)) { - // The scheme will always be the last element in the matches array. - return array_pop($matches); - } - - return FALSE; - } - - /** - * Mock function for ImageStyle::fileUriTarget(). - */ - public function fileUriTarget($uri) { - // Remove the scheme from the URI and remove erroneous leading or trailing, - // forward-slashes and backslashes. - $target = trim(preg_replace('/^[\w\-]+:\/\/|^data:/', '', $uri), '\/'); - - // If nothing was replaced, the URI doesn't have a valid scheme. - return $target !== $uri ? $target : FALSE; - } - /** * Mock function for ImageStyle::fileDefaultScheme(). */ diff --git a/core/modules/image/tests/src/Unit/PageCache/DenyPrivateImageStyleDownloadTest.php b/core/modules/image/tests/src/Unit/PageCache/DenyPrivateImageStyleDownloadTest.php index 65330904d..bca3d01e5 100644 --- a/core/modules/image/tests/src/Unit/PageCache/DenyPrivateImageStyleDownloadTest.php +++ b/core/modules/image/tests/src/Unit/PageCache/DenyPrivateImageStyleDownloadTest.php @@ -38,12 +38,12 @@ class DenyPrivateImageStyleDownloadTest extends UnitTestCase { /** * The current route match. * - * @var \Drupal\Core\Routing\RouteMatch|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Routing\RouteMatch|\PHPUnit\Framework\MockObject\MockObject */ protected $routeMatch; protected function setUp() { - $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $this->routeMatch = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); $this->policy = new DenyPrivateImageStyleDownload($this->routeMatch); $this->response = new Response(); $this->request = new Request(); diff --git a/core/modules/inline_form_errors/inline_form_errors.info.yml b/core/modules/inline_form_errors/inline_form_errors.info.yml index 5ccfbc26a..6465e19e8 100644 --- a/core/modules/inline_form_errors/inline_form_errors.info.yml +++ b/core/modules/inline_form_errors/inline_form_errors.info.yml @@ -1,12 +1,6 @@ type: module name: Inline Form Errors description: 'Places error messages adjacent to form inputs, for improved usability and accessibility.' -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x package: Core - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/inline_form_errors/tests/src/Functional/FormErrorHandlerFileUploadTest.php b/core/modules/inline_form_errors/tests/src/Functional/FormErrorHandlerFileUploadTest.php index 9535c99fb..20f4e3213 100644 --- a/core/modules/inline_form_errors/tests/src/Functional/FormErrorHandlerFileUploadTest.php +++ b/core/modules/inline_form_errors/tests/src/Functional/FormErrorHandlerFileUploadTest.php @@ -21,6 +21,11 @@ class FormErrorHandlerFileUploadTest extends BrowserTestBase { */ public static $modules = ['node', 'file', 'field_ui', 'inline_form_errors']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php b/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php index 7c9a74ab5..a99c1b838 100644 --- a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php +++ b/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php @@ -22,6 +22,11 @@ class FormErrorHandlerCKEditorTest extends WebDriverTestBase { */ public static $modules = ['node', 'ckeditor', 'inline_form_errors', 'filter']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ diff --git a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerQuickEditTest.php b/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerQuickEditTest.php index 00dc3d3f6..4fcac0f09 100644 --- a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerQuickEditTest.php +++ b/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerQuickEditTest.php @@ -23,6 +23,11 @@ class FormErrorHandlerQuickEditTest extends WebDriverTestBase { 'inline_form_errors', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * An editor user with permissions to access the in-place editor. * diff --git a/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php b/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php index 4f1f8db6d..d0937aed5 100644 --- a/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php +++ b/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php @@ -24,21 +24,21 @@ class FormErrorHandlerTest extends UnitTestCase { /** * The messenger. * - * @var \Drupal\Core\Messenger\MessengerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Messenger\MessengerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $messenger; /** * The renderer. * - * @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Render\RendererInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $renderer; /** * The link generator. * - * @var \Drupal\Core\Utility\LinkGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Utility\LinkGeneratorInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $linkGenerator; diff --git a/core/modules/jsonapi/jsonapi.api.php b/core/modules/jsonapi/jsonapi.api.php index 721f97a43..c011e77f7 100644 --- a/core/modules/jsonapi/jsonapi.api.php +++ b/core/modules/jsonapi/jsonapi.api.php @@ -84,7 +84,7 @@ * version-identifier * __|__ * / \ - * ?resource_version=foo:bar + * ?resourceVersion=foo:bar * \_/ \_/ * | | * version-negotiator | @@ -105,14 +105,14 @@ * that has a "Published" revision and a subsequent "Draft" revision. * * Using JSON:API, one could request the "Published" node by requesting - * `/jsonapi/node/page/{{uuid}}?resource_version=rel:latest-version`. + * `/jsonapi/node/page/{{uuid}}?resourceVersion=rel:latest-version`. * * To preview an entity that is still a work-in-progress (i.e. the "Draft" * revision) one could request - * `/jsonapi/node/page/{{uuid}}?resource_version=rel:working-copy`. + * `/jsonapi/node/page/{{uuid}}?resourceVersion=rel:working-copy`. * * To request a specific revision ID, one can request - * `/jsonapi/node/page/{{uuid}}?resource_version=id:{{revision_id}}`. + * `/jsonapi/node/page/{{uuid}}?resourceVersion=id:{{revision_id}}`. * * It is not yet possible to request a collection of revisions. This is still * under development in issue [#3009588]. diff --git a/core/modules/jsonapi/jsonapi.info.yml b/core/modules/jsonapi/jsonapi.info.yml index 88306c814..b99b55f10 100644 --- a/core/modules/jsonapi/jsonapi.info.yml +++ b/core/modules/jsonapi/jsonapi.info.yml @@ -1,14 +1,8 @@ name: JSON:API type: module description: Exposes entities as a JSON:API-specification-compliant web API. -# core: 8.x +core: 8.x package: Web services configure: jsonapi.settings dependencies: - drupal:serialization - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/jsonapi/jsonapi.install b/core/modules/jsonapi/jsonapi.install index add7e17dd..a1ada646d 100644 --- a/core/modules/jsonapi/jsonapi.install +++ b/core/modules/jsonapi/jsonapi.install @@ -5,6 +5,8 @@ * Module install file. */ +use Drupal\Core\Url; + /** * Implements hook_install(). */ @@ -58,7 +60,18 @@ function jsonapi_requirements($phase) { ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/revisions', ]), ]; - + $requirements['jsonapi_read_only_mode'] = [ + 'title' => t('JSON:API allowed operations'), + 'value' => t('Read-only'), + 'severity' => REQUIREMENT_INFO, + ]; + if (!\Drupal::configFactory()->get('jsonapi.settings')->get('read_only')) { + $requirements['jsonapi_read_only_mode']['value'] = t('All (create, read, update, delete)'); + $requirements['jsonapi_read_only_mode']['description'] = t('It is recommended to configure JSON:API to only accept all operations if the site requires it. Learn more about securing your site with JSON:API.', [ + ':docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/security-considerations', + ':configure-url' => Url::fromRoute('jsonapi.settings')->toString(), + ]); + } } return $requirements; } diff --git a/core/modules/jsonapi/jsonapi.links.task.yml b/core/modules/jsonapi/jsonapi.links.task.yml new file mode 100644 index 000000000..91001c7a7 --- /dev/null +++ b/core/modules/jsonapi/jsonapi.links.task.yml @@ -0,0 +1,4 @@ +jsonapi.settings: + route_name: jsonapi.settings + base_route: jsonapi.settings + title: 'Settings' diff --git a/core/modules/jsonapi/jsonapi.module b/core/modules/jsonapi/jsonapi.module index 0e7651770..d0a58ecf9 100644 --- a/core/modules/jsonapi/jsonapi.module +++ b/core/modules/jsonapi/jsonapi.module @@ -321,8 +321,6 @@ function jsonapi_jsonapi_user_filter_access(EntityTypeInterface $entity_type, Ac */ function jsonapi_jsonapi_workspace_filter_access(EntityTypeInterface $entity_type, $published, $owner, AccountInterface $account) { // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess() - // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for - // (isDefaultWorkspace()), so this does not have to. return ([ JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'), JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'), diff --git a/core/modules/jsonapi/jsonapi.routing.yml b/core/modules/jsonapi/jsonapi.routing.yml index e5bcaf72d..02b908a35 100644 --- a/core/modules/jsonapi/jsonapi.routing.yml +++ b/core/modules/jsonapi/jsonapi.routing.yml @@ -4,7 +4,7 @@ route_callbacks: jsonapi.settings: path: '/admin/config/services/jsonapi' defaults: - _form: 'Drupal\jsonapi\Form\JsonApiSettingsForm' + _form: '\Drupal\jsonapi\Form\JsonApiSettingsForm' _title: 'JSON:API' requirements: _permission: 'administer site configuration' diff --git a/core/modules/jsonapi/jsonapi.services.yml b/core/modules/jsonapi/jsonapi.services.yml index 16c7f4dc9..b830a1baa 100644 --- a/core/modules/jsonapi/jsonapi.services.yml +++ b/core/modules/jsonapi/jsonapi.services.yml @@ -44,8 +44,15 @@ services: - { name: jsonapi_normalizer } serializer.normalizer.resource_object.jsonapi: class: Drupal\jsonapi\Normalizer\ResourceObjectNormalizer + arguments: ['@jsonapi.normalization_cacher'] tags: - { name: jsonapi_normalizer } + jsonapi.normalization_cacher: + class: Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher + calls: + - ['setRenderCache', ['@render_cache']] + tags: + - { name: event_subscriber } serializer.normalizer.content_entity.jsonapi: class: Drupal\jsonapi\Normalizer\ContentEntityDenormalizer arguments: ['@entity_type.manager', '@entity_field.manager', '@plugin.manager.field.field_type'] @@ -65,18 +72,17 @@ services: class: Drupal\jsonapi\Normalizer\LinkCollectionNormalizer tags: - { name: jsonapi_normalizer } - serializer.normalizer.entity_reference_field.jsonapi: - class: Drupal\jsonapi\Normalizer\EntityReferenceFieldNormalizer + serializer.normalizer.relationship.jsonapi: + class: Drupal\jsonapi\Normalizer\RelationshipNormalizer tags: - # This must have a higher priority than the 'serializer.normalizer.field.jsonapi' to take effect. - - { name: jsonapi_normalizer, priority: 1 } + - { name: jsonapi_normalizer } serializer.encoder.jsonapi: class: Drupal\jsonapi\Encoder\JsonEncoder tags: - { name: jsonapi_encoder, format: 'api_json' } jsonapi.resource_type.repository: class: Drupal\jsonapi\ResourceType\ResourceTypeRepository - arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@cache.jsonapi_resource_types'] + arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@cache.jsonapi_resource_types', '@event_dispatcher'] jsonapi.route_enhancer: class: Drupal\jsonapi\Routing\RouteEnhancer tags: @@ -113,11 +119,27 @@ services: arguments: ['jsonapi'] # Cache. - cache.jsonapi_resource_types: + cache.jsonapi_memory: class: Drupal\Core\Cache\MemoryCache\MemoryCache - # We need this to add this to the Drupal's cache_tags.invalidator service. - # This way it can invalidate the data in here based on tags. + public: false + + # A chained cache with an in-memory cache as the first layer and a database- + # backed cache as the fallback is used. The first layer (memory) is necessary + # because ResourceType value objects are retrieved many times during a + # request. The second layer (by default a database) is necessary to avoid + # recomputing the ResourceType value objects on every request. + cache.jsonapi_resource_types: + class: Drupal\Core\Cache\BackendChain + calls: + - [appendBackend, ['@cache.jsonapi_memory']] + - [appendBackend, ['@cache.default']] tags: [{ name: cache.bin }] + cache.jsonapi_normalizations: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin } + factory: cache_factory:get + arguments: [jsonapi_normalizations] # Route filter. jsonapi.route_filter.format_setter: @@ -185,7 +207,7 @@ services: - { name: event_subscriber } jsonapi.resource_response_validator.subscriber: class: Drupal\jsonapi\EventSubscriber\ResourceResponseValidator - arguments: ['@jsonapi.serializer', '@logger.channel.jsonapi', '@module_handler', '@app.root'] + arguments: ['@logger.channel.jsonapi', '@module_handler', '@app.root'] calls: - [setValidator, []] tags: diff --git a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php index 5c6c9c8d4..dd3b0a8e3 100644 --- a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php +++ b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php @@ -15,7 +15,6 @@ use Drupal\jsonapi\Query\EntityCondition; use Drupal\jsonapi\Query\EntityConditionGroup; use Drupal\jsonapi\Query\Filter; -use Drupal\workspaces\WorkspaceInterface; /** * Adds sufficient access control to collection queries. @@ -306,20 +305,6 @@ protected static function getAccessCondition($entity_type_id, CacheableMetadata // @see \Drupal\user\UserAccessControlHandler::checkAccess() $specific_condition = new EntityCondition('uid', '0', '!='); break; - - case 'workspace': - // The default workspace is always viewable, no matter what, so if - // the generic condition prevents that, add an OR. - // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess() - if ($generic_condition) { - $specific_condition = new EntityConditionGroup('OR', [ - $generic_condition, - new EntityCondition('id', WorkspaceInterface::DEFAULT_WORKSPACE), - ]); - // The generic condition is now part of the specific condition. - $generic_condition = NULL; - } - break; } // Return a combined condition. diff --git a/core/modules/jsonapi/src/Context/FieldResolver.php b/core/modules/jsonapi/src/Context/FieldResolver.php index e41b8527d..7f65f3d8a 100644 --- a/core/modules/jsonapi/src/Context/FieldResolver.php +++ b/core/modules/jsonapi/src/Context/FieldResolver.php @@ -19,6 +19,7 @@ use Drupal\Core\TypedData\DataReferenceDefinitionInterface; use Drupal\Core\TypedData\DataReferenceTargetDefinition; use Drupal\jsonapi\ResourceType\ResourceType; +use Drupal\jsonapi\ResourceType\ResourceTypeRelationship; use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface; use Drupal\Core\Http\Exception\CacheableBadRequestHttpException; @@ -251,10 +252,8 @@ public static function resolveInternalIncludePath(ResourceType $resource_type, a * 'uid.field_first_name' -> 'uid.entity.field_first_name'. * 'author.firstName' -> 'field_author.entity.field_first_name' * - * @param string $entity_type_id - * The type of the entity for which to resolve the field name. - * @param string $bundle - * The bundle of the entity for which to resolve the field name. + * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type + * The JSON:API resource type from which to resolve the field name. * @param string $external_field_name * The public field name to map to a Drupal field name. * @@ -263,9 +262,21 @@ public static function resolveInternalIncludePath(ResourceType $resource_type, a * * @throws \Drupal\Core\Http\Exception\CacheableBadRequestHttpException */ - public function resolveInternalEntityQueryPath($entity_type_id, $bundle, $external_field_name) { + public function resolveInternalEntityQueryPath($resource_type, $external_field_name) { + $function_args = func_get_args(); + // @todo Remove this conditional block in drupal:9.0.0 and add a type hint + // to the first argument of this method. + // @see https://www.drupal.org/project/drupal/issues/3078045 + if (count($function_args) === 3) { + @trigger_error('Passing the entity type ID and bundle to ' . __METHOD__ . ' is deprecated in drupal:8.8.0 and will throw a fatal error in drupal:9.0.0. Pass a JSON:API resource type instead. See https://www.drupal.org/node/3078036', E_USER_DEPRECATED); + list($entity_type_id, $bundle, $external_field_name) = $function_args; + $resource_type = $this->resourceTypeRepository->get($entity_type_id, $bundle); + } + elseif (!$resource_type instanceof ResourceType) { + throw new \InvalidArgumentException("The first argument to " . __METHOD__ . " should be an instance of \Drupal\jsonapi\ResourceType\ResourceType, " . gettype($resource_type) . " given."); + } + $cacheability = (new CacheableMetadata())->addCacheContexts(['url.query_args:filter', 'url.query_args:sort']); - $resource_type = $this->resourceTypeRepository->get($entity_type_id, $bundle); if (empty($external_field_name)) { throw new CacheableBadRequestHttpException($cacheability, 'No field name was provided for the filter.'); } @@ -326,7 +337,7 @@ public function resolveInternalEntityQueryPath($entity_type_id, $bundle, $extern } // Get all of the referenceable resource types. - $resource_types = $this->getReferenceableResourceTypes($candidate_definitions); + $resource_types = $this->getRelatableResourceTypes($resource_types, $candidate_definitions); $at_least_one_entity_reference_field = FALSE; $candidate_property_names = array_unique(NestedArray::mergeDeepArray(array_map(function (FieldItemDataDefinitionInterface $definition) use (&$at_least_one_entity_reference_field) { @@ -536,54 +547,27 @@ protected function isMemberFilterable($external_name, array $resource_types) { /** * Get the referenceable ResourceTypes for a set of field definitions. * - * @param \Drupal\Core\Field\FieldDefinitionInterface[] $definitions + * @param \Drupal\jsonapi\ResourceType\ResourceType[] $resource_types * The resource types on which the reference field might exist. + * @param \Drupal\Core\Field\TypedData\FieldItemDataDefinitionInterface[] $definitions + * The field item definitions of targeted fields, keyed by the resource + * type name on which they reside. * * @return \Drupal\jsonapi\ResourceType\ResourceType[] * The referenceable target resource types. */ - protected function getReferenceableResourceTypes(array $definitions) { - return array_reduce($definitions, function ($result, $definition) { - $resource_types = array_filter( - $this->collectResourceTypesForReference($definition) - ); - $type_names = array_map(function ($resource_type) { - /* @var \Drupal\jsonapi\ResourceType\ResourceType $resource_type */ - return $resource_type->getTypeName(); - }, $resource_types); - return array_merge($result, array_combine($type_names, $resource_types)); - }, []); - } - - /** - * Build a list of resource types depending on which bundles are referenced. - * - * @param \Drupal\Core\Field\TypedData\FieldItemDataDefinitionInterface $item_definition - * The reference definition. - * - * @return \Drupal\jsonapi\ResourceType\ResourceType[] - * The list of resource types. - */ - protected function collectResourceTypesForReference(FieldItemDataDefinitionInterface $item_definition) { - $main_property_definition = $item_definition->getPropertyDefinition( - $item_definition->getMainPropertyName() - ); - - // Check if the field is a flavor of an Entity Reference field. - if (!$main_property_definition instanceof DataReferenceTargetDefinition) { - return []; + protected function getRelatableResourceTypes(array $resource_types, array $definitions) { + $relatable_resource_types = []; + foreach ($resource_types as $resource_type) { + $definition = $definitions[$resource_type->getTypeName()]; + $resource_type_field = $resource_type->getFieldByInternalName($definition->getFieldDefinition()->getName()); + if ($resource_type_field instanceof ResourceTypeRelationship) { + foreach ($resource_type_field->getRelatableResourceTypes() as $relatable_resource_type) { + $relatable_resource_types[$relatable_resource_type->getTypeName()] = $relatable_resource_type; + } + } } - $entity_type_id = $item_definition->getSetting('target_type'); - $handler_settings = $item_definition->getSetting('handler_settings'); - - $has_target_bundles = isset($handler_settings['target_bundles']) && !empty($handler_settings['target_bundles']); - $target_bundles = $has_target_bundles ? - $handler_settings['target_bundles'] - : $this->getAllBundlesForEntityType($entity_type_id); - - return array_map(function ($bundle) use ($entity_type_id) { - return $this->resourceTypeRepository->get($entity_type_id, $bundle); - }, $target_bundles); + return $relatable_resource_types; } /** @@ -612,19 +596,6 @@ protected function resourceTypesAreTraversable(array $resource_types) { return FALSE; } - /** - * Gets all bundle IDs for a given entity type. - * - * @param string $entity_type_id - * The entity type for which to get bundles. - * - * @return string[] - * The bundle IDs. - */ - protected function getAllBundlesForEntityType($entity_type_id) { - return array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)); - } - /** * Gets all unique reference property names from the given field definitions. * diff --git a/core/modules/jsonapi/src/Controller/EntityResource.php b/core/modules/jsonapi/src/Controller/EntityResource.php index 5c43d3ee2..7f6b5fc9b 100644 --- a/core/modules/jsonapi/src/Controller/EntityResource.php +++ b/core/modules/jsonapi/src/Controller/EntityResource.php @@ -30,16 +30,16 @@ use Drupal\jsonapi\Entity\EntityValidationTrait; use Drupal\jsonapi\Access\TemporaryQueryGuard; use Drupal\jsonapi\Exception\EntityAccessDeniedHttpException; -use Drupal\jsonapi\Exception\UnprocessableHttpEntityException; use Drupal\jsonapi\IncludeResolver; use Drupal\jsonapi\JsonApiResource\IncludedData; use Drupal\jsonapi\JsonApiResource\LinkCollection; use Drupal\jsonapi\JsonApiResource\NullIncludedData; +use Drupal\jsonapi\JsonApiResource\Relationship; use Drupal\jsonapi\JsonApiResource\ResourceIdentifier; use Drupal\jsonapi\JsonApiResource\Link; use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\JsonApiResource\ResourceObjectData; -use Drupal\jsonapi\Normalizer\EntityReferenceFieldNormalizer; +use Drupal\jsonapi\JsonApiResource\TopLevelDataInterface; use Drupal\jsonapi\Query\Filter; use Drupal\jsonapi\Query\Sort; use Drupal\jsonapi\Query\OffsetPage; @@ -47,12 +47,14 @@ use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel; use Drupal\jsonapi\ResourceResponse; use Drupal\jsonapi\ResourceType\ResourceType; +use Drupal\jsonapi\ResourceType\ResourceTypeField; use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface; use Drupal\jsonapi\Revisions\ResourceVersionRouteEnhancer; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Drupal\Core\Http\Exception\CacheableBadRequestHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\SerializerInterface; @@ -235,17 +237,22 @@ public function createIndividual(ResourceType $resource_type, Request $request) // by the user. Field access makes no distinction between 'create' and // 'update', so the 'edit' operation is used here. $document = Json::decode($request->getContent()); + $field_mapping = array_map(function (ResourceTypeField $field) { + return $field->getPublicName(); + }, $resource_type->getFields()); + // User resource objects contain a read-only attribute that is not a + // real field on the user entity type. + // @see \Drupal\jsonapi\JsonApiResource\ResourceObject::extractContentEntityFields() + // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254. + if ($resource_type->getEntityTypeId() === 'user') { + $field_mapping = array_diff($field_mapping, [$resource_type->getPublicName('display_name')]); + } foreach (['attributes', 'relationships'] as $data_member_name) { if (isset($document['data'][$data_member_name])) { - $valid_names = array_filter(array_map(function ($public_field_name) use ($resource_type) { - return $resource_type->getInternalName($public_field_name); - }, array_keys($document['data'][$data_member_name])), function ($internal_field_name) use ($resource_type) { - return $resource_type->hasField($internal_field_name); - }); - foreach ($valid_names as $field_name) { - $field_access = $parsed_entity->get($field_name)->access('edit', NULL, TRUE); + foreach (array_intersect_key(array_flip($field_mapping), $document['data'][$data_member_name]) as $internal_field_name) { + $field_access = $parsed_entity->get($internal_field_name)->access('edit', NULL, TRUE); if (!$field_access->isAllowed()) { - $public_field_name = $resource_type->getPublicName($field_name); + $public_field_name = $field_mapping[$internal_field_name]; throw new EntityAccessDeniedHttpException(NULL, $field_access, "/data/$data_member_name/$public_field_name", sprintf('The current user is not allowed to POST the selected field (%s).', $public_field_name)); } } @@ -317,6 +324,14 @@ public function patchIndividual(ResourceType $resource_type, EntityInterface $en $data += ['attributes' => [], 'relationships' => []]; $field_names = array_merge(array_keys($data['attributes']), array_keys($data['relationships'])); + // User resource objects contain a read-only attribute that is not a real + // field on the user entity type. + // @see \Drupal\jsonapi\JsonApiResource\ResourceObject::extractContentEntityFields() + // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254. + if ($entity->getEntityTypeId() === 'user') { + $field_names = array_diff($field_names, [$resource_type->getPublicName('display_name')]); + } + array_reduce($field_names, function (EntityInterface $destination, $field_name) use ($resource_type, $parsed_entity) { $this->updateEntityField($resource_type, $parsed_entity, $destination, $field_name); return $destination; @@ -496,7 +511,8 @@ protected function executeQueryInRenderContext(QueryInterface $query, CacheableM */ public function getRelated(ResourceType $resource_type, FieldableEntityInterface $entity, $related, Request $request) { /* @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field_list */ - $field_list = $entity->get($resource_type->getInternalName($related)); + $resource_relationship = $resource_type->getFieldByPublicName($related); + $field_list = $entity->get($resource_relationship->getInternalName()); // Remove the entities pointing to a resource that may be disabled. Even // though the normalizer skips disabled references, we can avoid unnecessary @@ -515,7 +531,7 @@ function (EntityInterface $entity) { foreach ($referenced_entities as $referenced_entity) { $collection_data[] = $this->entityAccessChecker->getAccessCheckedResourceObject($referenced_entity); } - $primary_data = new ResourceObjectData($collection_data, $field_list->getFieldDefinition()->getFieldStorageDefinition()->getCardinality()); + $primary_data = new ResourceObjectData($collection_data, $resource_relationship->hasOne() ? 1 : -1); $response = $this->buildWrappedResponse($primary_data, $request, $this->getIncludes($request, $primary_data)); // $response does not contain the entity list cache tag. We add the @@ -548,10 +564,8 @@ public function getRelationship(ResourceType $resource_type, FieldableEntityInte // Access will have already been checked by the RelationshipFieldAccess // service, so we don't need to call ::getAccessCheckedResourceObject(). $resource_object = ResourceObject::createFromEntity($resource_type, $entity); - $relationship_object_urls = EntityReferenceFieldNormalizer::getRelationshipLinks($resource_object, $related); - $response = $this->buildWrappedResponse($field_list, $request, $this->getIncludes($request, $resource_object), $response_code, [], array_reduce(array_keys($relationship_object_urls), function (LinkCollection $links, $key) use ($relationship_object_urls) { - return $links->withLink($key, new Link(new CacheableMetadata(), $relationship_object_urls[$key], [$key])); - }, new LinkCollection([]))); + $relationship = Relationship::createFromEntityReferenceField($resource_object, $field_list); + $response = $this->buildWrappedResponse($relationship, $request, $this->getIncludes($request, $resource_object), $response_code); // Add the host entity as a cacheable dependency. $response->addCacheableDependency($entity); return $response; @@ -584,11 +598,11 @@ public function getRelationship(ResourceType $resource_type, FieldableEntityInte */ public function addToRelationshipData(ResourceType $resource_type, FieldableEntityInterface $entity, $related, Request $request) { $resource_identifiers = $this->deserialize($resource_type, $request, ResourceIdentifier::class, $related); - $related = $resource_type->getInternalName($related); + $internal_relationship_field_name = $resource_type->getInternalName($related); // According to the specification, you are only allowed to POST to a // relationship if it is a to-many relationship. /* @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field_list */ - $field_list = $entity->{$related}; + $field_list = $entity->{$internal_relationship_field_name}; /* @var \Drupal\field\Entity\FieldConfig $field_definition */ $field_definition = $field_list->getFieldDefinition(); $is_multiple = $field_definition->getFieldStorageDefinition()->isMultiple(); @@ -648,12 +662,12 @@ public function addToRelationshipData(ResourceType $resource_type, FieldableEnti */ public function replaceRelationshipData(ResourceType $resource_type, EntityInterface $entity, $related, Request $request) { $resource_identifiers = $this->deserialize($resource_type, $request, ResourceIdentifier::class, $related); - $related = $resource_type->getInternalName($related); + $internal_relationship_field_name = $resource_type->getInternalName($related); /* @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $resource_identifiers */ // According to the specification, PATCH works a little bit different if the // relationship is to-one or to-many. /* @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field_list */ - $field_list = $entity->{$related}; + $field_list = $entity->{$internal_relationship_field_name}; $field_definition = $field_list->getFieldDefinition(); $is_multiple = $field_definition->getFieldStorageDefinition()->isMultiple(); $method = $is_multiple ? 'doPatchMultipleRelationship' : 'doPatchIndividualRelationship'; @@ -731,8 +745,9 @@ protected function doPatchMultipleRelationship(EntityInterface $entity, array $r */ public function removeFromRelationshipData(ResourceType $resource_type, EntityInterface $entity, $related, Request $request) { $resource_identifiers = $this->deserialize($resource_type, $request, ResourceIdentifier::class, $related); + $internal_relationship_field_name = $resource_type->getInternalName($related); /* @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field_list */ - $field_list = $entity->{$related}; + $field_list = $entity->{$internal_relationship_field_name}; $is_multiple = $field_list->getFieldDefinition() ->getFieldStorageDefinition() ->isMultiple(); @@ -822,10 +837,10 @@ protected function deserialize(ResourceType $resource_type, Request $request, $c // These two serialization exception types mean there was a problem with // the structure of the decoded data and it's not valid. catch (UnexpectedValueException $e) { - throw new UnprocessableHttpEntityException($e->getMessage()); + throw new UnprocessableEntityHttpException($e->getMessage()); } catch (InvalidArgumentException $e) { - throw new UnprocessableHttpEntityException($e->getMessage()); + throw new UnprocessableEntityHttpException($e->getMessage()); } } @@ -862,7 +877,7 @@ protected function getCollectionQuery(ResourceType $resource_type, array $params // Apply any sorts to the entity query. if (isset($params[Sort::KEY_NAME]) && $sort = $params[Sort::KEY_NAME]) { foreach ($sort->fields() as $field) { - $path = $this->fieldResolver->resolveInternalEntityQueryPath($resource_type->getEntityTypeId(), $resource_type->getBundle(), $field[Sort::PATH_KEY]); + $path = $this->fieldResolver->resolveInternalEntityQueryPath($resource_type, $field[Sort::PATH_KEY]); $direction = isset($field[Sort::DIRECTION_KEY]) ? $field[Sort::DIRECTION_KEY] : 'ASC'; $langcode = isset($field[Sort::LANGUAGE_KEY]) ? $field[Sort::LANGUAGE_KEY] : NULL; $query->sort($path, $direction, $langcode); @@ -954,7 +969,7 @@ protected static function relationshipResponseRequiresBody(array $received_resou /** * Builds a response with the appropriate wrapped document. * - * @param mixed $data + * @param \Drupal\jsonapi\JsonApiResource\TopLevelDataInterface $data * The data to wrap. * @param \Symfony\Component\HttpFoundation\Request $request * The request object. @@ -973,11 +988,10 @@ protected static function relationshipResponseRequiresBody(array $received_resou * @return \Drupal\jsonapi\ResourceResponse * The response. */ - protected function buildWrappedResponse($data, Request $request, IncludedData $includes, $response_code = 200, array $headers = [], LinkCollection $links = NULL, array $meta = []) { - assert($data instanceof Data || $data instanceof FieldItemListInterface); + protected function buildWrappedResponse(TopLevelDataInterface $data, Request $request, IncludedData $includes, $response_code = 200, array $headers = [], LinkCollection $links = NULL, array $meta = []) { $links = ($links ?: new LinkCollection([])); if (!$links->hasLinkWithKey('self')) { - $self_link = new Link(new CacheableMetadata(), self::getRequestLink($request), ['self']); + $self_link = new Link(new CacheableMetadata(), self::getRequestLink($request), 'self'); $links = $links->withLink('self', $self_link); } $response = new ResourceResponse(new JsonApiDocumentTopLevel($data, $includes, $links, $meta), $response_code, $headers); @@ -1260,20 +1274,20 @@ protected static function getPagerLinks(Request $request, OffsetPage $page_param // Check if this is not the last page. if ($link_context['has_next_page']) { $next_url = static::getRequestLink($request, static::getPagerQueries('next', $offset, $size, $query)); - $pager_links = $pager_links->withLink('next', new Link(new CacheableMetadata(), $next_url, ['next'])); + $pager_links = $pager_links->withLink('next', new Link(new CacheableMetadata(), $next_url, 'next')); if (!empty($total)) { $last_url = static::getRequestLink($request, static::getPagerQueries('last', $offset, $size, $query, $total)); - $pager_links = $pager_links->withLink('last', new Link(new CacheableMetadata(), $last_url, ['last'])); + $pager_links = $pager_links->withLink('last', new Link(new CacheableMetadata(), $last_url, 'last')); } } // Check if this is not the first page. if ($offset > 0) { $first_url = static::getRequestLink($request, static::getPagerQueries('first', $offset, $size, $query)); - $pager_links = $pager_links->withLink('first', new Link(new CacheableMetadata(), $first_url, ['first'])); + $pager_links = $pager_links->withLink('first', new Link(new CacheableMetadata(), $first_url, 'first')); $prev_url = static::getRequestLink($request, static::getPagerQueries('prev', $offset, $size, $query)); - $pager_links = $pager_links->withLink('prev', new Link(new CacheableMetadata(), $prev_url, ['prev'])); + $pager_links = $pager_links->withLink('prev', new Link(new CacheableMetadata(), $prev_url, 'prev')); } return $pager_links; diff --git a/core/modules/jsonapi/src/Controller/EntryPoint.php b/core/modules/jsonapi/src/Controller/EntryPoint.php index bbbc2d56d..4f98708fb 100644 --- a/core/modules/jsonapi/src/Controller/EntryPoint.php +++ b/core/modules/jsonapi/src/Controller/EntryPoint.php @@ -82,14 +82,22 @@ public function index() { return !$resource->isInternal(); }); - $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.resource_list'), ['self']); + $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.resource_list'), 'self'); $urls = array_reduce($resources, function (LinkCollection $carry, ResourceType $resource_type) { if ($resource_type->isLocatable() || $resource_type->isMutable()) { $route_suffix = $resource_type->isLocatable() ? 'collection' : 'collection.post'; $url = Url::fromRoute(sprintf('jsonapi.%s.%s', $resource_type->getTypeName(), $route_suffix))->setAbsolute(); + // Using a resource type name in place of a link relation type is not + // technically valid. However, since it matches the link key, it will + // not actually be serialized since the rel is omitted if it matches the + // link key; because of that no client can rely on it. Once an extension + // relation type is implemented for links to a collection, that should + // be used instead. Unfortunately, the `collection` link relation type + // would not be semantically correct since it would imply that the + // entrypoint is a *member* of the link target. // @todo: implement an extension relation type to signal that this is a primary collection resource. - $link_relation_types = []; - return $carry->withLink($resource_type->getTypeName(), new Link(new CacheableMetadata(), $url, $link_relation_types)); + $link_relation_type = $resource_type->getTypeName(); + return $carry->withLink($resource_type->getTypeName(), new Link(new CacheableMetadata(), $url, $link_relation_type)); } return $carry; }, new LinkCollection(['self' => $self_link])); diff --git a/core/modules/jsonapi/src/Controller/FileUpload.php b/core/modules/jsonapi/src/Controller/FileUpload.php index b07dd51d0..eb9fd6cd4 100644 --- a/core/modules/jsonapi/src/Controller/FileUpload.php +++ b/core/modules/jsonapi/src/Controller/FileUpload.php @@ -128,7 +128,7 @@ public function handleFileUploadForExistingResource(Request $request, ResourceTy throw new UnprocessableEntityHttpException($message); } - if ($field_definition->getFieldStorageDefinition()->getCardinality() === 1) { + if ($resource_type->getFieldByInternalName($file_field_name)->hasOne()) { $entity->{$file_field_name} = $file; } else { @@ -178,7 +178,7 @@ public function handleFileUploadForNewResource(Request $request, ResourceType $r } // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. - $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.file--file.individual', ['entity' => $file->uuid()]), ['self']); + $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.file--file.individual', ['entity' => $file->uuid()]), 'self'); /* $self_link = new Link(new CacheableMetadata(), $this->entity->toUrl('jsonapi'), ['self']); */ $links = new LinkCollection(['self' => $self_link]); diff --git a/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php index 59fd3fafe..389b4659d 100644 --- a/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php +++ b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php @@ -187,8 +187,8 @@ public function handleFileUploadForField(FieldDefinitionInterface $field_definit } // Move the file to the correct location after validation. Use - // FILE_EXISTS_ERROR as the file location has already been determined above - // in FileSystem::getDestinationFilename(). + // FileSystemInterface::EXISTS_ERROR as the file location has already been + // determined above in FileSystem::getDestinationFilename(). try { $this->fileSystem->move($temp_file_path, $file_uri, FileSystemInterface::EXISTS_ERROR); } diff --git a/core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php b/core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php new file mode 100644 index 000000000..9c651caba --- /dev/null +++ b/core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php @@ -0,0 +1,200 @@ +renderCache = $render_cache; + } + + /** + * Reads an entity normalization from cache. + * + * The returned normalization may only be a partial normalization because it + * was previously normalized with a sparse fieldset. + * + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object + * The resource object for which to generate a cache item. + * + * @return array|false + * The cached normalization parts, or FALSE if not yet cached. + * + * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::renderArrayToResponse() + */ + public function get(ResourceObject $object) { + $cached = $this->renderCache->get(static::generateLookupRenderArray($object)); + return $cached ? $cached['#data'] : FALSE; + } + + /** + * Adds a normalization to be cached after the response has been sent. + * + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object + * The resource object for which to generate a cache item. + * @param array $normalization_parts + * The normalization parts to cache. + */ + public function saveOnTerminate(ResourceObject $object, array $normalization_parts) { + assert( + array_keys($normalization_parts) === [ + static::RESOURCE_CACHE_SUBSET_BASE, + static::RESOURCE_CACHE_SUBSET_FIELDS, + ] + ); + $resource_type = $object->getResourceType(); + $key = $resource_type->getTypeName() . ':' . $object->getId(); + $this->toCache[$key] = [$object, $normalization_parts]; + } + + /** + * Writes normalizations of entities to cache, if any were created. + * + * @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event + * The Event to process. + */ + public function onTerminate(PostResponseEvent $event) { + foreach ($this->toCache as $value) { + list($object, $normalization_parts) = $value; + $this->set($object, $normalization_parts); + } + } + + /** + * Writes a normalization to cache. + * + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object + * The resource object for which to generate a cache item. + * @param array $normalization_parts + * The normalization parts to cache. + * + * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::responseToRenderArray() + * @todo Refactor/remove once https://www.drupal.org/node/2551419 lands. + */ + protected function set(ResourceObject $object, array $normalization_parts) { + $base = static::generateLookupRenderArray($object); + $data_as_render_array = $base + [ + // The data we actually care about. + '#data' => $normalization_parts, + // Tell RenderCache to cache the #data property: the data we actually care + // about. + '#cache_properties' => ['#data'], + // These exist only to fulfill the requirements of the RenderCache, which + // is designed to work with render arrays only. We don't care about these. + '#markup' => '', + '#attached' => '', + ]; + + // Merge the entity's cacheability metadata with that of the normalization + // parts, so that RenderCache can take care of cache redirects for us. + CacheableMetadata::createFromObject($object) + ->merge(static::mergeCacheableDependencies($normalization_parts[static::RESOURCE_CACHE_SUBSET_BASE])) + ->merge(static::mergeCacheableDependencies($normalization_parts[static::RESOURCE_CACHE_SUBSET_FIELDS])) + ->applyTo($data_as_render_array); + + $this->renderCache->set($data_as_render_array, $base); + } + + /** + * Generates a lookup render array for a normalization. + * + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object + * The resource object for which to generate a cache item. + * + * @return array + * A render array for use with the RenderCache service. + * + * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::$dynamicPageCacheRedirectRenderArray + */ + protected static function generateLookupRenderArray(ResourceObject $object) { + return [ + '#cache' => [ + 'keys' => [$object->getResourceType()->getTypeName(), $object->getId()], + 'bin' => 'jsonapi_normalizations', + ], + ]; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[KernelEvents::TERMINATE][] = ['onTerminate']; + return $events; + } + + /** + * Determines the joint cacheability of all provided dependencies. + * + * @param \Drupal\Core\Cache\CacheableDependencyInterface|object[] $dependencies + * The dependencies. + * + * @return \Drupal\Core\Cache\CacheableMetadata + * The cacheability of all dependencies. + * + * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface::addCacheableDependency() + */ + protected static function mergeCacheableDependencies(array $dependencies) { + $merged_cacheability = new CacheableMetadata(); + array_walk($dependencies, function ($dependency) use ($merged_cacheability) { + $merged_cacheability->addCacheableDependency($dependency); + }); + return $merged_cacheability; + } + +} diff --git a/core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php b/core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php index 61b5827fd..1502a2191 100644 --- a/core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php +++ b/core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php @@ -11,7 +11,6 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Serializer\SerializerInterface; /** * Response subscriber that validates a JSON:API response. @@ -28,13 +27,6 @@ */ class ResourceResponseValidator implements EventSubscriberInterface { - /** - * The serializer. - * - * @var \Symfony\Component\Serializer\SerializerInterface - */ - protected $serializer; - /** * The JSON:API logger channel. * @@ -68,8 +60,6 @@ class ResourceResponseValidator implements EventSubscriberInterface { /** * Constructs a ResourceResponseValidator object. * - * @param \Symfony\Component\Serializer\SerializerInterface $serializer - * The serializer. * @param \Psr\Log\LoggerInterface $logger * The JSON:API logger channel. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler @@ -77,8 +67,7 @@ class ResourceResponseValidator implements EventSubscriberInterface { * @param string $app_root * The application's root file path. */ - public function __construct(SerializerInterface $serializer, LoggerInterface $logger, ModuleHandlerInterface $module_handler, $app_root) { - $this->serializer = $serializer; + public function __construct(LoggerInterface $logger, ModuleHandlerInterface $module_handler, $app_root) { $this->logger = $logger; $this->moduleHandler = $module_handler; $this->appRoot = $app_root; @@ -125,9 +114,7 @@ public function onResponse(FilterResponseEvent $event) { * @see self::validateResponse */ public function doValidateResponse(Response $response, Request $request) { - if (PHP_MAJOR_VERSION >= 7 || assert_options(ASSERT_ACTIVE)) { - assert($this->validateResponse($response, $request), 'A JSON:API response failed validation (see the logs for details). Please report this in the issue queue on drupal.org'); - } + assert($this->validateResponse($response, $request), 'A JSON:API response failed validation (see the logs for details). Please report this in the issue queue on drupal.org'); } /** diff --git a/core/modules/jsonapi/src/IncludeResolver.php b/core/modules/jsonapi/src/IncludeResolver.php index ff6fc73e3..d8a283df0 100644 --- a/core/modules/jsonapi/src/IncludeResolver.php +++ b/core/modules/jsonapi/src/IncludeResolver.php @@ -176,11 +176,17 @@ protected static function toIncludeTree(ResourceObjectData $data, $include_param $exploded_paths = array_map(function ($include_path) { return array_map('trim', explode('.', $include_path)); }, $include_paths); - $resolved_paths = []; + $resolved_paths_per_resource_type = []; /* @var \Drupal\jsonapi\JsonApiResource\ResourceIdentifierInterface $resource_object */ foreach ($data as $resource_object) { - $resolved_paths = array_merge($resolved_paths, static::resolveInternalIncludePaths($resource_object->getResourceType(), $exploded_paths)); + $resource_type = $resource_object->getResourceType(); + $resource_type_name = $resource_type->getTypeName(); + if (isset($resolved_paths_per_resource_type[$resource_type_name])) { + continue; + } + $resolved_paths_per_resource_type[$resource_type_name] = static::resolveInternalIncludePaths($resource_type, $exploded_paths); } + $resolved_paths = array_reduce($resolved_paths_per_resource_type, 'array_merge', []); return static::buildTree($resolved_paths); } diff --git a/core/modules/jsonapi/src/JsonApiResource/Data.php b/core/modules/jsonapi/src/JsonApiResource/Data.php index c050835b1..57dd0ad67 100644 --- a/core/modules/jsonapi/src/JsonApiResource/Data.php +++ b/core/modules/jsonapi/src/JsonApiResource/Data.php @@ -148,7 +148,7 @@ public function getCardinality() { * @param \Drupal\jsonapi\JsonApiResource\Data $b * A Data object to be merged. * - * @return \Drupal\jsonapi\JsonApiResource\Data + * @return static * A new merged Data object. */ public static function merge(Data $a, Data $b) { diff --git a/core/modules/jsonapi/src/JsonApiResource/JsonApiDocumentTopLevel.php b/core/modules/jsonapi/src/JsonApiResource/JsonApiDocumentTopLevel.php index 124bc0723..5fc7291f9 100644 --- a/core/modules/jsonapi/src/JsonApiResource/JsonApiDocumentTopLevel.php +++ b/core/modules/jsonapi/src/JsonApiResource/JsonApiDocumentTopLevel.php @@ -2,8 +2,6 @@ namespace Drupal\jsonapi\JsonApiResource; -use Drupal\Core\Field\EntityReferenceFieldItemListInterface; - /** * Represents a JSON:API document's "top level". * @@ -57,7 +55,7 @@ class JsonApiDocumentTopLevel { /** * Instantiates a JsonApiDocumentTopLevel object. * - * @param \Drupal\jsonapi\JsonApiResource\ResourceIdentifierInterface|\Drupal\jsonapi\JsonApiResource\Data|\Drupal\jsonapi\JsonApiResource\ErrorCollection|\Drupal\Core\Field\EntityReferenceFieldItemListInterface $data + * @param \Drupal\jsonapi\JsonApiResource\TopLevelDataInterface|\Drupal\jsonapi\JsonApiResource\ErrorCollection $data * The data to normalize. It can be either a ResourceObject, or a stand-in * for one, or a collection of the same. * @param \Drupal\jsonapi\JsonApiResource\IncludedData $includes @@ -69,13 +67,13 @@ class JsonApiDocumentTopLevel { * (optional) The metadata to normalize. */ public function __construct($data, IncludedData $includes, LinkCollection $links, array $meta = []) { - assert($data instanceof ResourceIdentifierInterface || $data instanceof Data || $data instanceof ErrorCollection || $data instanceof EntityReferenceFieldItemListInterface); + assert($data instanceof TopLevelDataInterface || $data instanceof ErrorCollection); assert(!$data instanceof ErrorCollection || $includes instanceof NullIncludedData); - $this->data = $data instanceof ResourceObjectData ? $data->getAccessible() : $data; - $this->includes = $includes->getAccessible(); - $this->links = $links->withContext($this); - $this->meta = $meta; - $this->omissions = $data instanceof ResourceObjectData + $this->data = $data instanceof TopLevelDataInterface ? $data->getData() : $data; + $this->includes = $includes->getData(); + $this->links = $data instanceof TopLevelDataInterface ? $data->getMergedLinks($links->withContext($this)) : $links->withContext($this); + $this->meta = $data instanceof TopLevelDataInterface ? $data->getMergedMeta($meta) : $meta; + $this->omissions = $data instanceof TopLevelDataInterface ? OmittedData::merge($data->getOmissions(), $includes->getOmissions()) : $includes->getOmissions(); } @@ -83,7 +81,7 @@ public function __construct($data, IncludedData $includes, LinkCollection $links /** * Gets the data. * - * @return \Drupal\jsonapi\JsonApiResource\ResourceObject|\Drupal\jsonapi\JsonApiResource\Data|\Drupal\jsonapi\JsonApiResource\LabelOnlyResourceObject|\Drupal\jsonapi\JsonApiResource\ErrorCollection + * @return \Drupal\jsonapi\JsonApiResource\Data|\Drupal\jsonapi\JsonApiResource\ErrorCollection * The data. */ public function getData() { diff --git a/core/modules/jsonapi/src/JsonApiResource/Link.php b/core/modules/jsonapi/src/JsonApiResource/Link.php index 1da2d0afe..f098572a6 100644 --- a/core/modules/jsonapi/src/JsonApiResource/Link.php +++ b/core/modules/jsonapi/src/JsonApiResource/Link.php @@ -3,6 +3,7 @@ namespace Drupal\jsonapi\JsonApiResource; use Drupal\Component\Assertion\Inspector; +use Drupal\Component\Utility\DiffArray; use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Cache\CacheableDependencyTrait; use Drupal\Core\Cache\CacheableMetadata; @@ -41,6 +42,9 @@ final class Link implements CacheableDependencyInterface { * The link relation types. * * @var string[] + * + * @todo: change this type documentation to be a single string in + * https://www.drupal.org/project/drupal/issues/3080467. */ protected $rel; @@ -64,26 +68,31 @@ final class Link implements CacheableDependencyInterface { * entity on which the link will appear. * @param \Drupal\Core\Url $url * The Url object for the link. - * @param string[] $link_relation_types + * @param string $link_relation_type * An array of registered or extension RFC8288 link relation types. * @param array $target_attributes * An associative array of target attributes for the link. * * @see https://tools.ietf.org/html/rfc8288#section-2.1 */ - public function __construct(CacheableMetadata $cacheability, Url $url, array $link_relation_types, array $target_attributes = []) { - // @todo: uncomment the extra assertion below when JSON:API begins to use its own extension relation types. - assert(/* !empty($link_relation_types) && */Inspector::assertAllStrings($link_relation_types)); + public function __construct(CacheableMetadata $cacheability, Url $url, $link_relation_type, array $target_attributes = []) { + // @todo Remove this conditional block in drupal:9.0.0 and add a type hint to the $link_relation_type argument of this method in https://www.drupal.org/project/drupal/issues/3080467. + if (is_array($link_relation_type)) { + @trigger_error('Constructing a ' . self::class . ' with an array of link relation types is deprecated in drupal:8.8.0 and will throw a fatal error in drupal:9.0.0. Pass a single string instead. See https://www.drupal.org/node/3087821.', E_USER_DEPRECATED); + assert(Inspector::assertAllStrings($link_relation_type)); + } + else { + assert(is_string($link_relation_type)); + $link_relation_type = [$link_relation_type]; + } assert(Inspector::assertAllStrings(array_keys($target_attributes))); assert(Inspector::assertAll(function ($target_attribute_value) { - return is_string($target_attribute_value) - || is_array($target_attribute_value) - && Inspector::assertAllStrings($target_attribute_value); + return is_string($target_attribute_value) || is_array($target_attribute_value); }, array_values($target_attributes))); $generated_url = $url->setAbsolute()->toString(TRUE); $this->href = $generated_url->getGeneratedUrl(); $this->uri = $url; - $this->rel = $link_relation_types; + $this->rel = $link_relation_type; $this->attributes = $target_attributes; $this->setCacheability($cacheability->addCacheableDependency($generated_url)); } @@ -108,13 +117,26 @@ public function getHref() { return $this->href; } + /** + * Gets the link's relation type. + * + * @return string + * The link's relation type. + */ + public function getLinkRelationType() { + return reset($this->rel); + } + /** * Gets the link's relation types. * * @return string[] * The link's relation types. + * + * @todo: remove this method in https://www.drupal.org/project/drupal/issues/3080467. */ public function getLinkRelationTypes() { + @trigger_error(__METHOD__ . '() is deprecated in drupal:8.8.0 and will be removed in drupal:9.0.0. Use getLinkRelationType() instead. See https://www.drupal.org/node/3087821.', E_USER_DEPRECATED); return $this->rel; } @@ -129,7 +151,7 @@ public function getTargetAttributes() { } /** - * Compares two links by their href. + * Compares two links. * * @param \Drupal\jsonapi\JsonApiResource\Link $a * The first link. @@ -137,16 +159,35 @@ public function getTargetAttributes() { * The second link. * * @return int - * The result of strcmp() on the links' hrefs. + * 0 if the links can be considered identical, an integer greater than or + * less than 0 otherwise. */ public static function compare(Link $a, Link $b) { - return strcmp($a->getHref(), $b->getHref()); + // @todo: Remove $rel_to_string function once rel property is a single + // string in https://www.drupal.org/project/drupal/issues/3080467. + $rel_to_string = function (array $rel) { + // Sort the link relation type array so that the order of link relation + // types does not matter during link comparison. + sort($rel); + return implode(' ', $rel); + }; + // Any string concatenation would work, but a Link header-like format makes + // it clear what is being compared. + $a_string = sprintf('<%s>;rel="%s"', $a->getHref(), $rel_to_string($a->rel)); + $b_string = sprintf('<%s>;rel="%s"', $b->getHref(), $rel_to_string($b->rel)); + $cmp = strcmp($a_string, $b_string); + // If the `href` or `rel` of the links are not equivalent, it's not + // necessary to compare target attributes. + if ($cmp === 0) { + return (int) !empty(DiffArray::diffAssocRecursive($a->getTargetAttributes(), $b->getTargetAttributes())); + } + return $cmp; } /** - * Merges two link objects' relation types and target attributes. + * Merges two equivalent links into one link with the merged cacheability. * - * The links must share the same URI. + * The links must share the same URI, link relation type and attributes. * * @param \Drupal\jsonapi\JsonApiResource\Link $a * The first link. @@ -154,25 +195,12 @@ public static function compare(Link $a, Link $b) { * The second link. * * @return static - * A new JSON:API Link object with the link relation type and target - * attributes merged. + * A new JSON:API Link object with the cacheability of both links merged. */ public static function merge(Link $a, Link $b) { - assert(static::compare($a, $b) === 0); - $merged_rels = array_unique(array_merge($a->getLinkRelationTypes(), $b->getLinkRelationTypes())); - $merged_attributes = $a->getTargetAttributes(); - foreach ($b->getTargetAttributes() as $key => $value) { - if (isset($merged_attributes[$key])) { - // The attribute values can be either a string or an array of strings. - $value = array_unique(array_merge( - is_string($merged_attributes[$key]) ? [$merged_attributes[$key]] : $merged_attributes[$key], - is_string($value) ? [$value] : $value - )); - } - $merged_attributes[$key] = count($value) === 1 ? reset($value) : $value; - } + assert(static::compare($a, $b) === 0, 'Only equivalent links can be merged.'); $merged_cacheability = (new CacheableMetadata())->addCacheableDependency($a)->addCacheableDependency($b); - return new static($merged_cacheability, $a->getUri(), $merged_rels, $merged_attributes); + return new static($merged_cacheability, $a->getUri(), $a->getLinkRelationType(), $a->getTargetAttributes()); } } diff --git a/core/modules/jsonapi/src/JsonApiResource/LinkCollection.php b/core/modules/jsonapi/src/JsonApiResource/LinkCollection.php index 4464f4e51..098f580b7 100644 --- a/core/modules/jsonapi/src/JsonApiResource/LinkCollection.php +++ b/core/modules/jsonapi/src/JsonApiResource/LinkCollection.php @@ -28,7 +28,7 @@ final class LinkCollection implements \IteratorAggregate { * All links objects exist within a context object. Links form a relationship * between a source IRI and target IRI. A context is the link's source. * - * @var \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject + * @var \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject|\Drupal\jsonapi\JsonApiResource\Relationship * * @see https://tools.ietf.org/html/rfc8288#section-3.2 */ @@ -39,7 +39,7 @@ final class LinkCollection implements \IteratorAggregate { * * @param \Drupal\jsonapi\JsonApiResource\Link[] $links * An associated array of key names and JSON:API Link objects. - * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject $context + * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject|\Drupal\jsonapi\JsonApiResource\Relationship $context * (internal use only) The context object. Use the self::withContext() * method to establish a context. This should be done automatically when * a LinkCollection is passed into a context object. @@ -51,7 +51,7 @@ public function __construct(array $links, $context = NULL) { assert(Inspector::assertAll(function ($link) { return $link instanceof Link || is_array($link) && Inspector::assertAllObjects($link, Link::class); }, $links)); - assert(is_null($context) || Inspector::assertAllObjects([$context], JsonApiDocumentTopLevel::class, ResourceObject::class)); + assert(is_null($context) || Inspector::assertAllObjects([$context], JsonApiDocumentTopLevel::class, ResourceObject::class, Relationship::class)); ksort($links); $this->links = array_map(function ($link) { return is_array($link) ? $link : [$link]; @@ -71,8 +71,9 @@ public function getIterator() { * Gets a new LinkCollection with the given link inserted. * * @param string $key - * A key for the link. If the key already exists and the link shares an href - * with an existing link with that key, those links will be merged together. + * A key for the link. If the key already exists and the link shares an + * href, link relation type and attributes with an existing link with that + * key, those links will be merged together. * @param \Drupal\jsonapi\JsonApiResource\Link $new_link * The link to insert. * @@ -111,7 +112,7 @@ public function hasLinkWithKey($key) { /** * Establishes a new context for a LinkCollection. * - * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject $context + * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject|\Drupal\jsonapi\JsonApiResource\Relationship $context * The new context object. * * @return static @@ -124,7 +125,7 @@ public function withContext($context) { /** * Gets the LinkCollection's context object. * - * @return \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject + * @return \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel|\Drupal\jsonapi\JsonApiResource\ResourceObject|\Drupal\jsonapi\JsonApiResource\Relationship * The LinkCollection's context. */ public function getContext() { diff --git a/core/modules/jsonapi/src/JsonApiResource/Relationship.php b/core/modules/jsonapi/src/JsonApiResource/Relationship.php new file mode 100644 index 000000000..6e89907a9 --- /dev/null +++ b/core/modules/jsonapi/src/JsonApiResource/Relationship.php @@ -0,0 +1,261 @@ +fieldName = $public_field_name; + $this->data = $data; + $this->links = $links->withContext($this); + $this->meta = $meta; + $this->context = $context; + } + + /** + * Creates a new Relationship from an entity reference field. + * + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $context + * The context resource object of the relationship to be created. + * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field + * The entity reference field from which to create the relationship. + * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links + * (optional) Any extra links for the Relationship, if a `self` link is not + * provided, one will be automatically added if the context resource is + * locatable and is not internal. + * @param array $meta + * (optional) Any relationship metadata. + * + * @return static + * An instantiated relationship object. + */ + public static function createFromEntityReferenceField(ResourceObject $context, EntityReferenceFieldItemListInterface $field, LinkCollection $links = NULL, array $meta = []) { + $context_resource_type = $context->getResourceType(); + $resource_field = $context_resource_type->getFieldByInternalName($field->getName()); + return new static( + $resource_field->getPublicName(), + new RelationshipData(ResourceIdentifier::toResourceIdentifiers($field), $resource_field->hasOne() ? 1 : -1), + static::buildLinkCollectionFromEntityReferenceField($context, $field, $links ?: new LinkCollection([])), + $meta, + $context + ); + } + + /** + * Gets context resource object of the relationship. + * + * @return \Drupal\jsonapi\JsonApiResource\ResourceObject + * The context ResourceObject. + * + * @see \Drupal\jsonapi\JsonApiResource\Relationship::$context + */ + public function getContext() { + return $this->context; + } + + /** + * Gets the relationship object's public field name. + * + * @return string + * The relationship's field name. + */ + public function getFieldName() { + return $this->fieldName; + } + + /** + * Gets the relationship object's data. + * + * @return \Drupal\jsonapi\JsonApiResource\RelationshipData + * The relationship's data. + */ + public function getData() { + return $this->data; + } + + /** + * Gets the relationship object's links. + * + * @return \Drupal\jsonapi\JsonApiResource\LinkCollection + * The relationship object's links. + */ + public function getLinks() { + return $this->links; + } + + /** + * Gets the relationship object's metadata. + * + * @return array + * The relationship object's metadata. + */ + public function getMeta() { + return $this->meta; + } + + /** + * {@inheritdoc} + */ + public function getOmissions() { + return new OmittedData([]); + } + + /** + * {@inheritdoc} + */ + public function getMergedLinks(LinkCollection $top_level_links) { + // When directly fetching a relationship object, the relationship object's + // links become the top-level object's links unless they've been + // overridden. Overrides are especially important for the `self` link, which + // must match the link that generated the response. For example, the + // top-level `self` link might have an `include` query parameter that would + // be lost otherwise. + // See https://jsonapi.org/format/#fetching-relationships-responses-200 and + // https://jsonapi.org/format/#document-top-level. + return LinkCollection::merge($top_level_links, $this->getLinks()->filter(function ($key) use ($top_level_links) { + return !$top_level_links->hasLinkWithKey($key); + })->withContext($top_level_links->getContext())); + } + + /** + * {@inheritdoc} + */ + public function getMergedMeta(array $top_level_meta) { + return NestedArray::mergeDeep($top_level_meta, $this->getMeta()); + } + + /** + * Builds a LinkCollection for the given entity reference field. + * + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $context + * The context resource object of the relationship object. + * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $field + * The entity reference field from which to create the links. + * @param \Drupal\jsonapi\JsonApiResource\LinkCollection $links + * Any extra links for the Relationship, if a `self` link is not provided, + * one will be automatically added if the context resource is locatable and + * is not internal. + * + * @return \Drupal\jsonapi\JsonApiResource\LinkCollection + * The built links. + */ + protected static function buildLinkCollectionFromEntityReferenceField(ResourceObject $context, EntityReferenceFieldItemListInterface $field, LinkCollection $links) { + $context_resource_type = $context->getResourceType(); + $public_field_name = $context_resource_type->getPublicName($field->getName()); + if ($context_resource_type->isLocatable() && !$context_resource_type->isInternal()) { + $context_is_versionable = $context_resource_type->isVersionable(); + if (!$links->hasLinkWithKey('self')) { + $route_name = Routes::getRouteName($context_resource_type, "$public_field_name.relationship.get"); + $self_link = Url::fromRoute($route_name, ['entity' => $context->getId()]); + if ($context_is_versionable) { + $self_link->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => $context->getVersionIdentifier()]); + } + $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_link, 'self')); + } + $has_non_internal_resource_type = array_reduce($context_resource_type->getRelatableResourceTypesByField($public_field_name), function ($carry, ResourceType $target) { + return $carry ?: !$target->isInternal(); + }, FALSE); + // If a `related` link was not provided, automatically generate one from + // the relationship object to the collection resource with all of the + // resources targeted by this relationship. However, that link should + // *not* be generated if all of the relatable resources are internal. + // That's because, in that case, a route will not exist for it. + if (!$links->hasLinkWithKey('related') && $has_non_internal_resource_type) { + $route_name = Routes::getRouteName($context_resource_type, "$public_field_name.related"); + $related_link = Url::fromRoute($route_name, ['entity' => $context->getId()]); + if ($context_is_versionable) { + $related_link->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => $context->getVersionIdentifier()]); + } + $links = $links->withLink('related', new Link(new CacheableMetadata(), $related_link, 'related')); + } + } + return $links; + } + +} diff --git a/core/modules/jsonapi/src/JsonApiResource/ResourceIdentifier.php b/core/modules/jsonapi/src/JsonApiResource/ResourceIdentifier.php index f129281b1..b6bca7e3b 100644 --- a/core/modules/jsonapi/src/JsonApiResource/ResourceIdentifier.php +++ b/core/modules/jsonapi/src/JsonApiResource/ResourceIdentifier.php @@ -311,12 +311,15 @@ public static function toResourceIdentifier(EntityReferenceItem $item, $arity = */ public static function toResourceIdentifiers(EntityReferenceFieldItemListInterface $items) { $relationships = []; - foreach ($items as $item) { + foreach ($items->filterEmptyItems() as $item) { // Create a ResourceIdentifier from the field item. This will make it // comparable with all previous field items. Here, it is assumed that the // resource identifier is unique so it has no arity. If a parallel // relationship is encountered, it will be assigned later. $relationship = static::toResourceIdentifier($item); + if ($relationship->getResourceType()->isInternal()) { + continue; + } // Now, iterate over the previously seen resource identifiers in reverse // order. Reverse order is important so that when a parallel relationship // is encountered, it will have the highest arity value so the current @@ -416,7 +419,8 @@ protected static function getVirtualOrMissingResourceIdentifier(EntityReferenceI assert($host_entity instanceof EntityInterface); $resource_type = $resource_type_repository->get($host_entity->getEntityTypeId(), $host_entity->bundle()); assert($resource_type instanceof ResourceType); - $relatable_resource_types = $resource_type->getRelatableResourceTypesByField($field->getName()); + $relatable_resource_types = $resource_type->getRelatableResourceTypesByField($resource_type->getPublicName($field->getName())); + assert(!empty($relatable_resource_types)); $get_metadata = function ($type) { return [ 'links' => [ diff --git a/core/modules/jsonapi/src/JsonApiResource/ResourceObject.php b/core/modules/jsonapi/src/JsonApiResource/ResourceObject.php index 8a08cb3d4..cce7d45a3 100644 --- a/core/modules/jsonapi/src/JsonApiResource/ResourceObject.php +++ b/core/modules/jsonapi/src/JsonApiResource/ResourceObject.php @@ -15,6 +15,7 @@ use Drupal\jsonapi\ResourceType\ResourceType; use Drupal\jsonapi\Revisions\VersionByRel; use Drupal\jsonapi\Routing\Routes; +use Drupal\user\UserInterface; /** * Represents a JSON:API resource object. @@ -243,19 +244,19 @@ protected static function buildLinksFromEntity(ResourceType $resource_type, Enti // revision changes and to disambiguate resource objects with the same // `type` and `id` in a `version-history` collection. $self_with_version_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'id:' . $entity->getRevisionId()]); - $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_with_version_url, ['self'])); + $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_with_version_url, 'self')); } if (!$entity->isDefaultRevision()) { $latest_version_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'rel:' . VersionByRel::LATEST_VERSION]); - $links = $links->withLink(VersionByRel::LATEST_VERSION, new Link(new CacheableMetadata(), $latest_version_url, [VersionByRel::LATEST_VERSION])); + $links = $links->withLink(VersionByRel::LATEST_VERSION, new Link(new CacheableMetadata(), $latest_version_url, VersionByRel::LATEST_VERSION)); } if (!$entity->isLatestRevision()) { $working_copy_url = $self_url->setOption('query', [JsonApiSpec::VERSION_QUERY_PARAMETER => 'rel:' . VersionByRel::WORKING_COPY]); - $links = $links->withLink(VersionByRel::WORKING_COPY, new Link(new CacheableMetadata(), $working_copy_url, [VersionByRel::WORKING_COPY])); + $links = $links->withLink(VersionByRel::WORKING_COPY, new Link(new CacheableMetadata(), $working_copy_url, VersionByRel::WORKING_COPY)); } } if (!$links->hasLinkWithKey('self')) { - $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_url, ['self'])); + $links = $links->withLink('self', new Link(new CacheableMetadata(), $self_url, 'self')); } } return $links; @@ -281,11 +282,15 @@ protected static function extractContentEntityFields(ResourceType $resource_type [$resource_type, 'isFieldEnabled'] ); - // The "label" field needs special treatment: some entity types have a label - // field that is actually backed by a label callback. + // Special handling for user entities that allows a JSON:API user agent to + // access the display name of a user. For example, this is useful when + // displaying the name of a node's author. + // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254. $entity_type = $entity->getEntityType(); - if ($entity_type->hasLabelCallback()) { - $fields[static::getLabelFieldName($entity)]->value = $entity->label(); + if ($entity_type->id() == 'user' && $resource_type->isFieldEnabled('display_name')) { + assert($entity instanceof UserInterface); + $display_name = $resource_type->getPublicName('display_name'); + $output[$display_name] = $entity->getDisplayName(); } // Return a sub-array of $output containing the keys in $enabled_fields. @@ -294,6 +299,7 @@ protected static function extractContentEntityFields(ResourceType $resource_type $public_field_name = $resource_type->getPublicName($field_name); $output[$public_field_name] = $field_value; } + return $output; } @@ -308,9 +314,13 @@ protected static function extractContentEntityFields(ResourceType $resource_type */ protected static function getLabelFieldName(EntityInterface $entity) { $label_field_name = $entity->getEntityType()->getKey('label'); - // @todo Remove this work-around after https://www.drupal.org/project/drupal/issues/2450793 lands. + // Special handling for user entities that allows a JSON:API user agent to + // access the display name of a user. This is useful when displaying the + // name of a node's author. + // @see \Drupal\jsonapi\JsonApiResource\ResourceObject::extractContentEntityFields() + // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254. if ($entity->getEntityTypeId() === 'user') { - $label_field_name = 'name'; + $label_field_name = 'display_name'; } return $label_field_name; } diff --git a/core/modules/jsonapi/src/JsonApiResource/ResourceObjectData.php b/core/modules/jsonapi/src/JsonApiResource/ResourceObjectData.php index 41430f585..5fadfb035 100644 --- a/core/modules/jsonapi/src/JsonApiResource/ResourceObjectData.php +++ b/core/modules/jsonapi/src/JsonApiResource/ResourceObjectData.php @@ -14,7 +14,7 @@ * @see https://www.drupal.org/project/jsonapi/issues/3032787 * @see jsonapi.api.php */ -class ResourceObjectData extends Data { +class ResourceObjectData extends Data implements TopLevelDataInterface { /** * ResourceObjectData constructor. @@ -31,6 +31,13 @@ public function __construct($data, $cardinality = -1) { parent::__construct($data, $cardinality); } + /** + * {@inheritdoc} + */ + public function getData() { + return $this->getAccessible(); + } + /** * Gets only data to be exposed. * @@ -61,4 +68,18 @@ public function getOmissions() { return new OmittedData($omitted_data); } + /** + * {@inheritdoc} + */ + public function getMergedLinks(LinkCollection $top_level_links) { + return $top_level_links; + } + + /** + * {@inheritdoc} + */ + public function getMergedMeta(array $top_level_meta) { + return $top_level_meta; + } + } diff --git a/core/modules/jsonapi/src/JsonApiResource/TopLevelDataInterface.php b/core/modules/jsonapi/src/JsonApiResource/TopLevelDataInterface.php new file mode 100644 index 000000000..a01da1568 --- /dev/null +++ b/core/modules/jsonapi/src/JsonApiResource/TopLevelDataInterface.php @@ -0,0 +1,54 @@ +getKey('bundle'); $uuid_key = $entity_type_definition->getKey('uuid'); + // User resource objects contain a read-only attribute that is not a real + // field on the user entity type. + // @see \Drupal\jsonapi\JsonApiResource\ResourceObject::extractContentEntityFields() + // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254. + if ($entity_type_id === 'user') { + $data = array_diff_key($data, array_flip([$resource_type->getPublicName('display_name')])); + } + // Translate the public fields into the entity fields. foreach ($data as $public_field_name => $field_value) { $internal_name = $resource_type->getInternalName($public_field_name); diff --git a/core/modules/jsonapi/src/Normalizer/EntityReferenceFieldNormalizer.php b/core/modules/jsonapi/src/Normalizer/EntityReferenceFieldNormalizer.php index 1096de42d..c79d56ae1 100644 --- a/core/modules/jsonapi/src/Normalizer/EntityReferenceFieldNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/EntityReferenceFieldNormalizer.php @@ -10,6 +10,7 @@ use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\JsonApiSpec; use Drupal\jsonapi\Normalizer\Value\CacheableNormalization; +use Drupal\jsonapi\ResourceType\ResourceTypeRelationship; use Drupal\jsonapi\Routing\Routes; /** @@ -35,28 +36,25 @@ public function normalize($field, $format = NULL, array $context = []) { assert($field instanceof EntityReferenceFieldItemListInterface); // Build the relationship object based on the Entity Reference and normalize // that object instead. - $definition = $field->getFieldDefinition(); - $cardinality = $definition - ->getFieldStorageDefinition() - ->getCardinality(); $resource_identifiers = array_filter(ResourceIdentifier::toResourceIdentifiers($field->filterEmptyItems()), function (ResourceIdentifierInterface $resource_identifier) { return !$resource_identifier->getResourceType()->isInternal(); }); - $context['field_name'] = $field->getName(); $normalized_items = CacheableNormalization::aggregate($this->serializer->normalize($resource_identifiers, $format, $context)); assert($context['resource_object'] instanceof ResourceObject); + $resource_relationship = $context['resource_object']->getResourceType()->getFieldByInternalName($field->getName()); + assert($resource_relationship instanceof ResourceTypeRelationship); $link_cacheability = new CacheableMetadata(); $links = array_map(function (Url $link) use ($link_cacheability) { $href = $link->setAbsolute()->toString(TRUE); $link_cacheability->addCacheableDependency($href); return ['href' => $href->getGeneratedUrl()]; - }, static::getRelationshipLinks($context['resource_object'], $field->getName())); + }, static::getRelationshipLinks($context['resource_object'], $resource_relationship)); $data_normalization = $normalized_items->getNormalization(); $normalization = [ // Empty 'to-one' relationships must be NULL. // Empty 'to-many' relationships must be an empty array. // @link http://jsonapi.org/format/#document-resource-object-linkage - 'data' => $cardinality === 1 ? array_shift($data_normalization) : $data_normalization, + 'data' => $resource_relationship->hasOne() ? array_shift($data_normalization) : $data_normalization, ]; if (!empty($links)) { $normalization['links'] = $links; @@ -69,18 +67,18 @@ public function normalize($field, $format = NULL, array $context = []) { * * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $relationship_context * The JSON:API resource object context of the relationship. - * @param string $relationship_field_name - * The internal relationship field name. + * @param \Drupal\jsonapi\ResourceType\ResourceTypeRelationship $resource_relationship + * The resource type relationship field. * * @return array * The relationship's links. */ - public static function getRelationshipLinks(ResourceObject $relationship_context, $relationship_field_name) { + public static function getRelationshipLinks(ResourceObject $relationship_context, ResourceTypeRelationship $resource_relationship) { $resource_type = $relationship_context->getResourceType(); if ($resource_type->isInternal() || !$resource_type->isLocatable()) { return []; } - $public_field_name = $resource_type->getPublicName($relationship_field_name); + $public_field_name = $resource_relationship->getPublicName(); $relationship_route_name = Routes::getRouteName($resource_type, "$public_field_name.relationship.get"); $links = [ 'self' => Url::fromRoute($relationship_route_name, ['entity' => $relationship_context->getId()]), diff --git a/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php b/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php index 8a6673383..183fac073 100644 --- a/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php @@ -112,6 +112,12 @@ public function denormalize($data, $class, $format = NULL, array $context = []) // be expanded to an array of all properties, we special-case single-value // properties. if (!is_array($data)) { + // The NULL normalization means there is no value, hence we can return + // early. Note that this is not just an optimization but a necessity for + // field types without main properties (such as the "map" field type). + if ($data === NULL) { + return $data; + } $property_value = $data; $property_name = $item_definition->getMainPropertyName(); $property_value_class = $property_definitions[$property_name]->getClass(); diff --git a/core/modules/jsonapi/src/Normalizer/FieldNormalizer.php b/core/modules/jsonapi/src/Normalizer/FieldNormalizer.php index 3bd46a601..b33677c17 100644 --- a/core/modules/jsonapi/src/Normalizer/FieldNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/FieldNormalizer.php @@ -4,7 +4,9 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\Normalizer\Value\CacheableNormalization; +use Drupal\jsonapi\ResourceType\ResourceType; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; /** @@ -31,12 +33,8 @@ class FieldNormalizer extends NormalizerBase implements DenormalizerInterface { public function normalize($field, $format = NULL, array $context = []) { /* @var \Drupal\Core\Field\FieldItemListInterface $field */ $normalized_items = $this->normalizeFieldItems($field, $format, $context); - - $cardinality = $field->getFieldDefinition() - ->getFieldStorageDefinition() - ->getCardinality(); - - return $cardinality === 1 + assert($context['resource_object'] instanceof ResourceObject); + return $context['resource_object']->getResourceType()->getFieldByInternalName($field->getName())->hasOne() ? array_shift($normalized_items) ?: CacheableNormalization::permanent(NULL) : CacheableNormalization::aggregate($normalized_items); } @@ -47,6 +45,8 @@ public function normalize($field, $format = NULL, array $context = []) { public function denormalize($data, $class, $format = NULL, array $context = []) { $field_definition = $context['field_definition']; assert($field_definition instanceof FieldDefinitionInterface); + $resource_type = $context['resource_type']; + assert($resource_type instanceof ResourceType); // If $data contains items (recognizable by numerical array keys, which // Drupal's Field API calls "deltas"), then it already is itemized; it's not @@ -61,7 +61,7 @@ public function denormalize($data, $class, $format = NULL, array $context = []) // Single-cardinality fields don't need itemization. $field_item_class = $field_definition->getItemDefinition()->getClass(); - if (count($itemized_data) === 1 && $field_definition->getFieldStorageDefinition()->getCardinality() === 1) { + if (count($itemized_data) === 1 && $resource_type->getFieldByInternalName($field_definition->getName())->hasOne()) { return $this->serializer->denormalize($itemized_data[0], $field_item_class, $format, $context); } diff --git a/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php b/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php index 5062f11a6..461381581 100644 --- a/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php @@ -49,7 +49,9 @@ public function __construct(AccountInterface $current_user) { * {@inheritdoc} */ public function normalize($object, $format = NULL, array $context = []) { - return new HttpExceptionNormalizerValue(new CacheableMetadata(), static::rasterizeValueRecursive($this->buildErrorObjects($object))); + $cacheability = new CacheableMetadata(); + $cacheability->addCacheableDependency($object); + return new HttpExceptionNormalizerValue($cacheability, static::rasterizeValueRecursive($this->buildErrorObjects($object))); } /** @@ -85,7 +87,7 @@ protected function buildErrorObjects(HttpException $exception) { // Exceptions thrown without an explicitly defined code get assigned zero by // default. Since this is no helpful information, omit it. if ($exception->getCode() !== 0) { - $error['code'] = $exception->getCode(); + $error['code'] = (string) $exception->getCode(); } if ($this->currentUser->hasPermission('access site reports')) { // The following information may contain sensitive information. Only show diff --git a/core/modules/jsonapi/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php b/core/modules/jsonapi/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php index 02bb732d7..b6abaf7ea 100644 --- a/core/modules/jsonapi/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/JsonApiDocumentTopLevelNormalizer.php @@ -7,10 +7,8 @@ use Drupal\Component\Uuid\Uuid; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\jsonapi\JsonApiResource\ErrorCollection; use Drupal\jsonapi\JsonApiResource\OmittedData; -use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\JsonApiSpec; use Drupal\jsonapi\Normalizer\Value\CacheableOmission; use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel; @@ -190,13 +188,7 @@ public function normalize($object, $format = NULL, array $context = []) { } else { // Add data. - // @todo: remove this if-else and just call $this->serializer->normalize($data...) in https://www.drupal.org/project/jsonapi/issues/3036285. - if ($data instanceof EntityReferenceFieldItemListInterface) { - $document['data'] = $this->normalizeEntityReferenceFieldItemList($object, $format, $context); - } - else { - $document['data'] = $this->serializer->normalize($data, $format, $context); - } + $document['data'] = $this->serializer->normalize($data, $format, $context); // Add includes. $document['included'] = $this->serializer->normalize($object->getIncludes(), $format, $context)->omitIfEmpty(); // Add omissions and metadata. @@ -240,32 +232,6 @@ protected function normalizeErrorDocument(JsonApiDocumentTopLevel $document, $fo return new CacheableNormalization($cacheability, $errors); } - /** - * Normalizes an entity reference field, i.e. a relationship document. - * - * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel $document - * The document to normalize. - * @param string $format - * The normalization format. - * @param array $context - * The normalization context. - * - * @return \Drupal\jsonapi\Normalizer\Value\CacheableNormalization - * The normalized document. - * - * @todo: remove this in https://www.drupal.org/project/jsonapi/issues/3036285. - */ - protected function normalizeEntityReferenceFieldItemList(JsonApiDocumentTopLevel $document, $format, array $context = []) { - $data = $document->getData(); - $parent_entity = $data->getEntity(); - $resource_type = $this->resourceTypeRepository->get($parent_entity->getEntityTypeId(), $parent_entity->bundle()); - $context['resource_object'] = ResourceObject::createFromEntity($resource_type, $parent_entity); - $normalized_relationship = $this->serializer->normalize($data, $format, $context); - assert($normalized_relationship instanceof CacheableNormalization); - unset($context['resource_object']); - return new CacheableNormalization($normalized_relationship, $normalized_relationship->getNormalization()['data']); - } - /** * Normalizes omitted data into a set of omission links. * @@ -309,7 +275,7 @@ protected function normalizeOmissionsLinks(OmittedData $omissions, $format, arra // random salt and the link href. This ensures that the key is non- // deterministic while letting use deduplicate the links by their // href. The salt is *not* used for any cryptographic reason. - $link_key = 'item:' . static::getLinkHash($link_hash_salt, $error['links']['via']['href']); + $link_key = 'item--' . static::getLinkHash($link_hash_salt, $error['links']['via']['href']); $omission_links['links'][$link_key] = [ 'href' => $error['links']['via']['href'], 'meta' => [ diff --git a/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php b/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php index f742759ac..a883389d3 100644 --- a/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php @@ -16,8 +16,8 @@ * than one link for a given key. * * When normalizing more than one link in a LinkCollection with the same key, a - * unique and random string is appended to the link's key after a colon (:) to - * differentiate the links. + * unique and random string is appended to the link's key after a double dash + * (--) to differentiate the links. * * This may change with a later version of the JSON:API specification. * @@ -72,7 +72,7 @@ public function normalize($object, $format = NULL, array $context = []) { foreach ($object as $key => $links) { $is_multiple = count($links) > 1; foreach ($links as $link) { - $link_key = $is_multiple ? sprintf('%s:%s', $key, $this->hashByHref($link)) : $key; + $link_key = $is_multiple ? sprintf('%s--%s', $key, $this->hashByHref($link)) : $key; $attributes = $link->getTargetAttributes(); $normalization = array_merge(['href' => $link->getHref()], !empty($attributes) ? ['meta' => $attributes] : []); $normalized[$link_key] = new CacheableNormalization($link, $normalization); diff --git a/core/modules/jsonapi/src/Normalizer/RelationshipNormalizer.php b/core/modules/jsonapi/src/Normalizer/RelationshipNormalizer.php new file mode 100644 index 000000000..53354af0b --- /dev/null +++ b/core/modules/jsonapi/src/Normalizer/RelationshipNormalizer.php @@ -0,0 +1,32 @@ + $this->serializer->normalize($object->getData(), $format, $context), + 'links' => $this->serializer->normalize($object->getLinks(), $format, $context)->omitIfEmpty(), + 'meta' => CacheableNormalization::permanent($object->getMeta())->omitIfEmpty(), + ]); + } + +} diff --git a/core/modules/jsonapi/src/Normalizer/ResourceObjectNormalizer.php b/core/modules/jsonapi/src/Normalizer/ResourceObjectNormalizer.php index 4e0f881e1..10a5e4cca 100644 --- a/core/modules/jsonapi/src/Normalizer/ResourceObjectNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/ResourceObjectNormalizer.php @@ -3,7 +3,10 @@ namespace Drupal\jsonapi\Normalizer; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher; +use Drupal\jsonapi\JsonApiResource\Relationship; use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\Normalizer\Value\CacheableNormalization; use Drupal\jsonapi\Normalizer\Value\CacheableOmission; @@ -24,6 +27,23 @@ class ResourceObjectNormalizer extends NormalizerBase { */ protected $supportedInterfaceOrClass = ResourceObject::class; + /** + * The entity normalization cacher. + * + * @var \Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher + */ + protected $cacher; + + /** + * Constructs a ResourceObjectNormalizer object. + * + * @param \Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher $cacher + * The entity normalization cacher. + */ + public function __construct(ResourceObjectNormalizationCacher $cacher) { + $this->cacher = $cacher; + } + /** * {@inheritdoc} */ @@ -49,23 +69,91 @@ public function normalize($object, $format = NULL, array $context = []) { else { $field_names = array_keys($fields); } - $normalizer_values = []; - foreach ($fields as $field_name => $field) { - $in_sparse_fieldset = in_array($field_name, $field_names); - // Omit fields not listed in sparse fieldsets. - if (!$in_sparse_fieldset) { - continue; - } - $normalizer_values[$field_name] = $this->serializeField($field, $context, $format); - } + + $normalization_parts = $this->getNormalization($field_names, $object, $format, $context); + + // Keep only the requested fields (the cached normalization gradually grows + // to the complete set of fields). + $fields = $normalization_parts[ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_FIELDS]; + $field_normalizations = array_intersect_key($fields, array_flip($field_names)); + $relationship_field_names = array_keys($resource_type->getRelatableResourceTypes()); - return CacheableNormalization::aggregate([ - 'type' => CacheableNormalization::permanent($resource_type->getTypeName()), - 'id' => CacheableNormalization::permanent($object->getId()), - 'attributes' => CacheableNormalization::aggregate(array_diff_key($normalizer_values, array_flip($relationship_field_names)))->omitIfEmpty(), - 'relationships' => CacheableNormalization::aggregate(array_intersect_key($normalizer_values, array_flip($relationship_field_names)))->omitIfEmpty(), - 'links' => $this->serializer->normalize($object->getLinks(), $format, $context)->omitIfEmpty(), - ])->withCacheableDependency($object); + $attributes = array_diff_key($field_normalizations, array_flip($relationship_field_names)); + $relationships = array_intersect_key($field_normalizations, array_flip($relationship_field_names)); + $entity_normalization = array_filter( + $normalization_parts[ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_BASE] + [ + 'attributes' => CacheableNormalization::aggregate($attributes)->omitIfEmpty(), + 'relationships' => CacheableNormalization::aggregate($relationships)->omitIfEmpty(), + ] + ); + return CacheableNormalization::aggregate($entity_normalization)->withCacheableDependency($object); + } + + /** + * Normalizes an entity using the given fieldset. + * + * @param string[] $field_names + * The field names to normalize (the sparse fieldset, if any). + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object + * The resource object to partially normalize. + * @param string $format + * The format in which the normalization will be encoded. + * @param array $context + * Context options for the normalizer. + * + * @return array + * An array with two key-value pairs: + * - 'base': array, the base normalization of the entity, that does not + * depend on which sparse fieldset was requested. + * - 'fields': CacheableNormalization for all requested fields. + * + * @see ::normalize() + */ + protected function getNormalization(array $field_names, ResourceObject $object, $format = NULL, array $context = []) { + $cached_normalization_parts = $this->cacher->get($object); + $normalizer_values = $cached_normalization_parts !== FALSE + ? $cached_normalization_parts + : static::buildEmptyNormalization($object); + $fields = &$normalizer_values[ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_FIELDS]; + $non_cached_fields = array_diff_key($object->getFields(), $fields); + $non_cached_requested_fields = array_intersect_key($non_cached_fields, array_flip($field_names)); + foreach ($non_cached_requested_fields as $field_name => $field) { + $fields[$field_name] = $this->serializeField($field, $context, $format); + } + // Add links if missing. + $base = &$normalizer_values[ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_BASE]; + $base['links'] = isset($base['links']) + ? $base['links'] + : $this->serializer + ->normalize($object->getLinks(), $format, $context) + ->omitIfEmpty(); + + if (!empty($non_cached_requested_fields)) { + $this->cacher->saveOnTerminate($object, $normalizer_values); + } + + return $normalizer_values; + } + + /** + * Builds the empty normalization structure for cache misses. + * + * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object + * The resource object being normalized. + * + * @return array + * The normalization structure as defined in ::getNormalization(). + * + * @see ::getNormalization() + */ + protected static function buildEmptyNormalization(ResourceObject $object) { + return [ + ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_BASE => [ + 'type' => CacheableNormalization::permanent($object->getResourceType()->getTypeName()), + 'id' => CacheableNormalization::permanent($object->getId()), + ], + ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_FIELDS => [], + ]; } /** @@ -90,7 +178,17 @@ protected function serializeField($field, array $context, $format) { if (!$field_access_result->isAllowed()) { return new CacheableOmission(CacheableMetadata::createFromObject($field_access_result)); } - $normalized_field = $this->serializer->normalize($field, $format, $context); + if ($field instanceof EntityReferenceFieldItemListInterface) { + // Build the relationship object based on the entity reference and + // normalize that object instead. + assert(!empty($context['resource_object']) && $context['resource_object'] instanceof ResourceObject); + $resource_object = $context['resource_object']; + $relationship = Relationship::createFromEntityReferenceField($resource_object, $field); + $normalized_field = $this->serializer->normalize($relationship, $format, $context); + } + else { + $normalized_field = $this->serializer->normalize($field, $format, $context); + } assert($normalized_field instanceof CacheableNormalization); return $normalized_field->withCacheableDependency(CacheableMetadata::createFromObject($field_access_result)); } diff --git a/core/modules/jsonapi/src/ParamConverter/EntityUuidConverter.php b/core/modules/jsonapi/src/ParamConverter/EntityUuidConverter.php index aeec4c5d4..75f718179 100644 --- a/core/modules/jsonapi/src/ParamConverter/EntityUuidConverter.php +++ b/core/modules/jsonapi/src/ParamConverter/EntityUuidConverter.php @@ -2,11 +2,10 @@ namespace Drupal\jsonapi\ParamConverter; -use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\TranslatableInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\ParamConverter\EntityConverter; -use Drupal\Core\TypedData\TranslatableInterface; use Drupal\jsonapi\Routing\Routes; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; @@ -62,7 +61,7 @@ public function convert($value, $definition, $name, array $defaults) { $entity = reset($entities); // If the entity type is translatable, ensure we return the proper // translation object for the current context. - if ($entity instanceof EntityInterface && $entity instanceof TranslatableInterface) { + if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) { // @see https://www.drupal.org/project/drupal/issues/2624770 $entity_repository = isset($this->entityRepository) ? $this->entityRepository : $this->entityManager; $entity = $entity_repository->getTranslationFromContext($entity, NULL, ['operation' => 'entity_upcast']); diff --git a/core/modules/jsonapi/src/Query/Filter.php b/core/modules/jsonapi/src/Query/Filter.php index 58d398582..e87f0081a 100644 --- a/core/modules/jsonapi/src/Query/Filter.php +++ b/core/modules/jsonapi/src/Query/Filter.php @@ -157,7 +157,7 @@ public static function createFromQueryParameter($parameter, ResourceType $resour foreach ($expanded as &$filter_item) { if (isset($filter_item[static::CONDITION_KEY][EntityCondition::PATH_KEY])) { $unresolved = $filter_item[static::CONDITION_KEY][EntityCondition::PATH_KEY]; - $filter_item[static::CONDITION_KEY][EntityCondition::PATH_KEY] = $field_resolver->resolveInternalEntityQueryPath($resource_type->getEntityTypeId(), $resource_type->getBundle(), $unresolved); + $filter_item[static::CONDITION_KEY][EntityCondition::PATH_KEY] = $field_resolver->resolveInternalEntityQueryPath($resource_type, $unresolved); } } return new static(static::buildEntityConditionGroup($expanded)); diff --git a/core/modules/jsonapi/src/Query/OffsetPage.php b/core/modules/jsonapi/src/Query/OffsetPage.php index e620ca8e6..70a752ef2 100644 --- a/core/modules/jsonapi/src/Query/OffsetPage.php +++ b/core/modules/jsonapi/src/Query/OffsetPage.php @@ -104,7 +104,7 @@ public function getSize() { * @param mixed $parameter * The `page` query parameter from the Symfony request object. * - * @return \Drupal\jsonapi\Query\OffsetPage + * @return static * An OffsetPage object with defaults. */ public static function createFromQueryParameter($parameter) { diff --git a/core/modules/jsonapi/src/ResourceType/ResourceType.php b/core/modules/jsonapi/src/ResourceType/ResourceType.php index 24a883547..955feff35 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceType.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceType.php @@ -2,6 +2,10 @@ namespace Drupal\jsonapi\ResourceType; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\TypedData\DataReferenceTargetDefinition; + /** * Value object containing all metadata for a JSON:API resource type. * @@ -82,28 +86,19 @@ class ResourceType { protected $fields; /** - * The list of disabled fields. Disabled by default: uuid, id, type. - * - * @var string[] + * An array of arrays of relatable resource types, keyed by public field name. * - * @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getFieldMapping() + * @var array */ - protected $disabledFields; + protected $relatableResourceTypesByField; /** - * The mapping for field aliases: keys=internal names, values=public names. + * The mapping for field aliases: keys=public names, values=internal names. * * @var string[] */ protected $fieldMapping; - /** - * The inverse of $fieldMapping. - * - * @var string[] - */ - protected $invertedFieldMapping; - /** * Gets the entity type ID. * @@ -160,8 +155,8 @@ public function getDeserializationTargetClass() { */ public function getPublicName($field_name) { // By default the entity field name is the public field name. - return isset($this->fieldMapping[$field_name]) - ? $this->fieldMapping[$field_name] + return isset($this->fields[$field_name]) + ? $this->fields[$field_name]->getPublicName() : $field_name; } @@ -176,9 +171,47 @@ public function getPublicName($field_name) { */ public function getInternalName($field_name) { // By default the entity field name is the public field name. - return isset($this->invertedFieldMapping[$field_name]) - ? $this->invertedFieldMapping[$field_name] - : $field_name; + return $this->fieldMapping[$field_name] ?? $field_name; + } + + /** + * Gets the attribute and relationship fields of this resource type. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeField[] + * The field objects on this resource type. + */ + public function getFields() { + return $this->fields; + } + + /** + * Gets a particular attribute or relationship field by public field name. + * + * @param string $public_field_name + * The public field name of the desired field. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeField|null + * A resource type field object or NULL if the field does not exist on this + * resource type. + */ + public function getFieldByPublicName($public_field_name) { + return isset($this->fieldMapping[$public_field_name]) + ? $this->getFieldByInternalName($this->fieldMapping[$public_field_name]) + : NULL; + } + + /** + * Gets a particular attribute or relationship field by internal field name. + * + * @param string $internal_field_name + * The internal field name of the desired field. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeField|null + * A resource type field object or NULL if the field does not exist on this + * resource type. + */ + public function getFieldByInternalName($internal_field_name) { + return $this->fields[$internal_field_name] ?? NULL; } /** @@ -199,7 +232,7 @@ public function getInternalName($field_name) { * otherwise. */ public function hasField($field_name) { - return in_array($field_name, $this->fields, TRUE); + return array_key_exists($field_name, $this->fields); } /** @@ -216,7 +249,7 @@ public function hasField($field_name) { * of the data model. FALSE otherwise. */ public function isFieldEnabled($field_name) { - return $this->hasField($field_name) && !in_array($field_name, $this->disabledFields, TRUE); + return $this->hasField($field_name) && $this->fields[$field_name]->isFieldEnabled(); } /** @@ -307,10 +340,13 @@ public function isVersionable() { * (optional) Whether the resource type is mutable. * @param bool $is_versionable * (optional) Whether the resource type is versionable. - * @param array $field_mapping - * (optional) The field mapping to use. + * @param \Drupal\jsonapi\ResourceType\ResourceTypeField[] $fields + * (optional) The resource type fields, keyed by internal field name. */ - public function __construct($entity_type_id, $bundle, $deserialization_target_class, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $field_mapping = []) { + public function __construct($entity_type_id, $bundle, $deserialization_target_class, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $fields = []) { + if (!empty($fields) && !reset($fields) instanceof ResourceTypeField) { + $fields = $this->updateDeprecatedFieldMapping($fields, $entity_type_id, $bundle); + } $this->entityTypeId = $entity_type_id; $this->bundle = $bundle; $this->deserializationTargetClass = $deserialization_target_class; @@ -318,17 +354,15 @@ public function __construct($entity_type_id, $bundle, $deserialization_target_cl $this->isLocatable = $is_locatable; $this->isMutable = $is_mutable; $this->isVersionable = $is_versionable; + $this->fields = $fields; $this->typeName = $this->bundle === '?' ? 'unknown' : sprintf('%s--%s', $this->entityTypeId, $this->bundle); - $this->fields = array_keys($field_mapping); - $this->disabledFields = array_keys(array_filter($field_mapping, function ($v) { - return $v === FALSE; - })); - $this->fieldMapping = array_filter($field_mapping, 'is_string'); - $this->invertedFieldMapping = array_flip($this->fieldMapping); + $this->fieldMapping = array_flip(array_map(function (ResourceTypeField $field) { + return $field->getPublicName(); + }, $this->fields)); } /** @@ -341,7 +375,16 @@ public function __construct($entity_type_id, $bundle, $deserialization_target_cl * across resource types across fields, but not within a field. */ public function setRelatableResourceTypes(array $relatable_resource_types) { - $this->relatableResourceTypes = $relatable_resource_types; + $this->fields = array_reduce(array_keys($relatable_resource_types), function ($fields, $public_field_name) use ($relatable_resource_types) { + if (!isset($this->fieldMapping[$public_field_name])) { + throw new \LogicException('A field must exist for relatable resource types to be set on it.'); + } + $internal_field_name = $this->fieldMapping[$public_field_name]; + $field = $fields[$internal_field_name]; + assert($field instanceof ResourceTypeRelationship); + $fields[$internal_field_name] = $field->withRelatableResourceTypes($relatable_resource_types[$public_field_name]); + return $fields; + }, $this->fields); } /** @@ -353,10 +396,14 @@ public function setRelatableResourceTypes(array $relatable_resource_types) { * @see self::setRelatableResourceTypes() */ public function getRelatableResourceTypes() { - if (!isset($this->relatableResourceTypes)) { - throw new \LogicException("setRelatableResourceTypes() must be called before getting relatable resource types."); + if (!isset($this->relatableResourceTypesByField)) { + $this->relatableResourceTypesByField = array_reduce(array_map(function (ResourceTypeRelationship $field) { + return [$field->getPublicName() => $field->getRelatableResourceTypes()]; + }, array_filter($this->fields, function (ResourceTypeField $field) { + return $field instanceof ResourceTypeRelationship; + })), 'array_merge', []); } - return $this->relatableResourceTypes; + return $this->relatableResourceTypesByField; } /** @@ -371,10 +418,9 @@ public function getRelatableResourceTypes() { * @see self::getRelatableResourceTypes() */ public function getRelatableResourceTypesByField($field_name) { - $relatable_resource_types = $this->getRelatableResourceTypes(); - return isset($relatable_resource_types[$field_name]) ? - $relatable_resource_types[$field_name] : - []; + return ($field = $this->getFieldByPublicName($field_name)) && $field instanceof ResourceTypeRelationship + ? $field->getRelatableResourceTypes() + : []; } /** @@ -389,4 +435,92 @@ public function getPath() { return sprintf('/%s/%s', $this->getEntityTypeId(), $this->getBundle()); } + /** + * {@inheritdoc} + */ + public function __get($name) { + $class_name = self::class; + @trigger_error("Using the ${$name} protected property of a {$class_name} is deprecated in Drupal 8.8.0 and will not be allowed in Drupal 9.0.0. Use {$class_name}::getFields() instead. See https://www.drupal.org/node/3084721.", E_USER_DEPRECATED); + if ($name === 'disabledFields') { + return array_map(function (ResourceTypeField $field) { + return $field->getInternalName(); + }, array_filter($this->getFields(), function (ResourceTypeField $field) { + return !$field->isFieldEnabled(); + })); + } + if ($name === 'invertedFieldMapping') { + return array_reduce($this->getFields(), function ($inverted_field_mapping, ResourceTypeField $field) { + $internal_field_name = $field->getInternalName(); + $public_field_name = $field->getPublicName(); + if ($field->isFieldEnabled() && $internal_field_name !== $public_field_name) { + $inverted_field_mapping[$public_field_name] = $internal_field_name; + } + return $inverted_field_mapping; + }, []); + } + } + + /** + * Takes a deprecated field mapping and converts it to ResourceTypeFields. + * + * @param array $field_mapping + * The deprecated field mapping. + * @param string $entity_type_id + * The entity type ID of the field mapping. + * @param string $bundle + * The bundle ID of the field mapping or the entity type ID if the entity + * type does not have bundles. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeField[] + * The updated field mapping objects. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * self::getFields() instead. + * + * @see https://www.drupal.org/project/drupal/issues/3014277 + */ + private function updateDeprecatedFieldMapping(array $field_mapping, $entity_type_id, $bundle) { + $class_name = self::class; + @trigger_error("Passing an array with strings or booleans as a field mapping to {$class_name}::__construct() is deprecated in Drupal 8.8.0 and will not be allowed in Drupal 9.0.0. See \Drupal\jsonapi\ResourceTypeRepository::getFields(). See https://www.drupal.org/node/3084746.", E_USER_DEPRECATED); + + // See \Drupal\jsonapi\ResourceType\ResourceTypeRepository::isReferenceFieldDefinition(). + $is_reference_field_definition = function (FieldDefinitionInterface $field_definition) { + static $field_type_is_reference = []; + + if (isset($field_type_is_reference[$field_definition->getType()])) { + return $field_type_is_reference[$field_definition->getType()]; + } + + /* @var \Drupal\Core\Field\TypedData\FieldItemDataDefinition $item_definition */ + $item_definition = $field_definition->getItemDefinition(); + $main_property = $item_definition->getMainPropertyName(); + $property_definition = $item_definition->getPropertyDefinition($main_property); + + return $field_type_is_reference[$field_definition->getType()] = $property_definition instanceof DataReferenceTargetDefinition; + }; + + /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */ + $entity_type_manager = \Drupal::service('entity_type.manager'); + $is_fieldable = $entity_type_manager->getDefinition($entity_type_id)->entityClassImplements(FieldableEntityInterface::class); + $field_definitions = $is_fieldable + ? \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_id, $bundle) + : []; + + $fields = []; + foreach ($field_mapping as $internal_field_name => $public_field_name) { + assert(is_bool($public_field_name) || is_string($public_field_name)); + $field_definition = $is_fieldable && !empty($field_definitions[$internal_field_name]) + ? $field_definitions[$internal_field_name] + : NULL; + $is_relationship_field = $field_definition && $is_reference_field_definition($field_definition); + $has_one = !$field_definition || $field_definition->getFieldStorageDefinition()->getCardinality() === 1; + $alias = is_string($public_field_name) ? $public_field_name : NULL; + $fields[$internal_field_name] = $is_relationship_field + ? new ResourceTypeRelationship($internal_field_name, $alias, $public_field_name !== FALSE, $has_one) + : new ResourceTypeAttribute($internal_field_name, $alias, $public_field_name !== FALSE, $has_one); + } + + return $fields; + } + } diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeAttribute.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeAttribute.php new file mode 100644 index 000000000..19f71c192 --- /dev/null +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeAttribute.php @@ -0,0 +1,16 @@ +resourceTypeName = $resource_type_name; + $this->fields = $fields; + } + + /** + * Creates a new ResourceTypeBuildEvent. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * An entity type for the resource type to be built. + * @param string $bundle + * A bundle name for the resource type to be built. If the entity type does + * not have bundles, the entity type ID. + * @param \Drupal\jsonapi\ResourceType\ResourceTypeField[] $fields + * The fields of the resource type to be built. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent + * A new event. + */ + public static function createFromEntityTypeAndBundle(EntityTypeInterface $entity_type, $bundle, array $fields) { + return new static(sprintf('%s--%s', $entity_type->id(), $bundle), $fields); + } + + /** + * Gets current resource type name of the resource type to be built. + * + * @return string + * The resource type name. + */ + public function getResourceTypeName() { + return $this->resourceTypeName; + } + + /** + * Disables the resource type to be built. + */ + public function disableResourceType() { + $this->disabled = TRUE; + } + + /** + * Whether the resource type to be built should be disabled. + * + * @return bool + * TRUE if the resource type should be disabled, FALSE otherwise. + */ + public function resourceTypeShouldBeDisabled() { + return $this->disabled; + } + + /** + * Gets the current fields of the resource type to be built. + * + * @return \Drupal\jsonapi\ResourceType\ResourceTypeField[] + * The current fields of the resource type to be built. + */ + public function getFields() { + return $this->fields; + } + + /** + * Sets the public name of the given field on the resource type to be built. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeField $field + * The field for which to set a public name. + * @param string $public_field_name + * The public field name to set. + */ + public function setPublicFieldName(ResourceTypeField $field, $public_field_name) { + foreach ($this->fields as $index => $value) { + if ($field === $value) { + $this->fields[$index] = $value->withPublicName($public_field_name); + return; + } + } + } + + /** + * Disables the given field on the resource type to be built. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeField $field + * The field for which to set a public name. + */ + public function disableField(ResourceTypeField $field) { + foreach ($this->fields as $index => $value) { + if ($field === $value) { + $this->fields[$index] = $value->disabled(); + return; + } + } + } + +} diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvents.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvents.php new file mode 100644 index 000000000..abfd066e3 --- /dev/null +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeBuildEvents.php @@ -0,0 +1,18 @@ +internalName = $internal_name; + $this->publicName = $public_name ?: $internal_name; + $this->enabled = $enabled; + $this->hasOne = $has_one; + } + + /** + * Gets the internal name of the field. + * + * @return string + * The internal name of the field. + */ + public function getInternalName() { + return $this->internalName; + } + + /** + * Gets the public name of the field. + * + * @return string + * The public name of the field. + */ + public function getPublicName() { + return $this->publicName; + } + + /** + * Establishes a new public name for the field. + * + * @param string $public_name + * The public name. + * + * @return static + * A new instance of the field with the given public name. + */ + public function withPublicName($public_name) { + return new static($this->internalName, $public_name, $this->enabled, $this->hasOne); + } + + /** + * Gets a new instance of the field that is disabled. + * + * @return static + * A new instance of the field that is disabled. + */ + public function disabled() { + return new static($this->internalName, $this->publicName, FALSE, $this->hasOne); + } + + /** + * Whether the field is enabled. + * + * @return bool + * Whether the field is enabled. FALSE if the field should not be in the + * JSON:API response. + */ + public function isFieldEnabled() { + return $this->enabled; + } + + /** + * Whether the field can only have one value. + * + * @return bool + * TRUE if the field can have only one value, FALSE otherwise. + */ + public function hasOne() { + return $this->hasOne; + } + + /** + * Whether the field can have many values. + * + * @return bool + * TRUE if the field can have more than one value, FALSE otherwise. + */ + public function hasMany() { + return !$this->hasOne; + } + +} diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php new file mode 100644 index 000000000..8e782a527 --- /dev/null +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRelationship.php @@ -0,0 +1,73 @@ +internalName, $this->publicName, $this->enabled, $this->hasOne); + $relationship->relatableResourceTypes = $resource_types; + return $relationship; + } + + /** + * Gets the relatable resource types. + * + * @return \Drupal\jsonapi\ResourceType\ResourceType[] + * The resource type to which this relationships can relate. + */ + public function getRelatableResourceTypes() { + if (!isset($this->relatableResourceTypes)) { + throw new \LogicException("withRelatableResourceTypes() must be called before getting relatable resource types."); + } + return $this->relatableResourceTypes; + } + + /** + * {@inheritdoc} + */ + public function withPublicName($public_name) { + $relationship = parent::withPublicName($public_name); + return isset($this->relatableResourceTypes) + ? $relationship->withRelatableResourceTypes($this->relatableResourceTypes) + : $relationship; + } + + /** + * {@inheritdoc} + */ + public function disabled() { + $relationship = parent::disabled(); + return isset($this->relatableResourceTypes) + ? $relationship->withRelatableResourceTypes($this->relatableResourceTypes) + : $relationship; + } + +} diff --git a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php index 995dfeed4..724264732 100644 --- a/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php +++ b/core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php @@ -15,6 +15,7 @@ use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\TypedData\DataReferenceTargetDefinition; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; /** @@ -61,18 +62,33 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { protected $entityFieldManager; /** - * The static cache backend. + * The cache backend. * * @var \Drupal\Core\Cache\CacheBackendInterface */ - protected $staticCache; + protected $cache; /** - * Instance data cache. + * The event dispatcher. * - * @var array + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ - protected $cache = []; + protected $eventDispatcher; + + /** + * Cache tags used for caching the repository. + * + * @var string[] + */ + protected $cacheTags = [ + 'jsonapi_resource_types', + // Invalidate whenever field definitions are modified. + 'entity_field_info', + // Invalidate whenever the set of bundles changes. + 'entity_bundles', + // Invalidate whenever the set of entity types changes. + 'entity_types', + ]; /** * Instantiates a ResourceTypeRepository object. @@ -83,33 +99,40 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { * The entity type bundle info service. * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager * The entity field manager. - * @param \Drupal\Core\Cache\CacheBackendInterface $static_cache - * The static cache backend. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache + * The cache backend. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher + * The event dispatcher. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_bundle_info, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $static_cache) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_bundle_info, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache, EventDispatcherInterface $dispatcher) { $this->entityTypeManager = $entity_type_manager; $this->entityTypeBundleInfo = $entity_bundle_info; $this->entityFieldManager = $entity_field_manager; - $this->staticCache = $static_cache; + $this->cache = $cache; + $this->eventDispatcher = $dispatcher; } /** * {@inheritdoc} */ public function all() { - $cached = $this->staticCache->get('jsonapi.resource_types', FALSE); + $cached = $this->cache->get('jsonapi.resource_types', FALSE); if ($cached === FALSE) { $resource_types = []; foreach ($this->entityTypeManager->getDefinitions() as $entity_type) { - $resource_types = array_merge($resource_types, array_map(function ($bundle) use ($entity_type) { - return $this->createResourceType($entity_type, $bundle); - }, array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type->id())))); + $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type->id())); + $resource_types = array_reduce($bundles, function ($resource_types, $bundle) use ($entity_type) { + $resource_type = $this->createResourceType($entity_type, (string) $bundle); + return array_merge($resource_types, [ + $resource_type->getTypeName() => $resource_type, + ]); + }, $resource_types); } foreach ($resource_types as $resource_type) { $relatable_resource_types = $this->calculateRelatableResourceTypes($resource_type, $resource_types); $resource_type->setRelatableResourceTypes($relatable_resource_types); } - $this->staticCache->set('jsonapi.resource_types', $resource_types, Cache::PERMANENT, ['jsonapi_resource_types']); + $this->cache->set('jsonapi.resource_types', $resource_types, Cache::PERMANENT, $this->cacheTags); } return $cached ? $cached->data : $resource_types; } @@ -127,15 +150,23 @@ public function all() { */ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) { $raw_fields = $this->getAllFieldNames($entity_type, $bundle); + $internalize_resource_type = $entity_type->isInternal(); + $fields = static::getFields($raw_fields, $entity_type, $bundle); + if (!$internalize_resource_type) { + $event = ResourceTypeBuildEvent::createFromEntityTypeAndBundle($entity_type, $bundle, $fields); + $this->eventDispatcher->dispatch(ResourceTypeBuildEvents::BUILD, $event); + $internalize_resource_type = $event->resourceTypeShouldBeDisabled(); + $fields = $event->getFields(); + } return new ResourceType( $entity_type->id(), $bundle, $entity_type->getClass(), - $entity_type->isInternal(), + $internalize_resource_type, static::isLocatableResourceType($entity_type, $bundle), static::isMutableResourceType($entity_type, $bundle), static::isVersionableResourceType($entity_type), - static::getFieldMapping($raw_fields, $entity_type, $bundle) + $fields ); } @@ -148,31 +179,15 @@ public function get($entity_type_id, $bundle) { throw new PreconditionFailedHttpException('Server error. The current route is malformed.'); } - $cid = "jsonapi:resource_type:$entity_type_id:$bundle"; - if (!array_key_exists($cid, $this->cache)) { - $result = NULL; - foreach ($this->all() as $resource) { - if ($resource->getEntityTypeId() == $entity_type_id && $resource->getBundle() == $bundle) { - $result = $resource; - break; - } - } - $this->cache[$cid] = $result; - } - - return $this->cache[$cid]; + return $this->getByTypeName("$entity_type_id--$bundle"); } /** * {@inheritdoc} */ public function getByTypeName($type_name) { - foreach ($this->all() as $resource) { - if ($resource->getTypeName() == $type_name) { - return $resource; - } - } - return NULL; + $resource_types = $this->all(); + return isset($resource_types[$type_name]) ? $resource_types[$type_name] : NULL; } /** @@ -185,21 +200,14 @@ public function getByTypeName($type_name) { * @param string $bundle * The bundle to assess. * - * @return array - * An array with: - * - keys are (real/internal) field names - * - values are either FALSE (indicating the field is not exposed despite - * not being internal), TRUE (indicating the field should be exposed under - * its internal name) or a string (indicating the field should not be - * exposed using its internal name, but the name specified in the string) + * @return \Drupal\jsonapi\ResourceType\ResourceTypeField[] + * An array of JSON:API resource type fields keyed by internal field names. */ - protected static function getFieldMapping(array $field_names, EntityTypeInterface $entity_type, $bundle) { + protected function getFields(array $field_names, EntityTypeInterface $entity_type, $bundle) { assert(Inspector::assertAllStrings($field_names)); assert($entity_type instanceof ContentEntityTypeInterface || $entity_type instanceof ConfigEntityTypeInterface); assert(is_string($bundle) && !empty($bundle), 'A bundle ID is required. Bundleless entity types should pass the entity type ID again.'); - $mapping = []; - // JSON:API resource identifier objects are sufficient to identify // entities. By exposing all fields as attributes, we expose unwanted, // confusing or duplicate information: @@ -214,12 +222,12 @@ protected static function getFieldMapping(array $field_names, EntityTypeInterfac // @see http://jsonapi.org/format/#document-resource-identifier-objects $id_field_name = $entity_type->getKey('id'); $uuid_field_name = $entity_type->getKey('uuid'); - if ($uuid_field_name !== 'id') { - $mapping[$uuid_field_name] = FALSE; + if ($uuid_field_name && $uuid_field_name !== 'id') { + $fields[$uuid_field_name] = new ResourceTypeAttribute($uuid_field_name, NULL, FALSE); } - $mapping[$id_field_name] = "drupal_internal__$id_field_name"; + $fields[$id_field_name] = new ResourceTypeAttribute($id_field_name, "drupal_internal__$id_field_name"); if ($entity_type->isRevisionable() && ($revision_id_field_name = $entity_type->getKey('revision'))) { - $mapping[$revision_id_field_name] = "drupal_internal__$revision_id_field_name"; + $fields[$revision_id_field_name] = new ResourceTypeAttribute($revision_id_field_name, "drupal_internal__$revision_id_field_name"); } if ($entity_type instanceof ConfigEntityTypeInterface) { // The '_core' key is reserved by Drupal core to handle complex edge cases @@ -227,28 +235,95 @@ protected static function getFieldMapping(array $field_names, EntityTypeInterfac // configuration, and is not allowed to be set by clients writing // configuration: it is for Drupal core only, and managed by Drupal core. // @see https://www.drupal.org/node/2653358 - $mapping['_core'] = FALSE; + $fields['_core'] = new ResourceTypeAttribute('_core', NULL, FALSE); + } + + $is_fieldable = $entity_type->entityClassImplements(FieldableEntityInterface::class); + if ($is_fieldable) { + $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type->id(), $bundle); } // For all other fields, use their internal field name also as their public // field name. Unless they're called "id" or "type": those names are // reserved by the JSON:API spec. // @see http://jsonapi.org/format/#document-resource-object-fields - foreach (array_diff($field_names, array_keys($mapping)) as $field_name) { - if ($field_name === 'id' || $field_name === 'type') { + $reserved_field_names = ['id', 'type']; + foreach (array_diff($field_names, array_keys($fields)) as $field_name) { + $alias = $field_name; + // Alias the fields reserved by the JSON:API spec with `{entity_type}_`. + if (in_array($field_name, $reserved_field_names, TRUE)) { $alias = $entity_type->id() . '_' . $field_name; - if (isset($field_name[$alias])) { - throw new \LogicException('The generated alias conflicts with an existing field. Please report this in the JSON:API issue queue!'); - } - $mapping[$field_name] = $alias; - continue; } // The default, which applies to most fields: expose as-is. - $mapping[$field_name] = TRUE; + $field_definition = $is_fieldable && !empty($field_definitions[$field_name]) ? $field_definitions[$field_name] : NULL; + $is_relationship_field = $field_definition && static::isReferenceFieldDefinition($field_definition); + $has_one = !$field_definition || $field_definition->getFieldStorageDefinition()->getCardinality() === 1; + $fields[$field_name] = $is_relationship_field + ? new ResourceTypeRelationship($field_name, $alias, TRUE, $has_one) + : new ResourceTypeAttribute($field_name, $alias, TRUE, $has_one); } - return $mapping; + // With all fields now aliased, detect any conflicts caused by the + // automatically generated aliases above. + foreach (array_intersect($reserved_field_names, array_keys($fields)) as $reserved_field_name) { + /* @var \Drupal\jsonapi\ResourceType\ResourceTypeField $aliased_reserved_field */ + $aliased_reserved_field = $fields[$reserved_field_name]; + /* @var \Drupal\jsonapi\ResourceType\ResourceTypeField $field */ + foreach (array_diff_key($fields, array_flip([$reserved_field_name])) as $field) { + if ($aliased_reserved_field->getPublicName() === $field->getPublicName()) { + throw new \LogicException("The generated alias '{$aliased_reserved_field->getPublicName()}' for field name '{$aliased_reserved_field->getInternalName()}' conflicts with an existing field. Please report this in the JSON:API issue queue!"); + } + } + } + + // Special handling for user entities that allows a JSON:API user agent to + // access the display name of a user. This is useful when displaying the + // name of a node's author. + // @see \Drupal\jsonapi\JsonApiResource\ResourceObject::extractContentEntityFields() + // @todo: eliminate this special casing in https://www.drupal.org/project/drupal/issues/3079254. + if ($entity_type->id() === 'user') { + $fields['display_name'] = new ResourceTypeAttribute('display_name'); + } + + return $fields; + } + + /** + * Gets the field mapping for the given field names and entity type + bundle. + * + * @param string[] $field_names + * All field names on a bundle of the given entity type. + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type for which to get the field mapping. + * @param string $bundle + * The bundle to assess. + * + * @return array + * An array with: + * - keys are (real/internal) field names + * - values are either FALSE (indicating the field is not exposed despite + * not being internal), TRUE (indicating the field should be exposed under + * its internal name) or a string (indicating the field should not be + * exposed using its internal name, but the name specified in the string) + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * self::getFields() instead. + * + * @see https://www.drupal.org/project/drupal/issues/3014277 + */ + protected function getFieldMapping(array $field_names, EntityTypeInterface $entity_type, $bundle) { + $class_name = self::class; + @trigger_error("{$class_name}::getFieldMapping() is deprecated in Drupal 8.8.0 and will not be allowed in Drupal 9.0.0. Use {$class_name}::getFields() instead. See https://www.drupal.org/project/drupal/issues/3014277.", E_USER_DEPRECATED); + $fields = $this->getFields($field_names, $entity_type, $bundle); + return array_map(function (ResourceTypeField $field) { + if ($field->isFieldEnabled()) { + return $field->getInternalName() !== $field->getPublicName() + ? $field->getPublicName() + : TRUE; + } + return FALSE; + }, $fields); } /** @@ -395,11 +470,8 @@ protected function getRelatableResourceTypesFromFieldDefinition(FieldDefinitionI : $this->getAllBundlesForEntityType($entity_type_id); return array_map(function ($target_bundle) use ($entity_type_id, $resource_types) { - foreach ($resource_types as $resource_type) { - if ($resource_type->getEntityTypeId() === $entity_type_id && $resource_type->getBundle() === $target_bundle) { - return $resource_type; - } - } + $type_name = "$entity_type_id--$target_bundle"; + return isset($resource_types[$type_name]) ? $resource_types[$type_name] : NULL; }, $target_bundles); } @@ -414,11 +486,18 @@ protected function getRelatableResourceTypesFromFieldDefinition(FieldDefinitionI * otherwise. */ protected function isReferenceFieldDefinition(FieldDefinitionInterface $field_definition) { + static $field_type_is_reference = []; + + if (isset($field_type_is_reference[$field_definition->getType()])) { + return $field_type_is_reference[$field_definition->getType()]; + } + /* @var \Drupal\Core\Field\TypedData\FieldItemDataDefinition $item_definition */ $item_definition = $field_definition->getItemDefinition(); $main_property = $item_definition->getMainPropertyName(); $property_definition = $item_definition->getPropertyDefinition($main_property); - return $property_definition instanceof DataReferenceTargetDefinition; + + return $field_type_is_reference[$field_definition->getType()] = $property_definition instanceof DataReferenceTargetDefinition; } /** diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/jsonapi_test_collection_count.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/jsonapi_test_collection_count.info.yml index 248b95135..a5664b719 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/jsonapi_test_collection_count.info.yml +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/jsonapi_test_collection_count.info.yml @@ -1,10 +1,4 @@ name: 'JSON API test collection counts' type: module package: Testing -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php index 33c9c1a4d..39a3974c2 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php @@ -22,7 +22,8 @@ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) $entity_type->isInternal(), static::isLocatableResourceType($entity_type, $bundle), static::isMutableResourceType($entity_type, $bundle), - static::getFieldMapping($raw_fields, $entity_type, $bundle) + static::isVersionableResourceType($entity_type), + static::getFields($raw_fields, $entity_type, $bundle) ); } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_data_type/jsonapi_test_data_type.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_data_type/jsonapi_test_data_type.info.yml index a4d925f22..a72ccb10a 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_data_type/jsonapi_test_data_type.info.yml +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_data_type/jsonapi_test_data_type.info.yml @@ -1,10 +1,4 @@ name: 'JSON API test format-agnostic @DataType normalizers' type: module package: Testing -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_field_access/jsonapi_test_field_access.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_field_access/jsonapi_test_field_access.info.yml index 5d26dbb8c..4cfaf77cf 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_field_access/jsonapi_test_field_access.info.yml +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_field_access/jsonapi_test_field_access.info.yml @@ -2,10 +2,4 @@ name: 'JSON API field access' type: module description: 'Provides a custom field access hook to test JSON API field access security.' package: Testing -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.info.yml index 87b133862..14a5c32b6 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.info.yml +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.info.yml @@ -1,10 +1,4 @@ name: 'JSON:API test field aliasing' type: module package: Testing -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceTypeRepository.php b/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceTypeRepository.php index 59850dad6..218deb042 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceTypeRepository.php +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceTypeRepository.php @@ -13,14 +13,14 @@ class AliasingResourceTypeRepository extends ResourceTypeRepository { /** * {@inheritdoc} */ - protected static function getFieldMapping(array $field_names, EntityTypeInterface $entity_type, $bundle) { - $mapping = parent::getFieldMapping($field_names, $entity_type, $bundle); - foreach ($field_names as $field_name) { + protected function getFields(array $field_names, EntityTypeInterface $entity_type, $bundle) { + $fields = parent::getFields($field_names, $entity_type, $bundle); + foreach ($fields as $field_name => $field) { if (strpos($field_name, 'field_test_alias_') === 0) { - $mapping[$field_name] = 'field_test_alias'; + $fields[$field_name] = $fields[$field_name]->withPublicName('field_test_alias'); } } - return $mapping; + return $fields; } } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_field_filter_access/jsonapi_test_field_filter_access.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_field_filter_access/jsonapi_test_field_filter_access.info.yml index a8ece9e8b..0ffa03d4a 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_field_filter_access/jsonapi_test_field_filter_access.info.yml +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_field_filter_access/jsonapi_test_field_filter_access.info.yml @@ -2,10 +2,4 @@ name: 'JSON:API filter access' type: module description: 'Provides custom access related code to test JSON:API filter security.' package: Testing -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_field_type/jsonapi_test_field_type.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_field_type/jsonapi_test_field_type.info.yml index 9963f29ef..30944dbb7 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_field_type/jsonapi_test_field_type.info.yml +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_field_type/jsonapi_test_field_type.info.yml @@ -1,10 +1,4 @@ name: 'JSON API test format-agnostic @FieldType normalizers' type: module package: Testing -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_normalizers_kernel/jsonapi_test_normalizers_kernel.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_normalizers_kernel/jsonapi_test_normalizers_kernel.info.yml index b1dcd5789..b8ab6476e 100644 --- a/core/modules/jsonapi/tests/modules/jsonapi_test_normalizers_kernel/jsonapi_test_normalizers_kernel.info.yml +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_normalizers_kernel/jsonapi_test_normalizers_kernel.info.yml @@ -1,10 +1,4 @@ name: 'JSON API test: normalizers kernel tests, public aliases for select JSON API normalizers' type: module package: Testing -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.info.yml new file mode 100644 index 000000000..43d9a8381 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.info.yml @@ -0,0 +1,4 @@ +name: 'JSON:API test resource type building API' +type: module +package: Testing +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.services.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.services.yml new file mode 100644 index 000000000..77dccb48b --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/jsonapi_test_resource_type_building.services.yml @@ -0,0 +1,5 @@ +services: + jsonapi_test_resource_type_building.build_subscriber: + class: Drupal\jsonapi_test_resource_type_building\EventSubscriber\ResourceTypeBuildEventSubscriber + tags: + - { name: event_subscriber } diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php new file mode 100644 index 000000000..af4f1a267 --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_resource_type_building/src/EventSubscriber/ResourceTypeBuildEventSubscriber.php @@ -0,0 +1,78 @@ + [ + ['disableResourceType'], + ['aliasResourceTypeFields'], + ['disableResourceTypeFields'], + ], + ]; + } + + /** + * Disables any resource types that have been disabled by a test. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event + * The build event. + */ + public function disableResourceType(ResourceTypeBuildEvent $event) { + $disabled_resource_types = \Drupal::state()->get('jsonapi_test_resource_type_builder.disabled_resource_types', []); + if (in_array($event->getResourceTypeName(), $disabled_resource_types, TRUE)) { + $event->disableResourceType(); + } + } + + /** + * Aliases any resource type fields that have been aliased by a test. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event + * The build event. + */ + public function aliasResourceTypeFields(ResourceTypeBuildEvent $event) { + $aliases = \Drupal::state()->get('jsonapi_test_resource_type_builder.resource_type_field_aliases', []); + $resource_type_name = $event->getResourceTypeName(); + if (in_array($resource_type_name, array_keys($aliases), TRUE)) { + foreach ($event->getFields() as $field) { + if (isset($aliases[$resource_type_name][$field->getInternalName()])) { + $event->setPublicFieldName($field, $aliases[$resource_type_name][$field->getInternalName()]); + } + } + } + } + + /** + * Disables any resource type fields that have been aliased by a test. + * + * @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event + * The build event. + */ + public function disableResourceTypeFields(ResourceTypeBuildEvent $event) { + $aliases = \Drupal::state()->get('jsonapi_test_resource_type_builder.disabled_resource_type_fields', []); + $resource_type_name = $event->getResourceTypeName(); + if (in_array($resource_type_name, array_keys($aliases), TRUE)) { + foreach ($event->getFields() as $field) { + if (isset($aliases[$resource_type_name][$field->getInternalName()]) && $aliases[$resource_type_name][$field->getInternalName()] === TRUE) { + $event->disableField($field); + } + } + } + } + +} diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_user/jsonapi_test_user.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_user/jsonapi_test_user.info.yml new file mode 100644 index 000000000..179eda4da --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_user/jsonapi_test_user.info.yml @@ -0,0 +1,4 @@ +name: 'JSON:API user tests' +type: module +package: Testing +core: 8.x diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_user/jsonapi_test_user.module b/core/modules/jsonapi/tests/modules/jsonapi_test_user/jsonapi_test_user.module new file mode 100644 index 000000000..4056bd64d --- /dev/null +++ b/core/modules/jsonapi/tests/modules/jsonapi_test_user/jsonapi_test_user.module @@ -0,0 +1,17 @@ +isAnonymous()) { + $name = 'User ' . $account->id(); + } +} diff --git a/core/modules/jsonapi/tests/src/Functional/ActionTest.php b/core/modules/jsonapi/tests/src/Functional/ActionTest.php index 796494c21..724e08455 100644 --- a/core/modules/jsonapi/tests/src/Functional/ActionTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ActionTest.php @@ -35,6 +35,11 @@ class ActionTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php b/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php index 98c56d050..39bdd150c 100644 --- a/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php +++ b/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php @@ -35,6 +35,11 @@ class BaseFieldOverrideTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php b/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php index b06ffdc1b..d5e51e7a8 100644 --- a/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php +++ b/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php @@ -39,6 +39,11 @@ class BlockContentTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php b/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php index 0abe7283d..49268a986 100644 --- a/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php @@ -34,6 +34,11 @@ class BlockContentTypeTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/BlockTest.php b/core/modules/jsonapi/tests/src/Functional/BlockTest.php index 3827b0701..848dfa49f 100644 --- a/core/modules/jsonapi/tests/src/Functional/BlockTest.php +++ b/core/modules/jsonapi/tests/src/Functional/BlockTest.php @@ -35,6 +35,11 @@ class BlockTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/CommentTest.php b/core/modules/jsonapi/tests/src/Functional/CommentTest.php index 019417763..4fd1bb786 100644 --- a/core/modules/jsonapi/tests/src/Functional/CommentTest.php +++ b/core/modules/jsonapi/tests/src/Functional/CommentTest.php @@ -41,6 +41,11 @@ class CommentTest extends ResourceTestBase { */ protected static $resourceTypeName = 'comment--comment'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -323,12 +328,7 @@ public function testPostIndividualDxWithoutCriticalBaseFields() { $this->assertResourceErrorResponse(422, 'entity_id: This value should not be null.', NULL, $response, '/data/attributes/entity_id'); } catch (\Exception $e) { - if (version_compare(phpversion(), '7.0') >= 0) { - $this->assertSame("Error: Call to a member function get() on null\nDrupal\\comment\\Plugin\\Validation\\Constraint\\CommentNameConstraintValidator->getAnonymousContactDetailsSetting()() (Line: 96)\n", $e->getMessage()); - } - else { - $this->assertSame(500, $response->getStatusCode()); - } + $this->assertSame("Error: Call to a member function get() on null\nDrupal\\comment\\Plugin\\Validation\\Constraint\\CommentNameConstraintValidator->getAnonymousContactDetailsSetting()() (Line: 96)\n", $e->getMessage()); } // DX: 422 when missing 'field_name' field. diff --git a/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php b/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php index 26ce5bac8..fd8c73a99 100644 --- a/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php @@ -34,6 +34,11 @@ class CommentTypeTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php b/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php index e6d873d04..3c6c7bd53 100644 --- a/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php @@ -34,6 +34,11 @@ class ConfigTestTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php b/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php index 3aa479745..fd03d5334 100644 --- a/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php @@ -38,6 +38,11 @@ class ConfigurableLanguageTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php b/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php index 993cff56e..3153e1853 100644 --- a/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php @@ -34,6 +34,11 @@ class ContactFormTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php b/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php index 9eb9da53f..597bcbc79 100644 --- a/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php @@ -37,6 +37,11 @@ class ContentLanguageSettingsTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php b/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php index 5c1dd997c..18b388e91 100644 --- a/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php +++ b/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php @@ -39,6 +39,11 @@ class DateFormatTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/EditorTest.php b/core/modules/jsonapi/tests/src/Functional/EditorTest.php index ab6f727f0..9b3c38052 100644 --- a/core/modules/jsonapi/tests/src/Functional/EditorTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EditorTest.php @@ -37,6 +37,11 @@ class EditorTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -73,7 +78,7 @@ protected function createEntity() { $camelids ->setImageUploadSettings([ 'status' => FALSE, - 'scheme' => file_default_scheme(), + 'scheme' => 'public', 'directory' => 'inline-images', 'max_size' => '', 'max_dimensions' => [ @@ -223,7 +228,7 @@ protected function createAnotherEntity($key) { $entity->setImageUploadSettings([ 'status' => FALSE, - 'scheme' => file_default_scheme(), + 'scheme' => 'public', 'directory' => 'inline-images', 'max_size' => '', 'max_dimensions' => [ diff --git a/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php b/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php index b28efdb8f..1f914fec0 100644 --- a/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php @@ -35,6 +35,11 @@ class EntityFormDisplayTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -139,6 +144,7 @@ protected function getExpectedDocument() { 'weight' => 5, 'settings' => [ 'match_operator' => 'CONTAINS', + 'match_limit' => 10, 'size' => 60, 'placeholder' => '', ], diff --git a/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php b/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php index 9a1c36d75..637c77ea8 100644 --- a/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php @@ -36,6 +36,11 @@ class EntityFormModeTest extends ResourceTestBase { */ protected $entity; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/EntityTestMapFieldTest.php b/core/modules/jsonapi/tests/src/Functional/EntityTestMapFieldTest.php index b78f96cd7..82cea7700 100644 --- a/core/modules/jsonapi/tests/src/Functional/EntityTestMapFieldTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EntityTestMapFieldTest.php @@ -56,6 +56,11 @@ class EntityTestMapFieldTest extends ResourceTestBase { ], ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/EntityTestTest.php b/core/modules/jsonapi/tests/src/Functional/EntityTestTest.php index fd3bf6b3a..fd6836eab 100644 --- a/core/modules/jsonapi/tests/src/Functional/EntityTestTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EntityTestTest.php @@ -23,6 +23,11 @@ class EntityTestTest extends ResourceTestBase { */ public static $modules = ['entity_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/EntityViewDisplayTest.php b/core/modules/jsonapi/tests/src/Functional/EntityViewDisplayTest.php index 1b154cc10..0026706d3 100644 --- a/core/modules/jsonapi/tests/src/Functional/EntityViewDisplayTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EntityViewDisplayTest.php @@ -18,6 +18,11 @@ class EntityViewDisplayTest extends ResourceTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php b/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php index 991b35fc9..839b1fae6 100644 --- a/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php @@ -19,6 +19,11 @@ class EntityViewModeTest extends ResourceTestBase { */ public static $modules = ['user', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php b/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php index 2934ba066..2ab8da8c0 100644 --- a/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php +++ b/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php @@ -29,6 +29,11 @@ class EntryPointTest extends BrowserTestBase { 'basic_auth', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test GETing the entry point. */ diff --git a/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php b/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php index a2b7b2735..33f6aab0d 100644 --- a/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php @@ -21,6 +21,11 @@ */ class ExternalNormalizersTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The original value for the test field. * @@ -142,7 +147,7 @@ public function testFormatAgnosticNormalizers($test_module, $expected_value_json // Asserts the expected JSON:API normalization. // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute('jsonapi.entity_test--entity_test.individual', ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $client = $this->getSession()->getDriver()->getClient()->getClient(); $response = $client->request('GET', $url->setAbsolute(TRUE)->toString()); $document = Json::decode((string) $response->getBody()); diff --git a/core/modules/jsonapi/tests/src/Functional/FeedTest.php b/core/modules/jsonapi/tests/src/Functional/FeedTest.php index 5ca4f906f..7944a95c6 100644 --- a/core/modules/jsonapi/tests/src/Functional/FeedTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FeedTest.php @@ -20,6 +20,11 @@ class FeedTest extends ResourceTestBase { */ public static $modules = ['aggregator']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php index 311c098e4..25a3889f4 100644 --- a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php @@ -21,6 +21,11 @@ class FieldConfigTest extends ResourceTestBase { */ public static $modules = ['field', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php b/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php index 0f75906bb..6e03be126 100644 --- a/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php @@ -17,6 +17,11 @@ class FieldStorageConfigTest extends ResourceTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/FileTest.php b/core/modules/jsonapi/tests/src/Functional/FileTest.php index ff674174e..deaa5be82 100644 --- a/core/modules/jsonapi/tests/src/Functional/FileTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FileTest.php @@ -26,6 +26,11 @@ class FileTest extends ResourceTestBase { */ public static $modules = ['file', 'user']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php b/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php index d2b638cb2..3561ebae6 100644 --- a/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php @@ -27,6 +27,11 @@ class FileUploadTest extends ResourceTestBase { */ public static $modules = ['entity_test', 'file']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} * @@ -440,6 +445,34 @@ public function testPostFileUploadDuplicateFile() { $this->assertSame($this->testFileData, file_get_contents('public://foobar/example_0.txt')); } + /** + * Tests using the file upload POST route twice, simulating a race condition. + * + * A validation error should occur when the filenames are not unique. + */ + public function testPostFileUploadDuplicateFileRaceCondition() { + $this->setUpAuthorization('POST'); + $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); + + $uri = Url::fromUri('base:' . static::$postUri); + + // This request will have the default 'application/octet-stream' content + // type header. + $response = $this->fileRequest($uri, $this->testFileData); + + $this->assertSame(201, $response->getStatusCode()); + + // Simulate a race condition where two files are uploaded at almost the same + // time, by removing the first uploaded file from disk (leaving the entry in + // the file_managed table) before trying to upload another file with the + // same name. + unlink(\Drupal::service('file_system')->realpath('public://foobar/example.txt')); + + // Make the same request again. The upload should fail validation. + $response = $this->fileRequest($uri, $this->testFileData); + $this->assertResourceErrorResponse(422, PlainTextOutput::renderFromHtml("Unprocessable Entity: file validation failed.\nThe file public://foobar/example.txt already exists. Enter a unique file URI."), $uri, $response); + } + /** * Tests using the file upload route with any path prefixes being stripped. * diff --git a/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php b/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php index 8af68bb36..c5b581d9a 100644 --- a/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php +++ b/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php @@ -17,6 +17,11 @@ class FilterFormatTest extends ResourceTestBase { */ public static $modules = ['filter']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php b/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php index 55193d81a..47ec1ace8 100644 --- a/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php @@ -17,6 +17,11 @@ class ImageStyleTest extends ResourceTestBase { */ public static $modules = ['image']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php b/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php index e170222a0..a0550d159 100644 --- a/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php +++ b/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php @@ -30,6 +30,11 @@ class InternalEntitiesTest extends BrowserTestBase { 'serialization', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A test user. * diff --git a/core/modules/jsonapi/tests/src/Functional/ItemTest.php b/core/modules/jsonapi/tests/src/Functional/ItemTest.php index 6e3a48f02..eae850acc 100644 --- a/core/modules/jsonapi/tests/src/Functional/ItemTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ItemTest.php @@ -20,6 +20,11 @@ class ItemTest extends ResourceTestBase { */ public static $modules = ['aggregator']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalMultilingualTest.php b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalMultilingualTest.php index 16799ef6f..1cc79ac75 100644 --- a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalMultilingualTest.php +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalMultilingualTest.php @@ -15,7 +15,6 @@ * Tests JSON:API multilingual support. * * @group jsonapi - * @group legacy * * @internal */ @@ -26,8 +25,14 @@ class JsonApiFunctionalMultilingualTest extends JsonApiFunctionalTestBase { */ public static $modules = [ 'language', + 'content_translation', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -46,6 +51,13 @@ protected function setUp() { ->set('url.prefixes.ca-fr', 'ca-fr') ->save(); + ContentLanguageSettings::create([ + 'target_entity_type_id' => 'node', + 'target_bundle' => 'article', + ]) + ->setThirdPartySetting('content_translation', 'enabled', TRUE) + ->save(); + $this->createDefaultContent(5, 5, TRUE, TRUE, static::IS_MULTILINGUAL, FALSE); } @@ -139,10 +151,8 @@ public function testPatchTranslation() { // Specifying a langcode is allowed once configured to be alterable. But // modifying the language of a non-default translation is still not allowed. - ContentLanguageSettings::create([ - 'target_entity_type_id' => 'node', - 'target_bundle' => 'article', - ])->setLanguageAlterable(TRUE) + ContentLanguageSettings::loadByEntityTypeBundle('node', 'article') + ->setLanguageAlterable(TRUE) ->save(); $response = $this->request('PATCH', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options); $this->assertSame(500, $response->getStatusCode()); @@ -267,10 +277,8 @@ public function testPostTranslation() { // Specifying a langcode is allowed once configured to be alterable. Now an // entity can be created with the specified langcode. - ContentLanguageSettings::create([ - 'target_entity_type_id' => 'node', - 'target_bundle' => 'article', - ])->setLanguageAlterable(TRUE) + ContentLanguageSettings::loadByEntityTypeBundle('node', 'article') + ->setLanguageAlterable(TRUE) ->save(); $request_document['data']['attributes']['langcode'] = 'ca'; $request_options[RequestOptions::BODY] = Json::encode($request_document); @@ -311,7 +319,7 @@ public function testDeleteMultilingual() { $response = $this->request('DELETE', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), []); $this->assertSame(204, $response->getStatusCode()); - $this->assertFalse(Node::load($this->nodes[0]->id())); + $this->assertNull(Node::load($this->nodes[0]->id())); } } diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php index b2d6f1033..3acde839e 100644 --- a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php @@ -11,7 +11,6 @@ * General functional test class. * * @group jsonapi - * @group legacy * * @internal */ @@ -24,6 +23,11 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase { 'basic_auth', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test the GET method. */ @@ -191,7 +195,7 @@ public function testRead() { }))); $link_keys = array_keys($single_output['meta']['omitted']['links']); $this->assertSame('help', reset($link_keys)); - $this->assertRegExp('/^item:[a-zA-Z0-9]{7}$/', next($link_keys)); + $this->assertRegExp('/^item--[a-zA-Z0-9]{7}$/', next($link_keys)); $this->nodes[1]->set('status', TRUE); $this->nodes[1]->save(); // 13. Test filtering when using short syntax. @@ -271,7 +275,7 @@ public function testRead() { ]); $response = $this->request('GET', $user_url, [ 'auth' => [ - $this->userCanViewProfiles->getUsername(), + $this->userCanViewProfiles->getAccountName(), $this->userCanViewProfiles->pass_raw, ], ]); @@ -564,7 +568,7 @@ public function testWrite() { ]; $response = $this->request('POST', $collection_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $created_response = Json::decode($response->getBody()->__toString()); @@ -587,7 +591,7 @@ public function testWrite() { // 2.1 Authorization error with a user without create permissions. $response = $this->request('POST', $collection_url, [ 'body' => Json::encode($body), - 'auth' => [$this->userCanViewProfiles->getUsername(), $this->userCanViewProfiles->pass_raw], + 'auth' => [$this->userCanViewProfiles->getAccountName(), $this->userCanViewProfiles->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $created_response = Json::decode($response->getBody()->__toString()); @@ -598,7 +602,7 @@ public function testWrite() { // 3. Missing Content-Type error. $response = $this->request('POST', $collection_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Accept' => 'application/vnd.api+json'], ]); $created_response = Json::decode($response->getBody()->__toString()); @@ -609,7 +613,7 @@ public function testWrite() { $invalid_body['data']['id'] = Node::load(1)->uuid(); $response = $this->request('POST', $collection_url, [ 'body' => Json::encode($invalid_body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json', @@ -625,7 +629,7 @@ public function testWrite() { $body_invalid_tags['data']['relationships']['field_tags']['data'][1]['id'] = 'ipsum'; $response = $this->request('POST', $collection_url, [ 'body' => Json::encode($body_invalid_tags), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $created_response = Json::decode($response->getBody()->__toString()); @@ -633,7 +637,7 @@ public function testWrite() { // 6. Decoding error. $response = $this->request('POST', $collection_url, [ 'body' => '{"bad json",,,}', - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', @@ -646,7 +650,7 @@ public function testWrite() { // 6.1 Denormalizing error. $response = $this->request('POST', $collection_url, [ 'body' => '{"data":{"type":"something"},"valid yet nonsensical json":[]}', - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', @@ -662,7 +666,7 @@ public function testWrite() { $malformed_body['relationships'] = $body['data']['relationships']; $response = $this->request('POST', $collection_url, [ 'body' => Json::encode($malformed_body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json', @@ -678,7 +682,7 @@ public function testWrite() { unset($missing_type['data']['type']); $response = $this->request('POST', $collection_url, [ 'body' => Json::encode($missing_type), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Accept' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json', @@ -702,7 +706,7 @@ public function testWrite() { ]); $response = $this->request('PATCH', $individual_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $updated_response = Json::decode($response->getBody()->__toString()); @@ -722,7 +726,7 @@ public function testWrite() { ]); $response = $this->request('PATCH', $individual_url, [ 'body' => Json::encode($body), - 'auth' => [$this->userCanViewProfiles->getUsername(), $this->userCanViewProfiles->pass_raw], + 'auth' => [$this->userCanViewProfiles->getAccountName(), $this->userCanViewProfiles->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $this->assertEquals(403, $response->getStatusCode()); @@ -740,7 +744,7 @@ public function testWrite() { ]; $response = $this->request('PATCH', $individual_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $updated_response = Json::decode($response->getBody()->__toString()); @@ -748,7 +752,7 @@ public function testWrite() { $this->assertEquals("The current user is not allowed to PATCH the selected field (status). The 'administer nodes' permission is required.", $updated_response['errors'][0]['detail']); - $node = \Drupal::entityManager()->loadEntityByUuid('node', $uuid); + $node = \Drupal::service('entity.repository')->loadEntityByUuid('node', $uuid); $this->assertEquals(1, $node->get('status')->value, 'Node status was not changed.'); // 9. Successful POST to related endpoint. $body = [ @@ -764,7 +768,7 @@ public function testWrite() { ]); $response = $this->request('POST', $relationship_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $updated_response = Json::decode($response->getBody()->__toString()); @@ -783,7 +787,7 @@ public function testWrite() { ]; $response = $this->request('PATCH', $relationship_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $this->assertEquals(204, $response->getStatusCode()); @@ -791,7 +795,7 @@ public function testWrite() { // 11. Successful DELETE to related endpoint. $response = $this->request('DELETE', $relationship_url, [ // Send a request with no body. - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', @@ -812,7 +816,7 @@ public function testWrite() { $response = $this->request('DELETE', $relationship_url, [ // Remove the existing relationship item. 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => ['Content-Type' => 'application/vnd.api+json'], ]); $this->assertEquals(204, $response->getStatusCode()); @@ -834,7 +838,7 @@ public function testWrite() { ]; $response = $this->request('PATCH', $individual_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', @@ -863,7 +867,7 @@ public function testWrite() { ]; $response = $this->request('PATCH', $individual_url, [ 'body' => Json::encode($body), - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], 'headers' => [ 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', @@ -875,7 +879,7 @@ public function testWrite() { $updated_response['errors']['0']['detail']); // 14. Successful DELETE. $response = $this->request('DELETE', $individual_url, [ - 'auth' => [$this->user->getUsername(), $this->user->pass_raw], + 'auth' => [$this->user->getAccountName(), $this->user->pass_raw], ]); $this->assertEquals(204, $response->getStatusCode()); $response = $this->request('GET', $individual_url, []); diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php index bca821d01..f907ccfc4 100644 --- a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php @@ -42,6 +42,11 @@ abstract class JsonApiFunctionalTestBase extends BrowserTestBase { 'link', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test user. * diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php b/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php index 5576834ec..2aec3d96a 100644 --- a/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php @@ -6,9 +6,12 @@ use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; use Drupal\comment\Tests\CommentTestTrait; use Drupal\Component\Serialization\Json; +use Drupal\Core\Entity\TranslatableInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Url; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem; +use Drupal\entity_test\Entity\EntityTest; use Drupal\entity_test\Entity\EntityTestMapField; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -26,7 +29,6 @@ * JSON:API regression tests. * * @group jsonapi - * @group legacy * * @internal */ @@ -41,6 +43,11 @@ class JsonApiRegressionTest extends JsonApiFunctionalTestBase { 'basic_auth', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Ensure filtering on relationships works with bundle-specific target types. * @@ -70,7 +77,7 @@ public function testBundleSpecificTargetEntityTypeFromIssue2953207() { ]); $response = $this->request('GET', Url::fromUri('internal:/jsonapi/comment/tcomment?include=entity_id&filter[entity_id.name]=foobar'), [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], ]); @@ -134,7 +141,7 @@ public function testDeepNestedIncludeMultiTargetEntityTypeFieldFromIssue2973681( ]); $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/page?include=field_comment,field_comment.entity_id,field_comment.entity_id.uid'), [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], ]); @@ -181,7 +188,7 @@ public function testBundlelessRelationshipMutationFromIssue2973681() { 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', ], - RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], RequestOptions::JSON => [ 'data' => [ ['type' => 'user--user', 'id' => $target->uuid()], @@ -221,7 +228,7 @@ public function testGetTermWhenMultipleVocabulariesExistFromIssue2977879() { ]); $response = $this->request('GET', Url::fromUri('internal:/jsonapi/taxonomy_term/one'), [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], ]); @@ -283,7 +290,7 @@ public function testDanglingReferencesInAnEntityReferenceFieldFromIssue2968972() 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', ], - RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], RequestOptions::JSON => [ 'data' => [ 'type' => 'node--journal_article', @@ -322,7 +329,7 @@ public function testGetNodeCollectionWithHookNodeGrantsImplementationsFromIssue2 ]); $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/article'), [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], ]); @@ -408,7 +415,7 @@ public function testDanglingReferencesInAnEntityReferenceFieldFromIssue2984647() 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', ], - RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], ]; $issue_node->delete(); $response = $this->request('GET', $url, $request_options); @@ -469,7 +476,7 @@ public function testThatRoutesAreRebuiltAfterDataModelChangesFromIssue2984886() $user = $this->drupalCreateUser(['access content']); $request_options = [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], ]; @@ -563,7 +570,7 @@ public function testDenormalizeAliasedRelationshipFromIssue2953207() { // Test. $response = $this->request('PATCH', Url::fromUri(sprintf('internal:/jsonapi/taxonomy_term/tags/%s/relationships/%s', Term::load(1)->uuid(), $public_relationship_field_name)), [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], RequestOptions::HEADERS => [ @@ -637,7 +644,7 @@ public function testFilterByIdFromIssue3015759() { ]); $response = $this->request('GET', Url::fromUri('internal:/jsonapi/shortcut/default?filter[drupal_internal__id]=' . $shortcut->id()), [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], ]); @@ -689,7 +696,7 @@ public function testPatchingDateTimeNormalizedWrongTimeZoneIssue3021194() { ]); $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/page/' . $page->uuid()), [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], ]); @@ -757,7 +764,7 @@ public function testPatchingDateTimeFieldsFromIssue3021194() { ]); $request_options = [ RequestOptions::AUTH => [ - $user->getUsername(), + $user->getAccountName(), $user->pass_raw, ], RequestOptions::HEADERS => [ @@ -801,7 +808,7 @@ public function testPostToIncludeUrlDoesNotReturnIncludeFromIssue3026030() { 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', ], - RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], RequestOptions::JSON => [ 'data' => [ 'type' => 'node--page', @@ -846,7 +853,7 @@ public function testPatchToIncludeUrlDoesNotReturnIncludeFromIssue3026030() { 'Content-Type' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json', ], - RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], RequestOptions::JSON => [ 'data' => [ 'type' => 'node--page', @@ -873,21 +880,26 @@ public function testMapFieldTypeNormalizationFromIssue3040590() { $this->assertTrue($this->container->get('module_installer')->install(['entity_test'], TRUE), 'Installed modules.'); // Create data. - $entity = EntityTestMapField::create([ + $entity_a = EntityTestMapField::create([ + 'name' => 'A', 'data' => [ 'foo' => 'bar', 'baz' => 'qux', ], ]); - $entity->save(); + $entity_a->save(); + $entity_b = EntityTestMapField::create([ + 'name' => 'B', + ]); + $entity_b->save(); $user = $this->drupalCreateUser([ 'administer entity_test content', ]); // Test. - $url = Url::fromUri(sprintf('internal:/jsonapi/entity_test_map_field/entity_test_map_field', $entity->uuid())); + $url = Url::fromUri('internal:/jsonapi/entity_test_map_field/entity_test_map_field?sort=drupal_internal__id'); $request_options = [ - RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], ]; $response = $this->request('GET', $url, $request_options); $this->assertSame(200, $response->getStatusCode()); @@ -896,7 +908,8 @@ public function testMapFieldTypeNormalizationFromIssue3040590() { 'foo' => 'bar', 'baz' => 'qux', ], $data['data'][0]['attributes']['data']); - $entity->set('data', [ + $this->assertNull($data['data'][1]['attributes']['data']); + $entity_a->set('data', [ 'foo' => 'bar', ])->save(); $response = $this->request('GET', $url, $request_options); @@ -931,7 +944,7 @@ public function testRecursionDetectedWhenResponseContainsViolationsFrom3042124() $admin = $this->drupalCreateUser([], 'Gandalf', TRUE); // Make request as regular user. - $request_options[RequestOptions::AUTH] = [$user->getUsername(), $user->pass_raw]; + $request_options[RequestOptions::AUTH] = [$user->getAccountName(), $user->pass_raw]; $this->request('POST', $url, $request_options); $response = $this->request('POST', $url, $request_options); @@ -942,7 +955,7 @@ public function testRecursionDetectedWhenResponseContainsViolationsFrom3042124() $this->assertSame(sprintf('title: This value should not be null.'), $data['errors'][0]['detail']); // Make request as regular user. - $request_options[RequestOptions::AUTH] = [$admin->getUsername(), $admin->pass_raw]; + $request_options[RequestOptions::AUTH] = [$admin->getAccountName(), $admin->pass_raw]; $this->request('POST', $url, $request_options); $response = $this->request('POST', $url, $request_options); @@ -953,4 +966,278 @@ public function testRecursionDetectedWhenResponseContainsViolationsFrom3042124() $this->assertSame(sprintf('title: This value should not be null.'), $data['errors'][0]['detail']); } + /** + * Ensure that child comments can be retrieved via JSON:API. + */ + public function testLeakedCacheMetadataViaRdfFromIssue3053827() { + $this->assertTrue($this->container->get('module_installer')->install(['comment', 'rdf'], TRUE), 'Installed modules.'); + $this->addDefaultCommentField('node', 'article', 'comment', CommentItemInterface::OPEN, 'comment'); + $this->rebuildAll(); + + // Create data. + Node::create([ + 'title' => 'Commented Node', + 'type' => 'article', + ])->save(); + $default_values = [ + 'entity_id' => 1, + 'entity_type' => 'node', + 'field_name' => 'comment', + 'status' => 1, + ]; + $parent = Comment::create(['subject' => 'Marlin'] + $default_values); + $parent->save(); + $child = Comment::create(['subject' => 'Nemo', 'pid' => $parent->id()] + $default_values); + $child->save(); + + // Test. + $user = $this->drupalCreateUser(['access comments']); + $request_options = [ + RequestOptions::AUTH => [ + $user->getAccountName(), + $user->pass_raw, + ], + ]; + // Requesting the comment collection should succeed. + $response = $this->request('GET', Url::fromUri('internal:/jsonapi/comment/comment'), $request_options); + $this->assertSame(200, $response->getStatusCode()); + } + + /** + * Ensure non-translatable entities can be PATCHed with an alternate language. + * + * @see https://www.drupal.org/project/drupal/issues/3043168 + */ + public function testNonTranslatableEntityUpdatesFromIssue3043168() { + // Enable write-mode. + $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); + // Set the site language to Russian. + $this->config('system.site')->set('langcode', 'ru')->set('default_langcode', 'ru')->save(TRUE); + // Install a "custom" entity type that is not translatable. + $this->assertTrue($this->container->get('module_installer')->install(['entity_test'], TRUE), 'Installed modules.'); + // Clear and rebuild caches and routes. + $this->rebuildAll(); + // Create a test entity. + // @see \Drupal\language\DefaultLanguageItem + $entity = EntityTest::create([ + 'name' => 'Alexander', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]); + $entity->save(); + // Ensure it is an instance of TranslatableInterface and that it is *not* + // translatable. + $this->assertInstanceOf(TranslatableInterface::class, $entity); + $this->assertFalse($entity->isTranslatable()); + // Set up a test user with permission to view and update the test entity. + $user = $this->drupalCreateUser(['view test entity', 'administer entity_test content']); + $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; + $request_options[RequestOptions::AUTH] = [ + $user->getAccountName(), + $user->pass_raw, + ]; + // GET the test entity via JSON:API. + $entity_url = Url::fromUri('internal:/jsonapi/entity_test/entity_test/' . $entity->uuid()); + $response = $this->request('GET', $entity_url, $request_options); + $this->assertSame(200, $response->getStatusCode()); + $response_document = Json::decode($response->getBody()); + // Ensure that the entity's langcode attribute is 'und'. + $this->assertSame(LanguageInterface::LANGCODE_NOT_SPECIFIED, $response_document['data']['attributes']['langcode']); + // Prepare to PATCH the entity via JSON:API. + $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; + $request_options[RequestOptions::JSON] = [ + 'data' => [ + 'type' => 'entity_test--entity_test', + 'id' => $entity->uuid(), + 'attributes' => [ + 'name' => 'Constantine', + ], + ], + ]; + // Issue the PATCH request and verify that the test entity was successfully + // updated. + $response = $this->request('PATCH', $entity_url, $request_options); + $this->assertSame(200, $response->getStatusCode(), (string) $response->getBody()); + $response_document = Json::decode($response->getBody()); + // Ensure that the entity's langcode attribute is still 'und' and the name + // was successfully updated. + $this->assertSame(LanguageInterface::LANGCODE_NOT_SPECIFIED, $response_document['data']['attributes']['langcode']); + $this->assertSame('Constantine', $response_document['data']['attributes']['name']); + } + + /** + * Ensure POSTing invalid data results in a 422 response, not a PHP error. + * + * @see https://www.drupal.org/project/drupal/issues/3052954 + */ + public function testInvalidDataTriggersUnprocessableEntityErrorFromIssue3052954() { + $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); + + // Set up data model. + $user = $this->drupalCreateUser(['bypass node access']); + + // Test. + $request_options = [ + RequestOptions::HEADERS => [ + 'Content-Type' => 'application/vnd.api+json', + 'Accept' => 'application/vnd.api+json', + ], + RequestOptions::JSON => [ + 'data' => [ + 'type' => 'article', + 'attributes' => [ + 'title' => 'foobar', + 'created' => 'not_a_date', + ], + ], + ], + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], + ]; + $response = $this->request('POST', Url::fromUri('internal:/jsonapi/node/article'), $request_options); + $this->assertSame(422, $response->getStatusCode()); + } + + /** + * Ensure optional `@FieldType=map` fields are denormalized correctly. + */ + public function testEmptyMapFieldTypeDenormalization() { + $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); + + // Set up data model. + $this->assertTrue($this->container->get('module_installer')->install(['entity_test'], TRUE), 'Installed modules.'); + + // Create data. + $entity = EntityTestMapField::create([ + 'name' => 'foo', + ]); + $entity->save(); + $user = $this->drupalCreateUser([ + 'administer entity_test content', + ]); + + // Test. + $url = Url::fromUri(sprintf('internal:/jsonapi/entity_test_map_field/entity_test_map_field/%s', $entity->uuid())); + $request_options = [ + RequestOptions::AUTH => [$user->getAccountName(), $user->pass_raw], + ]; + // Retrieve the current representation of the entity. + $response = $this->request('GET', $url, $request_options); + $this->assertSame(200, $response->getStatusCode()); + $doc = Json::decode((string) $response->getBody()); + // Modify the title. The @FieldType=map normalization is not changed. (The + // name of this field is confusingly also 'data'.) + $doc['data']['attributes']['name'] = 'bar'; + $request_options[RequestOptions::HEADERS] = [ + 'Content-Type' => 'application/vnd.api+json', + 'Accept' => 'application/vnd.api+json', + ]; + $request_options[RequestOptions::BODY] = Json::encode($doc); + $response = $this->request('PATCH', $url, $request_options); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame($doc['data']['attributes']['data'], Json::decode((string) $response->getBody())['data']['attributes']['data']); + } + + /** + * Ensure EntityAccessDeniedHttpException cacheability is taken into account. + */ + public function testLeakCacheMetadataInOmitted() { + $term = Term::create([ + 'name' => 'Llama term', + 'vid' => 'tags', + ]); + $term->setUnpublished(); + $term->save(); + + $node = Node::create([ + 'type' => 'article', + 'title' => 'Llama node', + 'field_tags' => ['target_id' => $term->id()], + ]); + $node->save(); + + $user = $this->drupalCreateUser([ + 'access content', + ]); + $request_options = [ + RequestOptions::AUTH => [ + $user->getAccountName(), + $user->pass_raw, + ], + ]; + + // Request with unpublished term. At this point it would include the term + // into "omitted" part of the response. The point here is that we + // purposefully warm up the cache where it is excluded from response and + // on the next run we will assure merely publishing term is enough to make + // it visible, i.e. that the 1st response was invalidated in Drupal cache. + $url = Url::fromUri('internal:/jsonapi/' . $node->getEntityTypeId() . '/' . $node->bundle(), [ + 'query' => ['include' => 'field_tags'], + ]); + $response = $this->request('GET', $url, $request_options); + $this->assertSame(200, $response->getStatusCode()); + + $response = Json::decode((string) $response->getBody()); + $this->assertArrayNotHasKey('included', $response, 'JSON API response does not contain "included" taxonomy term as the latter is not published, i.e not accessible.'); + + $omitted = $response['meta']['omitted']['links']; + unset($omitted['help']); + $omitted = reset($omitted); + $expected_url = Url::fromUri('internal:/jsonapi/' . $term->getEntityTypeId() . '/' . $term->bundle() . '/' . $term->uuid()); + $expected_url->setAbsolute(); + $this->assertSame($expected_url->toString(), $omitted['href'], 'Entity that is excluded due to access constraints is correctly reported in the "Omitted" section of the JSON API response.'); + + $term->setPublished(); + $term->save(); + $response = $this->request('GET', $url, $request_options); + $this->assertSame(200, $response->getStatusCode()); + $this->assertEquals($term->uuid(), Json::decode((string) $response->getBody())['included'][0]['id'], 'JSON API response contains "included" taxonomy term as it became published, i.e accessible.'); + } + + /** + * Tests that "virtual/missing" resources can exist for renamed fields. + * + * @see https://www.drupal.org/project/jsonapi/issues/3034786 + * @see https://www.drupal.org/project/jsonapi_extras/issues/3035544 + */ + public function testAliasedFieldsWithVirtualRelationships() { + // Set up the data model. + $this->assertTrue($this->container->get('module_installer')->install([ + 'taxonomy', + 'jsonapi_test_resource_type_building', + ], TRUE), 'Installed modules.'); + \Drupal::state()->set('jsonapi_test_resource_type_builder.resource_type_field_aliases', [ + 'node--article' => [ + 'field_tags' => 'field_aliased', + ], + ]); + $this->rebuildAll(); + + $tag_term = Term::create([ + 'vid' => 'tags', + 'name' => 'test_tag', + ]); + $tag_term->save(); + + $article_node = Node::create([ + 'type' => 'article', + 'title' => 'test_article', + 'field_tags' => ['target_id' => $tag_term->id()], + ]); + $article_node->save(); + + // Make a broken reference. + $tag_term->delete(); + + // Make sure that accessing a node that references a deleted term does not + // cause an error. + $user = $this->drupalCreateUser(['bypass node access']); + $request_options = [ + RequestOptions::AUTH => [ + $user->getAccountName(), + $user->pass_raw, + ], + ]; + $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/article/' . $article_node->uuid()), $request_options); + $this->assertSame(200, $response->getStatusCode()); + } + } diff --git a/core/modules/jsonapi/tests/src/Functional/MediaTest.php b/core/modules/jsonapi/tests/src/Functional/MediaTest.php index 049305a2f..f604ca609 100644 --- a/core/modules/jsonapi/tests/src/Functional/MediaTest.php +++ b/core/modules/jsonapi/tests/src/Functional/MediaTest.php @@ -24,6 +24,11 @@ class MediaTest extends ResourceTestBase { */ public static $modules = ['media']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -350,8 +355,6 @@ public function testPostIndividual() { /** * {@inheritdoc} - * - * @todo Determine if this override should be removed in https://www.drupal.org/project/jsonapi/issues/2952522 */ protected function getExpectedGetRelationshipDocumentData($relationship_field_name, EntityInterface $entity = NULL) { $data = parent::getExpectedGetRelationshipDocumentData($relationship_field_name, $entity); diff --git a/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php b/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php index 3e2a0151e..8ae5e1bb7 100644 --- a/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php @@ -17,6 +17,11 @@ class MediaTypeTest extends ResourceTestBase { */ public static $modules = ['media']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php b/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php index 09935427d..847c49ff9 100644 Binary files a/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php and b/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php differ diff --git a/core/modules/jsonapi/tests/src/Functional/MenuTest.php b/core/modules/jsonapi/tests/src/Functional/MenuTest.php index 2acb15f01..6a6d9946a 100644 --- a/core/modules/jsonapi/tests/src/Functional/MenuTest.php +++ b/core/modules/jsonapi/tests/src/Functional/MenuTest.php @@ -17,6 +17,11 @@ class MenuTest extends ResourceTestBase { */ public static $modules = []; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/MessageTest.php b/core/modules/jsonapi/tests/src/Functional/MessageTest.php index 48b82ed2b..5fcb72512 100644 --- a/core/modules/jsonapi/tests/src/Functional/MessageTest.php +++ b/core/modules/jsonapi/tests/src/Functional/MessageTest.php @@ -21,6 +21,11 @@ class MessageTest extends ResourceTestBase { */ public static $modules = ['contact']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -115,7 +120,8 @@ protected function getExpectedUnauthorizedAccessMessage($method) { */ public function testGetIndividual() { // Contact Message entities are not stored, so they cannot be retrieved. - $this->setExpectedException(RouteNotFoundException::class, 'Route "jsonapi.contact_message--camelids.individual" does not exist.'); + $this->expectException(RouteNotFoundException::class); + $this->expectExceptionMessage('Route "jsonapi.contact_message--camelids.individual" does not exist.'); Url::fromRoute('jsonapi.contact_message--camelids.individual')->toString(TRUE); } @@ -125,7 +131,8 @@ public function testGetIndividual() { */ public function testPatchIndividual() { // Contact Message entities are not stored, so they cannot be modified. - $this->setExpectedException(RouteNotFoundException::class, 'Route "jsonapi.contact_message--camelids.individual" does not exist.'); + $this->expectException(RouteNotFoundException::class); + $this->expectExceptionMessage('Route "jsonapi.contact_message--camelids.individual" does not exist.'); Url::fromRoute('jsonapi.contact_message--camelids.individual')->toString(TRUE); } @@ -135,7 +142,8 @@ public function testPatchIndividual() { */ public function testDeleteIndividual() { // Contact Message entities are not stored, so they cannot be deleted. - $this->setExpectedException(RouteNotFoundException::class, 'Route "jsonapi.contact_message--camelids.individual" does not exist.'); + $this->expectException(RouteNotFoundException::class); + $this->expectExceptionMessage('Route "jsonapi.contact_message--camelids.individual" does not exist.'); Url::fromRoute('jsonapi.contact_message--camelids.individual')->toString(TRUE); } @@ -145,7 +153,8 @@ public function testDeleteIndividual() { */ public function testRelated() { // Contact Message entities are not stored, so they cannot be retrieved. - $this->setExpectedException(RouteNotFoundException::class, 'Route "jsonapi.contact_message--camelids.related" does not exist.'); + $this->expectException(RouteNotFoundException::class); + $this->expectExceptionMessage('Route "jsonapi.contact_message--camelids.related" does not exist.'); Url::fromRoute('jsonapi.contact_message--camelids.related')->toString(TRUE); } @@ -155,7 +164,8 @@ public function testRelated() { */ public function testRelationships() { // Contact Message entities are not stored, so they cannot be retrieved. - $this->setExpectedException(RouteNotFoundException::class, 'Route "jsonapi.contact_message--camelids.relationship.get" does not exist.'); + $this->expectException(RouteNotFoundException::class); + $this->expectExceptionMessage('Route "jsonapi.contact_message--camelids.relationship.get" does not exist.'); Url::fromRoute('jsonapi.contact_message--camelids.relationship.get')->toString(TRUE); } @@ -181,7 +191,8 @@ public function testCollection() { */ public function testRevisions() { // Contact Message entities are not stored, so they cannot be retrieved. - $this->setExpectedException(RouteNotFoundException::class, 'Route "jsonapi.contact_message--camelids.individual" does not exist.'); + $this->expectException(RouteNotFoundException::class); + $this->expectExceptionMessage('Route "jsonapi.contact_message--camelids.individual" does not exist.'); Url::fromRoute('jsonapi.contact_message--camelids.individual')->toString(TRUE); } diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTest.php index 81c8b5448..8493da671 100644 --- a/core/modules/jsonapi/tests/src/Functional/NodeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/NodeTest.php @@ -7,6 +7,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Url; use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer; +use Drupal\jsonapi\Normalizer\Value\CacheableNormalization; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; use Drupal\Tests\jsonapi\Traits\CommonCollectionFilterAccessTestPatternsTrait; @@ -27,6 +28,11 @@ class NodeTest extends ResourceTestBase { */ public static $modules = ['node', 'path']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -267,7 +273,7 @@ public function testPatchPath() { // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); // GET node's current normalization. $response = $this->request('GET', $url, $this->getAuthenticationRequestOptions()); @@ -301,12 +307,13 @@ public function testPatchPath() { public function testGetIndividual() { parent::testGetIndividual(); + $this->assertCacheableNormalizations(); // Unpublish node. $this->entity->setUnpublished()->save(); // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $request_options = $this->getAuthenticationRequestOptions(); // 403 when accessing own unpublished node. @@ -350,6 +357,65 @@ public function testGetIndividual() { $this->assertResourceResponse(200, FALSE, $response, $this->getExpectedCacheTags(), $expected_cache_contexts, FALSE, 'UNCACHEABLE'); } + /** + * Asserts that normalizations are cached in an incremental way. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + protected function assertCacheableNormalizations() { + // Save the entity to invalidate caches. + $this->entity->save(); + $uuid = $this->entity->uuid(); + $cache = \Drupal::service('render_cache')->get([ + '#cache' => [ + 'keys' => ['node--camelids', $uuid], + 'bin' => 'jsonapi_normalizations', + ], + ]); + // After saving the entity the normalization should not be cached. + $this->assertFalse($cache); + // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. + $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $uuid]); + // $url = $this->entity->toUrl('jsonapi'); + $request_options = $this->getAuthenticationRequestOptions(); + $request_options[RequestOptions::QUERY] = ['fields' => ['node--camelids' => 'title']]; + $this->request('GET', $url, $request_options); + // Ensure the normalization cache is being incrementally built. After + // requesting the title, only the title is in the cache. + $this->assertNormalizedFieldsAreCached(['title']); + $request_options[RequestOptions::QUERY] = ['fields' => ['node--camelids' => 'field_rest_test']]; + $this->request('GET', $url, $request_options); + // After requesting an additional field, then that field is in the cache and + // the old one is still there. + $this->assertNormalizedFieldsAreCached(['title', 'field_rest_test']); + } + + /** + * Checks that the provided field names are the only fields in the cache. + * + * The normalization cache should only have these fields, which build up + * across responses. + * + * @param string[] $field_names + * The field names. + */ + protected function assertNormalizedFieldsAreCached($field_names) { + $cache = \Drupal::service('render_cache')->get([ + '#cache' => [ + 'keys' => ['node--camelids', $this->entity->uuid()], + 'bin' => 'jsonapi_normalizations', + ], + ]); + $cached_fields = $cache['#data']['fields']; + $this->assertCount(count($field_names), $cached_fields); + array_walk($field_names, function ($field_name) use ($cached_fields) { + $this->assertInstanceOf( + CacheableNormalization::class, + $cached_fields[$field_name] + ); + }); + } + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php index eeeb4645a..c7bc3e222 100644 --- a/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php @@ -17,6 +17,11 @@ class NodeTypeTest extends ResourceTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php b/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php new file mode 100644 index 000000000..ce2e90465 --- /dev/null +++ b/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php @@ -0,0 +1,119 @@ +grantPermissionsToTestedRole(['administer url aliases']); + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + $path_alias = PathAlias::create([ + 'alias' => '/frontpage1', + 'path' => '/', + 'langcode' => 'en', + ]); + $path_alias->save(); + return $path_alias; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedDocument() { + $self_url = Url::fromUri('base:/jsonapi/path_alias/path_alias/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl(); + return [ + 'jsonapi' => [ + 'meta' => [ + 'links' => [ + 'self' => ['href' => 'http://jsonapi.org/format/1.0/'], + ], + ], + 'version' => '1.0', + ], + 'links' => [ + 'self' => ['href' => $self_url], + ], + 'data' => [ + 'id' => $this->entity->uuid(), + 'type' => static::$resourceTypeName, + 'links' => [ + 'self' => ['href' => $self_url], + ], + 'attributes' => [ + 'alias' => '/frontpage1', + 'path' => '/', + 'langcode' => 'en', + 'status' => TRUE, + 'drupal_internal__id' => 1, + 'drupal_internal__revision_id' => 1, + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getPostDocument() { + return [ + 'data' => [ + 'type' => static::$resourceTypeName, + 'attributes' => [ + 'alias' => '/frontpage1', + 'path' => '/', + 'langcode' => 'en', + ], + ], + ]; + } + +} diff --git a/core/modules/jsonapi/tests/src/Functional/RdfMappingTest.php b/core/modules/jsonapi/tests/src/Functional/RdfMappingTest.php index 2570e9756..0082d52a3 100644 --- a/core/modules/jsonapi/tests/src/Functional/RdfMappingTest.php +++ b/core/modules/jsonapi/tests/src/Functional/RdfMappingTest.php @@ -18,6 +18,11 @@ class RdfMappingTest extends ResourceTestBase { */ public static $modules = ['node', 'rdf']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ResourceResponseTestTrait.php b/core/modules/jsonapi/tests/src/Functional/ResourceResponseTestTrait.php index 04a173ff7..59f9a3fdb 100644 --- a/core/modules/jsonapi/tests/src/Functional/ResourceResponseTestTrait.php +++ b/core/modules/jsonapi/tests/src/Functional/ResourceResponseTestTrait.php @@ -594,7 +594,7 @@ protected static function errorsToOmittedObject(array $errors) { ], ]; foreach ($errors as $error) { - $omitted['links']['item:' . substr(Crypt::hashBase64($error['links']['via']['href']), 0, 7)] = [ + $omitted['links']['item--' . substr(Crypt::hashBase64($error['links']['via']['href']), 0, 7)] = [ 'href' => $error['links']['via']['href'], 'meta' => [ 'detail' => $error['detail'], @@ -658,7 +658,7 @@ protected static function resetOmittedLinkKeys(array &$omitted) { $reindexed = []; $links = array_diff_key($omitted['links'], array_flip(['help'])); foreach (array_values($links) as $index => $link) { - $reindexed['item:' . $index] = $link; + $reindexed['item--' . $index] = $link; } $omitted['links'] = ['help' => $help] + $reindexed; } diff --git a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php index f50c15804..3c39f097e 100644 --- a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php +++ b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php @@ -136,14 +136,11 @@ abstract class ResourceTestBase extends BrowserTestBase { protected static $secondCreatedEntityId = 3; /** - * Optionally specify which field is the 'label' field. - * - * Some entities specify a 'label_callback', but not a 'label' entity key. - * For example: User. + * Specify which field is the 'label' field for testing a POST edge case. * * @var string|null * - * @see ::getInvalidNormalizedEntityToCreate() + * @see ::testPostIndividual() */ protected static $labelFieldName = NULL; @@ -377,7 +374,10 @@ protected function getData() { * The JSON:API normalization for the given entity. */ protected function normalize(EntityInterface $entity, Url $url) { - $self_link = new Link(new CacheableMetadata(), $url, ['self']); + // Don't use cached normalizations in tests. + $this->container->get('cache.jsonapi_normalizations')->deleteAll(); + + $self_link = new Link(new CacheableMetadata(), $url, 'self'); $resource_type = $this->container->get('jsonapi.resource_type.repository')->getByTypeName(static::$resourceTypeName); $doc = new JsonApiDocumentTopLevel(new ResourceObjectData([ResourceObject::createFromEntity($resource_type, $entity)], 1), new NullIncludedData(), new LinkCollection(['self' => $self_link])); return $this->serializer->normalize($doc, 'api_json', [ @@ -908,7 +908,7 @@ public function testGetIndividual() { // - to eventually result in a well-formed request that succeeds. // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $request_options = []; $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); @@ -1719,7 +1719,6 @@ protected function getExpectedGetRelationshipDocument($relationship_field_name, if (static::$resourceTypeIsVersionable) { assert($entity instanceof RevisionableInterface); $version_query = ['resourceVersion' => 'id:' . $entity->getRevisionId()]; - $self_link->setOption('query', $version_query); $related_link->setOption('query', $version_query); } $data = $this->getExpectedGetRelationshipDocumentData($relationship_field_name, $entity); @@ -1880,7 +1879,9 @@ public function testPostIndividual() { $parseable_valid_request_body = Json::encode($this->getPostDocument()); /* $parseable_valid_request_body_2 = Json::encode($this->getNormalizedPostEntity()); */ $parseable_invalid_request_body_missing_type = Json::encode($this->removeResourceTypeFromDocument($this->getPostDocument(), 'type')); - $parseable_invalid_request_body = Json::encode($this->makeNormalizationInvalid($this->getPostDocument(), 'label')); + if ($this->entity->getEntityType()->hasKey('label')) { + $parseable_invalid_request_body = Json::encode($this->makeNormalizationInvalid($this->getPostDocument(), 'label')); + } $parseable_invalid_request_body_2 = Json::encode(NestedArray::mergeDeep(['data' => ['id' => $this->randomMachineName(129)]], $this->getPostDocument())); $parseable_invalid_request_body_3 = Json::encode(NestedArray::mergeDeep(['data' => ['attributes' => ['field_rest_test' => $this->randomString()]]], $this->getPostDocument())); $parseable_invalid_request_body_4 = Json::encode(NestedArray::mergeDeep(['data' => ['attributes' => ['field_nonexistent' => $this->randomString()]]], $this->getPostDocument())); @@ -1936,13 +1937,14 @@ public function testPostIndividual() { $response = $this->request('POST', $url, $request_options); $this->assertResourceErrorResponse(400, 'Resource object must include a "type".', $url, $response, FALSE); - $request_options[RequestOptions::BODY] = $parseable_invalid_request_body; - - // DX: 422 when invalid entity: multiple values sent for single-value field. - $response = $this->request('POST', $url, $request_options); - $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; - $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); - $this->assertResourceErrorResponse(422, "$label_field: $label_field_capitalized: this field cannot hold more than 1 values.", NULL, $response, '/data/attributes/' . $label_field); + if ($this->entity->getEntityType()->hasKey('label')) { + $request_options[RequestOptions::BODY] = $parseable_invalid_request_body; + // DX: 422 when invalid entity: multiple values sent for single-value field. + $response = $this->request('POST', $url, $request_options); + $label_field = $this->entity->getEntityType()->getKey('label'); + $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); + $this->assertResourceErrorResponse(422, "$label_field: $label_field_capitalized: this field cannot hold more than 1 values.", NULL, $response, '/data/attributes/' . $label_field); + } $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; @@ -2047,7 +2049,10 @@ public function testPostIndividual() { // 500 when creating an entity with a duplicate UUID. $doc = $this->getModifiedEntityForPostTesting(); $doc['data']['id'] = $uuid; - $doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]]; + $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; + if (isset($label_field)) { + $doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]]; + } $request_options[RequestOptions::BODY] = Json::encode($doc); $response = $this->request('POST', $url, $request_options); @@ -2057,7 +2062,9 @@ public function testPostIndividual() { $doc = $this->getModifiedEntityForPostTesting(); $new_uuid = \Drupal::service('uuid')->generate(); $doc['data']['id'] = $new_uuid; - $doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]]; + if (isset($label_field)) { + $doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]]; + } $request_options[RequestOptions::BODY] = Json::encode($doc); $response = $this->request('POST', $url, $request_options); @@ -2091,7 +2098,9 @@ public function testPatchIndividual() { $unparseable_request_body = '!{>}<'; $parseable_valid_request_body = Json::encode($this->getPatchDocument()); /* $parseable_valid_request_body_2 = Json::encode($this->getNormalizedPatchEntity()); */ - $parseable_invalid_request_body = Json::encode($this->makeNormalizationInvalid($this->getPatchDocument(), 'label')); + if ($this->entity->getEntityType()->hasKey('label')) { + $parseable_invalid_request_body = Json::encode($this->makeNormalizationInvalid($this->getPatchDocument(), 'label')); + } $parseable_invalid_request_body_2 = Json::encode(NestedArray::mergeDeep(['data' => ['attributes' => ['field_rest_test' => $this->randomString()]]], $this->getPatchDocument())); // The 'field_rest_test' field does not allow 'view' access, so does not end // up in the JSON:API document. Even when we explicitly add it to the JSON @@ -2110,7 +2119,7 @@ public function testPatchIndividual() { // - to eventually result in a well-formed request that succeeds. // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $request_options = []; $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); @@ -2145,13 +2154,14 @@ public function testPatchIndividual() { $response = $this->request('PATCH', $url, $request_options); $this->assertResourceErrorResponse(400, 'Syntax error', $url, $response, FALSE); - $request_options[RequestOptions::BODY] = $parseable_invalid_request_body; - // DX: 422 when invalid entity: multiple values sent for single-value field. - $response = $this->request('PATCH', $url, $request_options); - $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; - $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); - $this->assertResourceErrorResponse(422, "$label_field: $label_field_capitalized: this field cannot hold more than 1 values.", NULL, $response, '/data/attributes/' . $label_field); + if ($this->entity->getEntityType()->hasKey('label')) { + $request_options[RequestOptions::BODY] = $parseable_invalid_request_body; + $response = $this->request('PATCH', $url, $request_options); + $label_field = $this->entity->getEntityType()->getKey('label'); + $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); + $this->assertResourceErrorResponse(422, "$label_field: $label_field_capitalized: this field cannot hold more than 1 values.", NULL, $response, '/data/attributes/' . $label_field); + } $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; @@ -2336,7 +2346,7 @@ public function testPatchIndividual() { // Ensure that PATCHing an entity that is not the latest revision is // unsupported. - if (!$this->entity->getEntityType()->isRevisionable() || !$this->entity instanceof FieldableEntityInterface) { + if (!$this->entity->getEntityType()->isRevisionable() || !$this->entity->getEntityType()->hasHandlerClass('moderation') || !$this->entity instanceof FieldableEntityInterface) { return; } assert($this->entity instanceof RevisionableInterface); @@ -2403,7 +2413,7 @@ public function testDeleteIndividual() { // - to eventually result in a well-formed request that succeeds. // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $request_options = []; $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); @@ -2723,7 +2733,7 @@ public function testRevisions() { // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()])->setAbsolute(); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $collection_url = Url::fromRoute(sprintf('jsonapi.%s.collection', static::$resourceTypeName))->setAbsolute(); $relationship_url = Url::fromRoute(sprintf('jsonapi.%s.%s.relationship.get', static::$resourceTypeName, 'field_jsonapi_test_entity_ref'), ['entity' => $this->entity->uuid()])->setAbsolute(); $related_url = Url::fromRoute(sprintf('jsonapi.%s.%s.related', static::$resourceTypeName, 'field_jsonapi_test_entity_ref'), ['entity' => $this->entity->uuid()])->setAbsolute(); @@ -3089,6 +3099,7 @@ public function testRevisions() { $actual_response = $this->request('GET', $relationship_url, $request_options); $expected_response = $this->getExpectedGetRelationshipResponse('field_jsonapi_test_entity_ref', $revision); $expected_document = $expected_response->getResponseData(); + $expected_document['links']['self']['href'] = $relationship_url->setAbsolute()->toString(); $expected_cacheability = $expected_response->getCacheableMetadata(); $this->assertResourceResponse(200, $expected_document, $actual_response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, 'MISS'); // Request the related route. diff --git a/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php b/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php index 298dc48d0..d70d4b75e 100644 --- a/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php @@ -17,6 +17,11 @@ class ResponsiveImageStyleTest extends ResourceTestBase { */ public static $modules = ['responsive_image']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/RestExportJsonApiUnsupported.php b/core/modules/jsonapi/tests/src/Functional/RestExportJsonApiUnsupported.php index 5e8387711..eb38ccd01 100644 --- a/core/modules/jsonapi/tests/src/Functional/RestExportJsonApiUnsupported.php +++ b/core/modules/jsonapi/tests/src/Functional/RestExportJsonApiUnsupported.php @@ -9,7 +9,6 @@ * Ensures that the 'api_json' format is not supported by the REST module. * * @group jsonapi - * @group legacy * * @internal */ @@ -25,6 +24,11 @@ class RestExportJsonApiUnsupported extends ViewTestBase { */ public static $modules = ['jsonapi', 'rest_test_views', 'views_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php b/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php index 87c0f61b2..27cc2b221 100644 --- a/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php +++ b/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php @@ -12,7 +12,6 @@ * Ensures that the 'api_json' format is not supported by the REST module. * * @group jsonapi - * @group legacy * * @internal */ @@ -25,6 +24,11 @@ class RestJsonApiUnsupported extends ResourceTestBase { */ public static $modules = ['jsonapi', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php b/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php index f44ebd0de..b1de33b04 100644 --- a/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php +++ b/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php @@ -17,6 +17,11 @@ class RestResourceConfigTest extends ResourceTestBase { */ public static $modules = ['rest', 'dblog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/RoleTest.php b/core/modules/jsonapi/tests/src/Functional/RoleTest.php index 4f8d609a2..c3ad6e460 100644 --- a/core/modules/jsonapi/tests/src/Functional/RoleTest.php +++ b/core/modules/jsonapi/tests/src/Functional/RoleTest.php @@ -18,6 +18,11 @@ class RoleTest extends ResourceTestBase { */ public static $modules = ['user']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php b/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php index 4f741d633..ebf9275f0 100644 --- a/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php +++ b/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php @@ -17,6 +17,11 @@ class SearchPageTest extends ResourceTestBase { */ public static $modules = ['node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php b/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php index 0ab41a38b..a6bcce4ec 100644 --- a/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php @@ -17,6 +17,11 @@ class ShortcutSetTest extends ResourceTestBase { */ public static $modules = ['shortcut']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php b/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php index 9fc53b988..925bef473 100644 --- a/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ShortcutTest.php @@ -25,6 +25,11 @@ class ShortcutTest extends ResourceTestBase { */ public static $modules = ['comment', 'shortcut']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -146,27 +151,6 @@ protected function getExpectedUnauthorizedAccessMessage($method) { return "The shortcut set must be the currently displayed set for the user and the user must have 'access shortcuts' AND 'customize shortcut links' permissions."; } - /** - * {@inheritdoc} - */ - public function testPostIndividual() { - $this->markTestSkipped('Disabled until https://www.drupal.org/project/drupal/issues/2982060 is fixed.'); - } - - /** - * {@inheritdoc} - */ - public function testRelationships() { - $this->markTestSkipped('Disabled until https://www.drupal.org/project/drupal/issues/2982060 is fixed.'); - } - - /** - * {@inheritdoc} - */ - public function testPatchIndividual() { - $this->markTestSkipped('Disabled until https://www.drupal.org/project/drupal/issues/2982060 is fixed.'); - } - /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/TermTest.php b/core/modules/jsonapi/tests/src/Functional/TermTest.php index 52b3b2ab7..f5f20aeec 100644 --- a/core/modules/jsonapi/tests/src/Functional/TermTest.php +++ b/core/modules/jsonapi/tests/src/Functional/TermTest.php @@ -26,6 +26,11 @@ class TermTest extends ResourceTestBase { */ public static $modules = ['taxonomy', 'path']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -369,7 +374,7 @@ public function testPatchPath() { // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $request_options = []; $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; @@ -435,7 +440,7 @@ public function testGetIndividualTermWithParent(array $parent_term_ids) { // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); - /* $url = $this->entity->toUrl('jsonapi'); */ + // $url = $this->entity->toUrl('jsonapi'); $request_options = []; $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); diff --git a/core/modules/jsonapi/tests/src/Functional/TestCoverageTest.php b/core/modules/jsonapi/tests/src/Functional/TestCoverageTest.php index 217d3d0d9..92de8b451 100644 --- a/core/modules/jsonapi/tests/src/Functional/TestCoverageTest.php +++ b/core/modules/jsonapi/tests/src/Functional/TestCoverageTest.php @@ -19,13 +19,18 @@ class TestCoverageTest extends BrowserTestBase { */ protected $definitions; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - $all_modules = system_rebuild_module_data(); + $all_modules = \Drupal::service('extension.list.module')->getList(); $stable_core_modules = array_filter($all_modules, function ($module) { // Filter out contrib, hidden, testing, and experimental modules. We also // don't need to enable modules that are already enabled. diff --git a/core/modules/jsonapi/tests/src/Functional/TourTest.php b/core/modules/jsonapi/tests/src/Functional/TourTest.php index e6d348877..80b1ea8c3 100644 --- a/core/modules/jsonapi/tests/src/Functional/TourTest.php +++ b/core/modules/jsonapi/tests/src/Functional/TourTest.php @@ -17,6 +17,11 @@ class TourTest extends ResourceTestBase { */ public static $modules = ['tour']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/UserTest.php b/core/modules/jsonapi/tests/src/Functional/UserTest.php index 1a72f7f2b..f4ce93f2a 100644 --- a/core/modules/jsonapi/tests/src/Functional/UserTest.php +++ b/core/modules/jsonapi/tests/src/Functional/UserTest.php @@ -22,7 +22,12 @@ class UserTest extends ResourceTestBase { /** * {@inheritdoc} */ - public static $modules = ['user']; + public static $modules = ['user', 'jsonapi_test_user']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; /** * {@inheritdoc} @@ -56,7 +61,7 @@ class UserTest extends ResourceTestBase { /** * {@inheritdoc} */ - protected static $labelFieldName = 'name'; + protected static $labelFieldName = 'display_name'; /** * {@inheritdoc} @@ -138,6 +143,7 @@ protected function getExpectedDocument() { 'self' => ['href' => $self_url], ], 'attributes' => [ + 'display_name' => 'Llama', 'created' => '1973-11-29T21:33:09+00:00', 'changed' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339), 'default_langcode' => TRUE, @@ -201,13 +207,6 @@ public function testPatchDxForSecuritySensitiveBaseFields() { $url = Url::fromRoute(sprintf('jsonapi.user--user.individual'), ['entity' => $this->account->uuid()]); /* $url = $this->account->toUrl('jsonapi'); */ - $original_normalization = $this->normalize($this->account, $url); - // @todo Remove the array_diff_key() call in https://www.drupal.org/node/2821077. - $original_normalization['data']['attributes'] = array_diff_key( - $original_normalization['data']['attributes'], - ['created' => TRUE, 'changed' => TRUE, 'name' => TRUE] - ); - // Since this test must be performed by the user that is being modified, // we must use $this->account, not $this->entity. $request_options = []; @@ -215,6 +214,9 @@ public function testPatchDxForSecuritySensitiveBaseFields() { $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); + $response = $this->request('GET', $url, $request_options); + $original_normalization = Json::decode((string) $response->getBody()); + // Test case 1: changing email. $normalization = $original_normalization; $normalization['data']['attributes']['mail'] = 'new-email@example.com'; @@ -246,7 +248,7 @@ public function testPatchDxForSecuritySensitiveBaseFields() { $this->assertResourceResponse(200, FALSE, $response); // Test case 2: changing password. - $normalization = $original_normalization; + $normalization = Json::decode((string) $response->getBody()); $normalization['data']['attributes']['mail'] = 'new-email@example.com'; $new_password = $this->randomString(); $normalization['data']['attributes']['pass']['value'] = $new_password; @@ -274,7 +276,7 @@ public function testPatchDxForSecuritySensitiveBaseFields() { $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); // Test case 3: changing name. - $normalization = $original_normalization; + $normalization = Json::decode((string) $response->getBody()); $normalization['data']['attributes']['mail'] = 'new-email@example.com'; $normalization['data']['attributes']['pass']['existing'] = $new_password; $normalization['data']['attributes']['name'] = 'Cooler Llama'; @@ -445,7 +447,7 @@ public function testCollectionContainsAnonymousUser() { $this->assertCount(4, $doc['data']); $this->assertSame(User::load(0)->uuid(), $doc['data'][0]['id']); - $this->assertSame('Anonymous', $doc['data'][0]['attributes']['name']); + $this->assertSame('User 0', $doc['data'][0]['attributes']['display_name']); } /** @@ -555,4 +557,55 @@ public function testCollectionFilterAccess() { $this->assertSame($user_b->uuid(), $doc['data'][0]['id']); } + /** + * Tests users with altered display names. + */ + public function testResaveAccountName() { + $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); + $this->setUpAuthorization('PATCH'); + + $original_name = $this->entity->get('name')->value; + + $url = Url::fromRoute('jsonapi.user--user.individual', ['entity' => $this->entity->uuid()]); + $request_options = []; + $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; + $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); + + $response = $this->request('GET', $url, $request_options); + + // Send the unchanged data back. + $request_options[RequestOptions::BODY] = (string) $response->getBody(); + $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; + $response = $this->request('PATCH', $url, $request_options); + $this->assertEquals(200, $response->getStatusCode()); + + // Load the user entity again, make sure the name was not changed. + $this->entityStorage->resetCache(); + $updated_user = $this->entityStorage->load($this->entity->id()); + $this->assertEquals($original_name, $updated_user->get('name')->value); + } + + /** + * {@inheritdoc} + */ + protected function getModifiedEntityForPostTesting() { + $modified = parent::getModifiedEntityForPostTesting(); + $modified['data']['attributes']['name'] = $this->randomMachineName(); + return $modified; + } + + /** + * {@inheritdoc} + */ + protected function makeNormalizationInvalid(array $document, $entity_key) { + if ($entity_key === 'label') { + $document['data']['attributes']['name'] = [ + 0 => $document['data']['attributes']['name'], + 1 => 'Second Title', + ]; + return $document; + } + return parent::makeNormalizationInvalid($document, $entity_key); + } + } diff --git a/core/modules/jsonapi/tests/src/Functional/ViewTest.php b/core/modules/jsonapi/tests/src/Functional/ViewTest.php index acd8f86cb..3817541f1 100644 --- a/core/modules/jsonapi/tests/src/Functional/ViewTest.php +++ b/core/modules/jsonapi/tests/src/Functional/ViewTest.php @@ -17,6 +17,11 @@ class ViewTest extends ResourceTestBase { */ public static $modules = ['views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -79,7 +84,6 @@ protected function getExpectedDocument() { 'attributes' => [ 'base_field' => 'nid', 'base_table' => 'node', - 'core' => '8.x', 'dependencies' => [], 'description' => '', 'display' => [ diff --git a/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php b/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php index 61ecdc811..a3e2a066c 100644 --- a/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php +++ b/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php @@ -17,6 +17,11 @@ class VocabularyTest extends ResourceTestBase { */ public static $modules = ['taxonomy']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php b/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php index 6ad93d50f..50ffd46fb 100644 --- a/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php +++ b/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php @@ -17,6 +17,11 @@ class WorkflowTest extends ResourceTestBase { */ public static $modules = ['workflows', 'workflow_type_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php b/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php index df693f93e..62fb3befa 100644 --- a/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php @@ -11,7 +11,6 @@ /** * @coversDefaultClass \Drupal\jsonapi\Context\FieldResolver * @group jsonapi - * @group legacy * * @internal */ @@ -127,7 +126,10 @@ public function resolveInternalIncludePathProvider() { */ public function testResolveInternalIncludePathError($entity_type, $bundle, $external_path, $expected_message = '') { $path_parts = explode('.', $external_path); - $this->setExpectedException(CacheableBadRequestHttpException::class, $expected_message); + $this->expectException(CacheableBadRequestHttpException::class); + if (!empty($expected_message)) { + $this->expectExceptionMessage($expected_message); + } $resource_type = $this->resourceTypeRepository->get($entity_type, $bundle); $this->sut->resolveInternalIncludePath($resource_type, $path_parts); } @@ -166,7 +168,8 @@ public function resolveInternalIncludePathErrorProvider() { * @dataProvider resolveInternalEntityQueryPathProvider */ public function testResolveInternalEntityQueryPath($expect, $external_path, $entity_type_id = 'entity_test_with_bundle', $bundle = 'bundle1') { - $this->assertEquals($expect, $this->sut->resolveInternalEntityQueryPath($entity_type_id, $bundle, $external_path)); + $resource_type = $this->resourceTypeRepository->get($entity_type_id, $bundle); + $this->assertEquals($expect, $this->sut->resolveInternalEntityQueryPath($resource_type, $external_path)); } /** @@ -246,8 +249,12 @@ public function resolveInternalEntityQueryPathProvider() { * @dataProvider resolveInternalEntityQueryPathErrorProvider */ public function testResolveInternalEntityQueryPathError($entity_type, $bundle, $external_path, $expected_message = '') { - $this->setExpectedException(CacheableBadRequestHttpException::class, $expected_message); - $this->sut->resolveInternalEntityQueryPath($entity_type, $bundle, $external_path); + $this->expectException(CacheableBadRequestHttpException::class); + if (!empty($expected_message)) { + $this->expectExceptionMessage($expected_message); + } + $resource_type = $this->resourceTypeRepository->get($entity_type, $bundle); + $this->sut->resolveInternalEntityQueryPath($resource_type, $external_path); } /** diff --git a/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php b/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php index 7270b63af..d60d66dcf 100644 --- a/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php @@ -2,15 +2,8 @@ namespace Drupal\Tests\jsonapi\Kernel\Controller; -use Drupal\Component\Serialization\Json; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; -use Drupal\jsonapi\Exception\EntityAccessDeniedHttpException; -use Drupal\jsonapi\JsonApiResource\ResourceIdentifier; -use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\ResourceType\ResourceType; -use Drupal\jsonapi\Controller\EntityResource; use Drupal\jsonapi\JsonApiResource\Data; use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel; use Drupal\node\Entity\Node; @@ -21,13 +14,10 @@ use Drupal\user\RoleInterface; use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\ConflictHttpException; -use Symfony\Component\HttpKernel\Exception\HttpException; /** * @coversDefaultClass \Drupal\jsonapi\Controller\EntityResource * @group jsonapi - * @group legacy * * @internal */ @@ -187,115 +177,7 @@ protected function setUp() { * An EntityResource instance. */ protected function createEntityResource() { - return new EntityResource( - $this->container->get('entity_type.manager'), - $this->container->get('entity_field.manager'), - $this->container->get('jsonapi.resource_type.repository'), - $this->container->get('renderer'), - $this->container->get('entity.repository'), - $this->container->get('jsonapi.include_resolver'), - $this->container->get('jsonapi.entity_access_checker'), - $this->container->get('jsonapi.field_resolver'), - $this->container->get('jsonapi.serializer'), - $this->container->get('datetime.time'), - $this->container->get('current_user') - ); - } - - /** - * @covers ::getIndividual - */ - public function testGetIndividual() { - $response = $this->entityResource->getIndividual($this->node, Request::create('/jsonapi/node/article')); - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $resource_object = $response->getResponseData()->getData()->getIterator()->offsetGet(0); - $this->assertEquals($this->node->uuid(), $resource_object->getId()); - } - - /** - * @covers ::getIndividual - */ - public function testGetIndividualDenied() { - $role = Role::load(RoleInterface::ANONYMOUS_ID); - $role->revokePermission('access content'); - $role->save(); - $this->setExpectedException(EntityAccessDeniedHttpException::class); - $this->entityResource->getIndividual($this->node, Request::create('/jsonapi/node/article')); - } - - /** - * @covers ::getCollection - */ - public function testGetCollection() { - $request = Request::create('/jsonapi/node/article'); - $request->query = new ParameterBag(['sort' => 'nid']); - - // Get the response. - $resource_type = new ResourceType('node', 'article', NULL); - $response = $this->entityResource->getCollection($resource_type, $request); - - // Assertions. - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $this->assertInstanceOf(Data::class, $response->getResponseData()->getData()); - $this->assertEquals($this->node->uuid(), $response->getResponseData()->getData()->getIterator()->current()->getId()); - $this->assertEquals([ - 'node:1', - 'node:2', - 'node:3', - 'node:4', - 'node_list', - ], $response->getCacheableMetadata()->getCacheTags()); - } - - /** - * @covers ::getCollection - */ - public function testGetFilteredCollection() { - $request = Request::create('/jsonapi/node/article'); - $request->query = new ParameterBag(['filter' => ['type' => 'article']]); - - $entity_resource = $this->createEntityResource(); - - // Get the response. - $resource_type = $this->container->get('jsonapi.resource_type.repository')->get('node_type', 'node_type'); - $response = $entity_resource->getCollection($resource_type, $request); - - // Assertions. - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $this->assertInstanceOf(Data::class, $response->getResponseData()->getData()); - $this->assertCount(1, $response->getResponseData()->getData()); - $expected_cache_tags = [ - 'config:node.type.article', - 'config:node_type_list', - ]; - $this->assertSame($expected_cache_tags, $response->getCacheableMetadata()->getCacheTags()); - } - - /** - * @covers ::getCollection - */ - public function testGetSortedCollection() { - $request = Request::create('/jsonapi/node/article'); - $request->query = new ParameterBag(['sort' => '-type']); - - $entity_resource = $this->createEntityResource(); - - // Get the response. - $resource_type = $this->container->get('jsonapi.resource_type.repository')->get('node_type', 'node_type'); - $response = $entity_resource->getCollection($resource_type, $request); - - // Assertions. - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $this->assertInstanceOf(Data::class, $response->getResponseData()->getData()); - $this->assertCount(2, $response->getResponseData()->getData()); - // `drupal_internal__type` is the alias for a node_type entity's ID field. - $this->assertEquals($response->getResponseData()->getData()->toArray()[0]->getField('drupal_internal__type'), 'lorem'); - $expected_cache_tags = [ - 'config:node.type.article', - 'config:node.type.lorem', - 'config:node_type_list', - ]; - $this->assertSame($expected_cache_tags, $response->getCacheableMetadata()->getCacheTags()); + return $this->container->get('jsonapi.entity_resource'); } /** @@ -344,362 +226,4 @@ public function testGetEmptyCollection() { $this->assertEquals(['node_list'], $response->getCacheableMetadata()->getCacheTags()); } - /** - * @covers ::getRelated - */ - public function testGetRelated() { - // to-one relationship. - $resource_type = new ResourceType('node', 'article', NULL); - $resource_type->setRelatableResourceTypes([ - 'uid' => [new ResourceType('user', 'user', NULL)], - 'roles' => [new ResourceType('user_role', 'user_role', NULL)], - 'field_relationships' => [new ResourceType('node', 'article', NULL)], - ]); - $response = $this->entityResource->getRelated($resource_type, $this->node, 'uid', Request::create('/jsonapi/node/article/' . $this->node->uuid(), '/uid')); - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $this->assertInstanceOf(ResourceObject::class, $response->getResponseData()->getData()->toArray()[0]); - $this->assertEquals($this->user->uuid(), $response->getResponseData()->getData()->toArray()[0]->getId()); - $this->assertEquals(['node:1'], $response->getCacheableMetadata()->getCacheTags()); - // to-many relationship. - $response = $this->entityResource->getRelated($resource_type, $this->node4, 'field_relationships', Request::create('/jsonapi/node/article/' . $this->node4->uuid(), '/field_relationships')); - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response - ->getResponseData()); - $this->assertInstanceOf(Data::class, $response - ->getResponseData() - ->getData()); - $this->assertEquals(['node:4'], $response->getCacheableMetadata()->getCacheTags()); - } - - /** - * @covers ::getRelationship - */ - public function testGetRelationship() { - // to-one relationship. - $resource_type = new ResourceType('node', 'article', NULL); - $resource_type->setRelatableResourceTypes([ - 'uid' => [new ResourceType('user', 'user', NULL)], - ]); - $response = $this->entityResource->getRelationship($resource_type, $this->node, 'uid', Request::create('/jsonapi/node/article/' . $this->node->uuid() . '/relationships/uid')); - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $this->assertInstanceOf( - EntityReferenceFieldItemListInterface::class, - $response->getResponseData()->getData() - ); - $this->assertEquals(1, $response - ->getResponseData() - ->getData() - ->getEntity() - ->id() - ); - $this->assertEquals('node', $response - ->getResponseData() - ->getData() - ->getEntity() - ->getEntityTypeId() - ); - } - - /** - * @covers ::createIndividual - */ - public function testCreateIndividual() { - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('create article content') - ->save(); - $content = Json::encode([ - 'data' => [ - 'type' => 'node--article', - 'attributes' => [ - 'title' => 'Lorem ipsum', - ], - ], - ]); - $request = Request::create('/jsonapi/node/article', 'POST', [], [], [], [], $content); - $resource_type = new ResourceType('node', 'article', Node::class); - $resource_type->setRelatableResourceTypes([ - 'field_relationships' => [new ResourceType('node', 'article', NULL)], - ]); - $response = $this->entityResource->createIndividual($resource_type, $request); - // As a side effect, the node will also be saved. - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $this->assertTrue(entity_load_multiple_by_properties('node', ['uuid' => $response->getResponseData()->getData()->getIterator()->offsetGet(0)->getId()])); - $this->assertEquals(201, $response->getStatusCode()); - } - - /** - * @covers ::createIndividual - */ - public function testCreateIndividualWithMissingRequiredData() { - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('create article content') - ->save(); - $this->setExpectedException(HttpException::class, 'Unprocessable Entity: validation failed.'); - $resource_type = new ResourceType('node', 'article', Node::class); - $payload = Json::encode([ - 'data' => [ - 'type' => 'article', - ], - ]); - $this->entityResource->createIndividual($resource_type, Request::create('/jsonapi/node/article', 'POST', [], [], [], [], $payload)); - } - - /** - * @covers ::createIndividual - */ - public function testCreateIndividualDuplicateError() { - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('create article content') - ->save(); - - $node = Node::create([ - 'type' => 'article', - 'title' => 'Lorem ipsum', - ]); - $node->save(); - $node->enforceIsNew(); - - $payload = Json::encode([ - 'data' => [ - 'type' => 'article', - 'id' => $this->node->uuid(), - 'attributes' => [ - 'title' => 'foobar', - ], - ], - ]); - - $this->setExpectedException(ConflictHttpException::class, 'Conflict: Entity already exists.'); - $resource_type = new ResourceType('node', 'article', Node::class); - $resource_type->setRelatableResourceTypes([ - 'field_relationships' => [new ResourceType('node', 'article', NULL)], - ]); - $this->entityResource->createIndividual($resource_type, Request::create('/jsonapi/node/article', 'POST', [], [], [], [], $payload)); - } - - /** - * @covers ::patchIndividual - */ - public function testPatchIndividual() { - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('edit any article content') - ->save(); - $payload = Json::encode([ - 'data' => [ - 'type' => 'article', - 'id' => $this->node->uuid(), - 'attributes' => [ - 'title' => 'PATCHED', - ], - 'relationships' => [ - 'field_relationships' => [ - 'data' => [ - 'id' => Node::load(1)->uuid(), - 'type' => 'node--article', - ], - ], - ], - ], - ]); - $request = Request::create('/jsonapi/node/article/' . $this->node->uuid(), 'PATCH', [], [], [], [], $payload); - - // Create a new EntityResource that uses uuid. - $resource_type = new ResourceType('node', 'article', Node::class); - $resource_type->setRelatableResourceTypes([ - 'field_relationships' => [new ResourceType('node', 'article', NULL)], - ]); - $response = $this->entityResource->patchIndividual($resource_type, $this->node, $request); - - // As a side effect, the node will also be saved. - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $updated_node = $response->getResponseData()->getData()->getIterator()->offsetGet(0); - $this->assertInstanceOf(ResourceObject::class, $updated_node); - $this->assertSame('PATCHED', $this->node->getTitle()); - $this->assertSame([['target_id' => '1']], $this->node->get('field_relationships')->getValue()); - $this->assertEquals(200, $response->getStatusCode()); - } - - /** - * @covers ::deleteIndividual - */ - public function testDeleteIndividual() { - $node = Node::create([ - 'type' => 'article', - 'title' => 'Lorem ipsum', - ]); - $nid = $node->id(); - $node->save(); - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('delete own article content') - ->save(); - $response = $this->entityResource->deleteIndividual($node); - // As a side effect, the node will also be deleted. - $count = $this->container->get('entity_type.manager') - ->getStorage('node') - ->getQuery() - ->condition('nid', $nid) - ->count() - ->execute(); - $this->assertEquals(0, $count); - $this->assertNull($response->getResponseData()); - $this->assertEquals(204, $response->getStatusCode()); - } - - /** - * @covers ::addToRelationshipData - */ - public function testAddToRelationshipData() { - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('edit any article content') - ->save(); - - $resource_type = new ResourceType('node', 'article', NULL); - $resource_type->setRelatableResourceTypes([ - 'field_relationships' => [new ResourceType('node', 'article', NULL)], - ]); - $payload = Json::encode([ - 'data' => [ - [ - 'type' => 'node--article', - 'id' => $this->node->uuid(), - ], - ], - ]); - $request = Request::create('/jsonapi/node/article/' . $this->node->uuid() . '/relationships/field_relationships', 'POST', [], [], [], [], $payload); - $response = $this->entityResource->addToRelationshipData($resource_type, $this->node, 'field_relationships', $request); - - // As a side effect, the node will also be saved. - $this->assertNotEmpty($this->node->id()); - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $field_list = $response->getResponseData()->getData(); - $this->assertInstanceOf(EntityReferenceFieldItemListInterface::class, $field_list); - $this->assertSame('field_relationships', $field_list->getName()); - $this->assertEquals([['target_id' => 1]], $field_list->getValue()); - $this->assertEquals(204, $response->getStatusCode()); - } - - /** - * @covers ::replaceRelationshipData - * @dataProvider replaceRelationshipDataProvider - */ - public function testReplaceRelationshipData($relationships) { - $this->node->field_relationships->appendItem(['target_id' => $this->node->id()]); - $this->node->save(); - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('edit any article content') - ->save(); - - $resource_type = new ResourceType('node', 'article', NULL); - $resource_type->setRelatableResourceTypes([ - 'field_relationships' => [new ResourceType('node', 'article', NULL)], - ]); - $payload = ['data' => []]; - foreach ($relationships as $relationship) { - $payload['data'][] = [ - 'type' => $relationship->getTypeName(), - 'id' => $relationship->getId(), - ]; - } - $request = Request::create('/jsonapi/node/article/' . $this->node->uuid() . '/relationships/field_relationships', 'PATCH', [], [], [], [], Json::encode($payload)); - $response = $this->entityResource->replaceRelationshipData($resource_type, $this->node, 'field_relationships', $request); - - // As a side effect, the node will also be saved. - $this->assertNotEmpty($this->node->id()); - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $field_list = $response->getResponseData()->getData(); - $this->assertInstanceOf(EntityReferenceFieldItemListInterface::class, $field_list); - $this->assertSame('field_relationships', $field_list->getName()); - $this->assertEquals( - array_map(function (ResourceIdentifier $identifier) { - return $identifier->getId(); - }, $relationships), - array_map(function (EntityInterface $entity) { - return $entity->uuid(); - }, $field_list->referencedEntities()) - ); - $this->assertEquals(204, $response->getStatusCode()); - } - - /** - * Provides data for the testPatchRelationship. - * - * @return array - * The input data for the test function. - */ - public function replaceRelationshipDataProvider() { - return [ - // Replace relationships. - [ - [ - new ResourceIdentifier('node--article', static::$nodeUuid[1]), - new ResourceIdentifier('node--article', static::$nodeUuid[2]), - ], - ], - // Remove relationships. - [[]], - ]; - } - - /** - * @covers ::removeFromRelationshipData - * @dataProvider removeFromRelationshipDataProvider - */ - public function testRemoveFromRelationshipData($deleted_rels, $kept_rels) { - $this->node->field_relationships->appendItem(['target_id' => $this->node->id()]); - $this->node->field_relationships->appendItem(['target_id' => $this->node2->id()]); - $this->node->save(); - Role::load(Role::ANONYMOUS_ID) - ->grantPermission('edit any article content') - ->save(); - - $resource_type = new ResourceType('node', 'article', NULL); - $resource_type->setRelatableResourceTypes([ - 'field_relationships' => [new ResourceType('node', 'article', NULL)], - ]); - $payload = ['data' => []]; - foreach ($deleted_rels as $deleted_rel) { - $payload['data'][] = [ - 'type' => $deleted_rel->getTypeName(), - 'id' => $deleted_rel->getId(), - ]; - } - $request = Request::create('/jsonapi/node/article/' . $this->node->uuid() . '/relationships/field_relationships', 'DELETE', [], [], [], [], Json::encode($payload)); - $response = $this->entityResource->removeFromRelationshipData($resource_type, $this->node, 'field_relationships', $request); - - // As a side effect, the node will also be saved. - $this->assertInstanceOf(JsonApiDocumentTopLevel::class, $response->getResponseData()); - $field_list = $response->getResponseData()->getData(); - $this->assertInstanceOf(EntityReferenceFieldItemListInterface::class, $field_list); - $this->assertSame('field_relationships', $field_list->getName()); - $this->assertEquals($kept_rels, $field_list->getValue()); - $this->assertEquals(204, $response->getStatusCode()); - } - - /** - * Provides data for the testDeleteRelationship. - * - * @return array - * The input data for the test function. - */ - public function removeFromRelationshipDataProvider() { - return [ - // Remove one relationship. - [ - [ - new ResourceIdentifier('node--article', static::$nodeUuid[1]), - ], - [['target_id' => 2]], - ], - // Remove all relationships. - [ - [ - new ResourceIdentifier('node--article', static::$nodeUuid[2]), - new ResourceIdentifier('node--article', static::$nodeUuid[1]), - ], - [], - ], - // Remove no relationship. - [[], [['target_id' => 1], ['target_id' => 2]]], - ]; - } - } diff --git a/core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php b/core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php new file mode 100644 index 000000000..409cd783a --- /dev/null +++ b/core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php @@ -0,0 +1,95 @@ +resourceTypeRepository = $this->container->get('jsonapi.resource_type.repository'); + $this->serializer = $this->container->get('jsonapi.serializer'); + $this->cacher = $this->container->get('jsonapi.normalization_cacher'); + } + + /** + * Tests that link normalization cache information is not lost. + * + * @see https://www.drupal.org/project/drupal/issues/3077287 + */ + public function testLinkNormalizationCacheability() { + $user = User::create([ + 'name' => $this->randomMachineName(), + 'pass' => $this->randomString(), + ]); + $resource_type = $this->resourceTypeRepository->get($user->getEntityTypeId(), $user->bundle()); + $resource_object = ResourceObject::createFromEntity($resource_type, $user); + $cache_tag_to_invalidate = 'link_normalization'; + $normalized_links = $this->serializer + ->normalize($resource_object->getLinks(), 'api_json') + ->withCacheableDependency((new CacheableMetadata())->addCacheTags([$cache_tag_to_invalidate])); + assert($normalized_links instanceof CacheableNormalization); + $normalization_parts = [ + ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_BASE => [ + 'type' => CacheableNormalization::permanent($resource_object->getTypeName()), + 'id' => CacheableNormalization::permanent($resource_object->getId()), + 'links' => $normalized_links, + ], + ResourceObjectNormalizationCacher::RESOURCE_CACHE_SUBSET_FIELDS => [], + ]; + $this->cacher->saveOnTerminate($resource_object, $normalization_parts); + $event = $this->prophesize(PostResponseEvent::class); + $this->cacher->onTerminate($event->reveal()); + $this->assertNotFalse((bool) $this->cacher->get($resource_object)); + Cache::invalidateTags([$cache_tag_to_invalidate]); + $this->assertFalse((bool) $this->cacher->get($resource_object)); + } + +} diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/EntityReferenceFieldNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/EntityReferenceFieldNormalizerTest.php deleted file mode 100644 index 96e21ef81..000000000 --- a/core/modules/jsonapi/tests/src/Kernel/Normalizer/EntityReferenceFieldNormalizerTest.php +++ /dev/null @@ -1,326 +0,0 @@ -installEntitySchema('node'); - $this->installEntitySchema('user'); - $this->installEntitySchema('file'); - - // Add the additional table schemas. - $this->installSchema('system', ['sequences']); - $this->installSchema('node', ['node_access']); - $this->installSchema('file', ['file_usage']); - NodeType::create([ - 'type' => 'referencer', - ])->save(); - $this->createEntityReferenceField('node', 'referencer', 'field_user', 'User', 'user', 'default', ['target_bundles' => NULL], 1); - $this->createEntityReferenceField('node', 'referencer', 'field_users', 'Users', 'user', 'default', ['target_bundles' => NULL], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); - $field_storage_config = [ - 'type' => 'image', - 'entity_type' => 'node', - ]; - FieldStorageConfig::create(['field_name' => 'field_image', 'cardinality' => 1] + $field_storage_config)->save(); - FieldStorageConfig::create(['field_name' => 'field_images', 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED] + $field_storage_config)->save(); - $field_config = [ - 'entity_type' => 'node', - 'bundle' => 'referencer', - ]; - FieldConfig::create(['field_name' => 'field_image', 'label' => 'Image'] + $field_config)->save(); - FieldConfig::create(['field_name' => 'field_images', 'label' => 'Images'] + $field_config)->save(); - - // Set up the test data. - $this->account = $this->prophesize(AccountInterface::class)->reveal(); - $this->user1 = User::create([ - 'name' => $this->randomMachineName(), - 'mail' => $this->randomMachineName() . '@example.com', - 'uuid' => static::$userIds[0], - ]); - $this->user1->save(); - $this->user2 = User::create([ - 'name' => $this->randomMachineName(), - 'mail' => $this->randomMachineName() . '@example.com', - 'uuid' => static::$userIds[1], - ]); - $this->user2->save(); - - $this->image1 = File::create([ - 'uri' => 'public:/image1.png', - 'uuid' => static::$imageIds[0], - ]); - $this->image1->save(); - $this->image2 = File::create([ - 'uri' => 'public:/image2.png', - 'uuid' => static::$imageIds[1], - ]); - $this->image2->save(); - - // Create the node from which all the previously created entities will be - // referenced. - $this->referencer = Node::create([ - 'title' => 'Referencing node', - 'type' => 'referencer', - 'status' => 1, - 'uuid' => static::$referencerId, - ]); - $this->referencer->save(); - - // Set up the test dependencies. - $this->referencingResourceType = $this->container->get('jsonapi.resource_type.repository')->get('node', 'referencer'); - $this->normalizer = new EntityReferenceFieldNormalizer(); - $this->normalizer->setSerializer($this->container->get('jsonapi.serializer')); - } - - /** - * @covers ::normalize - * @dataProvider normalizeProvider - */ - public function testNormalize($entity_property_names, $field_name, $expected) { - // Links cannot be generated in the test provider because the container - // has not yet been set. - $expected['links'] = [ - 'self' => ['href' => Url::fromUri('base:/jsonapi/node/referencer/' . static::$referencerId . "/relationships/$field_name", ['query' => ['resourceVersion' => 'id:1']])->setAbsolute()->toString()], - 'related' => ['href' => Url::fromUri('base:/jsonapi/node/referencer/' . static::$referencerId . "/$field_name", ['query' => ['resourceVersion' => 'id:1']])->setAbsolute()->toString()], - ]; - // Set up different field values. - $this->referencer->{$field_name} = array_map(function ($entity_property_name) { - $value = ['target_id' => $this->{$entity_property_name === 'image1a' ? 'image1' : $entity_property_name}->id()]; - switch ($entity_property_name) { - case 'image1': - $value['alt'] = 'Cute llama'; - $value['title'] = 'My spirit animal'; - break; - - case 'image1a': - $value['alt'] = 'Ugly llama'; - $value['title'] = 'My alter ego'; - break; - - case 'image2': - $value['alt'] = 'Adorable llama'; - $value['title'] = 'My spirit animal 😍'; - break; - } - return $value; - }, $entity_property_names); - // Normalize. - $actual = $this->normalizer->normalize($this->referencer->{$field_name}, 'api_json', [ - 'account' => $this->account, - 'resource_object' => ResourceObject::createFromEntity($this->referencingResourceType, $this->referencer), - ]); - // Assert. - assert($actual instanceof CacheableNormalization); - $this->assertEquals($expected, $actual->getNormalization()); - } - - /** - * Data provider for testNormalize. - */ - public function normalizeProvider() { - return [ - 'single cardinality' => [ - ['user1'], - 'field_user', - [ - 'data' => ['type' => 'user--user', 'id' => static::$userIds[0]], - ], - ], - 'multiple cardinality' => [ - ['user1', 'user2'], 'field_users', [ - 'data' => [ - ['type' => 'user--user', 'id' => static::$userIds[0]], - ['type' => 'user--user', 'id' => static::$userIds[1]], - ], - ], - ], - 'multiple cardinality, all same values' => [ - ['user1', 'user1'], 'field_users', [ - 'data' => [ - [ - 'type' => 'user--user', - 'id' => static::$userIds[0], - 'meta' => ['arity' => 0], - ], - [ - 'type' => 'user--user', - 'id' => static::$userIds[0], - 'meta' => ['arity' => 1], - ], - ], - ], - ], - 'multiple cardinality, some same values' => [ - ['user1', 'user2', 'user1'], 'field_users', [ - 'data' => [ - [ - 'type' => 'user--user', - 'id' => static::$userIds[0], - 'meta' => ['arity' => 0], - ], - [ - 'type' => 'user--user', - 'id' => static::$userIds[1], - ], - [ - 'type' => 'user--user', - 'id' => static::$userIds[0], - 'meta' => ['arity' => 1], - ], - ], - ], - ], - 'single cardinality, with meta' => [ - ['image1'], 'field_image', [ - 'data' => [ - 'type' => 'file--file', - 'id' => static::$imageIds[0], - 'meta' => [ - 'alt' => 'Cute llama', - 'title' => 'My spirit animal', - 'width' => NULL, - 'height' => NULL, - ], - ], - ], - ], - 'multiple cardinality, all same values, with meta' => [ - ['image1', 'image1'], 'field_images', [ - 'data' => [ - [ - 'type' => 'file--file', - 'id' => static::$imageIds[0], - 'meta' => [ - 'alt' => 'Cute llama', - 'title' => 'My spirit animal', - 'width' => NULL, - 'height' => NULL, - 'arity' => 0, - ], - ], - [ - 'type' => 'file--file', - 'id' => static::$imageIds[0], - 'meta' => [ - 'alt' => 'Cute llama', - 'title' => 'My spirit animal', - 'width' => NULL, - 'height' => NULL, - 'arity' => 1, - ], - ], - ], - ], - ], - 'multiple cardinality, some same values with same values but different meta' => [ - ['image1', 'image1', 'image1a'], 'field_images', [ - 'data' => [ - [ - 'type' => 'file--file', - 'id' => static::$imageIds[0], - 'meta' => [ - 'alt' => 'Cute llama', - 'title' => 'My spirit animal', - 'width' => NULL, - 'height' => NULL, - 'arity' => 0, - ], - ], - [ - 'type' => 'file--file', - 'id' => static::$imageIds[0], - 'meta' => [ - 'alt' => 'Cute llama', - 'title' => 'My spirit animal', - 'width' => NULL, - 'height' => NULL, - 'arity' => 1, - ], - ], - [ - 'type' => 'file--file', - 'id' => static::$imageIds[0], - 'meta' => [ - 'alt' => 'Ugly llama', - 'title' => 'My alter ego', - 'width' => NULL, - 'height' => NULL, - 'arity' => 2, - ], - ], - ], - ], - ], - ]; - } - -} diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php index b1398d0cd..0d2f093f0 100644 --- a/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php @@ -31,7 +31,6 @@ /** * @coversDefaultClass \Drupal\jsonapi\Normalizer\JsonApiDocumentTopLevelNormalizer * @group jsonapi - * @group legacy * * @internal */ @@ -231,7 +230,7 @@ public function testNormalize() { 'field_image', ], 'user--user' => [ - 'name', + 'display_name', ], ], 'include' => [ @@ -255,8 +254,8 @@ public function testNormalize() { 'id' => NodeType::load('article')->uuid(), ], 'links' => [ - 'self' => ['href' => Url::fromUri('internal:/jsonapi/node/article/' . $this->node->uuid() . '/relationships/node_type', ['query' => ['resourceVersion' => 'id:' . $this->node->getRevisionId()]])->setAbsolute()->toString(TRUE)->getGeneratedUrl()], 'related' => ['href' => Url::fromUri('internal:/jsonapi/node/article/' . $this->node->uuid() . '/node_type', ['query' => ['resourceVersion' => 'id:' . $this->node->getRevisionId()]])->setAbsolute()->toString(TRUE)->getGeneratedUrl()], + 'self' => ['href' => Url::fromUri('internal:/jsonapi/node/article/' . $this->node->uuid() . '/relationships/node_type', ['query' => ['resourceVersion' => 'id:' . $this->node->getRevisionId()]])->setAbsolute()->toString(TRUE)->getGeneratedUrl()], ], ], $normalized['data']['relationships']['node_type']); $this->assertTrue(!isset($normalized['data']['attributes']['created'])); @@ -280,7 +279,7 @@ public function testNormalize() { $this->assertTrue(empty($normalized['meta']['omitted'])); $this->assertSame($this->user->uuid(), $normalized['included'][0]['id']); $this->assertSame('user--user', $normalized['included'][0]['type']); - $this->assertSame('user1', $normalized['included'][0]['attributes']['name']); + $this->assertSame('user1', $normalized['included'][0]['attributes']['display_name']); $this->assertCount(1, $normalized['included'][0]['attributes']); $this->assertSame($this->term1->uuid(), $normalized['included'][1]['id']); $this->assertSame('taxonomy_term--tags', $normalized['included'][1]['type']); diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/RelationshipNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/RelationshipNormalizerTest.php new file mode 100644 index 000000000..f77007177 --- /dev/null +++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/RelationshipNormalizerTest.php @@ -0,0 +1,326 @@ +installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installEntitySchema('file'); + + // Add the additional table schemas. + $this->installSchema('system', ['sequences']); + $this->installSchema('node', ['node_access']); + $this->installSchema('file', ['file_usage']); + NodeType::create([ + 'type' => 'referencer', + ])->save(); + $this->createEntityReferenceField('node', 'referencer', 'field_user', 'User', 'user', 'default', ['target_bundles' => NULL], 1); + $this->createEntityReferenceField('node', 'referencer', 'field_users', 'Users', 'user', 'default', ['target_bundles' => NULL], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + $field_storage_config = [ + 'type' => 'image', + 'entity_type' => 'node', + ]; + FieldStorageConfig::create(['field_name' => 'field_image', 'cardinality' => 1] + $field_storage_config)->save(); + FieldStorageConfig::create(['field_name' => 'field_images', 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED] + $field_storage_config)->save(); + $field_config = [ + 'entity_type' => 'node', + 'bundle' => 'referencer', + ]; + FieldConfig::create(['field_name' => 'field_image', 'label' => 'Image'] + $field_config)->save(); + FieldConfig::create(['field_name' => 'field_images', 'label' => 'Images'] + $field_config)->save(); + + // Set up the test data. + $this->account = $this->prophesize(AccountInterface::class)->reveal(); + $this->user1 = User::create([ + 'name' => $this->randomMachineName(), + 'mail' => $this->randomMachineName() . '@example.com', + 'uuid' => static::$userIds[0], + ]); + $this->user1->save(); + $this->user2 = User::create([ + 'name' => $this->randomMachineName(), + 'mail' => $this->randomMachineName() . '@example.com', + 'uuid' => static::$userIds[1], + ]); + $this->user2->save(); + + $this->image1 = File::create([ + 'uri' => 'public:/image1.png', + 'uuid' => static::$imageIds[0], + ]); + $this->image1->save(); + $this->image2 = File::create([ + 'uri' => 'public:/image2.png', + 'uuid' => static::$imageIds[1], + ]); + $this->image2->save(); + + // Create the node from which all the previously created entities will be + // referenced. + $this->referencer = Node::create([ + 'title' => 'Referencing node', + 'type' => 'referencer', + 'status' => 1, + 'uuid' => static::$referencerId, + ]); + $this->referencer->save(); + + // Set up the test dependencies. + $this->referencingResourceType = $this->container->get('jsonapi.resource_type.repository')->get('node', 'referencer'); + $this->normalizer = new RelationshipNormalizer(); + $this->normalizer->setSerializer($this->container->get('jsonapi.serializer')); + } + + /** + * @covers ::normalize + * @dataProvider normalizeProvider + */ + public function testNormalize($entity_property_names, $field_name, $expected) { + // Links cannot be generated in the test provider because the container + // has not yet been set. + $expected['links'] = [ + 'self' => ['href' => Url::fromUri('base:/jsonapi/node/referencer/' . static::$referencerId . "/relationships/$field_name", ['query' => ['resourceVersion' => 'id:1']])->setAbsolute()->toString()], + 'related' => ['href' => Url::fromUri('base:/jsonapi/node/referencer/' . static::$referencerId . "/$field_name", ['query' => ['resourceVersion' => 'id:1']])->setAbsolute()->toString()], + ]; + // Set up different field values. + $this->referencer->{$field_name} = array_map(function ($entity_property_name) { + $value = ['target_id' => $this->{$entity_property_name === 'image1a' ? 'image1' : $entity_property_name}->id()]; + switch ($entity_property_name) { + case 'image1': + $value['alt'] = 'Cute llama'; + $value['title'] = 'My spirit animal'; + break; + + case 'image1a': + $value['alt'] = 'Ugly llama'; + $value['title'] = 'My alter ego'; + break; + + case 'image2': + $value['alt'] = 'Adorable llama'; + $value['title'] = 'My spirit animal 😍'; + break; + } + return $value; + }, $entity_property_names); + $resource_object = ResourceObject::createFromEntity($this->referencingResourceType, $this->referencer); + $relationship = Relationship::createFromEntityReferenceField($resource_object, $resource_object->getField($field_name)); + // Normalize. + $actual = $this->normalizer->normalize($relationship, 'api_json'); + // Assert. + assert($actual instanceof CacheableNormalization); + $this->assertEquals($expected, $actual->getNormalization()); + } + + /** + * Data provider for testNormalize. + */ + public function normalizeProvider() { + return [ + 'single cardinality' => [ + ['user1'], + 'field_user', + [ + 'data' => ['type' => 'user--user', 'id' => static::$userIds[0]], + ], + ], + 'multiple cardinality' => [ + ['user1', 'user2'], 'field_users', [ + 'data' => [ + ['type' => 'user--user', 'id' => static::$userIds[0]], + ['type' => 'user--user', 'id' => static::$userIds[1]], + ], + ], + ], + 'multiple cardinality, all same values' => [ + ['user1', 'user1'], 'field_users', [ + 'data' => [ + [ + 'type' => 'user--user', + 'id' => static::$userIds[0], + 'meta' => ['arity' => 0], + ], + [ + 'type' => 'user--user', + 'id' => static::$userIds[0], + 'meta' => ['arity' => 1], + ], + ], + ], + ], + 'multiple cardinality, some same values' => [ + ['user1', 'user2', 'user1'], 'field_users', [ + 'data' => [ + [ + 'type' => 'user--user', + 'id' => static::$userIds[0], + 'meta' => ['arity' => 0], + ], + [ + 'type' => 'user--user', + 'id' => static::$userIds[1], + ], + [ + 'type' => 'user--user', + 'id' => static::$userIds[0], + 'meta' => ['arity' => 1], + ], + ], + ], + ], + 'single cardinality, with meta' => [ + ['image1'], 'field_image', [ + 'data' => [ + 'type' => 'file--file', + 'id' => static::$imageIds[0], + 'meta' => [ + 'alt' => 'Cute llama', + 'title' => 'My spirit animal', + 'width' => NULL, + 'height' => NULL, + ], + ], + ], + ], + 'multiple cardinality, all same values, with meta' => [ + ['image1', 'image1'], 'field_images', [ + 'data' => [ + [ + 'type' => 'file--file', + 'id' => static::$imageIds[0], + 'meta' => [ + 'alt' => 'Cute llama', + 'title' => 'My spirit animal', + 'width' => NULL, + 'height' => NULL, + 'arity' => 0, + ], + ], + [ + 'type' => 'file--file', + 'id' => static::$imageIds[0], + 'meta' => [ + 'alt' => 'Cute llama', + 'title' => 'My spirit animal', + 'width' => NULL, + 'height' => NULL, + 'arity' => 1, + ], + ], + ], + ], + ], + 'multiple cardinality, some same values with same values but different meta' => [ + ['image1', 'image1', 'image1a'], 'field_images', [ + 'data' => [ + [ + 'type' => 'file--file', + 'id' => static::$imageIds[0], + 'meta' => [ + 'alt' => 'Cute llama', + 'title' => 'My spirit animal', + 'width' => NULL, + 'height' => NULL, + 'arity' => 0, + ], + ], + [ + 'type' => 'file--file', + 'id' => static::$imageIds[0], + 'meta' => [ + 'alt' => 'Cute llama', + 'title' => 'My spirit animal', + 'width' => NULL, + 'height' => NULL, + 'arity' => 1, + ], + ], + [ + 'type' => 'file--file', + 'id' => static::$imageIds[0], + 'meta' => [ + 'alt' => 'Ugly llama', + 'title' => 'My alter ego', + 'width' => NULL, + 'height' => NULL, + 'arity' => 2, + ], + ], + ], + ], + ], + ]; + } + +} diff --git a/core/modules/jsonapi/tests/src/Kernel/Query/FilterTest.php b/core/modules/jsonapi/tests/src/Kernel/Query/FilterTest.php index 3c1e3820b..2a644615a 100644 --- a/core/modules/jsonapi/tests/src/Kernel/Query/FilterTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/Query/FilterTest.php @@ -17,7 +17,6 @@ * @coversDefaultClass \Drupal\jsonapi\Query\Filter * @group jsonapi * @group jsonapi_query - * @group legacy * * @internal */ @@ -47,6 +46,13 @@ class FilterTest extends JsonapiKernelTestBase { */ protected $nodeStorage; + /** + * The JSON:API resource type repository. + * + * @var \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface + */ + protected $resourceTypeRepository; + /** * {@inheritdoc} */ @@ -69,14 +75,16 @@ public function setUp() { $this->nodeStorage = $this->container->get('entity_type.manager')->getStorage('node'); $this->fieldResolver = $this->container->get('jsonapi.field_resolver'); + $this->resourceTypeRepository = $this->container->get('jsonapi.resource_type.repository'); } /** * @covers ::queryCondition */ public function testInvalidFilterPathDueToMissingPropertyName() { - $this->setExpectedException(CacheableBadRequestHttpException::class, 'Invalid nested filtering. The field `colors`, given in the path `colors` is incomplete, it must end with one of the following specifiers: `value`, `format`, `processed`.'); - $resource_type = new ResourceType('node', 'painting', NULL); + $this->expectException(CacheableBadRequestHttpException::class); + $this->expectExceptionMessage('Invalid nested filtering. The field `colors`, given in the path `colors` is incomplete, it must end with one of the following specifiers: `value`, `format`, `processed`.'); + $resource_type = $this->resourceTypeRepository->get('node', 'painting'); Filter::createFromQueryParameter(['colors' => ''], $resource_type, $this->fieldResolver); } @@ -84,8 +92,9 @@ public function testInvalidFilterPathDueToMissingPropertyName() { * @covers ::queryCondition */ public function testInvalidFilterPathDueToMissingPropertyNameReferenceFieldWithMetaProperties() { - $this->setExpectedException(CacheableBadRequestHttpException::class, 'Invalid nested filtering. The field `photo`, given in the path `photo` is incomplete, it must end with one of the following specifiers: `id`, `meta.alt`, `meta.title`, `meta.width`, `meta.height`.'); - $resource_type = new ResourceType('node', 'painting', NULL); + $this->expectException(CacheableBadRequestHttpException::class); + $this->expectExceptionMessage('Invalid nested filtering. The field `photo`, given in the path `photo` is incomplete, it must end with one of the following specifiers: `id`, `meta.alt`, `meta.title`, `meta.width`, `meta.height`.'); + $resource_type = $this->resourceTypeRepository->get('node', 'painting'); Filter::createFromQueryParameter(['photo' => ''], $resource_type, $this->fieldResolver); } @@ -93,8 +102,9 @@ public function testInvalidFilterPathDueToMissingPropertyNameReferenceFieldWithM * @covers ::queryCondition */ public function testInvalidFilterPathDueMissingMetaPrefixReferenceFieldWithMetaProperties() { - $this->setExpectedException(CacheableBadRequestHttpException::class, 'Invalid nested filtering. The property `alt`, given in the path `photo.alt` belongs to the meta object of a relationship and must be preceded by `meta`.'); - $resource_type = new ResourceType('node', 'painting', NULL); + $this->expectException(CacheableBadRequestHttpException::class); + $this->expectExceptionMessage('Invalid nested filtering. The property `alt`, given in the path `photo.alt` belongs to the meta object of a relationship and must be preceded by `meta`.'); + $resource_type = $this->resourceTypeRepository->get('node', 'painting'); Filter::createFromQueryParameter(['photo.alt' => ''], $resource_type, $this->fieldResolver); } @@ -102,8 +112,9 @@ public function testInvalidFilterPathDueMissingMetaPrefixReferenceFieldWithMetaP * @covers ::queryCondition */ public function testInvalidFilterPathDueToMissingPropertyNameReferenceFieldWithoutMetaProperties() { - $this->setExpectedException(CacheableBadRequestHttpException::class, 'Invalid nested filtering. The field `uid`, given in the path `uid` is incomplete, it must end with one of the following specifiers: `id`.'); - $resource_type = new ResourceType('node', 'painting', NULL); + $this->expectException(CacheableBadRequestHttpException::class); + $this->expectExceptionMessage('Invalid nested filtering. The field `uid`, given in the path `uid` is incomplete, it must end with one of the following specifiers: `id`.'); + $resource_type = $this->resourceTypeRepository->get('node', 'painting'); Filter::createFromQueryParameter(['uid' => ''], $resource_type, $this->fieldResolver); } @@ -111,8 +122,9 @@ public function testInvalidFilterPathDueToMissingPropertyNameReferenceFieldWitho * @covers ::queryCondition */ public function testInvalidFilterPathDueToNonexistentProperty() { - $this->setExpectedException(CacheableBadRequestHttpException::class, 'Invalid nested filtering. The property `foobar`, given in the path `colors.foobar`, does not exist. Must be one of the following property names: `value`, `format`, `processed`.'); - $resource_type = new ResourceType('node', 'painting', NULL); + $this->expectException(CacheableBadRequestHttpException::class); + $this->expectExceptionMessage('Invalid nested filtering. The property `foobar`, given in the path `colors.foobar`, does not exist. Must be one of the following property names: `value`, `format`, `processed`.'); + $resource_type = $this->resourceTypeRepository->get('node', 'painting'); Filter::createFromQueryParameter(['colors.foobar' => ''], $resource_type, $this->fieldResolver); } @@ -120,8 +132,9 @@ public function testInvalidFilterPathDueToNonexistentProperty() { * @covers ::queryCondition */ public function testInvalidFilterPathDueToElidedSoleProperty() { - $this->setExpectedException(CacheableBadRequestHttpException::class, 'Invalid nested filtering. The property `value`, given in the path `promote.value`, does not exist. Filter by `promote`, not `promote.value` (the JSON:API module elides property names from single-property fields).'); - $resource_type = new ResourceType('node', 'painting', NULL); + $this->expectException(CacheableBadRequestHttpException::class); + $this->expectExceptionMessage('Invalid nested filtering. The property `value`, given in the path `promote.value`, does not exist. Filter by `promote`, not `promote.value` (the JSON:API module elides property names from single-property fields).'); + $resource_type = $this->resourceTypeRepository->get('node', 'painting'); Filter::createFromQueryParameter(['promote.value' => ''], $resource_type, $this->fieldResolver); } @@ -150,7 +163,7 @@ public function testQueryCondition() { return (string) $p->getValue($entity_query); }; - $resource_type = new ResourceType('node', 'painting', NULL); + $resource_type = $this->resourceTypeRepository->get('node', 'painting'); foreach ($data as $case) { $parameter = $case[0]; $expected_query = $case[1]; @@ -400,7 +413,7 @@ public function testCreateFromQueryParameterNested() { */ protected function getFieldResolverMock(ResourceType $resource_type) { $field_resolver = $this->prophesize(FieldResolver::class); - $field_resolver->resolveInternalEntityQueryPath($resource_type->getEntityTypeId(), $resource_type->getBundle(), Argument::any())->willReturnArgument(2); + $field_resolver->resolveInternalEntityQueryPath($resource_type, Argument::any())->willReturnArgument(1); return $field_resolver->reveal(); } diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php index 34da1445a..79081e9a3 100644 --- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php @@ -2,28 +2,29 @@ namespace Drupal\Tests\jsonapi\Kernel\ResourceType; +use Drupal\Core\Cache\Cache; use Drupal\jsonapi\ResourceType\ResourceType; -use Drupal\KernelTests\KernelTestBase; use Drupal\node\Entity\NodeType; +use Drupal\Tests\jsonapi\Kernel\JsonapiKernelTestBase; /** * @coversDefaultClass \Drupal\jsonapi\ResourceType\ResourceTypeRepository * @group jsonapi - * @group legacy * * @internal */ -class ResourceTypeRepositoryTest extends KernelTestBase { +class ResourceTypeRepositoryTest extends JsonapiKernelTestBase { /** * {@inheritdoc} */ public static $modules = [ + 'field', 'node', - 'jsonapi', 'serialization', 'system', 'user', + 'jsonapi_test_resource_type_building', ]; /** @@ -51,6 +52,9 @@ protected function setUp() { NodeType::create([ 'type' => 'page', ])->save(); + NodeType::create([ + 'type' => '42', + ])->save(); $this->resourceTypeRepository = $this->container->get('jsonapi.resource_type.repository'); } @@ -92,9 +96,115 @@ public function testGet($entity_type_id, $bundle, $entity_class) { public function getProvider() { return [ ['node', 'article', 'Drupal\node\Entity\Node'], + ['node', '42', 'Drupal\node\Entity\Node'], ['node_type', 'node_type', 'Drupal\node\Entity\NodeType'], ['menu', 'menu', 'Drupal\system\Entity\Menu'], ]; } + /** + * Ensures that the ResourceTypeRepository's cache does not become stale. + */ + public function testCaching() { + $this->assertEmpty($this->resourceTypeRepository->get('node', 'article')->getRelatableResourceTypesByField('field_relationship')); + $this->createEntityReferenceField('node', 'article', 'field_relationship', 'Related entity', 'node'); + $this->assertCount(3, $this->resourceTypeRepository->get('node', 'article')->getRelatableResourceTypesByField('field_relationship')); + NodeType::create(['type' => 'camelids'])->save(); + $this->assertCount(4, $this->resourceTypeRepository->get('node', 'article')->getRelatableResourceTypesByField('field_relationship')); + } + + /** + * Ensures that a naming conflict in the mapping causes an exception to be + * thrown. + * + * @covers ::getFieldMapping + * @dataProvider getFieldMappingProvider + */ + public function testMappingNameConflictCheck($field_name_list) { + $entity_type = \Drupal::entityTypeManager()->getDefinition('node'); + $bundle = 'article'; + $reflection_class = new \ReflectionClass($this->resourceTypeRepository); + $reflection_method = $reflection_class->getMethod('getFields'); + $reflection_method->setAccessible(TRUE); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage("The generated alias '{$field_name_list[1]}' for field name '{$field_name_list[0]}' conflicts with an existing field. Please report this in the JSON:API issue queue!"); + $reflection_method->invokeArgs($this->resourceTypeRepository, [$field_name_list, $entity_type, $bundle]); + } + + /** + * Data provider for testGetFieldMapping. + * + * These field name lists are designed to trigger a naming conflict in the + * mapping: the special-cased names "type" or "id", and the name + * "{$entity_type_id}_type" or "{$entity_type_id}_id", respectively. + * + * @returns array + * The data for the test method. + */ + public function getFieldMappingProvider() { + return [ + [['type', 'node_type']], + [['id', 'node_id']], + ]; + } + + /** + * Tests that resource types can be disabled by a build subscriber. + */ + public function testResourceTypeDisabling() { + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--article')->isInternal()); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--page')->isInternal()); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('user--user')->isInternal()); + $disabled_resource_types = [ + 'node--page', + 'user--user', + ]; + \Drupal::state()->set('jsonapi_test_resource_type_builder.disabled_resource_types', $disabled_resource_types); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--article')->isInternal()); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--page')->isInternal()); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('user--user')->isInternal()); + } + + /** + * Tests that resource type fields can be aliased per resource type. + */ + public function testResourceTypeFieldAliasing() { + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--article')->getPublicName('uid'), 'uid'); + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--page')->getPublicName('uid'), 'uid'); + $resource_type_field_aliases = [ + 'node--article' => [ + 'uid' => 'author', + ], + 'node--page' => [ + 'uid' => 'owner', + ], + ]; + \Drupal::state()->set('jsonapi_test_resource_type_builder.resource_type_field_aliases', $resource_type_field_aliases); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--article')->getPublicName('uid'), 'author'); + $this->assertSame($this->resourceTypeRepository->getByTypeName('node--page')->getPublicName('uid'), 'owner'); + } + + /** + * Tests that resource type fields can be disabled per resource type. + */ + public function testResourceTypeFieldDisabling() { + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--article')->isFieldEnabled('uid')); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--page')->isFieldEnabled('uid')); + $disabled_resource_type_fields = [ + 'node--article' => [ + 'uid' => TRUE, + ], + 'node--page' => [ + 'uid' => FALSE, + ], + ]; + \Drupal::state()->set('jsonapi_test_resource_type_builder.disabled_resource_type_fields', $disabled_resource_type_fields); + Cache::invalidateTags(['jsonapi_resource_types']); + $this->assertFalse($this->resourceTypeRepository->getByTypeName('node--article')->isFieldEnabled('uid')); + $this->assertTrue($this->resourceTypeRepository->getByTypeName('node--page')->isFieldEnabled('uid')); + } + } diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeTest.php new file mode 100644 index 000000000..12618d75f --- /dev/null +++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeTest.php @@ -0,0 +1,46 @@ + 'author', + 'body' => FALSE, + ]; + $resource_type = new ResourceType('node', 'article', Node::class, FALSE, TRUE, TRUE, FALSE, $deprecated_field_mapping); + $this->assertSame('author', $resource_type->getFieldByInternalName('uid')->getPublicName()); + $this->assertFalse($resource_type->getFieldByInternalName('body')->isFieldEnabled()); + } + +} diff --git a/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php b/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php index e432e8ff9..774b51ec5 100644 --- a/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php @@ -134,7 +134,8 @@ public function testOldRevision() { * @covers \Drupal\jsonapi\Revisions\VersionById::getRevision */ public function testInvalidRevisionId() { - $this->setExpectedException(CacheableNotFoundHttpException::class, sprintf('The requested version, identified by `id:%s`, could not be found.', $this->node2->getRevisionId())); + $this->expectException(CacheableNotFoundHttpException::class); + $this->expectExceptionMessage(sprintf('The requested version, identified by `id:%s`, could not be found.', $this->node2->getRevisionId())); $this->versionNegotiator->getRevision($this->node, 'id:' . $this->node2->getRevisionId()); } @@ -161,7 +162,8 @@ public function testCurrentVersion() { * @covers \Drupal\jsonapi\Revisions\VersionByRel::getRevision */ public function testInvalidRevisionRel() { - $this->setExpectedException(CacheableBadRequestHttpException::class, 'An invalid resource version identifier, `rel:erroneous-revision-name`, was provided.'); + $this->expectException(CacheableBadRequestHttpException::class); + $this->expectExceptionMessage('An invalid resource version identifier, `rel:erroneous-revision-name`, was provided.'); $this->versionNegotiator->getRevision($this->node, 'rel:erroneous-revision-name'); } diff --git a/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php b/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php index 97f0c06be..e0cd524b9 100644 --- a/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php +++ b/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\jsonapi\Kernel\Serializer; use Drupal\Core\Render\Markup; +use Drupal\jsonapi\JsonApiResource\ResourceObject; use Drupal\jsonapi\Normalizer\Value\CacheableNormalization; use Drupal\jsonapi_test_data_type\TraversableObject; use Drupal\node\Entity\Node; @@ -41,6 +42,13 @@ class SerializerTest extends JsonapiKernelTestBase { */ protected $node; + /** + * A resource type for testing. + * + * @var \Drupal\jsonapi\ResourceType\ResourceType + */ + protected $resourceType; + /** * The subject under test. * @@ -80,6 +88,7 @@ protected function setUp() { ]); $this->node->save(); $this->container->setAlias('sut', 'jsonapi.serializer'); + $this->resourceType = $this->container->get('jsonapi.resource_type.repository')->get($this->node->getEntityTypeId(), $this->node->bundle()); $this->sut = $this->container->get('sut'); } @@ -87,7 +96,10 @@ protected function setUp() { * @covers \Drupal\jsonapi\Serializer\Serializer::normalize */ public function testFallbackNormalizer() { - $context = ['account' => $this->user]; + $context = [ + 'account' => $this->user, + 'resource_object' => ResourceObject::createFromEntity($this->resourceType, $this->node), + ]; $value = $this->sut->normalize($this->node->field_text, 'api_json', $context); $this->assertTrue($value instanceof CacheableNormalization); diff --git a/core/modules/jsonapi/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php b/core/modules/jsonapi/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php index 7e79c187f..1746400ef 100644 --- a/core/modules/jsonapi/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php +++ b/core/modules/jsonapi/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\jsonapi\Unit\EventSubscriber; -use Drupal\jsonapi\Encoder\JsonEncoder; use Drupal\jsonapi\EventSubscriber\ResourceResponseValidator; use Drupal\jsonapi\ResourceType\ResourceType; use Drupal\jsonapi\Routing\Routes; @@ -15,7 +14,6 @@ use Psr\Log\LoggerInterface; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Serializer\Serializer; /** * @coversDefaultClass \Drupal\jsonapi\EventSubscriber\ResourceResponseValidator @@ -47,12 +45,7 @@ public function setUp() { $module_path = dirname(dirname(dirname(dirname(__DIR__)))); $module->getPath()->willReturn($module_path); $module_handler->getModule('jsonapi')->willReturn($module->reveal()); - $encoders = [new JsonEncoder()]; - if (class_exists(JsonSchemaEncoder::class)) { - $encoders[] = new JsonSchemaEncoder(); - } $subscriber = new ResourceResponseValidator( - new Serializer([], $encoders), $this->prophesize(LoggerInterface::class)->reveal(), $module_handler->reveal(), '' diff --git a/core/modules/jsonapi/tests/src/Unit/JsonApiResource/LinkTest.php b/core/modules/jsonapi/tests/src/Unit/JsonApiResource/LinkTest.php new file mode 100644 index 000000000..bd09d83ba --- /dev/null +++ b/core/modules/jsonapi/tests/src/Unit/JsonApiResource/LinkTest.php @@ -0,0 +1,163 @@ +assertSame($expected, $actual === 0); + } + + /** + * Provides test data for link comparison. + */ + public function linkComparisonProvider() { + $this->mockUrlAssembler(); + return [ + 'same href and same link relation type' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self'), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self'), + TRUE, + ], + 'different href and same link relation type' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self'), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/bar'), 'self'), + FALSE, + ], + 'same href and different link relation type' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self'), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'related'), + FALSE, + ], + 'same href and same link relation type and empty target attributes' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', []), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', []), + TRUE, + ], + 'same href and same link relation type and same target attributes' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', ['anchor' => 'https://jsonapi.org']), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', ['anchor' => 'https://jsonapi.org']), + TRUE, + ], + // These links are not considered equivalent because it would while the + // `href` remains the same, the anchor changes the context of the link. + 'same href and same link relation type and different target attributes' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/boy'), 'self', ['title' => 'sue']), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/boy'), 'self', ['anchor' => '/sob', 'title' => 'pa']), + FALSE, + ], + 'same href and same link relation type and same nested target attributes' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', ['data' => ['foo' => 'bar']]), + new Link(new cacheablemetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', ['data' => ['foo' => 'bar']]), + TRUE, + ], + 'same href and same link relation type and different nested target attributes' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', ['data' => ['foo' => 'bar']]), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/foo'), 'self', ['data' => ['foo' => 'baz']]), + FALSE, + ], + // These links are not considered equivalent because it would be unclear + // which title corresponds to which link relation type. + 'same href and different link relation types and different target attributes' => [ + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/boy'), 'self', ['title' => 'A boy named Sue']), + new Link(new CacheableMetadata(), Url::fromUri('https://jsonapi.org/boy'), 'edit', ['title' => 'Change name to Bill or George']), + FALSE, + ], + ]; + } + + /** + * @covers ::merge + * @dataProvider linkMergeProvider + */ + public function testLinkMerge(Link $a, Link $b, $expected) { + if ($expected instanceof Link) { + $this->assertSame($expected->getCacheTags(), Link::merge($a, $b)->getCacheTags()); + } + else { + $this->expectExceptionObject($expected); + Link::merge($a, $b); + } + } + + /** + * Provides test data for link merging. + */ + public function linkMergeProvider() { + $this->mockUrlAssembler(); + return [ + 'same everything' => [ + new Link((new CacheableMetadata())->addCacheTags(['foo']), Url::fromUri('https://jsonapi.org/foo'), 'self'), + new Link((new CacheableMetadata())->addCacheTags(['foo']), Url::fromUri('https://jsonapi.org/foo'), 'self'), + new Link((new CacheableMetadata())->addCacheTags(['foo']), Url::fromUri('https://jsonapi.org/foo'), 'self'), + ], + 'different cache tags' => [ + new Link((new CacheableMetadata())->addCacheTags(['foo']), Url::fromUri('https://jsonapi.org/foo'), 'self'), + new Link((new CacheableMetadata())->addCacheTags(['bar']), Url::fromUri('https://jsonapi.org/foo'), 'self'), + new Link((new CacheableMetadata())->addCacheTags(['foo', 'bar']), Url::fromUri('https://jsonapi.org/foo'), 'self'), + ], + ]; + } + + /** + * @covers ::getLinkRelationType + */ + public function testGetLinkRelationType() { + $this->mockUrlAssembler(); + $link = new Link((new CacheableMetadata())->addCacheTags(['foo']), Url::fromUri('https://jsonapi.org/foo'), 'self'); + $this->assertSame('self', $link->getLinkRelationType()); + } + + /** + * @group legacy + * @expectedDeprecation Constructing a Drupal\jsonapi\JsonApiResource\Link with an array of link relation types is deprecated in drupal:8.8.0 and will throw a fatal error in drupal:9.0.0. Pass a single string instead. See https://www.drupal.org/node/3087821. + * @expectedDeprecation Drupal\jsonapi\JsonApiResource\Link::getLinkRelationTypes() is deprecated in drupal:8.8.0 and will be removed in drupal:9.0.0. Use getLinkRelationType() instead. See https://www.drupal.org/node/3087821. + * @covers ::__construct + * @covers ::getLinkRelationTypes + */ + public function testLinkDeprecations() { + $this->mockUrlAssembler(); + $link = new Link((new CacheableMetadata())->addCacheTags(['foo']), Url::fromUri('https://jsonapi.org/foo'), ['self', 'foo']); + $this->assertSame(['self', 'foo'], $link->getLinkRelationTypes()); + $this->assertSame('self', $link->getLinkRelationType()); + + $link = new Link((new CacheableMetadata())->addCacheTags(['foo']), Url::fromUri('https://jsonapi.org/foo'), 'self'); + $this->assertSame(['self'], $link->getLinkRelationTypes()); + } + + /** + * Mocks the unrouted URL assembler. + */ + protected function mockUrlAssembler() { + $url_assembler = $this->getMockBuilder(UnroutedUrlAssemblerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $url_assembler->method('assemble')->will($this->returnCallback(function ($uri) { + return (new GeneratedUrl())->setGeneratedUrl($uri); + })); + + $container = new ContainerBuilder(); + $container->set('unrouted_url_assembler', $url_assembler); + \Drupal::setContainer($container); + } + +} diff --git a/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php b/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php index b85f58e10..1e4352259 100644 --- a/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php @@ -36,8 +36,8 @@ public function testNormalize() { $error = $normalized[0]; $this->assertNotEmpty($error['meta']); $this->assertNotEmpty($error['source']); - $this->assertEquals(13, $error['code']); - $this->assertEquals(403, $error['status']); + $this->assertSame('13', $error['code']); + $this->assertSame('403', $error['status']); $this->assertEquals('Forbidden', $error['title']); $this->assertEquals('lorem', $error['detail']); $this->assertArrayHasKey('trace', $error['meta']); diff --git a/core/modules/jsonapi/tests/src/Unit/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php b/core/modules/jsonapi/tests/src/Unit/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php index a30b1f6a1..2cef29f9f 100644 --- a/core/modules/jsonapi/tests/src/Unit/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php @@ -216,10 +216,8 @@ public function testDenormalizeUuid($id, $expect_exception) { ['type' => 'node--article']; if ($expect_exception) { - $this->setExpectedException( - UnprocessableEntityHttpException::class, - 'IDs should be properly generated and formatted UUIDs as described in RFC 4122.' - ); + $this->expectException(UnprocessableEntityHttpException::class); + $this->expectExceptionMessage('IDs should be properly generated and formatted UUIDs as described in RFC 4122.'); } $denormalized = $this->normalizer->denormalize($data, NULL, 'api_json', [ diff --git a/core/modules/jsonapi/tests/src/Unit/Normalizer/ResourceIdentifierNormalizerTest.php b/core/modules/jsonapi/tests/src/Unit/Normalizer/ResourceIdentifierNormalizerTest.php index a2af02e82..8df76d655 100644 --- a/core/modules/jsonapi/tests/src/Unit/Normalizer/ResourceIdentifierNormalizerTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Normalizer/ResourceIdentifierNormalizerTest.php @@ -13,6 +13,7 @@ use Drupal\jsonapi\JsonApiResource\ResourceIdentifier; use Drupal\jsonapi\Normalizer\ResourceIdentifierNormalizer; use Drupal\jsonapi\ResourceType\ResourceType; +use Drupal\jsonapi\ResourceType\ResourceTypeRelationship; use Drupal\jsonapi\ResourceType\ResourceTypeRepository; use Drupal\Tests\UnitTestCase; use Prophecy\Argument; @@ -45,7 +46,11 @@ class ResourceIdentifierNormalizerTest extends UnitTestCase { */ public function setUp() { $target_resource_type = new ResourceType('lorem', 'dummy_bundle', NULL); - $this->resourceType = new ResourceType('fake_entity_type', 'dummy_bundle', NULL); + $relationship_fields = [ + 'field_dummy' => new ResourceTypeRelationship('field_dummy'), + 'field_dummy_single' => new ResourceTypeRelationship('field_dummy_single'), + ]; + $this->resourceType = new ResourceType('fake_entity_type', 'dummy_bundle', NULL, FALSE, TRUE, TRUE, FALSE, $relationship_fields); $this->resourceType->setRelatableResourceTypes([ 'field_dummy' => [$target_resource_type], 'field_dummy_single' => [$target_resource_type], @@ -149,7 +154,7 @@ public function testDenormalizeInvalidResource($data, $field_name) { 'related' => $field_name, 'target_entity' => $this->prophesize(FieldableEntityInterface::class)->reveal(), ]; - $this->setExpectedException(BadRequestHttpException::class); + $this->expectException(BadRequestHttpException::class); $this->normalizer->denormalize($data, NULL, 'api_json', $context); } diff --git a/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionGroupTest.php b/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionGroupTest.php index 67c4d8c20..cc0c41639 100644 --- a/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionGroupTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionGroupTest.php @@ -8,7 +8,6 @@ /** * @coversDefaultClass \Drupal\jsonapi\Query\EntityConditionGroup * @group jsonapi - * @group legacy * * @internal */ @@ -33,7 +32,7 @@ public function testConstruct($case) { * @covers ::__construct */ public function testConstructException() { - $this->setExpectedException(\InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); new EntityConditionGroup('NOT_ALLOWED', []); } diff --git a/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionTest.php b/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionTest.php index c8b8b02be..1659b6f80 100644 --- a/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Query/EntityConditionTest.php @@ -12,7 +12,6 @@ /** * @coversDefaultClass \Drupal\jsonapi\Query\EntityCondition * @group jsonapi - * @group legacy * * @internal */ @@ -76,7 +75,8 @@ public function queryParameterProvider() { */ public function testValidation($input, $exception) { if ($exception) { - $this->setExpectedException(get_class($exception), $exception->getMessage()); + $this->expectException(get_class($exception)); + $this->expectExceptionMessage($exception->getMessage()); } EntityCondition::createFromQueryParameter($input); $this->assertTrue(is_null($exception), 'No exception was expected.'); diff --git a/core/modules/jsonapi/tests/src/Unit/Query/OffsetPageTest.php b/core/modules/jsonapi/tests/src/Unit/Query/OffsetPageTest.php index 5e9287e7f..a845f4904 100644 --- a/core/modules/jsonapi/tests/src/Unit/Query/OffsetPageTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Query/OffsetPageTest.php @@ -12,7 +12,6 @@ /** * @coversDefaultClass \Drupal\jsonapi\Query\OffsetPage * @group jsonapi - * @group legacy * * @internal */ @@ -59,7 +58,7 @@ public function parameterProvider() { * @covers ::createFromQueryParameter */ public function testCreateFromQueryParameterFail() { - $this->setExpectedException(BadRequestHttpException::class); + $this->expectException(BadRequestHttpException::class); OffsetPage::createFromQueryParameter('lorem'); } diff --git a/core/modules/jsonapi/tests/src/Unit/Query/SortTest.php b/core/modules/jsonapi/tests/src/Unit/Query/SortTest.php index c97a562c1..cb2be5153 100644 --- a/core/modules/jsonapi/tests/src/Unit/Query/SortTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Query/SortTest.php @@ -12,7 +12,6 @@ /** * @coversDefaultClass \Drupal\jsonapi\Query\Sort * @group jsonapi - * @group legacy * * @internal */ @@ -85,7 +84,7 @@ public function parameterProvider() { * @dataProvider badParameterProvider */ public function testCreateFromQueryParameterFail($input) { - $this->setExpectedException(BadRequestHttpException::class); + $this->expectException(BadRequestHttpException::class); Sort::createFromQueryParameter($input); } diff --git a/core/modules/jsonapi/tests/src/Unit/Routing/RoutesTest.php b/core/modules/jsonapi/tests/src/Unit/Routing/RoutesTest.php index e444a0bd3..5ec52a6a9 100644 --- a/core/modules/jsonapi/tests/src/Unit/Routing/RoutesTest.php +++ b/core/modules/jsonapi/tests/src/Unit/Routing/RoutesTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\jsonapi\ResourceType\ResourceType; +use Drupal\jsonapi\ResourceType\ResourceTypeRelationship; use Drupal\jsonapi\ResourceType\ResourceTypeRepository; use Drupal\jsonapi\Routing\Routes; use Drupal\Tests\UnitTestCase; @@ -30,8 +31,13 @@ class RoutesTest extends UnitTestCase { */ protected function setUp() { parent::setUp(); - $type_1 = new ResourceType('entity_type_1', 'bundle_1_1', EntityInterface::class); - $type_2 = new ResourceType('entity_type_2', 'bundle_2_1', EntityInterface::class, TRUE); + $relationship_fields = [ + 'external' => new ResourceTypeRelationship('external'), + 'internal' => new ResourceTypeRelationship('internal'), + 'both' => new ResourceTypeRelationship('both'), + ]; + $type_1 = new ResourceType('entity_type_1', 'bundle_1_1', EntityInterface::class, FALSE, TRUE, TRUE, FALSE, $relationship_fields); + $type_2 = new ResourceType('entity_type_2', 'bundle_2_1', EntityInterface::class, TRUE, TRUE, TRUE, FALSE, $relationship_fields); $relatable_resource_types = [ 'external' => [$type_1], 'internal' => [$type_2], diff --git a/core/modules/language/config/optional/tour.tour.language-add.yml b/core/modules/language/config/optional/tour.tour.language-add.yml index 9f428b46d..5d5c37309 100644 --- a/core/modules/language/config/optional/tour.tour.language-add.yml +++ b/core/modules/language/config/optional/tour.tour.language-add.yml @@ -1,6 +1,8 @@ langcode: en status: true -dependencies: { } +dependencies: + module: + - language id: language-add label: 'Adding languages' module: language diff --git a/core/modules/language/config/optional/tour.tour.language-edit.yml b/core/modules/language/config/optional/tour.tour.language-edit.yml index 454fef1b9..8e63cdb8f 100644 --- a/core/modules/language/config/optional/tour.tour.language-edit.yml +++ b/core/modules/language/config/optional/tour.tour.language-edit.yml @@ -1,6 +1,8 @@ langcode: en status: true -dependencies: { } +dependencies: + module: + - language id: language-edit label: 'Editing languages' module: language diff --git a/core/modules/language/config/optional/tour.tour.language.yml b/core/modules/language/config/optional/tour.tour.language.yml index 7ab79d303..2d310cbf4 100644 --- a/core/modules/language/config/optional/tour.tour.language.yml +++ b/core/modules/language/config/optional/tour.tour.language.yml @@ -1,6 +1,8 @@ langcode: en status: true -dependencies: { } +dependencies: + module: + - language id: language label: Language module: language diff --git a/core/modules/language/language.info.yml b/core/modules/language/language.info.yml index fa5b7a2bb..30770401a 100644 --- a/core/modules/language/language.info.yml +++ b/core/modules/language/language.info.yml @@ -2,12 +2,6 @@ name: Language type: module description: 'Allows users to configure languages and apply them to content.' package: Multilingual -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: entity.configurable_language.collection - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 0df467dc1..2bbbedb80 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -270,12 +270,13 @@ function language_get_default_langcode($entity_type, $bundle) { /** * Reads language prefixes and uses the langcode if no prefix is set. * - * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. - * Use \Drupal::config('language.negotiation')->get('url.prefixes') instead. + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use + * \Drupal::config('language.negotiation')->get('url.prefixes') instead. * * @see https://www.drupal.org/node/2912748 */ function language_negotiation_url_prefixes() { + @trigger_error("language_negotiation_url_prefixes() is deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use \Drupal::config('language.negotiation')->get('url.prefixes') instead. See https://www.drupal.org/node/2912748", E_USER_DEPRECATED); return \Drupal::config('language.negotiation')->get('url.prefixes'); } @@ -301,12 +302,13 @@ function language_negotiation_url_prefixes_update() { /** * Reads language domains. * - * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. - * Use \Drupal::config('language.negotiation')->get('url.domains') instead. + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use + * \Drupal::config('language.negotiation')->get('url.domains') instead. * * @see https://www.drupal.org/node/2912748 */ function language_negotiation_url_domains() { + @trigger_error("language_negotiation_url_domains() is deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use \Drupal::config('language.negotiation')->get('url.domains') instead. See https://www.drupal.org/node/2912748", E_USER_DEPRECATED); return \Drupal::config('language.negotiation')->get('url.domains'); } @@ -330,7 +332,7 @@ function language_modules_installed($modules) { // this is not a hard dependency, and thus is not detected by the config // system, we have to clean up the related values manually. foreach (['entity_view_display', 'entity_form_display'] as $key) { - $displays = \Drupal::entityManager()->getStorage($key)->loadMultiple(); + $displays = \Drupal::entityTypeManager()->getStorage($key)->loadMultiple(); /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */ foreach ($displays as $display) { $display->save(); diff --git a/core/modules/language/language.routing.yml b/core/modules/language/language.routing.yml index 84740ee06..ffe4b5be4 100644 --- a/core/modules/language/language.routing.yml +++ b/core/modules/language/language.routing.yml @@ -1,7 +1,7 @@ language.negotiation_url: path: '/admin/config/regional/language/detection/url' defaults: - _form: 'Drupal\language\Form\NegotiationUrlForm' + _form: '\Drupal\language\Form\NegotiationUrlForm' _title: 'URL language detection configuration' requirements: _permission: 'administer languages' @@ -9,7 +9,7 @@ language.negotiation_url: language.negotiation_session: path: '/admin/config/regional/language/detection/session' defaults: - _form: 'Drupal\language\Form\NegotiationSessionForm' + _form: '\Drupal\language\Form\NegotiationSessionForm' _title: 'Session language detection configuration' requirements: _permission: 'administer languages' @@ -17,7 +17,7 @@ language.negotiation_session: language.negotiation_selected: path: '/admin/config/regional/language/detection/selected' defaults: - _form: 'Drupal\language\Form\NegotiationSelectedForm' + _form: '\Drupal\language\Form\NegotiationSelectedForm' _title: 'Selected language configuration' requirements: _permission: 'administer languages' @@ -82,6 +82,6 @@ language.content_settings_page: path: '/admin/config/regional/content-language' defaults: _title: 'Content language' - _form: 'Drupal\language\Form\ContentLanguageSettingsForm' + _form: '\Drupal\language\Form\ContentLanguageSettingsForm' requirements: _permission: 'administer languages' diff --git a/core/modules/language/migrations/d6_language_negotiation_settings.yml b/core/modules/language/migrations/d6_language_negotiation_settings.yml index a4ae19b87..e6bf27424 100644 --- a/core/modules/language/migrations/d6_language_negotiation_settings.yml +++ b/core/modules/language/migrations/d6_language_negotiation_settings.yml @@ -7,7 +7,7 @@ source: plugin: variable variables: - language_negotiation - source_module: language + source_module: locale process: session/parameter: plugin: default_value diff --git a/core/modules/language/migrations/d6_language_types.yml b/core/modules/language/migrations/d6_language_types.yml index 5c54fc6fb..47d62caf6 100644 --- a/core/modules/language/migrations/d6_language_types.yml +++ b/core/modules/language/migrations/d6_language_types.yml @@ -7,7 +7,7 @@ source: plugin: variable variables: - language_negotiation - source_module: language + source_module: locale process: all: plugin: default_value diff --git a/core/modules/language/migrations/d7_language_content_taxonomy_vocabulary_settings.yml b/core/modules/language/migrations/d7_language_content_taxonomy_vocabulary_settings.yml new file mode 100644 index 000000000..e170c71e2 --- /dev/null +++ b/core/modules/language/migrations/d7_language_content_taxonomy_vocabulary_settings.yml @@ -0,0 +1,54 @@ +id: d7_language_content_taxonomy_vocabulary_settings +label: Drupal 7 language taxonomy vocabulary settings +migration_tags: + - Drupal 7 + - Configuration +source: + plugin: d7_language_content_settings_taxonomy_vocabulary + constants: + target_type: 'taxonomy_term' + default_langcode: 'site_default' +process: + target_bundle: + - + plugin: migration_lookup + migration: d7_taxonomy_vocabulary + source: vid + - + plugin: skip_on_empty + method: row + # State is the value in the i18n_mode column of taxonomy_vocabulary table + # 0: No multilingual options. + # 1: Localize. Localizable object. Run through the localization system + # 2: Fixed Language. Predefined language for this object and all related ones + # 4: Translate. Multilingual objects, translatable but not localizable. + # 5: Objects are translatable (if they have language or localizable if not) + # Note: the Drupal 6 Per-language value (3) changed to 4 in Drupal 7. + language_alterable: + plugin: static_map + source: i18n_mode + map: + 0: false + 1: true + 2: false + 4: true + 5: true + 'third_party_settings/content_translation/enabled': + plugin: static_map + source: i18n_mode + map: + 0: false + 1: true + 2: false + 4: false + 5: true + target_entity_type_id: 'constants/target_type' + default_langcode: + plugin: default_value + default_value: site_default + source: language +destination: + plugin: entity:language_content_settings +migration_dependencies: + required: + - d7_taxonomy_vocabulary diff --git a/core/modules/language/migrations/d7_language_types.yml b/core/modules/language/migrations/d7_language_types.yml index 017f2e2d6..33df3cf0d 100644 --- a/core/modules/language/migrations/d7_language_types.yml +++ b/core/modules/language/migrations/d7_language_types.yml @@ -13,7 +13,7 @@ source: - locale_language_providers_weight_language - locale_language_providers_weight_language_content - locale_language_providers_weight_language_url - source_module: language + source_module: locale process: all: plugin: language_types diff --git a/core/modules/language/migrations/state/language.migrate_drupal.yml b/core/modules/language/migrations/state/language.migrate_drupal.yml new file mode 100644 index 000000000..3babdd9f0 --- /dev/null +++ b/core/modules/language/migrations/state/language.migrate_drupal.yml @@ -0,0 +1,13 @@ +finished: + 6: + i18n_taxonomy: language + locale: + - language + - system + system: language + taxonomy: language + 7: + i18n_taxonomy: language + locale: + - language + - system diff --git a/core/modules/language/src/Config/LanguageConfigFactoryOverrideInterface.php b/core/modules/language/src/Config/LanguageConfigFactoryOverrideInterface.php index 56dfc4796..5a58bee9d 100644 --- a/core/modules/language/src/Config/LanguageConfigFactoryOverrideInterface.php +++ b/core/modules/language/src/Config/LanguageConfigFactoryOverrideInterface.php @@ -37,7 +37,7 @@ public function setLanguage(LanguageInterface $language = NULL); * * @return $this * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This * method has been replaced by injecting the default language into the * constructor. */ diff --git a/core/modules/language/src/Entity/ContentLanguageSettings.php b/core/modules/language/src/Entity/ContentLanguageSettings.php index d89b1508f..509dbaf3c 100644 --- a/core/modules/language/src/Entity/ContentLanguageSettings.php +++ b/core/modules/language/src/Entity/ContentLanguageSettings.php @@ -86,8 +86,6 @@ class ContentLanguageSettings extends ConfigEntityBase implements ContentLanguag * - target_bundle: The bundle. * Other array elements will be used to set the corresponding properties on * the class; see the class property documentation for details. - * - * @see entity_create() */ public function __construct(array $values, $entity_type = 'language_content_settings') { if (empty($values['target_entity_type_id'])) { @@ -192,7 +190,7 @@ public static function loadByEntityTypeBundle($entity_type_id, $bundle) { if ($entity_type_id == NULL || $bundle == NULL) { return NULL; } - $config = \Drupal::entityManager()->getStorage('language_content_settings')->load($entity_type_id . '.' . $bundle); + $config = \Drupal::entityTypeManager()->getStorage('language_content_settings')->load($entity_type_id . '.' . $bundle); if ($config == NULL) { $config = ContentLanguageSettings::create(['target_entity_type_id' => $entity_type_id, 'target_bundle' => $bundle]); } @@ -206,7 +204,7 @@ public function calculateDependencies() { parent::calculateDependencies(); // Create dependency on the bundle. - $entity_type = \Drupal::entityManager()->getDefinition($this->target_entity_type_id); + $entity_type = \Drupal::entityTypeManager()->getDefinition($this->target_entity_type_id); $bundle_config_dependency = $entity_type->getBundleConfigDependency($this->target_bundle); $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']); diff --git a/core/modules/language/src/Form/LanguageAddForm.php b/core/modules/language/src/Form/LanguageAddForm.php index 54faca914..8e0b9b396 100644 --- a/core/modules/language/src/Form/LanguageAddForm.php +++ b/core/modules/language/src/Form/LanguageAddForm.php @@ -5,6 +5,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageManager; +use Drupal\Core\Url; use Drupal\language\Entity\ConfigurableLanguage; /** @@ -93,7 +94,7 @@ public function save(array $form, FormStateInterface $form_state) { if ($this->moduleHandler->moduleExists('block')) { // Tell the user they have the option to add a language switcher block // to their theme so they can switch between the languages. - $this->messenger()->addStatus($this->t('Use one of the language switcher blocks to allow site visitors to switch between languages. You can enable these blocks on the block administration page.', [':block-admin' => $this->url('block.admin_display')])); + $this->messenger()->addStatus($this->t('Use one of the language switcher blocks to allow site visitors to switch between languages. You can enable these blocks on the block administration page.', [':block-admin' => Url::fromRoute('block.admin_display')->toString()])); } $form_state->setRedirectUrl($this->entity->toUrl('collection')); } diff --git a/core/modules/language/src/Form/NegotiationConfigureForm.php b/core/modules/language/src/Form/NegotiationConfigureForm.php index 0b22bc70a..0679a563d 100644 --- a/core/modules/language/src/Form/NegotiationConfigureForm.php +++ b/core/modules/language/src/Form/NegotiationConfigureForm.php @@ -93,8 +93,8 @@ public function __construct(ConfigFactoryInterface $config_factory, Configurable * {@inheritdoc} */ public static function create(ContainerInterface $container) { - $entity_manager = $container->get('entity.manager'); - $block_storage = $entity_manager->hasHandler('block', 'storage') ? $entity_manager->getStorage('block') : NULL; + $entity_type_manager = $container->get('entity_type.manager'); + $block_storage = $entity_type_manager->hasHandler('block', 'storage') ? $entity_type_manager->getStorage('block') : NULL; return new static( $container->get('config.factory'), $container->get('language_manager'), diff --git a/core/modules/language/src/Form/NegotiationUrlForm.php b/core/modules/language/src/Form/NegotiationUrlForm.php index 89ba53609..707b5ebe9 100644 --- a/core/modules/language/src/Form/NegotiationUrlForm.php +++ b/core/modules/language/src/Form/NegotiationUrlForm.php @@ -7,6 +7,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl; @@ -151,7 +152,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { // Throw a form error if the prefix is blank for a non-default language, // although it is required for selected negotiation type. $form_state->setErrorByName("prefix][$langcode", $this->t('The prefix may only be left blank for the selected detection fallback language.', [ - ':url' => $this->getUrlGenerator()->generate('language.negotiation_selected'), + ':url' => Url::fromRoute('language.negotiation_selected')->toString(), ])); } } diff --git a/core/modules/language/src/LanguageAccessControlHandler.php b/core/modules/language/src/LanguageAccessControlHandler.php index 0a4fb9892..b80cfd3b0 100644 --- a/core/modules/language/src/LanguageAccessControlHandler.php +++ b/core/modules/language/src/LanguageAccessControlHandler.php @@ -10,7 +10,7 @@ /** * Defines the access control handler for the language entity type. * - * @see \Drupal\language\Entity\Language + * @see \Drupal\language\Entity\ConfigurableLanguage */ class LanguageAccessControlHandler extends EntityAccessControlHandler { diff --git a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php index 9218899b1..d02844479 100644 --- a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php +++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php @@ -2,8 +2,9 @@ namespace Drupal\language\Plugin\LanguageNegotiation; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Render\BubbleableMetadata; @@ -27,6 +28,12 @@ * ) */ class LanguageNegotiationContentEntity extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface, ContainerFactoryPluginInterface { + use DeprecatedServicePropertyTrait; + + /** + * {@inheritdoc} + */ + protected $deprecatedProperties = ['entityManager' => 'entity.manager']; /** * The language negotiation method ID. @@ -62,20 +69,20 @@ class LanguageNegotiationContentEntity extends LanguageNegotiationMethodBase imp protected $paths; /** - * The entity manager. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * Constructs a new LanguageNegotiationContentEntity instance. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. */ - public function __construct(EntityManagerInterface $entity_manager) { - $this->entityManager = $entity_manager; + public function __construct(EntityTypeManagerInterface $entity_type_manager) { + $this->entityTypeManager = $entity_type_manager; $this->paths = new \SplObjectStorage(); } @@ -83,7 +90,7 @@ public function __construct(EntityManagerInterface $entity_manager) { * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static($container->get('entity.manager')); + return new static($container->get('entity_type.manager')); } /** @@ -261,7 +268,7 @@ protected function getContentEntityTypeIdForCurrentRequest(Request $request) { protected function getContentEntityPaths() { if (!isset($this->contentEntityPaths)) { $this->contentEntityPaths = []; - $entity_types = $this->entityManager->getDefinitions(); + $entity_types = $this->entityTypeManager->getDefinitions(); foreach ($entity_types as $entity_type_id => $entity_type) { if ($entity_type->entityClassImplements(ContentEntityInterface::class)) { $entity_paths = array_fill_keys($entity_type->getLinkTemplates(), $entity_type_id); diff --git a/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettingsTaxonomyVocabulary.php b/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettingsTaxonomyVocabulary.php new file mode 100644 index 000000000..eb2995062 --- /dev/null +++ b/core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettingsTaxonomyVocabulary.php @@ -0,0 +1,41 @@ +getDatabase() + ->schema() + ->fieldExists('taxonomy_vocabulary', 'i18n_mode')) { + $query->addField('v', 'language'); + $query->addField('v', 'i18n_mode'); + } + return $query; + } + + /** + * {@inheritdoc} + */ + public function fields() { + $fields = parent::fields(); + $fields['language'] = $this->t('i18n language'); + $fields['i18n_mode'] = $this->t('i18n mode'); + return $fields; + } + +} diff --git a/core/modules/language/tests/language_config_override_test/language_config_override_test.info.yml b/core/modules/language/tests/language_config_override_test/language_config_override_test.info.yml index 95caba29c..4345db5a4 100644 --- a/core/modules/language/tests/language_config_override_test/language_config_override_test.info.yml +++ b/core/modules/language/tests/language_config_override_test/language_config_override_test.info.yml @@ -1,12 +1,6 @@ name: 'Language config overridetest' type: module description: 'Support module for the language config override test.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/language/tests/language_elements_test/language_elements_test.info.yml b/core/modules/language/tests/language_elements_test/language_elements_test.info.yml index c015407cc..35ed3e03c 100644 --- a/core/modules/language/tests/language_elements_test/language_elements_test.info.yml +++ b/core/modules/language/tests/language_elements_test/language_elements_test.info.yml @@ -1,14 +1,8 @@ name: 'Language form elements test' type: module description: 'Support module for the language form elements tests.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION +version: VERSION dependencies: - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/language/tests/language_entity_field_access_test/language_entity_field_access_test.info.yml b/core/modules/language/tests/language_entity_field_access_test/language_entity_field_access_test.info.yml index 1c5d000e1..295a65b4f 100644 --- a/core/modules/language/tests/language_entity_field_access_test/language_entity_field_access_test.info.yml +++ b/core/modules/language/tests/language_entity_field_access_test/language_entity_field_access_test.info.yml @@ -1,18 +1,12 @@ name: 'Language entity field access test' type: module description: 'Support module for verifying entity field access and the language selector.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION +version: VERSION dependencies: - drupal:node - drupal:text - drupal:field - drupal:filter - drupal:language - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/language/tests/language_test/config/optional/views.view.no_entity_translation_view.yml b/core/modules/language/tests/language_test/config/optional/views.view.no_entity_translation_view.yml index 8e7ef4502..3876dc995 100644 --- a/core/modules/language/tests/language_test/config/optional/views.view.no_entity_translation_view.yml +++ b/core/modules/language/tests/language_test/config/optional/views.view.no_entity_translation_view.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: no_language_entity_test base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/language/tests/language_test/language_test.info.yml b/core/modules/language/tests/language_test/language_test.info.yml index 253e01e0d..03051cd8c 100644 --- a/core/modules/language/tests/language_test/language_test.info.yml +++ b/core/modules/language/tests/language_test/language_test.info.yml @@ -1,12 +1,6 @@ name: 'Language test' type: module description: 'Support module for the language layer tests.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/language/tests/src/Functional/AdminPathEntityConverterLanguageTest.php b/core/modules/language/tests/src/Functional/AdminPathEntityConverterLanguageTest.php index f689a9233..a9ea8cc8c 100644 --- a/core/modules/language/tests/src/Functional/AdminPathEntityConverterLanguageTest.php +++ b/core/modules/language/tests/src/Functional/AdminPathEntityConverterLanguageTest.php @@ -14,6 +14,11 @@ class AdminPathEntityConverterLanguageTest extends BrowserTestBase { public static $modules = ['language', 'language_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $permissions = [ diff --git a/core/modules/language/tests/src/Functional/ConfigurableLanguageManagerTest.php b/core/modules/language/tests/src/Functional/ConfigurableLanguageManagerTest.php index d36aee0ac..49d9c0823 100644 --- a/core/modules/language/tests/src/Functional/ConfigurableLanguageManagerTest.php +++ b/core/modules/language/tests/src/Functional/ConfigurableLanguageManagerTest.php @@ -3,6 +3,9 @@ namespace Drupal\Tests\language\Functional; use Drupal\Core\Cache\Cache; +use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\language\Entity\ContentLanguageSettings; use Drupal\node\Entity\Node; @@ -31,6 +34,11 @@ class ConfigurableLanguageManagerTest extends BrowserTestBase { 'user', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -186,4 +194,76 @@ public function testUrlContentTranslationWithSessionLanguage() { $assert_session->pageTextContains('Powered by'); } + /** + * Tests translation of the user profile edit form. + * + * The user profile edit form is a special case when used with the preferred + * admin language negotiator because of the recursive way that the negotiator + * is called. + */ + public function testUserProfileTranslationWithPreferredAdminLanguage() { + $assert_session = $this->assertSession(); + // Set the interface language to use the preferred administration language. + /** @var \Drupal\language\LanguageNegotiatorInterface $language_negotiator */ + $language_negotiator = \Drupal::getContainer()->get('language_negotiator'); + $language_negotiator->saveConfiguration('language_interface', [ + 'language-user-admin' => 1, + 'language-selected' => 2, + ]); + + // Create a field on the user entity. + $field_name = mb_strtolower($this->randomMachineName()); + $label = mb_strtolower($this->randomMachineName()); + $field_label_en = "English $label"; + $field_label_es = "Español $label"; + + $field_storage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'user', + 'type' => 'string', + ]); + $field_storage->save(); + + $instance = FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'user', + 'label' => $field_label_en, + ]); + $instance->save(); + + // Add a Spanish translation. + \Drupal::languageManager() + ->getLanguageConfigOverride('es', "field.field.user.user.$field_name") + ->set('label', $field_label_es) + ->save(); + + // Add the new field to the edit form. + EntityFormDisplay::create([ + 'targetEntityType' => 'user', + 'bundle' => 'user', + 'mode' => 'default', + 'status' => TRUE, + ]) + ->setComponent($field_name, [ + 'type' => 'string_textfield', + ]) + ->save(); + + $user_id = \Drupal::currentUser()->id(); + $this->drupalGet("/user/$user_id/edit"); + // Admin language choice is "No preference" so we should get the default. + $assert_session->pageTextContains($field_label_en); + $assert_session->pageTextNotContains($field_label_es); + + // Set admin language to Spanish. + $this->drupalPostForm(NULL, ['edit-preferred-admin-langcode' => 'es'], 'edit-submit'); + $assert_session->pageTextContains($field_label_es); + $assert_session->pageTextNotContains($field_label_en); + + // Set admin language to English. + $this->drupalPostForm(NULL, ['edit-preferred-admin-langcode' => 'en'], 'edit-submit'); + $assert_session->pageTextContains($field_label_en); + $assert_session->pageTextNotContains($field_label_es); + } + } diff --git a/core/modules/language/tests/src/Functional/EntityTypeWithoutLanguageFormTest.php b/core/modules/language/tests/src/Functional/EntityTypeWithoutLanguageFormTest.php index f7b619148..b5d85afd2 100644 --- a/core/modules/language/tests/src/Functional/EntityTypeWithoutLanguageFormTest.php +++ b/core/modules/language/tests/src/Functional/EntityTypeWithoutLanguageFormTest.php @@ -24,6 +24,11 @@ class EntityTypeWithoutLanguageFormTest extends BrowserTestBase { 'language_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonAnonTest.php b/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonAnonTest.php index c548c1724..f2759d93f 100644 --- a/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonAnonTest.php +++ b/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonAnonTest.php @@ -17,6 +17,11 @@ class ConfigurableLanguageHalJsonAnonTest extends ConfigurableLanguageResourceTe */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonBasicAuthTest.php b/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonBasicAuthTest.php index 4647acb82..f0803aa55 100644 --- a/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonBasicAuthTest.php +++ b/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class ConfigurableLanguageHalJsonBasicAuthTest extends ConfigurableLanguageResou */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonCookieTest.php b/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonCookieTest.php index 9ad756106..68fe2bf45 100644 --- a/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonCookieTest.php +++ b/core/modules/language/tests/src/Functional/Hal/ConfigurableLanguageHalJsonCookieTest.php @@ -17,6 +17,11 @@ class ConfigurableLanguageHalJsonCookieTest extends ConfigurableLanguageResource */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonAnonTest.php b/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonAnonTest.php index d5b7947ed..88bf96eb1 100644 --- a/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonAnonTest.php +++ b/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonAnonTest.php @@ -17,6 +17,11 @@ class ContentLanguageSettingsHalJsonAnonTest extends ContentLanguageSettingsReso */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonBasicAuthTest.php b/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonBasicAuthTest.php index fefd0db73..ec6f172a5 100644 --- a/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonBasicAuthTest.php +++ b/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class ContentLanguageSettingsHalJsonBasicAuthTest extends ContentLanguageSetting */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonCookieTest.php b/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonCookieTest.php index 63584f648..e9ed29cf3 100644 --- a/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonCookieTest.php +++ b/core/modules/language/tests/src/Functional/Hal/ContentLanguageSettingsHalJsonCookieTest.php @@ -17,6 +17,11 @@ class ContentLanguageSettingsHalJsonCookieTest extends ContentLanguageSettingsRe */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/LanguageBlockSettingsVisibilityTest.php b/core/modules/language/tests/src/Functional/LanguageBlockSettingsVisibilityTest.php index 003c5a63f..1511bf1f1 100644 --- a/core/modules/language/tests/src/Functional/LanguageBlockSettingsVisibilityTest.php +++ b/core/modules/language/tests/src/Functional/LanguageBlockSettingsVisibilityTest.php @@ -13,6 +13,11 @@ class LanguageBlockSettingsVisibilityTest extends BrowserTestBase { public static $modules = ['block', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + public function testUnnecessaryLanguageSettingsVisibility() { $admin_user = $this->drupalCreateUser(['administer languages', 'access administration pages', 'administer blocks']); $this->drupalLogin($admin_user); diff --git a/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php b/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php index e15fcf7a6..964da0d04 100644 --- a/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php +++ b/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php @@ -19,6 +19,11 @@ class LanguageBreadcrumbTest extends BrowserTestBase { */ public static $modules = ['language', 'block', 'filter']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/LanguageBrowserDetectionAcceptLanguageTest.php b/core/modules/language/tests/src/Functional/LanguageBrowserDetectionAcceptLanguageTest.php index 1d1ea9c59..b2ac07895 100644 --- a/core/modules/language/tests/src/Functional/LanguageBrowserDetectionAcceptLanguageTest.php +++ b/core/modules/language/tests/src/Functional/LanguageBrowserDetectionAcceptLanguageTest.php @@ -19,6 +19,11 @@ class LanguageBrowserDetectionAcceptLanguageTest extends BrowserTestBase { */ public static $modules = ['language', 'locale', 'content_translation', 'system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -74,32 +79,32 @@ public function testAcceptLanguageEmptyDefault() { $this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'en']); $this->assertSession()->responseHeaderEquals('Content-Language', 'en'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache')); // Check with UK browser. $this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'en-UK,en']); $this->assertSession()->responseHeaderEquals('Content-Language', 'en'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache')); // Check with french browser. $this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'fr-FR,fr']); $this->assertSession()->responseHeaderEquals('Content-Language', 'fr'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache')); // Check with browser without language settings - should return fallback language. $this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => NULL]); $this->assertSession()->responseHeaderEquals('Content-Language', 'en'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache')); // Check with french browser again. $this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'fr-FR,fr']); $this->assertSession()->responseHeaderEquals('Content-Language', 'fr'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache')); // Check with UK browser. $this->drupalGet('/system-test/echo/language test', [], ['Accept-Language' => 'en-UK,en']); $this->assertSession()->responseHeaderEquals('Content-Language', 'en'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache')); // Check if prefixed URLs are still cached. $this->drupalGet('/en/system-test/echo/language test', [], ['Accept-Language' => 'en']); diff --git a/core/modules/language/tests/src/Functional/LanguageBrowserDetectionTest.php b/core/modules/language/tests/src/Functional/LanguageBrowserDetectionTest.php index b0b383bdf..6c76a0211 100644 --- a/core/modules/language/tests/src/Functional/LanguageBrowserDetectionTest.php +++ b/core/modules/language/tests/src/Functional/LanguageBrowserDetectionTest.php @@ -14,6 +14,11 @@ class LanguageBrowserDetectionTest extends BrowserTestBase { public static $modules = ['language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests for adding, editing and deleting mappings between browser language * codes and Drupal language codes. diff --git a/core/modules/language/tests/src/Functional/LanguageConfigOverrideImportTest.php b/core/modules/language/tests/src/Functional/LanguageConfigOverrideImportTest.php index 2ddd4963e..03f3df28b 100644 --- a/core/modules/language/tests/src/Functional/LanguageConfigOverrideImportTest.php +++ b/core/modules/language/tests/src/Functional/LanguageConfigOverrideImportTest.php @@ -19,6 +19,11 @@ class LanguageConfigOverrideImportTest extends BrowserTestBase { */ public static $modules = ['language', 'config', 'locale', 'config_translation']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that language can be enabled and overrides are created during a sync. */ diff --git a/core/modules/language/tests/src/Functional/LanguageConfigSchemaTest.php b/core/modules/language/tests/src/Functional/LanguageConfigSchemaTest.php index e19fe53da..b2e384161 100644 --- a/core/modules/language/tests/src/Functional/LanguageConfigSchemaTest.php +++ b/core/modules/language/tests/src/Functional/LanguageConfigSchemaTest.php @@ -21,6 +21,11 @@ class LanguageConfigSchemaTest extends BrowserTestBase { */ public static $modules = ['language', 'menu_link_content']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with administrative permissions. * diff --git a/core/modules/language/tests/src/Functional/LanguageConfigurationElementTest.php b/core/modules/language/tests/src/Functional/LanguageConfigurationElementTest.php index 0f3dd1f0b..386f6d93e 100644 --- a/core/modules/language/tests/src/Functional/LanguageConfigurationElementTest.php +++ b/core/modules/language/tests/src/Functional/LanguageConfigurationElementTest.php @@ -22,6 +22,11 @@ class LanguageConfigurationElementTest extends BrowserTestBase { */ public static $modules = ['taxonomy', 'node', 'language', 'language_elements_test', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $user = $this->drupalCreateUser(['access administration pages', 'administer languages', 'administer content types']); @@ -131,7 +136,7 @@ public function testDefaultLangcode() { // Check the default value of a language field when authors preferred option // is selected. - // Create first an user and assign a preferred langcode to him. + // Create first an user and assign a preferred langcode. $some_user = $this->drupalCreateUser(); $some_user->preferred_langcode = 'bb'; $some_user->save(); @@ -203,16 +208,16 @@ public function testNodeTypeDelete() { $this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type')); // Check the language default configuration for articles is present. - $configuration = \Drupal::entityManager()->getStorage('language_content_settings')->load('node.article'); - $this->assertTrue($configuration, 'The language configuration is present.'); + $configuration = \Drupal::entityTypeManager()->getStorage('language_content_settings')->load('node.article'); + $this->assertNotEmpty($configuration, 'The language configuration is present.'); // Delete 'article' bundle. $this->drupalPostForm('admin/structure/types/manage/article/delete', [], t('Delete')); // Check that the language configuration has been deleted. - \Drupal::entityManager()->getStorage('language_content_settings')->resetCache(); - $configuration = \Drupal::entityManager()->getStorage('language_content_settings')->load('node.article'); - $this->assertFalse($configuration, 'The language configuration was deleted after bundle was deleted.'); + \Drupal::entityTypeManager()->getStorage('language_content_settings')->resetCache(); + $configuration = \Drupal::entityTypeManager()->getStorage('language_content_settings')->load('node.article'); + $this->assertNull($configuration, 'The language configuration was deleted after bundle was deleted.'); } /** diff --git a/core/modules/language/tests/src/Functional/LanguageConfigurationTest.php b/core/modules/language/tests/src/Functional/LanguageConfigurationTest.php index 639b4d958..06f4cee3b 100644 --- a/core/modules/language/tests/src/Functional/LanguageConfigurationTest.php +++ b/core/modules/language/tests/src/Functional/LanguageConfigurationTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\language\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; use Drupal\Core\Language\LanguageInterface; use Drupal\language\Entity\ConfigurableLanguage; @@ -21,6 +22,11 @@ class LanguageConfigurationTest extends BrowserTestBase { */ public static $modules = ['language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Functional tests for adding, editing and deleting languages. */ @@ -189,7 +195,7 @@ protected function checkConfigurableLanguageWeight($state = 'by default') { $replacements = ['@event' => $state]; foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $locked_language) { $replacements['%language'] = $locked_language->getName(); - $this->assertTrue($locked_language->getWeight() > $max_configurable_language_weight, format_string('System language %language has higher weight than configurable languages @event', $replacements)); + $this->assertTrue($locked_language->getWeight() > $max_configurable_language_weight, new FormattableMarkup('System language %language has higher weight than configurable languages @event', $replacements)); } } diff --git a/core/modules/language/tests/src/Functional/LanguageCustomLanguageConfigurationTest.php b/core/modules/language/tests/src/Functional/LanguageCustomLanguageConfigurationTest.php index ab3148dac..52bbde117 100644 --- a/core/modules/language/tests/src/Functional/LanguageCustomLanguageConfigurationTest.php +++ b/core/modules/language/tests/src/Functional/LanguageCustomLanguageConfigurationTest.php @@ -21,6 +21,11 @@ class LanguageCustomLanguageConfigurationTest extends BrowserTestBase { */ public static $modules = ['language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Functional tests for adding, editing and deleting languages. */ diff --git a/core/modules/language/tests/src/Functional/LanguageEntityFieldAccessHookTest.php b/core/modules/language/tests/src/Functional/LanguageEntityFieldAccessHookTest.php index 19b321775..fac20719d 100644 --- a/core/modules/language/tests/src/Functional/LanguageEntityFieldAccessHookTest.php +++ b/core/modules/language/tests/src/Functional/LanguageEntityFieldAccessHookTest.php @@ -25,6 +25,11 @@ class LanguageEntityFieldAccessHookTest extends BrowserTestBase { 'language_entity_field_access_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests compatibility with hook_entity_field_access(). */ diff --git a/core/modules/language/tests/src/Functional/LanguageListModuleInstallTest.php b/core/modules/language/tests/src/Functional/LanguageListModuleInstallTest.php index 013d00d8d..e63c31726 100644 --- a/core/modules/language/tests/src/Functional/LanguageListModuleInstallTest.php +++ b/core/modules/language/tests/src/Functional/LanguageListModuleInstallTest.php @@ -19,6 +19,11 @@ class LanguageListModuleInstallTest extends BrowserTestBase { */ public static $modules = ['language_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests enabling Language. */ diff --git a/core/modules/language/tests/src/Functional/LanguageListTest.php b/core/modules/language/tests/src/Functional/LanguageListTest.php index 565ae586b..45ec251ba 100644 --- a/core/modules/language/tests/src/Functional/LanguageListTest.php +++ b/core/modules/language/tests/src/Functional/LanguageListTest.php @@ -22,6 +22,11 @@ class LanguageListTest extends BrowserTestBase { */ public static $modules = ['language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Functional tests for adding, editing and deleting languages. */ diff --git a/core/modules/language/tests/src/Functional/LanguageLocaleListTest.php b/core/modules/language/tests/src/Functional/LanguageLocaleListTest.php index 844a8af13..2809d3298 100644 --- a/core/modules/language/tests/src/Functional/LanguageLocaleListTest.php +++ b/core/modules/language/tests/src/Functional/LanguageLocaleListTest.php @@ -19,6 +19,11 @@ class LanguageLocaleListTest extends BrowserTestBase { */ public static $modules = ['language', 'locale']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php b/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php index b158078e7..66ea59ac1 100644 --- a/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php +++ b/core/modules/language/tests/src/Functional/LanguageNegotiationContentEntityTest.php @@ -27,6 +27,11 @@ class LanguageNegotiationContentEntityTest extends BrowserTestBase { */ public static $modules = ['language', 'language_test', 'entity_test', 'system']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The entity being used for testing. * diff --git a/core/modules/language/tests/src/Functional/LanguageNegotiationInfoTest.php b/core/modules/language/tests/src/Functional/LanguageNegotiationInfoTest.php index c5e3c2c7b..bab82fd8f 100644 --- a/core/modules/language/tests/src/Functional/LanguageNegotiationInfoTest.php +++ b/core/modules/language/tests/src/Functional/LanguageNegotiationInfoTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\language\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Language\LanguageInterface; use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI; use Drupal\Tests\BrowserTestBase; @@ -20,6 +21,11 @@ class LanguageNegotiationInfoTest extends BrowserTestBase { */ public static $modules = ['language', 'content_translation']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -112,10 +118,10 @@ public function testInfoAlterations() { foreach ($this->languageManager()->getLanguageTypes() as $type) { $form_field = $type . '[enabled][test_language_negotiation_method_ts]'; if ($type == $test_type) { - $this->assertFieldByName($form_field, NULL, format_string('Type-specific test language negotiation method available for %type.', ['%type' => $type])); + $this->assertFieldByName($form_field, NULL, new FormattableMarkup('Type-specific test language negotiation method available for %type.', ['%type' => $type])); } else { - $this->assertNoFieldByName($form_field, NULL, format_string('Type-specific test language negotiation method unavailable for %type.', ['%type' => $type])); + $this->assertNoFieldByName($form_field, NULL, new FormattableMarkup('Type-specific test language negotiation method unavailable for %type.', ['%type' => $type])); } } @@ -125,7 +131,7 @@ public function testInfoAlterations() { foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) { $langcode = $last[$type]; $value = $type == LanguageInterface::TYPE_CONTENT || strpos($type, 'test') !== FALSE ? 'it' : 'en'; - $this->assertEqual($langcode, $value, format_string('The negotiated language for %type is %language', ['%type' => $type, '%language' => $value])); + $this->assertEqual($langcode, $value, new FormattableMarkup('The negotiated language for %type is %language', ['%type' => $type, '%language' => $value])); } // Uninstall language_test and check that everything is set back to the @@ -135,7 +141,7 @@ public function testInfoAlterations() { // Check that only the core language types are available. foreach ($this->languageManager()->getDefinedLanguageTypes() as $type) { - $this->assertTrue(strpos($type, 'test') === FALSE, format_string('The %type language is still available', ['%type' => $type])); + $this->assertTrue(strpos($type, 'test') === FALSE, new FormattableMarkup('The %type language is still available', ['%type' => $type])); } // Check that fixed language types are properly configured, even those @@ -161,7 +167,7 @@ protected function checkFixedLanguageTypes() { if (!in_array($type, $configurable) && isset($info['fixed'])) { $negotiation = $this->config('language.types')->get('negotiation.' . $type . '.enabled'); $equal = array_keys($negotiation) === array_values($info['fixed']); - $this->assertTrue($equal, format_string('language negotiation for %type is properly set up', ['%type' => $type])); + $this->assertTrue($equal, new FormattableMarkup('language negotiation for %type is properly set up', ['%type' => $type])); } } } diff --git a/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php b/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php index 07ae85d43..673d08aeb 100644 --- a/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php +++ b/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php @@ -22,6 +22,11 @@ class LanguageNegotiationUrlTest extends BrowserTestBase { 'path', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * @var \Drupal\user\Entity\User */ diff --git a/core/modules/language/tests/src/Functional/LanguagePathMonolingualTest.php b/core/modules/language/tests/src/Functional/LanguagePathMonolingualTest.php index 17ff3f936..5c4bcfa01 100644 --- a/core/modules/language/tests/src/Functional/LanguagePathMonolingualTest.php +++ b/core/modules/language/tests/src/Functional/LanguagePathMonolingualTest.php @@ -18,6 +18,11 @@ class LanguagePathMonolingualTest extends BrowserTestBase { */ public static $modules = ['block', 'language', 'path']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/language/tests/src/Functional/LanguageSelectorTranslatableTest.php b/core/modules/language/tests/src/Functional/LanguageSelectorTranslatableTest.php index 9526d226f..676951085 100644 --- a/core/modules/language/tests/src/Functional/LanguageSelectorTranslatableTest.php +++ b/core/modules/language/tests/src/Functional/LanguageSelectorTranslatableTest.php @@ -26,6 +26,11 @@ class LanguageSelectorTranslatableTest extends BrowserTestBase { 'locale', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The user with administrator privileges. * diff --git a/core/modules/language/tests/src/Functional/LanguageSwitchingTest.php b/core/modules/language/tests/src/Functional/LanguageSwitchingTest.php index 1303082c1..6e0fa3b2f 100644 --- a/core/modules/language/tests/src/Functional/LanguageSwitchingTest.php +++ b/core/modules/language/tests/src/Functional/LanguageSwitchingTest.php @@ -22,6 +22,11 @@ class LanguageSwitchingTest extends BrowserTestBase { */ public static $modules = ['locale', 'locale_test', 'language', 'block', 'language_test', 'menu_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected function setUp() { parent::setUp(); @@ -64,7 +69,7 @@ public function testLanguageBlock() { * @param string $block_label * The label of the language switching block. * - * @see testLanguageBlock() + * @see self::testLanguageBlock() */ protected function doTestLanguageBlockAuthenticated($block_label) { // Assert that the language switching block is displayed on the frontpage. @@ -115,7 +120,7 @@ protected function doTestLanguageBlockAuthenticated($block_label) { * @param string $block_label * The label of the language switching block. * - * @see testLanguageBlock() + * @see self::testLanguageBlock() */ protected function doTestLanguageBlockAnonymous($block_label) { $this->drupalLogout(); @@ -287,7 +292,7 @@ public function testLanguageBodyClass() { /** * For authenticated users, the "active" class is set by JavaScript. * - * @see testLanguageLinkActiveClass() + * @see self::testLanguageLinkActiveClass() */ protected function doTestLanguageLinkActiveClassAuthenticated() { $function_name = '#type link'; @@ -347,7 +352,7 @@ protected function doTestLanguageLinkActiveClassAuthenticated() { /** * For anonymous users, the "active" class is set by PHP. * - * @see testLanguageLinkActiveClass() + * @see self::testLanguageLinkActiveClass() */ protected function doTestLanguageLinkActiveClassAnonymous() { $function_name = '#type link'; diff --git a/core/modules/language/tests/src/Functional/LanguageTourTest.php b/core/modules/language/tests/src/Functional/LanguageTourTest.php index 13b29f2ef..9bae57720 100644 --- a/core/modules/language/tests/src/Functional/LanguageTourTest.php +++ b/core/modules/language/tests/src/Functional/LanguageTourTest.php @@ -25,6 +25,11 @@ class LanguageTourTest extends TourTestBase { */ public static $modules = ['block', 'language', 'tour']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php b/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php index 23f2ec5c2..239a56142 100644 --- a/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/tests/src/Functional/LanguageUILanguageNegotiationTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\language\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Cache\Cache; use Drupal\Core\Url; use Drupal\file\Entity\File; @@ -59,6 +60,11 @@ class LanguageUILanguageNegotiationTest extends BrowserTestBase { */ public static $modules = ['locale', 'language_test', 'block', 'user', 'content_translation']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected function setUp() { parent::setUp(); @@ -513,19 +519,19 @@ public function testLanguageDomain() { $italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString(); $url_scheme = \Drupal::request()->isSecure() ? 'https://' : 'http://'; $correct_link = $url_scheme . $link; - $this->assertEqual($italian_url, $correct_link, format_string('The right URL (@url) in accordance with the chosen language', ['@url' => $italian_url])); + $this->assertEqual($italian_url, $correct_link, new FormattableMarkup('The right URL (@url) in accordance with the chosen language', ['@url' => $italian_url])); // Test HTTPS via options. $italian_url = Url::fromRoute('system.admin', [], ['https' => TRUE, 'language' => $languages['it']])->toString(); $correct_link = 'https://' . $link; - $this->assertTrue($italian_url == $correct_link, format_string('The right HTTPS URL (via options) (@url) in accordance with the chosen language', ['@url' => $italian_url])); + $this->assertTrue($italian_url == $correct_link, new FormattableMarkup('The right HTTPS URL (via options) (@url) in accordance with the chosen language', ['@url' => $italian_url])); // Test HTTPS via current URL scheme. $request = Request::create('', 'GET', [], [], [], ['HTTPS' => 'on']); $this->container->get('request_stack')->push($request); $italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString(); $correct_link = 'https://' . $link; - $this->assertTrue($italian_url == $correct_link, format_string('The right URL (via current URL scheme) (@url) in accordance with the chosen language', ['@url' => $italian_url])); + $this->assertTrue($italian_url == $correct_link, new FormattableMarkup('The right URL (via current URL scheme) (@url) in accordance with the chosen language', ['@url' => $italian_url])); } /** @@ -561,7 +567,7 @@ public function testDisableLanguageSwitcher() { // Check if the language switcher block has been created. $block = Block::load($block_id); - $this->assertTrue($block, 'Language switcher block was created.'); + $this->assertNotEmpty($block, 'Language switcher block was created.'); // Make sure language_content is not configurable. $edit = [ @@ -572,7 +578,7 @@ public function testDisableLanguageSwitcher() { // Check if the language switcher block has been removed. $block = Block::load($block_id); - $this->assertFalse($block, 'Language switcher block was removed.'); + $this->assertNull($block, 'Language switcher block was removed.'); } } diff --git a/core/modules/language/tests/src/Functional/LanguageUrlRewritingTest.php b/core/modules/language/tests/src/Functional/LanguageUrlRewritingTest.php index 7956b3df3..49007619d 100644 --- a/core/modules/language/tests/src/Functional/LanguageUrlRewritingTest.php +++ b/core/modules/language/tests/src/Functional/LanguageUrlRewritingTest.php @@ -23,6 +23,11 @@ class LanguageUrlRewritingTest extends BrowserTestBase { */ public static $modules = ['language', 'language_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An user with permissions to administer languages. * @@ -88,7 +93,7 @@ private function checkUrl(LanguageInterface $language, $message1, $message2) { // If the rewritten URL has not a language prefix we pick a random prefix so // we can always check the prefixed URL. - $prefixes = language_negotiation_url_prefixes(); + $prefixes = $this->config('language.negotiation')->get('url.prefixes'); $stored_prefix = isset($prefixes[$language->getId()]) ? $prefixes[$language->getId()] : $this->randomMachineName(); $this->assertNotEqual($stored_prefix, $prefix, $message1); $prefix = $stored_prefix; diff --git a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonAnonTest.php b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonAnonTest.php index 7533e31c0..572f2a951 100644 --- a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonAnonTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonAnonTest.php @@ -21,4 +21,9 @@ class ConfigurableLanguageJsonAnonTest extends ConfigurableLanguageResourceTestB */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonBasicAuthTest.php b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonBasicAuthTest.php index 5352c9a95..e61892a29 100644 --- a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonBasicAuthTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ConfigurableLanguageJsonBasicAuthTest extends ConfigurableLanguageResource */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonCookieTest.php b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonCookieTest.php index cd3d9ce73..a1b348b44 100644 --- a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonCookieTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageJsonCookieTest.php @@ -26,4 +26,9 @@ class ConfigurableLanguageJsonCookieTest extends ConfigurableLanguageResourceTes */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlAnonTest.php b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlAnonTest.php index 882b7bf45..c787cd343 100644 --- a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlAnonTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlAnonTest.php @@ -23,4 +23,9 @@ class ConfigurableLanguageXmlAnonTest extends ConfigurableLanguageResourceTestBa */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlBasicAuthTest.php b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlBasicAuthTest.php index afc017f7a..7aff2d00f 100644 --- a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlBasicAuthTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ConfigurableLanguageXmlBasicAuthTest extends ConfigurableLanguageResourceT */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlCookieTest.php b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlCookieTest.php index 7755d76e9..0d8746193 100644 --- a/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlCookieTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ConfigurableLanguageXmlCookieTest.php @@ -28,4 +28,9 @@ class ConfigurableLanguageXmlCookieTest extends ConfigurableLanguageResourceTest */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonAnonTest.php b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonAnonTest.php index 1bece3100..74de3ea30 100644 --- a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonAnonTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonAnonTest.php @@ -21,4 +21,9 @@ class ContentLanguageSettingsJsonAnonTest extends ContentLanguageSettingsResourc */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonBasicAuthTest.php b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonBasicAuthTest.php index f1063a3dc..d68559fcd 100644 --- a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonBasicAuthTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ContentLanguageSettingsJsonBasicAuthTest extends ContentLanguageSettingsRe */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonCookieTest.php b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonCookieTest.php index 84da80849..9d38614ca 100644 --- a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonCookieTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsJsonCookieTest.php @@ -26,4 +26,9 @@ class ContentLanguageSettingsJsonCookieTest extends ContentLanguageSettingsResou */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlAnonTest.php b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlAnonTest.php index fd6519387..102e85df5 100644 --- a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlAnonTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlAnonTest.php @@ -23,4 +23,9 @@ class ContentLanguageSettingsXmlAnonTest extends ContentLanguageSettingsResource */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlBasicAuthTest.php b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlBasicAuthTest.php index dee7ad27d..1d1fad462 100644 --- a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlBasicAuthTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ContentLanguageSettingsXmlBasicAuthTest extends ContentLanguageSettingsRes */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlCookieTest.php b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlCookieTest.php index a5b49684f..1c7ede1bf 100644 --- a/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlCookieTest.php +++ b/core/modules/language/tests/src/Functional/Rest/ContentLanguageSettingsXmlCookieTest.php @@ -28,4 +28,9 @@ class ContentLanguageSettingsXmlCookieTest extends ContentLanguageSettingsResour */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/language/tests/src/Kernel/EntityDefaultLanguageTest.php b/core/modules/language/tests/src/Kernel/EntityDefaultLanguageTest.php index 88abdd900..06047dfaf 100644 --- a/core/modules/language/tests/src/Kernel/EntityDefaultLanguageTest.php +++ b/core/modules/language/tests/src/Kernel/EntityDefaultLanguageTest.php @@ -29,7 +29,7 @@ protected function setUp() { $this->installEntitySchema('user'); // Activate Spanish language, so there are two languages activated. - $language = $this->container->get('entity.manager')->getStorage('configurable_language')->create([ + $language = $this->container->get('entity_type.manager')->getStorage('configurable_language')->create([ 'id' => 'es', ]); $language->save(); @@ -101,7 +101,7 @@ public function testEntityTranslationDefaultLanguageViaCode() { * Default language code of the nodes of this type. */ protected function createContentType($name, $langcode) { - $content_type = $this->container->get('entity.manager')->getStorage('node_type')->create([ + $content_type = $this->container->get('entity_type.manager')->getStorage('node_type')->create([ 'name' => 'Test ' . $name, 'title_label' => 'Title', 'type' => $name, @@ -134,7 +134,7 @@ protected function createNode($type, $langcode = NULL) { if (!empty($langcode)) { $values['langcode'] = $langcode; } - $node = $this->container->get('entity.manager')->getStorage('node')->create($values); + $node = $this->container->get('entity_type.manager')->getStorage('node')->create($values); return $node; } diff --git a/core/modules/language/tests/src/Kernel/LanguageDependencyInjectionTest.php b/core/modules/language/tests/src/Kernel/LanguageDependencyInjectionTest.php index 287ab9528..9fe5e1cb7 100644 --- a/core/modules/language/tests/src/Kernel/LanguageDependencyInjectionTest.php +++ b/core/modules/language/tests/src/Kernel/LanguageDependencyInjectionTest.php @@ -21,9 +21,7 @@ class LanguageDependencyInjectionTest extends LanguageTestBase { public function testDependencyInjectedNewLanguage() { $expected = $this->languageManager->getDefaultLanguage(); $result = $this->languageManager->getCurrentLanguage(); - foreach ($expected as $property => $value) { - $this->assertEqual($expected->$property, $result->$property, format_string('The dependency injected language object %prop property equals the new Language object %prop property.', ['%prop' => $property])); - } + $this->assertSame($expected, $result); } /** @@ -35,7 +33,8 @@ public function testDependencyInjectedNewLanguage() { public function testDependencyInjectedNewDefaultLanguage() { $default_language = ConfigurableLanguage::load(\Drupal::languageManager()->getDefaultLanguage()->getId()); // Change the language default object to different values. - ConfigurableLanguage::createFromLangcode('fr')->save(); + $fr = ConfigurableLanguage::createFromLangcode('fr'); + $fr->save(); $this->config('system.site')->set('default_langcode', 'fr')->save(); // The language system creates a Language object which contains the @@ -45,7 +44,7 @@ public function testDependencyInjectedNewDefaultLanguage() { // Delete the language to check that we fallback to the default. try { - entity_delete_multiple('configurable_language', ['fr']); + $fr->delete(); $this->fail('Expected DeleteDefaultLanguageException thrown.'); } catch (DeleteDefaultLanguageException $e) { @@ -55,7 +54,7 @@ public function testDependencyInjectedNewDefaultLanguage() { // Re-save the previous default language and the delete should work. $this->config('system.site')->set('default_langcode', $default_language->getId())->save(); - entity_delete_multiple('configurable_language', ['fr']); + $fr->delete(); $result = \Drupal::languageManager()->getCurrentLanguage(); $this->assertIdentical($result->getId(), $default_language->getId()); } diff --git a/core/modules/language/tests/src/Kernel/LanguageFallbackTest.php b/core/modules/language/tests/src/Kernel/LanguageFallbackTest.php index 17f4ca396..a6a12fa49 100644 --- a/core/modules/language/tests/src/Kernel/LanguageFallbackTest.php +++ b/core/modules/language/tests/src/Kernel/LanguageFallbackTest.php @@ -53,13 +53,13 @@ public function testCandidates() { $this->assertEqual(array_values($candidates), $expected, 'Language fallback candidates are alterable for specific operations.'); // Check that when the site is monolingual no language fallback is applied. - $langcodes_to_delete = []; + /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $configurable_language_storage */ + $configurable_language_storage = $this->container->get('entity_type.manager')->getStorage('configurable_language'); foreach ($language_list as $langcode => $language) { if (!$language->isDefault()) { - $langcodes_to_delete[] = $langcode; + $configurable_language_storage->load($langcode)->delete(); } } - entity_delete_multiple('configurable_language', $langcodes_to_delete); $candidates = $this->languageManager->getFallbackCandidates(); $this->assertEqual(array_values($candidates), [LanguageInterface::LANGCODE_DEFAULT], 'Language fallback is not applied when the Language module is not enabled.'); } diff --git a/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php index e76b4553e..16f723dfc 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php @@ -41,7 +41,7 @@ public function testMigrationWithNonExistentLanguage() { $this->executeMigrations(['language', 'default_language']); // Tests the migration log contains an error message. - $messages = $this->migration->getIdMap()->getMessageIterator(); + $messages = $this->migration->getIdMap()->getMessages(); $count = 0; foreach ($messages as $message) { $count++; @@ -62,7 +62,7 @@ public function testMigrationWithUnsetVariable() { $this->startCollectingMessages(); $this->executeMigrations(['language', 'default_language']); - $messages = $this->migration->getIdMap()->getMessageIterator()->fetchAll(); + $messages = $this->migration->getIdMap()->getMessages()->fetchAll(); // Make sure there's no migration exceptions. $this->assertEmpty($messages); // Make sure the default langcode is 'en', as it was the default on D6 & D7. diff --git a/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageContentTaxonomyVocabularySettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageContentTaxonomyVocabularySettingsTest.php index b7b3f4b8f..22ad70f44 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageContentTaxonomyVocabularySettingsTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageContentTaxonomyVocabularySettingsTest.php @@ -47,7 +47,7 @@ public function testLanguageContentTaxonomy() { // Localize terms. $this->assertLanguageContentSettings($target_entity, 'vocabulary_3_i_2_', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => FALSE]); // None translation enabled. - $this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_than', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => TRUE]); + $this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_th', LanguageInterface::LANGCODE_SITE_DEFAULT, TRUE, ['enabled' => TRUE]); $this->assertLanguageContentSettings($target_entity, 'tags', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]); $this->assertLanguageContentSettings($target_entity, 'forums', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]); $this->assertLanguageContentSettings($target_entity, 'type', LanguageInterface::LANGCODE_SITE_DEFAULT, FALSE, ['enabled' => FALSE]); diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php index 2a11b2562..9a1998b7c 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php @@ -41,7 +41,7 @@ public function testMigrationWithNonExistentLanguage() { $this->executeMigrations(['language', 'default_language']); // Tests the migration log contains an error message. - $messages = $this->migration->getIdMap()->getMessageIterator(); + $messages = $this->migration->getIdMap()->getMessages(); $count = 0; foreach ($messages as $message) { $count++; @@ -62,7 +62,7 @@ public function testMigrationWithUnsetVariable() { $this->startCollectingMessages(); $this->executeMigrations(['language', 'default_language']); - $messages = $this->migration->getIdMap()->getMessageIterator()->fetchAll(); + $messages = $this->migration->getIdMap()->getMessages()->fetchAll(); // Make sure there's no migration exceptions. $this->assertEmpty($messages); // Make sure the default langcode is 'en', as it was the default on D6 & D7. diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentSettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentSettingsTest.php index 7adc1f8b5..1e05e9193 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentSettingsTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentSettingsTest.php @@ -53,7 +53,7 @@ public function testLanguageContent() { $this->assertSame($config->getDefaultLangcode(), 'site_default'); // Make sure there's no migration exceptions. - $messages = $this->migration->getIdMap()->getMessageIterator()->fetchAll(); + $messages = $this->migration->getIdMap()->getMessages()->fetchAll(); $this->assertEmpty($messages); // Assert that a content type translatable with entity_translation is still diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php new file mode 100644 index 000000000..f259fe7e8 --- /dev/null +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php @@ -0,0 +1,81 @@ +installEntitySchema('taxonomy_term'); + $this->executeMigrations([ + 'language', + 'd7_taxonomy_vocabulary', + 'd7_language_content_taxonomy_vocabulary_settings', + ]); + } + + /** + * Tests migration of 18ntaxonomy vocabulary settings. + */ + public function testLanguageContentTaxonomy() { + $target_entity = 'taxonomy_term'; + // No multilingual options for terms, i18n_mode = 0. + $this->assertLanguageContentSettings($target_entity, 'tags', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]); + $this->assertLanguageContentSettings($target_entity, 'forums', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]); + $this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_th', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]); + $this->assertLanguageContentSettings($target_entity, 'test_vocabulary', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]); + // Localize, i18n_mode = 1. + $this->assertLanguageContentSettings($target_entity, 'vocablocalized', LanguageInterface::LANGCODE_NOT_SPECIFIED, TRUE, ['enabled' => TRUE]); + // Translate, i18n_mode = 4. + $this->assertLanguageContentSettings($target_entity, 'vocabtranslate', LanguageInterface::LANGCODE_NOT_SPECIFIED, TRUE, ['enabled' => FALSE]); + // Fixed language, i18n_mode = 2. + $this->assertLanguageContentSettings($target_entity, 'vocabfixed', 'fr', FALSE, ['enabled' => FALSE]); + } + + /** + * Asserts a content language settings configuration. + * + * @param string $target_entity + * The expected target entity type. + * @param string $bundle + * The expected bundle. + * @param string $default_langcode + * The default language code. + * @param bool $language_alterable + * The expected state of language alterable. + * @param array $third_party_settings + * The content translation setting. + */ + public function assertLanguageContentSettings($target_entity, $bundle, $default_langcode, $language_alterable, array $third_party_settings) { + $config = ContentLanguageSettings::load($target_entity . '.' . $bundle); + $this->assertInstanceOf(ContentLanguageSettings::class, $config); + $this->assertSame($target_entity, $config->getTargetEntityTypeId()); + $this->assertSame($bundle, $config->getTargetBundle()); + $this->assertSame($default_langcode, $config->getDefaultLangcode()); + $this->assertSame($language_alterable, $config->isLanguageAlterable()); + $this->assertSame($third_party_settings, $config->getThirdPartySettings('content_translation')); + } + +} diff --git a/core/modules/language/tests/src/Kernel/OverriddenConfigImportTest.php b/core/modules/language/tests/src/Kernel/OverriddenConfigImportTest.php index 6a243f5a0..feddee68b 100644 --- a/core/modules/language/tests/src/Kernel/OverriddenConfigImportTest.php +++ b/core/modules/language/tests/src/Kernel/OverriddenConfigImportTest.php @@ -48,7 +48,8 @@ protected function setUp() { $this->container->get('module_handler'), $this->container->get('module_installer'), $this->container->get('theme_handler'), - $this->container->get('string_translation') + $this->container->get('string_translation'), + $this->container->get('extension.list.module') ); } diff --git a/core/modules/language/tests/src/Kernel/Plugin/migrate/source/d7/LanguageContentTaxonomyVocabularySettingsTest.php b/core/modules/language/tests/src/Kernel/Plugin/migrate/source/d7/LanguageContentTaxonomyVocabularySettingsTest.php new file mode 100644 index 000000000..39762b59e --- /dev/null +++ b/core/modules/language/tests/src/Kernel/Plugin/migrate/source/d7/LanguageContentTaxonomyVocabularySettingsTest.php @@ -0,0 +1,42 @@ +storage = $this->getMock('Drupal\Core\Config\StorageInterface'); - $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - $this->typedConfig = $this->getMock('\Drupal\Core\Config\TypedConfigManagerInterface'); + $this->storage = $this->createMock('Drupal\Core\Config\StorageInterface'); + $this->eventDispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->typedConfig = $this->createMock('\Drupal\Core\Config\TypedConfigManagerInterface'); $this->configTranslation = new LanguageConfigOverride('config.test', $this->storage, $this->typedConfig, $this->eventDispatcher); - $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface'); + $this->cacheTagsInvalidator = $this->createMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface'); $container = new ContainerBuilder(); $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator); diff --git a/core/modules/language/tests/src/Unit/ContentLanguageSettingsUnitTest.php b/core/modules/language/tests/src/Unit/ContentLanguageSettingsUnitTest.php index c3955df56..b89883cd5 100644 --- a/core/modules/language/tests/src/Unit/ContentLanguageSettingsUnitTest.php +++ b/core/modules/language/tests/src/Unit/ContentLanguageSettingsUnitTest.php @@ -4,7 +4,6 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Entity\EntityManager; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeRepositoryInterface; use Drupal\language\Entity\ContentLanguageSettings; @@ -19,21 +18,14 @@ class ContentLanguageSettingsUnitTest extends UnitTestCase { /** * The entity type used for testing. * - * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityType; - /** - * The entity manager used for testing. - * - * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $entityManager; - /** * The entity type manager used for testing. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeManager; @@ -47,21 +39,21 @@ class ContentLanguageSettingsUnitTest extends UnitTestCase { /** * The UUID generator used for testing. * - * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $uuid; /** * The typed configuration manager used for testing. * - * @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $typedConfigManager; /** * The typed configuration manager used for testing. * - * @var \Drupal\Core\Config\Entity\ConfigEntityStorage|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\Entity\ConfigEntityStorage|\PHPUnit\Framework\MockObject\MockObject */ protected $configEntityStorageInterface; @@ -70,26 +62,21 @@ class ContentLanguageSettingsUnitTest extends UnitTestCase { */ protected function setUp() { $this->entityTypeId = $this->randomMachineName(); - $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $this->entityType = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); - $this->entityManager = new EntityManager(); - $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class); + $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); - $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface'); + $this->uuid = $this->createMock('\Drupal\Component\Uuid\UuidInterface'); - $this->typedConfigManager = $this->getMock('Drupal\Core\Config\TypedConfigManagerInterface'); + $this->typedConfigManager = $this->createMock('Drupal\Core\Config\TypedConfigManagerInterface'); - $this->configEntityStorageInterface = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $this->configEntityStorageInterface = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $container = new ContainerBuilder(); - $container->set('entity.manager', $this->entityManager); $container->set('entity_type.manager', $this->entityTypeManager); $container->set('uuid', $this->uuid); $container->set('config.typed', $this->typedConfigManager); $container->set('config.storage', $this->configEntityStorageInterface); - // Inject the container into entity.manager so it can defer to other entity - // services. - $this->entityManager->setContainer($container); \Drupal::setContainer($container); } @@ -98,7 +85,7 @@ protected function setUp() { */ public function testCalculateDependencies() { // Mock the interfaces necessary to create a dependency on a bundle entity. - $target_entity_type = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $target_entity_type = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); $target_entity_type->expects($this->any()) ->method('getBundleConfigDependency') ->will($this->returnValue(['type' => 'config', 'name' => 'test.test_entity_type.id'])); diff --git a/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php b/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php index 981228956..f36a1ada0 100644 --- a/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php +++ b/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php @@ -25,11 +25,11 @@ class LanguageNegotiationUrlTest extends UnitTestCase { protected function setUp() { // Set up some languages to be used by the language-based path processor. - $language_de = $this->getMock('\Drupal\Core\Language\LanguageInterface'); + $language_de = $this->createMock('\Drupal\Core\Language\LanguageInterface'); $language_de->expects($this->any()) ->method('getId') ->will($this->returnValue('de')); - $language_en = $this->getMock('\Drupal\Core\Language\LanguageInterface'); + $language_en = $this->createMock('\Drupal\Core\Language\LanguageInterface'); $language_en->expects($this->any()) ->method('getId') ->will($this->returnValue('en')); diff --git a/core/modules/language/tests/src/Unit/process/LanguageNegotiationTest.php b/core/modules/language/tests/src/Unit/process/LanguageNegotiationTest.php index 695c91f19..27aa0999e 100644 --- a/core/modules/language/tests/src/Unit/process/LanguageNegotiationTest.php +++ b/core/modules/language/tests/src/Unit/process/LanguageNegotiationTest.php @@ -79,7 +79,8 @@ public function testTransformWithoutWeights() { */ public function testStringInput() { $this->plugin = new LanguageNegotiation([], 'map', []); - $this->setExpectedException(MigrateException::class, 'The input should be an array'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The input should be an array'); $this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/language/tests/src/Unit/process/LanguageTypesTest.php b/core/modules/language/tests/src/Unit/process/LanguageTypesTest.php index 606572ecd..b65c9d050 100644 --- a/core/modules/language/tests/src/Unit/process/LanguageTypesTest.php +++ b/core/modules/language/tests/src/Unit/process/LanguageTypesTest.php @@ -53,7 +53,8 @@ public function testTransformConfigurable() { */ public function testStringInput() { $this->plugin = new LanguageTypes([], 'map', []); - $this->setExpectedException(MigrateException::class, 'The input should be an array'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The input should be an array'); $this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/language/tests/test_module/test_module.info.yml b/core/modules/language/tests/test_module/test_module.info.yml index 8969414d7..f68226f86 100644 --- a/core/modules/language/tests/test_module/test_module.info.yml +++ b/core/modules/language/tests/test_module/test_module.info.yml @@ -2,11 +2,5 @@ name: 'Test Module' type: module description: 'Support module for testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/layout_builder/css/layout-builder.css b/core/modules/layout_builder/css/layout-builder.css index 4228675b3..cbd03bb31 100644 --- a/core/modules/layout_builder/css/layout-builder.css +++ b/core/modules/layout_builder/css/layout-builder.css @@ -14,14 +14,14 @@ } .layout-builder__link--add { - padding-left: 24px; /* LTR */ + padding-left: 1.3em; /* LTR */ color: #686868; border-bottom: none; - background: url(../../../misc/icons/787878/plus.svg) transparent top left / 16px 16px no-repeat; /* LTR */ + background: url(../../../misc/icons/787878/plus.svg) transparent center left / 1em no-repeat; /* LTR */ } [dir="rtl"] .layout-builder__link--add { - padding-right: 24px; + padding-right: 1.3em; padding-left: 0; background-position-x: right; } @@ -89,6 +89,7 @@ .layout-builder-block { padding: 1.5em; cursor: move; + background-color: #fff; } .layout-builder-block [tabindex="-1"] { diff --git a/core/modules/layout_builder/js/layout-builder.es6.js b/core/modules/layout_builder/js/layout-builder.es6.js index 7fc5ed7a2..cf915714b 100644 --- a/core/modules/layout_builder/js/layout-builder.es6.js +++ b/core/modules/layout_builder/js/layout-builder.es6.js @@ -3,7 +3,7 @@ * Attaches the behaviors for the Layout Builder module. */ -(($, Drupal) => { +(($, Drupal, Sortable) => { const { ajax, behaviors, debounce, announce, formatPlural } = Drupal; /* @@ -13,12 +13,12 @@ let layoutBuilderBlocksFiltered = false; /** - * Provides the ability to filter the block listing in Add Block dialog. + * Provides the ability to filter the block listing in "Add block" dialog. * * @type {Drupal~behavior} * * @prop {Drupal~behaviorAttach} attach - * Attach block filtering behavior to Add Block dialog. + * Attach block filtering behavior to "Add block" dialog. */ behaviors.layoutBuilderBlockFilter = { attach(context) { @@ -100,6 +100,48 @@ }, }; + /** + * Callback used in {@link Drupal.behaviors.layoutBuilderBlockDrag}. + * + * @param {HTMLElement} item + * The HTML element representing the repositioned block. + * @param {HTMLElement} from + * The HTML element representing the previous parent of item + * @param {HTMLElement} to + * The HTML element representing the current parent of item + * + * @internal This method is a callback for layoutBuilderBlockDrag and is used + * in FunctionalJavascript tests. It may be renamed if the test changes. + * @see https://www.drupal.org/node/3084730 + */ + Drupal.layoutBuilderBlockUpdate = function(item, from, to) { + const $item = $(item); + const $from = $(from); + + // Check if the region from the event and region for the item match. + const itemRegion = $item.closest('.js-layout-builder-region'); + if (to === itemRegion[0]) { + // Find the destination delta. + const deltaTo = $item.closest('[data-layout-delta]').data('layout-delta'); + // If the block didn't leave the original delta use the destination. + const deltaFrom = $from + ? $from.closest('[data-layout-delta]').data('layout-delta') + : deltaTo; + ajax({ + url: [ + $item.closest('[data-layout-update-url]').data('layout-update-url'), + deltaFrom, + deltaTo, + itemRegion.data('region'), + $item.data('layout-block-uuid'), + $item.prev('[data-layout-block-uuid]').data('layout-block-uuid'), + ] + .filter(element => element !== undefined) + .join('/'), + }).execute(); + } + }; + /** * Provides the ability to drag blocks to new positions in the layout. * @@ -110,52 +152,19 @@ */ behaviors.layoutBuilderBlockDrag = { attach(context) { - $(context) - .find('.js-layout-builder-region') - .sortable({ - items: '> .js-layout-builder-block', - connectWith: '.js-layout-builder-region', - placeholder: 'ui-state-drop', - - /** - * Updates the layout with the new position of the block. - * - * @param {jQuery.Event} event - * The jQuery Event object. - * @param {Object} ui - * An object containing information about the item being sorted. - */ - update(event, ui) { - // Check if the region from the event and region for the item match. - const itemRegion = ui.item.closest('.js-layout-builder-region'); - if (event.target === itemRegion[0]) { - // Find the destination delta. - const deltaTo = ui.item - .closest('[data-layout-delta]') - .data('layout-delta'); - // If the block didn't leave the original delta use the destination. - const deltaFrom = ui.sender - ? ui.sender.closest('[data-layout-delta]').data('layout-delta') - : deltaTo; - ajax({ - url: [ - ui.item - .closest('[data-layout-update-url]') - .data('layout-update-url'), - deltaFrom, - deltaTo, - itemRegion.data('region'), - ui.item.data('layout-block-uuid'), - ui.item - .prev('[data-layout-block-uuid]') - .data('layout-block-uuid'), - ] - .filter(element => element !== undefined) - .join('/'), - }).execute(); - } - }, - }); + const regionSelector = '.js-layout-builder-region'; + Array.prototype.forEach.call( + context.querySelectorAll(regionSelector), + region => { + Sortable.create(region, { + draggable: '.js-layout-builder-block', + ghostClass: 'ui-state-drop', + group: 'builder-region', + onEnd: event => + Drupal.layoutBuilderBlockUpdate(event.item, event.from, event.to), + }); + }, + ); }, }; @@ -441,4 +450,4 @@ return `
${contentPreviewPlaceholderText}
`; }; -})(jQuery, Drupal); +})(jQuery, Drupal, Sortable); diff --git a/core/modules/layout_builder/js/layout-builder.js b/core/modules/layout_builder/js/layout-builder.js index 88bf8ede2..3068458e7 100644 --- a/core/modules/layout_builder/js/layout-builder.js +++ b/core/modules/layout_builder/js/layout-builder.js @@ -5,7 +5,7 @@ * @preserve **/ -(function ($, Drupal) { +(function ($, Drupal, Sortable) { var ajax = Drupal.ajax, behaviors = Drupal.behaviors, debounce = Drupal.debounce, @@ -53,26 +53,35 @@ } }; + Drupal.layoutBuilderBlockUpdate = function (item, from, to) { + var $item = $(item); + var $from = $(from); + + var itemRegion = $item.closest('.js-layout-builder-region'); + if (to === itemRegion[0]) { + var deltaTo = $item.closest('[data-layout-delta]').data('layout-delta'); + + var deltaFrom = $from ? $from.closest('[data-layout-delta]').data('layout-delta') : deltaTo; + ajax({ + url: [$item.closest('[data-layout-update-url]').data('layout-update-url'), deltaFrom, deltaTo, itemRegion.data('region'), $item.data('layout-block-uuid'), $item.prev('[data-layout-block-uuid]').data('layout-block-uuid')].filter(function (element) { + return element !== undefined; + }).join('/') + }).execute(); + } + }; + behaviors.layoutBuilderBlockDrag = { attach: function attach(context) { - $(context).find('.js-layout-builder-region').sortable({ - items: '> .js-layout-builder-block', - connectWith: '.js-layout-builder-region', - placeholder: 'ui-state-drop', - - update: function update(event, ui) { - var itemRegion = ui.item.closest('.js-layout-builder-region'); - if (event.target === itemRegion[0]) { - var deltaTo = ui.item.closest('[data-layout-delta]').data('layout-delta'); - - var deltaFrom = ui.sender ? ui.sender.closest('[data-layout-delta]').data('layout-delta') : deltaTo; - ajax({ - url: [ui.item.closest('[data-layout-update-url]').data('layout-update-url'), deltaFrom, deltaTo, itemRegion.data('region'), ui.item.data('layout-block-uuid'), ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid')].filter(function (element) { - return element !== undefined; - }).join('/') - }).execute(); + var regionSelector = '.js-layout-builder-region'; + Array.prototype.forEach.call(context.querySelectorAll(regionSelector), function (region) { + Sortable.create(region, { + draggable: '.js-layout-builder-block', + ghostClass: 'ui-state-drop', + group: 'builder-region', + onEnd: function onEnd(event) { + return Drupal.layoutBuilderBlockUpdate(event.item, event.from, event.to); } - } + }); }); } }; @@ -213,4 +222,4 @@ return '
' + contentPreviewPlaceholderText + '
'; }; -})(jQuery, Drupal); \ No newline at end of file +})(jQuery, Drupal, Sortable); \ No newline at end of file diff --git a/core/modules/layout_builder/layout_builder.info.yml b/core/modules/layout_builder/layout_builder.info.yml index 030a6870d..6e700e1b0 100644 --- a/core/modules/layout_builder/layout_builder.info.yml +++ b/core/modules/layout_builder/layout_builder.info.yml @@ -2,8 +2,8 @@ name: 'Layout Builder' type: module description: 'Allows users to add and arrange blocks and content fields directly on the content.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:layout_discovery - drupal:contextual @@ -11,9 +11,3 @@ dependencies: - drupal:field_ui # @todo Discuss removing in https://www.drupal.org/project/drupal/issues/3003610. - drupal:block - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/layout_builder/layout_builder.libraries.yml b/core/modules/layout_builder/layout_builder.libraries.yml index d77b62136..639c544db 100644 --- a/core/modules/layout_builder/layout_builder.libraries.yml +++ b/core/modules/layout_builder/layout_builder.libraries.yml @@ -6,7 +6,7 @@ drupal.layout_builder: js: js/layout-builder.js: {} dependencies: - - core/jquery.ui.sortable + - core/sortable - core/drupal.dialog.off_canvas - core/drupal.announce - core/drupal.debounce diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module index f03e73afd..cf1fcc9be 100644 --- a/core/modules/layout_builder/layout_builder.module +++ b/core/modules/layout_builder/layout_builder.module @@ -183,7 +183,7 @@ function layout_builder_entity_build_defaults_alter(array &$build, EntityInterfa } /** - * Implements hook_builder_module_implements_alter(). + * Implements hook_module_implements_alter(). */ function layout_builder_module_implements_alter(&$implementations, $hook) { if ($hook === 'entity_view_alter') { @@ -405,3 +405,15 @@ function layout_builder_preprocess_language_content_settings_table(&$variables) } } } + +/** + * Implements hook_theme_suggestions_HOOK_alter(). + */ +function layout_builder_theme_suggestions_field_alter(&$suggestions, array $variables) { + $element = $variables['element']; + if (isset($element['#third_party_settings']['layout_builder']['view_mode'])) { + // See system_theme_suggestions_field(). + $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'] . '__' . $element['#third_party_settings']['layout_builder']['view_mode']; + } + return $suggestions; +} diff --git a/core/modules/layout_builder/layout_builder.post_update.php b/core/modules/layout_builder/layout_builder.post_update.php index 9efb32061..1b4a2acb0 100644 --- a/core/modules/layout_builder/layout_builder.post_update.php +++ b/core/modules/layout_builder/layout_builder.post_update.php @@ -201,7 +201,12 @@ function layout_builder_post_update_make_layout_untranslatable() { if (isset($field_infos[OverridesSectionStorage::FIELD_NAME]['bundles'])) { $non_translatable_bundle_count = 0; foreach ($field_infos[OverridesSectionStorage::FIELD_NAME]['bundles'] as $bundle) { - $field_config = FieldConfig::loadByName($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME); + // The field map can contain stale information. If the field does not + // exist, ignore it. The field map will be rebuilt when the cache is + // cleared at the end of the update process. + if (!$field_config = FieldConfig::loadByName($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME)) { + continue; + } if (!$field_config->isTranslatable()) { $non_translatable_bundle_count++; // The layout field is already configured to be non-translatable so it diff --git a/core/modules/layout_builder/src/Element/LayoutBuilder.php b/core/modules/layout_builder/src/Element/LayoutBuilder.php index 403d64f22..80dcff90f 100644 --- a/core/modules/layout_builder/src/Element/LayoutBuilder.php +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php @@ -179,20 +179,20 @@ protected function buildAddSectionLink(SectionStorageInterface $section_storage, // layout or an empty layout. if ($delta === count($section_storage)) { if ($delta === 0) { - $title = $this->t('Add Section'); + $title = $this->t('Add section'); } else { - $title = $this->t('Add Section at end of layout'); + $title = $this->t('Add section at end of layout'); } } // If the delta and the count are different, it is either the beginning of // the layout or in between two sections. else { if ($delta === 0) { - $title = $this->t('Add Section at start of layout'); + $title = $this->t('Add section at start of layout'); } else { - $title = $this->t('Add Section between @first and @second', ['@first' => $delta, '@second' => $delta + 1]); + $title = $this->t('Add section between @first and @second', ['@first' => $delta, '@second' => $delta + 1]); } } @@ -244,6 +244,9 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s $section = $section_storage->getSection($delta); $layout = $section->getLayout(); + $layout_settings = $section->getLayoutSettings(); + $section_label = !empty($layout_settings['label']) ? $layout_settings['label'] : $this->t('Section @section', ['@section' => $delta + 1]); + $build = $section->toRenderArray($this->getAvailableContexts($section_storage), TRUE); $layout_definition = $layout->getPluginDefinition(); @@ -279,7 +282,7 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s $build[$region]['layout_builder_add_block']['link'] = [ '#type' => 'link', // Add one to the current delta since it is zero-indexed. - '#title' => $this->t('Add Block in section @section, @region region', ['@section' => $delta + 1, '@region' => $region_labels[$region]]), + '#title' => $this->t('Add block in @section, @region region', ['@section' => $section_label, '@region' => $region_labels[$region]]), '#url' => Url::fromRoute('layout_builder.choose_block', [ 'section_storage_type' => $storage_type, @@ -310,9 +313,9 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s $build[$region]['#attributes']['class'][] = 'layout-builder__region'; $build[$region]['#attributes']['class'][] = 'js-layout-builder-region'; $build[$region]['#attributes']['role'] = 'group'; - $build[$region]['#attributes']['aria-label'] = $this->t('@region region in section @section', [ + $build[$region]['#attributes']['aria-label'] = $this->t('@region region in @section', [ '@region' => $info['label'], - '@section' => $delta + 1, + '@section' => $section_label, ]); // Get weights of all children for use by the region label. @@ -349,11 +352,11 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s '#attributes' => [ 'class' => ['layout-builder__section'], 'role' => 'group', - 'aria-label' => $this->t('Section @section', ['@section' => $delta + 1]), + 'aria-label' => $section_label, ], 'remove' => [ '#type' => 'link', - '#title' => $this->t('Remove section @section', ['@section' => $delta + 1]), + '#title' => $this->t('Remove @section', ['@section' => $section_label]), '#url' => Url::fromRoute('layout_builder.remove_section', [ 'section_storage_type' => $storage_type, 'section_storage' => $storage_id, @@ -369,19 +372,15 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s 'data-dialog-renderer' => 'off_canvas', ], ], - // The section label is added to sections without a "Configure Section" + // The section label is added to sections without a "Configure section" // link, and is only visible when the move block dialog is open. 'section_label' => [ - '#markup' => $this->t('', ['@section' => $delta + 1]), + '#markup' => $this->t('', ['@section' => $section_label]), '#access' => !$layout instanceof PluginFormInterface, ], 'configure' => [ '#type' => 'link', - // There are two instances of @section, the one wrapped in - // .visually-hidden is for screen readers. The one wrapped in - // .layout-builder__section-label is only visible when the - // move block dialog is open and it is not seen by screen readers. - '#title' => $this->t('Configure section @section', ['@section' => $delta + 1]), + '#title' => $this->t('Configure @section', ['@section' => $section_label]), '#access' => $layout instanceof PluginFormInterface, '#url' => Url::fromRoute('layout_builder.configure_section', [ 'section_storage_type' => $storage_type, diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index 6619c580c..dc07388ba 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -72,10 +72,9 @@ public function setOverridable($overridable = TRUE) { * {@inheritdoc} */ public function isLayoutBuilderEnabled() { - // To prevent infinite recursion, Layout Builder must not be enabled for the - // '_custom' view mode that is used for on-the-fly rendering of fields in - // isolation from the entity. - if ($this->getOriginalMode() === static::CUSTOM_MODE) { + // Layout Builder must not be enabled for the '_custom' view mode that is + // used for on-the-fly rendering of fields in isolation from the entity. + if ($this->isCustomMode()) { return FALSE; } return (bool) $this->getThirdPartySetting('layout_builder', 'enabled'); @@ -251,12 +250,28 @@ protected function contextRepository() { return \Drupal::service('context.repository'); } + /** + * Indicates if this display is using the '_custom' view mode. + * + * @return bool + * TRUE if this display is using the '_custom' view mode, FALSE otherwise. + */ + protected function isCustomMode() { + return $this->getOriginalMode() === static::CUSTOM_MODE; + } + /** * {@inheritdoc} */ public function buildMultiple(array $entities) { $build_list = parent::buildMultiple($entities); + // Layout Builder can not be enabled for the '_custom' view mode that is + // used for on-the-fly rendering of fields in isolation from the entity. + if ($this->isCustomMode()) { + return $build_list; + } + foreach ($entities as $id => $entity) { $build_list[$id]['_layout_builder'] = $this->buildSections($entity); @@ -335,7 +350,7 @@ protected function getContextsForEntity(FieldableEntityInterface $entity) { * @return \Drupal\layout_builder\Section[] * The sections. * - * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. * \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext() * should be used instead. See https://www.drupal.org/node/3022574. */ diff --git a/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php b/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php index e1a977395..8cde7674a 100644 --- a/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php +++ b/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php @@ -120,20 +120,23 @@ public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) { 'content' => $content, ]; - if ($block instanceof PreviewFallbackInterface) { - $preview_fallback_string = $block->getPreviewFallbackString(); - } - else { - $preview_fallback_string = $this->t('"@block" block', ['@block' => $block->label()]); - } - // @todo Use new label methods so - // data-layout-content-preview-placeholder-label doesn't have to use - // preview fallback in https://www.drupal.org/node/2025649. - $build['#attributes']['data-layout-content-preview-placeholder-label'] = $preview_fallback_string; + if ($event->inPreview()) { + if ($block instanceof PreviewFallbackInterface) { + $preview_fallback_string = $block->getPreviewFallbackString(); + } + else { + $preview_fallback_string = $this->t('"@block" block', ['@block' => $block->label()]); + } + // @todo Use new label methods so + // data-layout-content-preview-placeholder-label doesn't have to use + // preview fallback in https://www.drupal.org/node/2025649. + $build['#attributes']['data-layout-content-preview-placeholder-label'] = $preview_fallback_string; - if ($is_content_empty && $is_placeholder_ready) { - $build['content']['#markup'] = $this->t('Placeholder for the @preview_fallback', ['@preview_fallback' => $block->getPreviewFallbackString()]); + if ($is_content_empty && $is_placeholder_ready) { + $build['content']['#markup'] = $this->t('Placeholder for the @preview_fallback', ['@preview_fallback' => $block->getPreviewFallbackString()]); + } } + $event->setBuild($build); } } diff --git a/core/modules/layout_builder/src/Field/LayoutSectionItemList.php b/core/modules/layout_builder/src/Field/LayoutSectionItemList.php index 58e78f256..3243be97a 100644 --- a/core/modules/layout_builder/src/Field/LayoutSectionItemList.php +++ b/core/modules/layout_builder/src/Field/LayoutSectionItemList.php @@ -22,12 +22,18 @@ class LayoutSectionItemList extends FieldItemList implements SectionListInterfac use SectionStorageTrait; + /** + * Numerically indexed array of field items. + * + * @var \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem[] + */ + protected $list = []; + /** * {@inheritdoc} */ public function getSections() { $sections = []; - /** @var \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem $item */ foreach ($this->list as $delta => $item) { $sections[$delta] = $item->section; } @@ -60,6 +66,18 @@ public function getEntity() { return $entity; } + /** + * {@inheritdoc} + */ + public function preSave() { + parent::preSave(); + // Loop through each section and reconstruct it to ensure that all default + // values are present. + foreach ($this->list as $delta => $item) { + $item->section = Section::fromArray($item->section->toArray()); + } + } + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/src/Form/AddBlockForm.php b/core/modules/layout_builder/src/Form/AddBlockForm.php index 6f73133de..63c79c2c4 100644 --- a/core/modules/layout_builder/src/Form/AddBlockForm.php +++ b/core/modules/layout_builder/src/Form/AddBlockForm.php @@ -28,7 +28,7 @@ public function getFormId() { * {@inheritdoc} */ protected function submitLabel() { - return $this->t('Add Block'); + return $this->t('Add block'); } /** diff --git a/core/modules/layout_builder/src/Form/ConfigureSectionForm.php b/core/modules/layout_builder/src/Form/ConfigureSectionForm.php index 3e58c668a..074901c8a 100644 --- a/core/modules/layout_builder/src/Form/ConfigureSectionForm.php +++ b/core/modules/layout_builder/src/Form/ConfigureSectionForm.php @@ -111,6 +111,9 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt if ($this->isUpdate) { $section = $this->sectionStorage->getSection($this->delta); + if ($label = $section->getLayoutSettings()['label']) { + $form['#title'] = $this->t('Configure @section', ['@section' => $label]); + } } else { $section = new Section($plugin_id); diff --git a/core/modules/layout_builder/src/Form/DefaultsEntityForm.php b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php index e26a55c7d..8fb71c52f 100644 --- a/core/modules/layout_builder/src/Form/DefaultsEntityForm.php +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php @@ -80,6 +80,7 @@ public function getBaseFormId() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) { + $form['#attributes']['class'][] = 'layout-builder-form'; $form['layout_builder'] = [ '#type' => 'layout_builder', '#section_storage' => $section_storage, diff --git a/core/modules/layout_builder/src/Form/MoveBlockForm.php b/core/modules/layout_builder/src/Form/MoveBlockForm.php index 49e97afa7..b3fd9dcbf 100644 --- a/core/modules/layout_builder/src/Form/MoveBlockForm.php +++ b/core/modules/layout_builder/src/Form/MoveBlockForm.php @@ -123,12 +123,14 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt foreach ($sections as $section_delta => $section) { $layout = $section->getLayout(); $layout_definition = $layout->getPluginDefinition(); - $section_label = $this->t('Section: @delta', ['@delta' => $section_delta + 1])->render(); + if (!($section_label = $section->getLayoutSettings()['label'])) { + $section_label = $this->t('Section: @delta', ['@delta' => $section_delta + 1])->render(); + } foreach ($layout_definition->getRegions() as $region_name => $region_info) { // Group regions by section. $region_options[$section_label]["$section_delta:$region_name"] = $this->t( - 'Section: @delta, Region: @region', - ['@delta' => $section_delta + 1, '@region' => $region_info['label']] + '@section, Region: @region', + ['@section' => $section_label, '@region' => $region_info['label']] ); } } @@ -155,7 +157,7 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt $form['components_wrapper']['components'] = [ '#type' => 'table', '#header' => [ - $this->t('Block Label'), + $this->t('Block label'), $this->t('Weight'), ], '#tabledrag' => [ diff --git a/core/modules/layout_builder/src/Form/OverridesEntityForm.php b/core/modules/layout_builder/src/Form/OverridesEntityForm.php index ac1282202..29f940bdc 100644 --- a/core/modules/layout_builder/src/Form/OverridesEntityForm.php +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php @@ -97,6 +97,7 @@ protected function init(FormStateInterface $form_state) { public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) { $this->sectionStorage = $section_storage; $form = parent::buildForm($form, $form_state); + $form['#attributes']['class'][] = 'layout-builder-form'; // @todo \Drupal\layout_builder\Field\LayoutSectionItemList::defaultAccess() // restricts all access to the field, explicitly allow access here until diff --git a/core/modules/layout_builder/src/Form/RemoveSectionForm.php b/core/modules/layout_builder/src/Form/RemoveSectionForm.php index 7a0cb0fd4..a78869876 100644 --- a/core/modules/layout_builder/src/Form/RemoveSectionForm.php +++ b/core/modules/layout_builder/src/Form/RemoveSectionForm.php @@ -24,6 +24,10 @@ public function getFormId() { * {@inheritdoc} */ public function getQuestion() { + $configuration = $this->sectionStorage->getSection($this->delta)->getLayoutSettings(); + if ($configuration['label']) { + return $this->t('Are you sure you want to remove @section?', ['@section' => $configuration['label']]); + } return $this->t('Are you sure you want to remove section @section?', ['@section' => $this->delta + 1]); } diff --git a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php index e1c4216ce..53b74490a 100644 --- a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php +++ b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php @@ -109,6 +109,7 @@ protected function getSectionStorageForEntity(EntityInterface $entity) { $view_mode = 'full'; if ($entity instanceof LayoutEntityDisplayInterface) { $contexts['display'] = EntityContext::fromEntity($entity); + $contexts['view_mode'] = new Context(new ContextDefinition('string'), $entity->getMode()); } else { $contexts['entity'] = EntityContext::fromEntity($entity); @@ -132,7 +133,7 @@ protected function getSectionStorageForEntity(EntityInterface $entity) { * @return bool * TRUE if the entity is using a field for a layout override. * - * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. * To determine if an entity has a layout override, use * \Drupal\layout_builder\LayoutEntityHelperTrait::getSectionStorageForEntity() * and check whether the result is an instance of diff --git a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php index e8a21d12d..493ed5dee 100644 --- a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php +++ b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php @@ -156,6 +156,7 @@ protected function getEntity() { */ public function build() { $display_settings = $this->getConfiguration()['formatter']; + $display_settings['third_party_settings']['layout_builder']['view_mode'] = $this->getContextValue('view_mode'); $entity = $this->getEntity(); try { $build = $entity->get($this->fieldName)->view($display_settings); diff --git a/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php b/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php index bb61ccc15..8de394c34 100644 --- a/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php +++ b/core/modules/layout_builder/src/Plugin/Derivative/ExtraFieldBlockDeriver.php @@ -12,6 +12,7 @@ use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Entity\EntityTypeRepositoryInterface; /** * Provides entity field block definitions for every field. @@ -44,6 +45,13 @@ class ExtraFieldBlockDeriver extends DeriverBase implements ContainerDeriverInte */ protected $entityTypeBundleInfo; + /** + * The entity type repository. + * + * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface + */ + protected $entityTypeRepository; + /** * Constructs new FieldBlockDeriver. * @@ -53,11 +61,14 @@ class ExtraFieldBlockDeriver extends DeriverBase implements ContainerDeriverInte * The entity type manager. * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info * The entity type bundle info. + * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository + * The entity type repository. */ - public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info) { + public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeRepositoryInterface $entity_type_repository) { $this->entityFieldManager = $entity_field_manager; $this->entityTypeManager = $entity_type_manager; $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->entityTypeRepository = $entity_type_repository; } /** @@ -67,7 +78,8 @@ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( $container->get('entity_field.manager'), $container->get('entity_type.manager'), - $container->get('entity_type.bundle.info') + $container->get('entity_type.bundle.info'), + $container->get('entity_type.repository') ); } @@ -75,6 +87,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) { * {@inheritdoc} */ public function getDerivativeDefinitions($base_plugin_definition) { + $entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels(); foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { // Only process fieldable entity types. if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) { @@ -92,7 +105,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { foreach ($extra_fields['display'] as $extra_field_id => $extra_field) { $derivative = $base_plugin_definition; - $derivative['category'] = $entity_type->getLabel(); + $derivative['category'] = $this->t('@entity fields', ['@entity' => $entity_type_labels[$entity_type_id]]); $derivative['admin_label'] = $extra_field['label']; diff --git a/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php b/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php index 97ac81140..a2a94a024 100644 --- a/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php +++ b/core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php @@ -9,6 +9,7 @@ use Drupal\Core\Field\FieldConfigInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Field\FormatterPluginManager; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -123,6 +124,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { $context_definition->addConstraint('Bundle', [$bundle]); $derivative['context_definitions'] = [ 'entity' => $context_definition, + 'view_mode' => new ContextDefinition('string'), ]; $derivative_id = $entity_type_id . PluginBase::DERIVATIVE_SEPARATOR . $bundle . PluginBase::DERIVATIVE_SEPARATOR . $field_name; diff --git a/core/modules/layout_builder/src/Plugin/Layout/MultiWidthLayoutBase.php b/core/modules/layout_builder/src/Plugin/Layout/MultiWidthLayoutBase.php index 71ab112df..9ad23e0a2 100644 --- a/core/modules/layout_builder/src/Plugin/Layout/MultiWidthLayoutBase.php +++ b/core/modules/layout_builder/src/Plugin/Layout/MultiWidthLayoutBase.php @@ -18,8 +18,9 @@ abstract class MultiWidthLayoutBase extends LayoutDefault implements PluginFormI * {@inheritdoc} */ public function defaultConfiguration() { + $configuration = parent::defaultConfiguration(); $width_classes = array_keys($this->getWidthOptions()); - return [ + return $configuration + [ 'column_widths' => array_shift($width_classes), ]; } @@ -35,19 +36,14 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#options' => $this->getWidthOptions(), '#description' => $this->t('Choose the column widths for this layout.'), ]; - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + return parent::buildConfigurationForm($form, $form_state); } /** * {@inheritdoc} */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); $this->configuration['column_widths'] = $form_state->getValue('column_widths'); } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php index 8c6e8afb2..de8f1bfc4 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php @@ -2,6 +2,7 @@ namespace Drupal\layout_builder\Plugin\SectionStorage; +use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\RefinableCacheableDependencyInterface; @@ -33,6 +34,7 @@ * weight = 20, * context_definitions = { * "display" = @ContextDefinition("entity:entity_view_display"), + * "view_mode" = @ContextDefinition("string", default_value = "default"), * }, * ) * @@ -432,4 +434,15 @@ public function isApplicable(RefinableCacheableDependencyInterface $cacheability return $this->isLayoutBuilderEnabled(); } + /** + * {@inheritdoc} + */ + public function setContext($name, ComponentContextInterface $context) { + // Set the view mode context based on the display context. + if ($name === 'display') { + $this->setContextValue('view_mode', $context->getContextValue()->getMode()); + } + parent::setContext($name, $context); + } + } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index cde6f497c..eb3e12ef9 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -39,7 +39,7 @@ * "entity" = @ContextDefinition("entity", constraints = { * "EntityHasField" = \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::FIELD_NAME, * }), - * "view_mode" = @ContextDefinition("string"), + * "view_mode" = @ContextDefinition("string", default_value = "default"), * } * ) * diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php index 8c6d506c1..71b444a68 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php @@ -2,6 +2,8 @@ namespace Drupal\layout_builder\Plugin\SectionStorage; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait; use Drupal\layout_builder\Section; @@ -28,7 +30,7 @@ abstract class SectionStorageBase extends ContextAwarePluginBase implements Sect * * @throws \Exception * - * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. This + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. This * method should no longer be used. The section list should be derived from * context. See https://www.drupal.org/node/3016262. */ @@ -109,7 +111,16 @@ public function removeAllSections($set_blank = FALSE) { * {@inheritdoc} */ public function getContextsDuringPreview() { - return $this->getContexts(); + $contexts = $this->getContexts(); + + // view_mode is a required context, but SectionStorage plugins are not + // required to return it (for example, the layout_library plugin provided + // in the Layout Library module. In these instances, explicitly create a + // view_mode context with the value "default". + if (!isset($contexts['view_mode']) || $contexts['view_mode']->validate()->count() || !$contexts['view_mode']->getContextValue()) { + $contexts['view_mode'] = new Context(new ContextDefinition('string'), 'default'); + } + return $contexts; } /** diff --git a/core/modules/layout_builder/src/QuickEditIntegration.php b/core/modules/layout_builder/src/QuickEditIntegration.php index 5492b4f33..9ee5045fb 100644 --- a/core/modules/layout_builder/src/QuickEditIntegration.php +++ b/core/modules/layout_builder/src/QuickEditIntegration.php @@ -129,15 +129,18 @@ public function entityViewAlter(array &$build, EntityInterface $entity, EntityVi $plugin_ids_to_update = []; foreach (Element::children($build['_layout_builder']) as $delta) { $section = $build['_layout_builder'][$delta]; - /** @var \Drupal\Core\Layout\LayoutDefinition $layout */ - $layout = $section['#layout']; - $regions = $layout->getRegionNames(); - foreach ($regions as $region) { - if (isset($section[$region])) { - foreach ($section[$region] as $uuid => $component) { - if (isset($component['#plugin_id']) && $this->supportQuickEditOnComponent($component, $entity)) { - $plugin_ids_to_update[$component['#plugin_id']][$delta][$region][$uuid] = $uuid; + if (!Element::isEmpty($section)) { + /** @var \Drupal\Core\Layout\LayoutDefinition $layout */ + $layout = $section['#layout']; + $regions = $layout->getRegionNames(); + + foreach ($regions as $region) { + if (isset($section[$region])) { + foreach ($section[$region] as $uuid => $component) { + if (isset($component['#plugin_id']) && $this->supportQuickEditOnComponent($component, $entity)) { + $plugin_ids_to_update[$component['#plugin_id']][$delta][$region][$uuid] = $uuid; + } } } } diff --git a/core/modules/layout_builder/src/Section.php b/core/modules/layout_builder/src/Section.php index 73225d8fb..41fe13c57 100644 --- a/core/modules/layout_builder/src/Section.php +++ b/core/modules/layout_builder/src/Section.php @@ -98,7 +98,7 @@ public function toRenderArray(array $contexts = [], $in_preview = FALSE) { * The layout plugin. */ public function getLayout() { - return $this->layoutPluginManager()->createInstance($this->getLayoutId(), $this->getLayoutSettings()); + return $this->layoutPluginManager()->createInstance($this->getLayoutId(), $this->layoutSettings); } /** @@ -124,7 +124,7 @@ public function getLayoutId() { * This method should only be used by code responsible for storing the data. */ public function getLayoutSettings() { - return $this->layoutSettings; + return $this->getLayout()->getConfiguration(); } /** diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php index 388d0027d..323fee50b 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php @@ -73,7 +73,7 @@ public function loadEmpty($type); * @throws \InvalidArgumentException * Thrown if the ID is invalid. * - * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. * \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::load() * should be used instead. See https://www.drupal.org/node/3012353. */ @@ -98,7 +98,7 @@ public function loadFromStorageId($type, $id); * * @see \Drupal\Core\ParamConverter\ParamConverterInterface::convert() * - * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. * \Drupal\layout_builder\SectionStorageInterface::deriveContextsFromRoute() * and \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::load() * should be used instead. See https://www.drupal.org/node/3012353. diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php index 41891bb7f..b555e9a93 100644 --- a/core/modules/layout_builder/src/SectionStorageInterface.php +++ b/core/modules/layout_builder/src/SectionStorageInterface.php @@ -47,7 +47,7 @@ public function getStorageType(); * @internal * This should only be called during section storage instantiation. * - * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. The + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. The * section list should be derived from context. See * https://www.drupal.org/node/3016262. */ @@ -105,7 +105,7 @@ public function getLayoutBuilderUrl($rel = 'view'); * @internal * This should only be called during section storage instantiation. * - * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. * \Drupal\layout_builder\SectionStorageInterface::deriveContextsFromRoute() * should be used instead. See https://www.drupal.org/node/3016262. */ diff --git a/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/layout_builder_defaults_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/layout_builder_defaults_test.info.yml index f75aeeac0..ab5323585 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/layout_builder_defaults_test.info.yml +++ b/core/modules/layout_builder/tests/modules/layout_builder_defaults_test/layout_builder_defaults_test.info.yml @@ -2,11 +2,5 @@ name: 'Layout Builder defaults test' type: module description: 'Support module for testing layout building defaults.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.info.yml new file mode 100644 index 000000000..589c25610 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.info.yml @@ -0,0 +1,6 @@ +name: 'Layout Builder Field Block Theme Suggestions Test' +type: module +description: 'Support module for testing.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.module b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.module new file mode 100644 index 000000000..729f3a333 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/layout_builder_field_block_theme_suggestions_test.module @@ -0,0 +1,19 @@ + [ + 'base hook' => 'field', + ], + ]; +} diff --git a/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/templates/field--node--body--bundle-with-section-field--default.html.twig b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/templates/field--node--body--bundle-with-section-field--default.html.twig new file mode 100644 index 000000000..d48ff3426 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_field_block_theme_suggestions_test/templates/field--node--body--bundle-with-section-field--default.html.twig @@ -0,0 +1 @@ +

I am a field template for a specific view mode!

diff --git a/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.info.yml index a71f2c837..607877e78 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.info.yml +++ b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.info.yml @@ -2,11 +2,5 @@ name: 'Layout Builder test' type: module description: 'Support module for testing layout building.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.services.yml b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.services.yml new file mode 100644 index 000000000..ed7b1f92c --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/layout_builder_fieldblock_test.services.yml @@ -0,0 +1,5 @@ +services: + layout_builder_fieldblock_test.fake_view_mode_context: + class: Drupal\layout_builder_fieldblock_test\ContextProvider\FakeViewModeContext + tags: + - { name: 'context_provider' } diff --git a/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/src/ContextProvider/FakeViewModeContext.php b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/src/ContextProvider/FakeViewModeContext.php new file mode 100644 index 000000000..1dfbf8f29 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_fieldblock_test/src/ContextProvider/FakeViewModeContext.php @@ -0,0 +1,30 @@ + new Context(new ContextDefinition('string'), 'default')]; + } + + /** + * {@inheritdoc} + */ + public function getAvailableContexts() { + return $this->getRuntimeContexts([]); + } + +} diff --git a/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml index 6355b82c3..f9ed71170 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml +++ b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml @@ -2,11 +2,5 @@ name: 'Layout Builder overrides test' type: module description: 'Support module for testing overriding layout building.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.info.yml index a71f2c837..607877e78 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.info.yml +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.info.yml @@ -2,11 +2,5 @@ name: 'Layout Builder test' type: module description: 'Support module for testing layout building.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/css/layout_builder_test_css_transitions.test.css b/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/css/layout_builder_test_css_transitions.test.css deleted file mode 100644 index e36e72352..000000000 --- a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/css/layout_builder_test_css_transitions.test.css +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Remove all transitions for testing. - */ -* { - /* CSS transitions. */ - transition: none !important; -} diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.info.yml deleted file mode 100644 index 12666ebdc..000000000 --- a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.info.yml +++ /dev/null @@ -1,13 +0,0 @@ -# @todo Remove this module & its usages in https://www.drupal.org/node/2901792. -name: 'Layout Builder Test Disable Animations' -type: module -description: 'Disables CSS animations for tests ' -package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.libraries.yml b/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.libraries.yml deleted file mode 100644 index f010cdfb7..000000000 --- a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.libraries.yml +++ /dev/null @@ -1,4 +0,0 @@ -layout_builder.disable_css_transitions: - css: - component: - css/layout_builder_test_css_transitions.test.css: {} diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.module b/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.module deleted file mode 100644 index 293910f6d..000000000 --- a/core/modules/layout_builder/tests/modules/layout_builder_test_css_transitions/layout_builder_test_css_transitions.module +++ /dev/null @@ -1,16 +0,0 @@ -createContentType([ + 'type' => 'bundle_with_section_field', + 'name' => 'Bundle with section field', + ]); + $this->createNode([ + 'type' => 'bundle_with_section_field', + 'title' => 'A node title', + 'body' => [ + [ + 'value' => 'This is content that the template should not render', + ], + ], + ]); + + $this->drupalLogin($this->drupalCreateUser([ + 'configure any layout', + 'administer node display', + ])); + + $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default'); + $this->drupalPostForm(NULL, ['layout[enabled]' => TRUE], 'Save'); + } + + /** + * Tests that of view mode specific field templates are suggested. + */ + public function testFieldBlockViewModeTemplates() { + $assert_session = $this->assertSession(); + + $this->drupalGet('node/1'); + // Confirm that content is displayed by layout builder. + $assert_session->elementExists('css', '.block-layout-builder'); + // Text that only appears in the view mode specific template. + $assert_session->pageTextContains('I am a field template for a specific view mode!'); + // The content of the body field should not be visible because it is + // displayed via a template that does not render it. + $assert_session->pageTextNotContains('This is content that the template should not render'); + } + +} diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderQuickEditTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderQuickEditTest.php index 755c9e5ea..6d786d2b1 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderQuickEditTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderQuickEditTest.php @@ -20,6 +20,11 @@ class LayoutBuilderQuickEditTest extends BrowserTestBase { 'quickedit', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -55,9 +60,9 @@ public function testPlaceFieldBlockFromDifferentEntityType() { // Place a field block for a user entity field. $this->drupalGet('node/1/layout'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $page->clickLink('Name'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $page->pressButton('Save layout'); $this->drupalGet('node/1'); diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php index c1dd02380..828bde2c1 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php @@ -20,6 +20,11 @@ class LayoutBuilderSectionStorageTest extends BrowserTestBase { 'layout_builder_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -62,11 +67,11 @@ public function testRenderByContextAwarePluginDelegate() { // Add a block to the defaults. $page->clickLink('Manage layout'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $page->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'Defaults block title'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $page->pressButton('Save layout'); $this->drupalGet('node/1'); diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php index a852d7bcc..daf54a4f7 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php @@ -29,6 +29,11 @@ class LayoutBuilderTest extends BrowserTestBase { 'layout_builder_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -109,11 +114,11 @@ public function testOverrides() { $assert_session->fieldNotExists('title[0][value]'); $assert_session->elementTextContains('css', '.layout-builder__message.layout-builder__message--overrides', 'You are editing the layout for this Bundle with section field content item. Edit the template for all Bundle with section field content items instead.'); $assert_session->linkExists('Edit the template for all Bundle with section field content items instead.'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $page->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'This is an override'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $page->pressButton('Save layout'); $assert_session->pageTextContains('This is an override'); @@ -189,9 +194,9 @@ public function testPreserverEntityValues() { // Create a layout override which will store the current node in the // tempstore. $page->clickLink('Layout'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $page->clickLink('Powered by Drupal'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); // Update the node to make a change that is not in the tempstore version. $node = Node::load(1); @@ -264,13 +269,13 @@ public function testLayoutBuilderUi() { $assert_session->pageTextContainsOnce('Placeholder for the "Extra label" field'); // Add a new block. - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->linkExists('Powered by Drupal'); $this->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'This is the label'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->pageTextContains('Powered by Drupal'); $assert_session->pageTextContains('This is the label'); $assert_session->addressEquals("$field_ui_prefix/display/default/layout"); @@ -296,12 +301,12 @@ public function testLayoutBuilderUi() { $assert_session->linkExists('Layout'); $this->clickLink('Layout'); $assert_session->pageTextContains('Placeholder for the "Extra label" field'); - $assert_session->linkExists('Remove section'); - $this->clickLink('Remove section'); + $assert_session->linkExists('Remove Section 1'); + $this->clickLink('Remove Section 1'); $page->pressButton('Remove'); // Add a new section. - $this->clickLink('Add Section'); + $this->clickLink('Add section'); $this->assertCorrectLayouts(); $assert_session->linkExists('Two column'); $this->clickLink('Two column'); @@ -320,11 +325,11 @@ public function testLayoutBuilderUi() { // Alter the defaults. $this->drupalGet("$field_ui_prefix/display/default/layout"); - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->linkExists('Title'); $this->clickLink('Title'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); // The title field is present. $assert_session->elementExists('css', '.field--name-title'); $page->pressButton('Save layout'); @@ -348,11 +353,11 @@ public function testLayoutBuilderUi() { // Reverting the override returns it to the defaults. $this->clickLink('Layout'); - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->linkExists('ID'); $this->clickLink('ID'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); // The title field is present. $assert_session->elementExists('css', '.field--name-nid'); $assert_session->pageTextContains('ID'); @@ -391,9 +396,16 @@ public function testLayoutBuilderUi() { $assert_session->pageTextNotContains('My text field'); $assert_session->elementNotExists('css', '.field--name-field-my-text'); + $this->clickLink('Add section'); + $this->clickLink('One column'); + $page->fillField('layout_settings[label]', 'My Cool Section'); + $page->pressButton('Add section'); + $expected_labels = [ - 'Section 1', - 'Content region in section 1', + 'My Cool Section', + 'Content region in My Cool Section', + 'Section 2', + 'Content region in Section 2', ]; $labels = []; foreach ($page->findAll('css', '[role="group"]') as $element) { @@ -525,10 +537,11 @@ public function testPluginDependencies() { $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display', ['layout[enabled]' => TRUE], 'Save'); $assert_session->linkExists('Manage layout'); $this->clickLink('Manage layout'); - $assert_session->linkExists('Add Section'); - $this->clickLink('Add Section'); + $assert_session->linkExists('Add section'); + $this->clickLink('Add section'); $assert_session->linkExists('Layout plugin (with dependencies)'); $this->clickLink('Layout plugin (with dependencies)'); + $page->pressButton('Add section'); $assert_session->elementExists('css', '.layout--layout-test-dependencies-plugin'); $assert_session->elementExists('css', '.field--name-body'); $page->pressButton('Save layout'); @@ -538,18 +551,18 @@ public function testPluginDependencies() { $assert_session->elementExists('css', '.field--name-body'); // Add a menu block. - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->linkExists('My Menu'); $this->clickLink('My Menu'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); // Add another block alongside the menu. - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->linkExists('Powered by Drupal'); $this->clickLink('Powered by Drupal'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); // Assert that the blocks are visible, and save the layout. $assert_session->pageTextContains('Powered by Drupal'); @@ -595,11 +608,11 @@ public function testLayoutBuilderUiFullViewMode() { // Customize the default view mode. $this->drupalGet("$field_ui_prefix/display/default/layout"); - $this->clickLink('Add Block'); + $this->clickLink('Add block'); $this->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'This is the default view mode'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->pageTextContains('This is the default view mode'); $page->pressButton('Save layout'); @@ -618,11 +631,11 @@ public function testLayoutBuilderUiFullViewMode() { $this->drupalPostForm("$field_ui_prefix/display/full", ['layout[enabled]' => TRUE], 'Save'); $this->drupalPostForm("$field_ui_prefix/display/full", ['layout[allow_custom]' => TRUE], 'Save'); $this->drupalGet("$field_ui_prefix/display/full/layout"); - $this->clickLink('Add Block'); + $this->clickLink('Add block'); $this->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'This is the full view mode'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->pageTextContains('This is the full view mode'); $page->pressButton('Save layout'); @@ -657,11 +670,11 @@ public function testLayoutBuilderUiFullViewMode() { $assert_session->pageTextNotContains('This is the default view mode'); // Create an override of the full view mode. - $this->clickLink('Add Block'); + $this->clickLink('Add block'); $this->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'This is an override of the full view mode'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->pageTextContains('This is an override of the full view mode'); $page->pressButton('Save layout'); @@ -695,11 +708,11 @@ public function testLayoutBuilderUiFullViewMode() { $assert_session->pageTextNotContains('This is the default view mode'); // Recreate an override of the full view mode. - $this->clickLink('Add Block'); + $this->clickLink('Add block'); $this->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'This is an override of the full view mode'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->pageTextContains('This is an override of the full view mode'); $page->pressButton('Save layout'); @@ -784,7 +797,7 @@ public function testLayoutBuilderChooseBlocksAlter() { $this->clickLink('Manage layout'); // Add a new block. - $this->clickLink('Add Block'); + $this->clickLink('Add block'); // Verify that blocks not modified are present. $assert_session->linkExists('Powered by Drupal'); @@ -803,13 +816,13 @@ public function testLayoutBuilderChooseBlocksAlter() { $this->clickLink('Manage layout'); // Add a new section. - $this->clickLink('Add Section', 1); + $this->clickLink('Add section', 1); $assert_session->linkExists('Two column'); $this->clickLink('Two column'); $assert_session->buttonExists('Add section'); $this->getSession()->getPage()->pressButton('Add section'); // Add a new block to second section. - $this->clickLink('Add Block', 1); + $this->clickLink('Add block', 1); // Verify that Changed block is present on second section. $assert_session->linkExists('Changed'); @@ -821,13 +834,24 @@ public function testLayoutBuilderChooseBlocksAlter() { public function testExtraFields() { $assert_session = $this->assertSession(); - $this->drupalLogin($this->drupalCreateUser(['administer node display'])); + $this->drupalLogin($this->drupalCreateUser([ + 'configure any layout', + 'administer node display', + ])); $this->drupalGet('node'); $assert_session->linkExists('Read more'); $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save'); + // Extra fields display under "Content fields". + $this->drupalGet("admin/structure/types/manage/bundle_with_section_field/display/default/layout"); + $this->clickLink('Add block'); + $page = $this->getSession()->getPage(); + $content_fields_category = $page->find('xpath', '//details/summary[contains(text(),"Content fields")]/parent::details'); + $extra_field = strpos($content_fields_category->getText(), 'Extra label'); + $this->assertTrue($extra_field !== FALSE); + $this->drupalGet('node'); $assert_session->linkExists('Read more'); } @@ -885,9 +909,9 @@ public function testDeletedView() { $assert_session->linkExists('Layout'); $this->clickLink('Layout'); - $this->clickLink('Add Block'); + $this->clickLink('Add block'); $this->clickLink('Test Block View'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->pageTextContains('Test Block View'); $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1'); @@ -921,13 +945,41 @@ public function testFormAlter() { $page->pressButton('Save'); $page->clickLink('Manage layout'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $page->clickLink('Powered by Drupal'); $assert_session->pageTextContains('Layout Builder Storage: node.bundle_with_section_field.default'); $assert_session->pageTextContains('Layout Builder Section: layout_onecol'); $assert_session->pageTextContains('Layout Builder Component: system_powered_by_block'); } + /** + * Tests the functionality of custom section labels. + */ + public function testSectionLabels() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $this->drupalLogin($this->drupalCreateUser([ + 'configure any layout', + 'administer node display', + ])); + + $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default'); + $page->checkField('layout[enabled]'); + $page->pressButton('Save'); + $page->checkField('layout[allow_custom]'); + $page->pressButton('Save'); + + $this->drupalGet('node/1/layout'); + $page->clickLink('Add section'); + $page->clickLink('One column'); + $page->fillField('layout_settings[label]', 'My Cool Section'); + $page->pressButton('Add section'); + $assert_session->pageTextContains('My Cool Section'); + $page->pressButton('Save layout'); + $assert_session->pageTextNotContains('My Cool Section'); + } + /** * Tests that sections can provide custom attributes. */ @@ -942,8 +994,9 @@ public function testCustomSectionAttributes() { $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save'); $page->clickLink('Manage layout'); - $page->clickLink('Add Section'); + $page->clickLink('Add section'); $page->clickLink('Layout Builder Test Plugin'); + $page->pressButton('Add section'); // See \Drupal\layout_builder_test\Plugin\Layout\LayoutBuilderTestPlugin::build(). $assert_session->elementExists('css', '.go-birds'); } @@ -970,10 +1023,10 @@ public function testBlockPlaceholder() { $this->drupalGet("$field_ui_prefix/display/default/layout"); // Add a block whose content is controlled by state and is empty by default. - $this->clickLink('Add Block'); + $this->clickLink('Add block'); $this->clickLink('Test block caching'); $page->fillField('settings[label]', 'The block label'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $block_content = 'I am content'; $placeholder_content = 'Placeholder for the "The block label" block'; @@ -1115,9 +1168,12 @@ public function testRemovingAllSections() { $assert_session = $this->assertSession(); $page = $this->getSession()->getPage(); + // Install Quick Edit as well. + $this->container->get('module_installer')->install(['quickedit']); $this->drupalLogin($this->drupalCreateUser([ 'configure any layout', 'administer node display', + 'access in-place editing', ])); $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field'; @@ -1136,7 +1192,7 @@ public function testRemovingAllSections() { $assert_session->elementsCount('css', '.layout-builder__add-section', 2); // Remove the only section from the override. - $page->clickLink('Remove section'); + $page->clickLink('Remove Section 1'); $page->pressButton('Remove'); $assert_session->elementsCount('css', '.layout', 0); $assert_session->elementsCount('css', '.layout-builder__add-block', 0); @@ -1153,8 +1209,9 @@ public function testRemovingAllSections() { $assert_session->elementsCount('css', '.layout-builder__add-section', 1); // Add one section to the override. - $page->clickLink('Add Section'); + $page->clickLink('Add section'); $page->clickLink('One column'); + $page->pressButton('Add section'); $assert_session->elementsCount('css', '.layout', 1); $assert_session->elementsCount('css', '.layout-builder__add-block', 1); $assert_session->elementsCount('css', '.layout-builder__add-section', 2); @@ -1170,7 +1227,7 @@ public function testRemovingAllSections() { $assert_session->elementsCount('css', '.layout-builder__add-section', 2); // Remove the only section from the default. - $page->clickLink('Remove section'); + $page->clickLink('Remove Section 1'); $page->pressButton('Remove'); $assert_session->elementsCount('css', '.layout', 0); $assert_session->elementsCount('css', '.layout-builder__add-block', 0); @@ -1205,10 +1262,10 @@ protected function assertCorrectLayouts() { $assert_session = $this->assertSession(); // Ensure the layouts provided by layout_builder are available. $expected_layouts_hrefs = [ - 'layout_builder/add/section/overrides/node.1/0/layout_onecol', + 'layout_builder/configure/section/overrides/node.1/0/layout_onecol', 'layout_builder/configure/section/overrides/node.1/0/layout_twocol_section', 'layout_builder/configure/section/overrides/node.1/0/layout_threecol_section', - 'layout_builder/add/section/overrides/node.1/0/layout_fourcol_section', + 'layout_builder/configure/section/overrides/node.1/0/layout_fourcol_section', ]; foreach ($expected_layouts_hrefs as $expected_layouts_href) { $assert_session->linkByHrefExists($expected_layouts_href); @@ -1222,6 +1279,7 @@ protected function assertCorrectLayouts() { ]; foreach ($unexpected_layouts as $unexpected_layout) { $assert_session->linkByHrefNotExists("layout_builder/add/section/overrides/node.1/0/$unexpected_layout"); + $assert_session->linkByHrefNotExists("layout_builder/configure/section/overrides/node.1/0/$unexpected_layout"); } } diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php index 819047cf2..0a0d5bedc 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php @@ -24,6 +24,11 @@ class LayoutBuilderTranslationTest extends ContentTranslationTestBase { 'block', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The entity used for testing. * @@ -204,11 +209,11 @@ protected function addLayoutOverride() { $assert_session->pageTextContains('The untranslated field value'); // Adjust the layout. - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->linkExists('Powered by Drupal'); $this->clickLink('Powered by Drupal'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->pageTextContains('Powered by Drupal'); $assert_session->buttonExists('Save layout'); diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php index e6e4a2b0d..a697b61e1 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php @@ -16,6 +16,11 @@ class LayoutDisplayTest extends BrowserTestBase { */ protected static $modules = ['field_ui', 'layout_builder', 'block', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -53,11 +58,11 @@ public function testMultipleViewModes() { $assert_session->pageTextNotContains('Powered by Drupal'); $this->drupalGet('node/1/layout'); - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->linkExists('Powered by Drupal'); $this->clickLink('Powered by Drupal'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $page->pressButton('Save'); $assert_session->pageTextContains('Powered by Drupal'); diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php index 86ae521b1..dc7956238 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php @@ -20,6 +20,11 @@ class LayoutSectionTest extends BrowserTestBase { */ public static $modules = ['field_ui', 'layout_builder', 'node', 'block_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonAnonTest.php b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonAnonTest.php index 61fa91eff..316c1f891 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonAnonTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonAnonTest.php @@ -22,4 +22,9 @@ class LayoutBuilderEntityViewDisplayJsonAnonTest extends LayoutBuilderEntityView */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonBasicAuthTest.php b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonBasicAuthTest.php index d92138f98..74fba46e5 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonBasicAuthTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class LayoutBuilderEntityViewDisplayJsonBasicAuthTest extends LayoutBuilderEntit */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonCookieTest.php b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonCookieTest.php index c96e7641b..de311a732 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonCookieTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayJsonCookieTest.php @@ -27,4 +27,9 @@ class LayoutBuilderEntityViewDisplayJsonCookieTest extends LayoutBuilderEntityVi */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlAnonTest.php b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlAnonTest.php index 8c337c655..63d3ba066 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlAnonTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlAnonTest.php @@ -24,4 +24,9 @@ class LayoutBuilderEntityViewDisplayXmlAnonTest extends LayoutBuilderEntityViewD */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlBasicAuthTest.php b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlBasicAuthTest.php index 6ec588069..6b9b9e35d 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlBasicAuthTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlBasicAuthTest.php @@ -19,6 +19,11 @@ class LayoutBuilderEntityViewDisplayXmlBasicAuthTest extends LayoutBuilderEntity */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlCookieTest.php b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlCookieTest.php index 92925eca9..fef266ca4 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlCookieTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutBuilderEntityViewDisplayXmlCookieTest.php @@ -29,4 +29,9 @@ class LayoutBuilderEntityViewDisplayXmlCookieTest extends LayoutBuilderEntityVie */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutRestTestBase.php b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutRestTestBase.php index 36e8ee74d..752289162 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/LayoutRestTestBase.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/LayoutRestTestBase.php @@ -74,11 +74,11 @@ public function setUp() { ]); $this->drupalGet('node/' . $this->node->id() . '/layout'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $page->clickLink('Powered by Drupal'); $page->fillField('settings[label]', 'This is an override'); $page->checkField('settings[label_display]'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $page->pressButton('Save layout'); $assert_session->pageTextContains('This is an override'); diff --git a/core/modules/layout_builder/tests/src/Functional/Rest/OverrideSectionsTest.php b/core/modules/layout_builder/tests/src/Functional/Rest/OverrideSectionsTest.php index 6d01a6803..8549cfcbe 100644 --- a/core/modules/layout_builder/tests/src/Functional/Rest/OverrideSectionsTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Rest/OverrideSectionsTest.php @@ -20,6 +20,11 @@ class OverrideSectionsTest extends LayoutRestTestBase { */ protected static $resourceConfigId = 'entity.node'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php index 413d0eae0..3531160ce 100644 --- a/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php @@ -58,6 +58,7 @@ public function testRunUpdates() { // The display with existing sections is enabled while the other is not. $expected['enabled'] = TRUE; + $expected['sections'][0]['layout_settings']['label'] = ''; $this->assertLayoutBuilderSettings($expected, 'block_content', 'basic', 'default'); $this->assertLayoutBuilderSettings(NULL, 'node', 'page', 'default'); diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php index 1667669d4..99580cdef 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php @@ -23,6 +23,11 @@ class AjaxBlockTest extends WebDriverTestBase { 'layout_builder_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -68,8 +73,8 @@ public function testAddAjaxBlock() { $assert_session->elementExists('css', '.field--name-body'); // Add a new block. - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->linkExists('TestAjax'); $this->clickLink('TestAjax'); @@ -86,7 +91,7 @@ public function testAddAjaxBlock() { } } // Then add the block. - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->assertWaitOnAjaxRequest(); $block_elements = $this->cssSelect('.block-layout-builder-test-testajax'); // Should be exactly one of these in there. diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFilterTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFilterTest.php index f7945d712..14e521cc9 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFilterTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFilterTest.php @@ -23,6 +23,11 @@ class BlockFilterTest extends WebDriverTestBase { 'user', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -53,8 +58,8 @@ public function testBlockFilter() { $assert_session->addressEquals("$field_ui_prefix/display/default/layout"); // Open the block listing. - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); // Get all blocks, for assertions later. diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFormMessagesTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFormMessagesTest.php index 226e670ce..39c366ed8 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFormMessagesTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/BlockFormMessagesTest.php @@ -26,6 +26,11 @@ class BlockFormMessagesTest extends WebDriverTestBase { 'contextual', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -38,6 +43,9 @@ protected function setUp() { * Tests that validation messages are shown on the block form. */ public function testValidationMessage() { + // @todo Work out why this fixes random fails in this test. + // https://www.drupal.org/project/drupal/issues/3055982 + $this->getSession()->resizeWindow(800, 1000); $assert_session = $this->assertSession(); $page = $this->getSession()->getPage(); @@ -56,18 +64,19 @@ public function testValidationMessage() { ); $this->clickElementWhenClickable($page->findLink('Manage layout')); $assert_session->addressEquals($field_ui_prefix . '/display/default/layout'); - $this->clickElementWhenClickable($page->findLink('Add Block')); + $this->clickElementWhenClickable($page->findLink('Add block')); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas .block-categories')); $this->clickElementWhenClickable($page->findLink('Powered by Drupal')); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas [name="settings[label]"]')); $page->findField('Title')->setValue(''); - $this->clickElementWhenClickable($page->findButton('Add Block')); + $this->clickElementWhenClickable($page->findButton('Add block')); $this->assertMessagesDisplayed(); $page->findField('Title')->setValue('New title'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $block_css_locator = '#layout-builder .block-system-powered-by-block'; $this->assertNotEmpty($assert_session->waitForElementVisible('css', $block_css_locator)); - $this->waitForNoElement('#drupal-off-canvas'); + + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->assertWaitOnAjaxRequest(); $this->drupalGet($this->getUrl()); $this->clickElementWhenClickable($page->findButton('Save layout')); @@ -83,21 +92,6 @@ public function testValidationMessage() { $this->assertMessagesDisplayed(); } - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * - * @todo Remove in https://www.drupal.org/node/2892440. - */ - protected function waitForNoElement($selector, $timeout = 10000) { - $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; - $this->assertJsCondition($condition, $timeout); - } - /** * Asserts that the validation messages are shown correctly. */ diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/ContentPreviewToggleTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/ContentPreviewToggleTest.php index 2b1395c48..ee5dfb1f0 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/ContentPreviewToggleTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/ContentPreviewToggleTest.php @@ -14,6 +14,7 @@ class ContentPreviewToggleTest extends WebDriverTestBase { use ContextualLinkClickTrait; + use LayoutBuilderSortTrait; /** * {@inheritdoc} @@ -25,6 +26,11 @@ class ContentPreviewToggleTest extends WebDriverTestBase { 'contextual', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -78,8 +84,7 @@ public function testContentPreviewToggle() { $page->uncheckField('layout-builder-content-preview'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.layout-builder-block__content-preview-placeholder-label')); - // Wait for preview content hide() to complete. - $this->waitForNoElement('[data-layout-content-preview-placeholder-label] .field--name-body:visible'); + // Confirm that block content is not on page. $assert_session->pageTextNotContains($content_preview_body_text); $this->assertContextualLinks(); @@ -92,9 +97,14 @@ public function testContentPreviewToggle() { // Confirm repositioning blocks works with content preview disabled. $this->assertOrderInPage([$links_field_placeholder_label, $body_field_placeholder_label]); - $links_block_placeholder_child = $assert_session->elementExists('css', "[data-layout-content-preview-placeholder-label='$links_field_placeholder_label'] div"); - $body_block_placeholder_child = $assert_session->elementExists('css', "[data-layout-content-preview-placeholder-label='$body_field_placeholder_label'] div"); - $body_block_placeholder_child->dragTo($links_block_placeholder_child); + $region_content = '.layout__region--content'; + $links_block = "[data-layout-content-preview-placeholder-label='$links_field_placeholder_label']"; + $body_block = "[data-layout-content-preview-placeholder-label='$body_field_placeholder_label']"; + + $assert_session->elementExists('css', $links_block . " div"); + $assert_session->elementExists('css', $body_block . " div"); + + $this->sortableAfter($links_block, $body_block, $region_content); $assert_session->assertWaitOnAjaxRequest(); // Check that the drag-triggered rebuild did not trigger content preview. @@ -124,7 +134,7 @@ protected function assertContextualLinks() { $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertNotEmpty($this->assertSession()->waitForButton('Close')); $page->pressButton('Close'); - $this->waitForNoElement('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); } /** @@ -147,19 +157,4 @@ protected function assertOrderInPage(array $items) { $this->assertCount(count($items), $blocks_with_expected_text); } - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * - * @todo Remove in https://www.drupal.org/node/2892440. - */ - protected function waitForNoElement($selector, $timeout = 10000) { - $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; - $this->assertJsCondition($condition, $timeout); - } - } diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/ContextualLinksTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/ContextualLinksTest.php index db398a096..630b9968c 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/ContextualLinksTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/ContextualLinksTest.php @@ -23,12 +23,16 @@ class ContextualLinksTest extends WebDriverTestBase { 'layout_builder', 'layout_builder_views_test', 'layout_test', - 'layout_builder_test_css_transitions', 'block', 'node', 'contextual', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -99,20 +103,21 @@ public function testContextualLinks() { * Adds block to the layout via Layout Builder's UI. * * @param string $block_name - * The block name as it appears in the Add Block form. + * The block name as it appears in the Add block form. */ protected function addBlock($block_name) { $assert_session = $this->assertSession(); $page = $this->getSession()->getPage(); - $assert_session->linkExists('Add Block'); - $page->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $page->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); $this->assertNotEmpty($assert_session->waitForElementVisible('css', "#drupal-off-canvas a:contains('$block_name')")); $page->clickLink($block_name); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '[data-drupal-selector=\'edit-actions-submit\']')); - $page->pressButton('Add Block'); - $this->waitForNoElement('#drupal-off-canvas'); + + $page->pressButton('Add block'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->assertWaitOnAjaxRequest(); } @@ -149,19 +154,4 @@ protected function assertCorrectContextualLinksInNode() { $this->assertNotEmpty($page->findAll('css', '.layout-content [data-contextual-id]')); } - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * - * @todo Remove in https://www.drupal.org/node/2892440. - */ - protected function waitForNoElement($selector, $timeout = 10000) { - $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; - $this->assertJsCondition($condition, $timeout); - } - } diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php index b0b371fd4..bfde4f09f 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php @@ -25,6 +25,11 @@ class FieldBlockTest extends WebDriverTestBase { 'layout_builder_fieldblock_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockPrivateFilesTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockPrivateFilesTest.php index c15bb4ddb..67f86e4e3 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockPrivateFilesTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockPrivateFilesTest.php @@ -26,6 +26,11 @@ class InlineBlockPrivateFilesTest extends InlineBlockTestBase { 'file', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * The file system service. * @@ -81,6 +86,9 @@ public function testPrivateFiles() { 'create and edit custom blocks', ])); $this->drupalGet('node/1/layout'); + // @todo Occasionally SQLite has database locks here. Waiting seems to + // resolve it. https://www.drupal.org/project/drupal/issues/3055983 + $assert_session->assertWaitOnAjaxRequest(); $file = $this->createPrivateFile('drupal.txt'); $file_real_path = $this->fileSystem->realpath($file->getFileUri()); @@ -192,7 +200,7 @@ protected function replaceFileInBlock(FileInterface $file) { protected function addInlineFileBlockToLayout($title, File $file) { $assert_session = $this->assertSession(); $page = $this->getSession()->getPage(); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); $this->assertNotEmpty($assert_session->waitForLink('Create custom block')); $this->clickLink('Create custom block'); @@ -200,7 +208,7 @@ protected function addInlineFileBlockToLayout($title, File $file) { $assert_session->fieldValueEquals('Title', ''); $page->findField('Title')->setValue($title); $this->attachFileToBlockForm($file); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $this->assertDialogClosedAndTextVisible($file->label(), static::INLINE_BLOCK_LOCATOR); } diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php index f1c2b67fb..a9356bacb 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php @@ -12,6 +12,11 @@ */ class InlineBlockTest extends InlineBlockTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Tests adding and editing of inline blocks. */ @@ -465,7 +470,7 @@ public function testAddWorkFlow() { $layout_default_path = 'admin/structure/types/manage/bundle_with_section_field/display/default/layout'; $this->drupalGet($layout_default_path); // Add a basic block with the body field set. - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); // Confirm that with no block content types the link does not appear. $assert_session->linkNotExists('Create custom block'); @@ -474,7 +479,7 @@ public function testAddWorkFlow() { $this->drupalGet($layout_default_path); // Add a basic block with the body field set. - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); // Confirm with only 1 type the "Create custom block" link goes directly t // block add form. @@ -487,7 +492,7 @@ public function testAddWorkFlow() { $this->drupalGet($layout_default_path); // Add a basic block with the body field set. - $page->clickLink('Add Block'); + $page->clickLink('Add block'); // Confirm that, when more than 1 type exists, "Create custom block" shows a // list of block types. $assert_session->assertWaitOnAjaxRequest(); @@ -519,7 +524,7 @@ public function testAddInlineBlocksPermission() { $this->drupalLogin($this->drupalCreateUser($permissions)); $this->drupalGet(static::FIELD_UI_PREFIX . '/display/default/layout'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas .block-categories')); if ($expected) { $assert_session->linkExists('Create custom block'); diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php index ca8cdc838..c3b7bf53d 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php @@ -116,8 +116,8 @@ protected function removeInlineBlockFromLayout() { $assert_session->waitForElement('css', "#drupal-off-canvas input[value='Remove']"); $assert_session->assertWaitOnAjaxRequest(); $page->find('css', '#drupal-off-canvas')->pressButton('Remove'); - $this->waitForNoElement('#drupal-off-canvas'); - $this->waitForNoElement(static::INLINE_BLOCK_LOCATOR); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', static::INLINE_BLOCK_LOCATOR); $assert_session->assertWaitOnAjaxRequest(); $assert_session->pageTextNotContains($block_text); } @@ -133,7 +133,7 @@ protected function removeInlineBlockFromLayout() { protected function addInlineBlockToLayout($title, $body) { $assert_session = $this->assertSession(); $page = $this->getSession()->getPage(); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); $this->assertNotEmpty($assert_session->waitForLink('Create custom block')); $this->clickLink('Create custom block'); @@ -143,7 +143,7 @@ protected function addInlineBlockToLayout($title, $body) { $assert_session->fieldValueEquals('Title', ''); $page->findField('Title')->setValue($title); $textarea->setValue($body); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $this->assertDialogClosedAndTextVisible($body, static::INLINE_BLOCK_LOCATOR); } @@ -167,7 +167,7 @@ protected function configureInlineBlock($old_body, $new_body, $block_css_locator $this->assertSame($old_body, $textarea->getValue()); $textarea->setValue($new_body); $page->pressButton('Update'); - $this->waitForNoElement('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->assertWaitOnAjaxRequest(); $this->assertDialogClosedAndTextVisible($new_body); } @@ -180,9 +180,11 @@ protected function configureInlineBlock($old_body, $new_body, $block_css_locator * @param int $timeout * (optional) Timeout in milliseconds, defaults to 10000. * - * @todo Remove in https://www.drupal.org/node/2892440. + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * Drupal\FunctionalJavascriptTests\JSWebAssert::assertNoElementAfterWait() */ protected function waitForNoElement($selector, $timeout = 10000) { + @trigger_error('::waitForNoElement is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Use \Drupal\FunctionalJavascriptTests\JSWebAssert::assertNoElementAfterWait() instead.', E_USER_DEPRECATED); $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; $this->assertJsCondition($condition, $timeout); } @@ -197,7 +199,7 @@ protected function waitForNoElement($selector, $timeout = 10000) { */ protected function assertDialogClosedAndTextVisible($text, $css_locator = NULL) { $assert_session = $this->assertSession(); - $this->waitForNoElement('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->elementNotExists('css', '#drupal-off-canvas'); if ($css_locator) { diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php index 92b48a776..73cb6ca99 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php @@ -19,6 +19,11 @@ class ItemLayoutFieldBlockTest extends WebDriverTestBase { 'layout_builder', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -64,7 +69,7 @@ public function testAddAjaxBlock() { $this->drupalGet('node/1/layout'); // Add a new block. - $this->clickLink('Add Block'); + $this->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); // Validate that only field blocks for layouted bundle are present. diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php index 8ee465821..4445b3ac9 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php @@ -8,7 +8,6 @@ use Drupal\Component\Render\FormattableMarkup; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait; -use WebDriver\Exception\UnknownError; /** * Tests the Layout Builder disables interactions of rendered blocks. @@ -31,9 +30,13 @@ class LayoutBuilderDisableInteractionsTest extends WebDriverTestBase { 'node', 'search', 'contextual', - 'layout_builder_test_css_transitions', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -74,7 +77,7 @@ protected function setUp() { 'info' => 'Block with iframe', 'body' => [ // Add iframe that should be non-interactive in Layout Builder preview. - 'value' => '', + 'value' => '', 'format' => 'full_html', ], ])->save(); @@ -84,6 +87,10 @@ protected function setUp() { * Tests that forms and links are disabled in the Layout Builder preview. */ public function testFormsLinksDisabled() { + // Resize window due to bug in Chromedriver when clicking on overlays over + // iFrames. + // @see https://bugs.chromium.org/p/chromedriver/issues/detail?id=2758 + $this->getSession()->resizeWindow(1200, 1200); $assert_session = $this->assertSession(); $page = $this->getSession()->getPage(); @@ -142,8 +149,8 @@ protected function addBlock($block_link_text, $rendered_locator) { $page = $this->getSession()->getPage(); // Add a new block. - $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#layout-builder a:contains(\'Add Block\')')); - $this->clickLink('Add Block'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#layout-builder a:contains(\'Add block\')')); + $this->clickLink('Add block'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas')); $assert_session->assertWaitOnAjaxRequest(); @@ -153,7 +160,7 @@ protected function addBlock($block_link_text, $rendered_locator) { // Wait for off-canvas dialog to reopen with block form. $this->assertNotEmpty($assert_session->waitForElementVisible('css', ".layout-builder-add-block")); $assert_session->assertWaitOnAjaxRequest(); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); // Wait for block form to be rendered in the Layout Builder. $this->assertNotEmpty($assert_session->waitForElement('css', $rendered_locator)); @@ -171,7 +178,7 @@ protected function assertElementUnclickable(NodeElement $element) { $tag_name = $element->getTagName(); $this->fail(new FormattableMarkup("@tag_name was clickable when it shouldn't have been", ['@tag_name' => $tag_name])); } - catch (UnknownError $e) { + catch (\Exception $e) { $this->assertContains('is not clickable at point', $e->getMessage()); } } @@ -203,7 +210,7 @@ protected function assertContextualLinksClickable() { $this->clickContextualLink('.block-field-blocknodebundle-with-section-fieldbody [data-contextual-id^="layout_builder_block"]', 'Configure'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.ui-dialog-titlebar [title="Close"]')); $page->pressButton('Close'); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); // Run the steps a second time after closing dialog, which reverses the // order that behaviors.layoutBuilderDisableInteractiveElements and @@ -211,7 +218,7 @@ protected function assertContextualLinksClickable() { $this->clickContextualLink('.block-field-blocknodebundle-with-section-fieldbody [data-contextual-id^="layout_builder_block"]', 'Configure'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas')); $page->pressButton('Close'); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $this->assertContextualLinkRetainsMouseup(); } @@ -300,26 +307,4 @@ protected function movePointerTo($selector) { $driver_session->moveto(['element' => $element->getID()]); } - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * @param string $message - * (optional) Custom message to display with the assertion. - * - * @todo: Remove after https://www.drupal.org/project/drupal/issues/2892440 - */ - public function assertNoElementAfterWait($selector, $timeout = 10000, $message = '') { - $page = $this->getSession()->getPage(); - if ($message === '') { - $message = "Element '$selector' was not on the page after wait."; - } - $this->assertTrue($page->waitFor($timeout / 1000, function () use ($page, $selector) { - return empty($page->find('css', $selector)); - }), $message); - } - } diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php index 03f69a770..be8f70d80 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php @@ -20,6 +20,11 @@ class LayoutBuilderOptInTest extends WebDriverTestBase { 'block', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderQuickEditTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderQuickEditTest.php index 1de9bd5e6..3c5fd56ff 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderQuickEditTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderQuickEditTest.php @@ -26,9 +26,13 @@ class LayoutBuilderQuickEditTest extends QuickEditJavascriptTestBase { public static $modules = [ 'node', 'layout_builder', - 'layout_builder_test_css_transitions', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * The article node under test. * @@ -96,12 +100,12 @@ public function testQuickEditIgnoresDuplicateFields() { $assert_session = $this->assertSession(); $this->loginLayoutAdmin(); $this->drupalGet('admin/structure/types/manage/article/display/default/layout'); - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas')); $assert_session->assertWaitOnAjaxRequest(); $page->clickLink('Body'); $assert_session->assertWaitOnAjaxRequest(); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->assertWaitOnAjaxRequest(); $page->pressButton('Save layout'); $this->assertNotEmpty($assert_session->waitForElement('css', '.messages--status')); diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderSortTrait.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderSortTrait.php new file mode 100644 index 000000000..23d4c83bf --- /dev/null +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderSortTrait.php @@ -0,0 +1,41 @@ + $script, + 'args' => [], + ]; + + $this->getSession()->getDriver()->getWebDriverSession()->execute($options); + } + +} diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php index a9622006a..f92195c9d 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php @@ -16,6 +16,7 @@ class LayoutBuilderTest extends WebDriverTestBase { use ContextualLinkClickTrait; + use LayoutBuilderSortTrait; /** * {@inheritdoc} @@ -28,6 +29,11 @@ class LayoutBuilderTest extends WebDriverTestBase { 'node', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * The node to customize with Layout Builder. * @@ -114,7 +120,7 @@ public function testLayoutBuilderUi() { $this->clickLink('Layout'); $this->markCurrentPage(); $assert_session->pageTextContains('The node body'); - $assert_session->linkExists('Add Section'); + $assert_session->linkExists('Add section'); // Add a new block. $this->openAddBlockForm('Powered by Drupal'); @@ -123,9 +129,9 @@ public function testLayoutBuilderUi() { $page->checkField('settings[label_display]'); // Save the new block, and ensure it is displayed on the page. - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->assertWaitOnAjaxRequest(); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->addressEquals($layout_url); $assert_session->pageTextContains('Powered by Drupal'); $assert_session->pageTextContains('This is the label'); @@ -149,8 +155,8 @@ public function testLayoutBuilderUi() { $this->drupalGet($layout_url); $this->markCurrentPage(); - $assert_session->linkExists('Add Section'); - $this->clickLink('Add Section'); + $assert_session->linkExists('Add section'); + $this->clickLink('Add section'); $this->assertNotEmpty($assert_session->waitForElementVisible('named', ['link', 'Two column'])); $this->clickLink('Two column'); @@ -158,16 +164,17 @@ public function testLayoutBuilderUi() { $page->pressButton('Add section'); $assert_session->assertWaitOnAjaxRequest(); - $this->assertNoElementAfterWait('.layout__region--second .block-system-powered-by-block'); + $assert_session->assertNoElementAfterWait('css', '.layout__region--second .block-system-powered-by-block'); $assert_session->elementTextNotContains('css', '.layout__region--second', 'Powered by Drupal'); // Drag the block to a region in different section. - $page->find('css', '.layout__region--content .block-system-powered-by-block')->dragTo($page->find('css', '.layout__region--second')); + $this->sortableTo('.block-system-powered-by-block', '.layout__region--content', '.layout__region--second'); $assert_session->assertWaitOnAjaxRequest(); // Ensure the drag succeeded. $assert_session->elementExists('css', '.layout__region--second .block-system-powered-by-block'); $assert_session->elementTextContains('css', '.layout__region--second', 'Powered by Drupal'); + $this->assertPageNotReloaded(); // Ensure the dragged block is still in the correct position after reload. @@ -190,7 +197,7 @@ public function testLayoutBuilderUi() { $page->fillField('settings[label]', 'This is the new label'); $page->pressButton('Update'); $assert_session->assertWaitOnAjaxRequest(); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->addressEquals($layout_url); $assert_session->pageTextContains('Powered by Drupal'); @@ -204,10 +211,10 @@ public function testLayoutBuilderUi() { $assert_session->pageTextContains('This action cannot be undone.'); $page->pressButton('Remove'); $assert_session->assertWaitOnAjaxRequest(); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->pageTextNotContains('Powered by Drupal'); - $assert_session->linkExists('Add Block'); + $assert_session->linkExists('Add block'); $assert_session->addressEquals($layout_url); $this->assertPageNotReloaded(); @@ -219,27 +226,27 @@ public function testLayoutBuilderUi() { $this->markCurrentPage(); $this->openAddBlockForm('My custom block'); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->pageTextContains('This is the block content'); // Remove both sections. - $assert_session->linkExists('Remove section'); - $this->clickLink('Remove section'); + $assert_session->linkExists('Remove Section 1'); + $this->clickLink('Remove Section 1'); $this->assertOffCanvasFormAfterWait('layout_builder_remove_section'); $assert_session->pageTextContains('Are you sure you want to remove section 1?'); $assert_session->pageTextContains('This action cannot be undone.'); $page->pressButton('Remove'); $assert_session->assertWaitOnAjaxRequest(); - $assert_session->linkExists('Remove section'); - $this->clickLink('Remove section'); + $assert_session->linkExists('Remove Section 1'); + $this->clickLink('Remove Section 1'); $this->assertOffCanvasFormAfterWait('layout_builder_remove_section'); $page->pressButton('Remove'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->pageTextNotContains('This is the block content'); - $assert_session->linkNotExists('Add Block'); + $assert_session->linkNotExists('Add block'); $this->assertPageNotReloaded(); $page->pressButton('Save layout'); @@ -274,8 +281,8 @@ public function testConfigurableLayoutSections() { $this->drupalGet($layout_url); $this->markCurrentPage(); - $assert_session->linkExists('Add Section'); - $this->clickLink('Add Section'); + $assert_session->linkExists('Add section'); + $this->clickLink('Add section'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->elementExists('css', '#drupal-off-canvas'); @@ -284,8 +291,8 @@ public function testConfigurableLayoutSections() { $assert_session->assertWaitOnAjaxRequest(); // Add another section. - $assert_session->linkExists('Add Section'); - $this->clickLink('Add Section'); + $assert_session->linkExists('Add section'); + $this->clickLink('Add section'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->elementExists('css', '#drupal-off-canvas'); @@ -296,18 +303,18 @@ public function testConfigurableLayoutSections() { $page->pressButton('Add section'); $assert_session->assertWaitOnAjaxRequest(); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->pageTextContains('Default'); - $assert_session->linkExists('Add Block'); + $assert_session->linkExists('Add block'); // Configure the existing section. - $assert_session->linkExists('Configure section 1'); - $this->clickLink('Configure section 1'); + $assert_session->linkExists('Configure Section 1'); + $this->clickLink('Configure Section 1'); $this->assertOffCanvasFormAfterWait('layout_builder_configure_section'); $page->fillField('layout_settings[setting_1]', 'Test setting value'); $page->pressButton('Update'); $assert_session->assertWaitOnAjaxRequest(); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $assert_session->pageTextContains('Test setting value'); $this->assertPageNotReloaded(); } @@ -341,6 +348,7 @@ public function testLayoutNoDialog() { ])); $assert_session->linkExists('One column'); $this->clickLink('One column'); + $page->pressButton('Add section'); // Add a block. $this->drupalGet(Url::fromRoute('layout_builder.add_block', [ @@ -350,10 +358,10 @@ public function testLayoutNoDialog() { 'region' => 'content', 'plugin_id' => 'system_powered_by_block', ])); - $this->assertNoElementAfterWait('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $page->fillField('settings[label]', 'The block label'); $page->fillField('settings[label_display]', TRUE); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); $assert_session->addressEquals($layout_url); $assert_session->pageTextContains('Powered by Drupal'); @@ -369,29 +377,7 @@ public function testLayoutNoDialog() { $assert_session->addressEquals($layout_url); $assert_session->pageTextNotContains('Powered by Drupal'); $assert_session->pageTextNotContains('The block label'); - $assert_session->linkNotExists('Add Block'); - } - - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * @param string $message - * (optional) Custom message to display with the assertion. - * - * @todo: Remove after https://www.drupal.org/project/drupal/issues/2892440 - */ - public function assertNoElementAfterWait($selector, $timeout = 10000, $message = '') { - $page = $this->getSession()->getPage(); - if ($message === '') { - $message = "Element '$selector' was not on the page after wait."; - } - $this->assertTrue($page->waitFor($timeout / 1000, function () use ($page, $selector) { - return empty($page->find('css', $selector)); - }), $message); + $assert_session->linkNotExists('Add block'); } /** @@ -464,8 +450,8 @@ private function enableLayoutsForBundle($path, $allow_custom = FALSE) { */ private function openAddBlockForm($block_title) { $assert_session = $this->assertSession(); - $assert_session->linkExists('Add Block'); - $this->clickLink('Add Block'); + $assert_session->linkExists('Add block'); + $this->clickLink('Add block'); $assert_session->assertWaitOnAjaxRequest(); $this->assertNotEmpty($assert_session->waitForElementVisible('named', ['link', $block_title])); $this->clickLink($block_title); diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderToolbarTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderToolbarTest.php index f1f61596a..de54949ef 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderToolbarTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderToolbarTest.php @@ -22,6 +22,11 @@ class LayoutBuilderToolbarTest extends WebDriverTestBase { 'toolbar', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php index eb6848abc..85cb669f4 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php @@ -29,9 +29,13 @@ class LayoutBuilderUiTest extends WebDriverTestBase { 'block_content', 'contextual', 'views', - 'layout_builder_test_css_transitions', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -65,19 +69,19 @@ public function testReloadWithNoSections() { // Remove all of the sections from the page. $this->drupalGet(static::FIELD_UI_PREFIX . '/display/default/layout'); - $page->clickLink('Remove section'); + $page->clickLink('Remove Section 1'); $assert_session->assertWaitOnAjaxRequest(); $page->pressButton('Remove'); $assert_session->assertWaitOnAjaxRequest(); // Assert that there are no sections on the page. - $assert_session->pageTextNotContains('Remove section'); - $assert_session->pageTextNotContains('Add Block'); + $assert_session->pageTextNotContains('Remove Section 1'); + $assert_session->pageTextNotContains('Add block'); // Reload the page. $this->drupalGet(static::FIELD_UI_PREFIX . '/display/default/layout'); // Assert that there are no sections on the page. - $assert_session->pageTextNotContains('Remove section'); - $assert_session->pageTextNotContains('Add Block'); + $assert_session->pageTextNotContains('Remove Section 1'); + $assert_session->pageTextNotContains('Add block'); } /** @@ -110,11 +114,13 @@ protected function assertModifiedLayout($path) { $page = $this->getSession()->getPage(); $this->drupalGet($path); - $page->clickLink('Add Section'); + $page->clickLink('Add section'); $assert_session->assertWaitOnAjaxRequest(); $assert_session->pageTextNotContains('You have unsaved changes.'); $page->clickLink('One column'); $assert_session->assertWaitOnAjaxRequest(); + $page->pressButton('Add section'); + $assert_session->assertWaitOnAjaxRequest(); $assert_session->pageTextContainsOnce('You have unsaved changes.'); // Reload the page. @@ -140,7 +146,7 @@ public function testAddHighlights() { $this->drupalGet(static::FIELD_UI_PREFIX . '/display/default/layout'); $assert_session->elementsCount('css', '.layout-builder__add-section', 2); $assert_session->elementNotExists('css', '.is-layout-builder-highlighted'); - $page->clickLink('Add Section'); + $page->clickLink('Add section'); $this->assertNotEmpty($assert_session->waitForElement('css', '#drupal-off-canvas .item-list')); $assert_session->assertWaitOnAjaxRequest(); @@ -162,14 +168,14 @@ public function testAddHighlights() { $assert_session->elementsCount('css', '.layout-builder__add-block', 3); // Add a custom block. - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'a:contains("Create custom block")')); $assert_session->assertWaitOnAjaxRequest(); // Highlight is present with ChooseBlockController::build(). $this->assertHighlightedElement('[data-layout-builder-highlight-id="block-0-first"]'); $page->clickLink('Create custom block'); - $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[value="Add Block"]')); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[value="Add block"]')); $assert_session->assertWaitOnAjaxRequest(); // Highlight is present with ChooseBlockController::inlineBlockList(). @@ -178,12 +184,12 @@ public function testAddHighlights() { $this->assertHighlightNotExists(); // The highlight should persist with all block config dialogs. - $page->clickLink('Add Block'); + $page->clickLink('Add block'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'a:contains("Recent content")')); $assert_session->assertWaitOnAjaxRequest(); $this->assertHighlightedElement('[data-layout-builder-highlight-id="block-0-first"]'); $page->clickLink('Recent content'); - $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[value="Add Block"]')); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[value="Add block"]')); // The highlight is present with ConfigureBlockFormBase::doBuildForm(). $this->assertHighlightedElement('[data-layout-builder-highlight-id="block-0-first"]'); @@ -191,14 +197,14 @@ public function testAddHighlights() { $this->assertHighlightNotExists(); // The highlight is present when the "Configure section" dialog is open. - $page->clickLink('Configure section'); + $page->clickLink('Configure Section 1'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas')); $this->assertHighlightedElement('[data-layout-builder-highlight-id="section-update-0"]'); $page->pressButton('Close'); $this->assertHighlightNotExists(); - // The highlight is present when the "Remove section" dialog is open. - $page->clickLink('Remove section'); + // The highlight is present when the "Remove Section" dialog is open. + $page->clickLink('Remove Section 1'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas')); $assert_session->assertWaitOnAjaxRequest(); $this->assertHighlightedElement('[data-layout-builder-highlight-id="section-update-0"]'); @@ -255,23 +261,10 @@ private function assertHighlightedElement($selector) { * Waits for the dialog to close and confirms no highlights are present. */ private function assertHighlightNotExists() { - $this->waitForNoElement('#drupal-off-canvas'); - $this->waitForNoElement('.is-layout-builder-highlighted'); - } + $assert_session = $this->assertSession(); - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * - * @todo Remove in https://www.drupal.org/node/2892440. - */ - protected function waitForNoElement($selector, $timeout = 10000) { - $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; - $this->assertJsCondition($condition, $timeout); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '.is-layout-builder-highlighted'); } } diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/MoveBlockFormTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/MoveBlockFormTest.php index d2a25a7af..7ceb0af8d 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/MoveBlockFormTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/MoveBlockFormTest.php @@ -31,6 +31,11 @@ class MoveBlockFormTest extends WebDriverTestBase { 'contextual', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -64,7 +69,7 @@ protected function setUp() { $this->assertRegionBlocksOrder(0, 'content', $expected_block_order); // Add a top section using the Two column layout. - $page->clickLink('Add Section'); + $page->clickLink('Add section'); $assert_session->waitForElementVisible('css', '#drupal-off-canvas'); $assert_session->assertWaitOnAjaxRequest(); $page->clickLink('Two column'); @@ -80,13 +85,13 @@ protected function setUp() { $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas a:contains("Powered by Drupal")')); $assert_session->assertWaitOnAjaxRequest(); $page->clickLink('Powered by Drupal'); - $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'input[value="Add Block"]')); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'input[value="Add block"]')); $assert_session->assertWaitOnAjaxRequest(); - $page->pressButton('Add Block'); + $page->pressButton('Add block'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', $first_region_block_locator)); // Ensure the request has completed before the test starts. - $this->waitForNoElement('#drupal-off-canvas'); $assert_session->assertWaitOnAjaxRequest(); } @@ -152,21 +157,6 @@ protected function assertBlockTable(array $expected_block_labels) { } } - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * - * @todo Remove in https://www.drupal.org/node/2892440. - */ - protected function waitForNoElement($selector, $timeout = 10000) { - $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; - $this->assertJsCondition($condition, $timeout); - } - /** * Moves a block in the draggable table. * @@ -221,7 +211,7 @@ protected function assertRegionBlocksOrder($section_delta, $region, array $expec $assert_session = $this->assertSession(); $assert_session->assertWaitOnAjaxRequest(); - $this->waitForNoElement('#drupal-off-canvas'); + $assert_session->assertNoElementAfterWait('css', '#drupal-off-canvas'); $region_selector = "[data-layout-delta=\"$section_delta\"] [data-region=\"$region\"]"; diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/TestMultiWidthLayoutsTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/TestMultiWidthLayoutsTest.php index 4b4ee0053..0049edc1e 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/TestMultiWidthLayoutsTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/TestMultiWidthLayoutsTest.php @@ -20,11 +20,15 @@ class TestMultiWidthLayoutsTest extends WebDriverTestBase { public static $modules = [ 'layout_builder', - 'layout_builder_test_css_transitions', 'block', 'node', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -82,8 +86,8 @@ public function testWidthChange() { ]; foreach ($width_options as $width_option) { $width = array_shift($width_option['widths']); - $assert_session->linkExists('Add Section'); - $page->clickLink('Add Section'); + $assert_session->linkExists('Add section'); + $page->clickLink('Add section'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', "#drupal-off-canvas a:contains(\"{$width_option['label']}\")")); $page->clickLink($width_option['label']); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[type="submit"][value="Add section"]')); @@ -91,36 +95,21 @@ public function testWidthChange() { $this->assertWidthClassApplied($width_option['class'] . $width); foreach ($width_option['widths'] as $width) { $width_class = $width_option['class'] . $width; - $assert_session->linkExists('Configure section 1'); - $page->clickLink('Configure section 1'); + $assert_session->linkExists('Configure Section 1'); + $page->clickLink('Configure Section 1'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[type="submit"][value="Update"]')); $page->findField('layout_settings[column_widths]')->setValue($width); $page->pressButton("Update"); $this->assertWidthClassApplied($width_class); } - $assert_session->linkExists('Remove section'); - $this->clickLink('Remove section'); + $assert_session->linkExists('Remove Section 1'); + $this->clickLink('Remove Section 1'); $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas input[type="submit"][value="Remove"]')); $page->pressButton('Remove'); - $this->waitForNoElement(".$width_class"); + $assert_session->assertNoElementAfterWait('css', ".$width_class"); } } - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - * - * @todo Remove in https://www.drupal.org/node/2892440. - */ - protected function waitForNoElement($selector, $timeout = 10000) { - $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; - $this->assertJsCondition($condition, $timeout); - } - /** * Asserts the width class is applied to the first section. * diff --git a/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php index 637da2994..1a3eeeb7e 100644 --- a/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/DefaultsSectionStorageTest.php @@ -2,7 +2,10 @@ namespace Drupal\Tests\layout_builder\Kernel; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\entity_test\Entity\EntityTest; use Drupal\KernelTests\KernelTestBase; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; @@ -51,7 +54,10 @@ protected function setUp() { $this->installEntitySchema('user'); $this->installConfig(['layout_builder_defaults_test']); - $this->plugin = DefaultsSectionStorage::create($this->container, [], 'defaults', new SectionStorageDefinition()); + $definition = (new SectionStorageDefinition()) + ->addContextDefinition('display', EntityContextDefinition::fromEntityTypeId('entity_view_display')) + ->addContextDefinition('view_mode', new ContextDefinition('string')); + $this->plugin = DefaultsSectionStorage::create($this->container, [], 'defaults', $definition); } /** @@ -63,7 +69,10 @@ public function testConfigInstall() { $section = $display->getSection(0); $this->assertInstanceOf(Section::class, $section); $this->assertEquals('layout_twocol_section', $section->getLayoutId()); - $this->assertEquals(['column_widths' => '50-50'], $section->getLayoutSettings()); + $this->assertEquals([ + 'column_widths' => '50-50', + 'label' => '', + ], $section->getLayoutSettings()); } /** @@ -141,8 +150,9 @@ public function testGetContexts() { $context = EntityContext::fromEntity($display); $this->plugin->setContext('display', $context); - $expected = ['display' => $context]; - $this->assertSame($expected, $this->plugin->getContexts()); + $result = $this->plugin->getContexts(); + $this->assertSame(['view_mode', 'display'], array_keys($result)); + $this->assertSame($context, $result['display']); } /** @@ -161,7 +171,7 @@ public function testGetContextsDuringPreview() { $this->plugin->setContext('display', $context); $result = $this->plugin->getContextsDuringPreview(); - $this->assertEquals(['display', 'layout_builder.entity'], array_keys($result)); + $this->assertSame(['view_mode', 'display', 'layout_builder.entity'], array_keys($result)); $this->assertSame($context, $result['display']); @@ -169,6 +179,10 @@ public function testGetContextsDuringPreview() { $result_value = $result['layout_builder.entity']->getContextValue(); $this->assertInstanceOf(EntityTest::class, $result_value); $this->assertSame('entity_test', $result_value->bundle()); + + $this->assertInstanceOf(Context::class, $result['view_mode']); + $result_value = $result['view_mode']->getContextValue(); + $this->assertSame('default', $result_value); } /** @@ -179,7 +193,8 @@ public function testGetContextsDuringPreview() { */ public function testSetSectionList() { $section_list = $this->prophesize(SectionListInterface::class); - $this->setExpectedException(\Exception::class, '\Drupal\layout_builder\SectionStorageInterface::setSectionList() must no longer be called. The section list should be derived from context. See https://www.drupal.org/node/3016262.'); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('\Drupal\layout_builder\SectionStorageInterface::setSectionList() must no longer be called. The section list should be derived from context. See https://www.drupal.org/node/3016262.'); $this->plugin->setSectionList($section_list->reveal()); } @@ -202,4 +217,24 @@ public function testGetTempstoreKey() { $this->assertSame('entity_test.entity_test.default', $result); } + /** + * Tests loading given a display. + */ + public function testLoadFromDisplay() { + $display = LayoutBuilderEntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + ]); + $display->save(); + $contexts = [ + 'display' => EntityContext::fromEntity($display), + ]; + + $section_storage_manager = $this->container->get('plugin.manager.layout_builder.section_storage'); + $section_storage = $section_storage_manager->load('defaults', $contexts); + $this->assertInstanceOf(DefaultsSectionStorage::class, $section_storage); + } + } diff --git a/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php b/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php index 4675046a8..ebf19c53a 100644 --- a/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FormatterPluginManager; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Form\EnforcedResponseException; use Drupal\Core\Plugin\Context\EntityContextDefinition; use Drupal\Core\Session\AccountInterface; @@ -210,6 +211,7 @@ protected function getTestBlock(ProphecyInterface $entity_prophecy, array $confi 'bundles' => ['entity_test'], 'context_definitions' => [ 'entity' => EntityContextDefinition::fromEntityTypeId('entity_test')->setLabel('Test'), + 'view_mode' => new ContextDefinition('string'), ], ]; $formatter_manager = $this->prophesize(FormatterPluginManager::class); @@ -225,6 +227,7 @@ protected function getTestBlock(ProphecyInterface $entity_prophecy, array $confi $this->logger->reveal() ); $block->setContextValue('entity', $entity_prophecy->reveal()); + $block->setContextValue('view_mode', 'default'); return $block; } @@ -300,7 +303,7 @@ public function testBuildWithFormException() { $entity->get('the_field_name')->willReturn($field->reveal()); $block = $this->getTestBlock($entity); - $this->setExpectedException(EnforcedResponseException::class); + $this->expectException(EnforcedResponseException::class); $block->build(); } diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php index 72ee01ac6..fa1047c37 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php @@ -47,7 +47,7 @@ protected function setUp() { // Set up a non-admin user that is allowed to view test entities. \Drupal::currentUser()->setAccount($this->createUser(['uid' => 2], ['view test entity'])); - \Drupal::service('theme_handler')->install(['classy']); + \Drupal::service('theme_installer')->install(['classy']); $this->config('system.theme')->set('default', 'classy')->save(); $field_storage = FieldStorageConfig::create([ diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php index 9a12b74b3..0e1639f8e 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php @@ -37,7 +37,7 @@ protected function getSectionStorage(array $section_data) { * Tests that configuration schema enforces valid values. */ public function testInvalidConfiguration() { - $this->setExpectedException(SchemaIncompleteException::class); + $this->expectException(SchemaIncompleteException::class); $this->sectionStorage->getSection(0)->getComponent('first-uuid')->setConfiguration(['id' => 'foo', 'bar' => 'baz']); $this->sectionStorage->save(); } @@ -61,6 +61,39 @@ public function testGetRuntimeSections() { $this->assertEquals($this->sectionStorage->getSections(), $result); } + /** + * @dataProvider providerTestIsLayoutBuilderEnabled + */ + public function testIsLayoutBuilderEnabled($expected, $view_mode, $enabled) { + $display = LayoutBuilderEntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => $view_mode, + 'status' => TRUE, + 'third_party_settings' => [ + 'layout_builder' => [ + 'enabled' => $enabled, + ], + ], + ]); + $result = $display->isLayoutBuilderEnabled(); + $this->assertSame($expected, $result); + } + + /** + * Provides test data for ::testIsLayoutBuilderEnabled(). + */ + public function providerTestIsLayoutBuilderEnabled() { + $data = []; + $data['default enabled'] = [TRUE, 'default', TRUE]; + $data['default disabled'] = [FALSE, 'default', FALSE]; + $data['full enabled'] = [TRUE, 'full', TRUE]; + $data['full disabled'] = [FALSE, 'full', FALSE]; + $data['_custom enabled'] = [FALSE, '_custom', TRUE]; + $data['_custom disabled'] = [FALSE, '_custom', FALSE]; + return $data; + } + /** * Tests that setting overridable enables Layout Builder only when set to TRUE. */ diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php index 54cc87d5b..dc9c412dd 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutEntityHelperTraitTest.php @@ -64,7 +64,7 @@ public function providerTestGetSectionStorageForEntity() { ], ], ], - ['display'], + ['display', 'view_mode'], ]; $data['fieldable entity'] = [ 'entity_test', diff --git a/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php index 66b4f1434..532a406ad 100644 --- a/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php @@ -120,7 +120,7 @@ public function testAccess($expected, $is_enabled, array $section_data, array $p */ public function providerTestAccess() { $section_data = [ - new Section('layout_default', [], [ + new Section('layout_onecol', [], [ 'first-uuid' => new SectionComponent('first-uuid', 'content', ['id' => 'foo']), ]), ]; @@ -216,7 +216,8 @@ public function testGetContextsDuringPreview() { */ public function testSetSectionList() { $section_list = $this->prophesize(SectionListInterface::class); - $this->setExpectedException(\Exception::class, '\Drupal\layout_builder\SectionStorageInterface::setSectionList() must no longer be called. The section list should be derived from context. See https://www.drupal.org/node/3016262.'); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('\Drupal\layout_builder\SectionStorageInterface::setSectionList() must no longer be called. The section list should be derived from context. See https://www.drupal.org/node/3016262.'); $this->plugin->setSectionList($section_list->reveal()); } @@ -285,7 +286,7 @@ public function testIsOverridden() { ->save(); $entity = EntityTest::create(); - $entity->set(OverridesSectionStorage::FIELD_NAME, [new Section('layout_default')]); + $entity->set(OverridesSectionStorage::FIELD_NAME, [new Section('layout_onecol')]); $entity->save(); $entity = EntityTest::load($entity->id()); diff --git a/core/modules/layout_builder/tests/src/Kernel/SectionListTraitTest.php b/core/modules/layout_builder/tests/src/Kernel/SectionListTraitTest.php index bb5b84d4b..02be9fefc 100644 --- a/core/modules/layout_builder/tests/src/Kernel/SectionListTraitTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/SectionListTraitTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\layout_builder\Kernel; +use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionListInterface; use Drupal\layout_builder\SectionStorage\SectionStorageTrait; @@ -23,7 +24,8 @@ protected function getSectionStorage(array $section_data) { * @covers ::addBlankSection */ public function testAddBlankSection() { - $this->setExpectedException(\Exception::class, 'A blank section must only be added to an empty list'); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('A blank section must only be added to an empty list'); $this->sectionStorage->addBlankSection(); } @@ -46,7 +48,11 @@ class TestSectionList implements SectionListInterface { * TestSectionList constructor. */ public function __construct(array $sections) { - $this->setSections($sections); + // Loop through each section and reconstruct it to ensure that all default + // values are present. + foreach ($sections as $section) { + $this->sections[] = Section::fromArray($section->toArray()); + } } /** diff --git a/core/modules/layout_builder/tests/src/Kernel/SectionStorageTestBase.php b/core/modules/layout_builder/tests/src/Kernel/SectionStorageTestBase.php index f943d5183..65a06183c 100644 --- a/core/modules/layout_builder/tests/src/Kernel/SectionStorageTestBase.php +++ b/core/modules/layout_builder/tests/src/Kernel/SectionStorageTestBase.php @@ -62,7 +62,7 @@ abstract protected function getSectionStorage(array $section_data); */ public function testGetSections() { $expected = [ - new Section('layout_test_plugin', [], [ + new Section('layout_test_plugin', ['setting_1' => 'Default'], [ 'first-uuid' => new SectionComponent('first-uuid', 'content', ['id' => 'foo']), ]), new Section('layout_test_plugin', ['setting_1' => 'bar'], [ @@ -83,7 +83,8 @@ public function testGetSection() { * @covers ::getSection */ public function testGetSectionInvalidDelta() { - $this->setExpectedException(\OutOfBoundsException::class, 'Invalid delta "2"'); + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage('Invalid delta "2"'); $this->sectionStorage->getSection(2); } @@ -92,16 +93,16 @@ public function testGetSectionInvalidDelta() { */ public function testInsertSection() { $expected = [ - new Section('layout_test_plugin', [], [ + new Section('layout_test_plugin', ['setting_1' => 'Default'], [ 'first-uuid' => new SectionComponent('first-uuid', 'content', ['id' => 'foo']), ]), - new Section('setting_1'), + new Section('layout_onecol'), new Section('layout_test_plugin', ['setting_1' => 'bar'], [ 'second-uuid' => new SectionComponent('second-uuid', 'content', ['id' => 'foo']), ]), ]; - $this->sectionStorage->insertSection(1, new Section('setting_1')); + $this->sectionStorage->insertSection(1, new Section('layout_onecol')); $this->assertSections($expected); } @@ -110,16 +111,16 @@ public function testInsertSection() { */ public function testAppendSection() { $expected = [ - new Section('layout_test_plugin', [], [ + new Section('layout_test_plugin', ['setting_1' => 'Default'], [ 'first-uuid' => new SectionComponent('first-uuid', 'content', ['id' => 'foo']), ]), new Section('layout_test_plugin', ['setting_1' => 'bar'], [ 'second-uuid' => new SectionComponent('second-uuid', 'content', ['id' => 'foo']), ]), - new Section('foo'), + new Section('layout_onecol'), ]; - $this->sectionStorage->appendSection(new Section('foo')); + $this->sectionStorage->appendSection(new Section('layout_onecol')); $this->assertSections($expected); } @@ -180,11 +181,11 @@ public function testRemoveMultipleSections() { * Tests __clone(). */ public function testClone() { - $this->assertSame([], $this->sectionStorage->getSection(0)->getLayoutSettings()); + $this->assertSame(['setting_1' => 'Default'], $this->sectionStorage->getSection(0)->getLayoutSettings()); $new_section_storage = clone $this->sectionStorage; $new_section_storage->getSection(0)->setLayoutSettings(['asdf' => 'qwer']); - $this->assertSame([], $this->sectionStorage->getSection(0)->getLayoutSettings()); + $this->assertSame(['setting_1' => 'Default'], $this->sectionStorage->getSection(0)->getLayoutSettings()); } /** diff --git a/core/modules/layout_builder/tests/src/Kernel/TranslatableFieldTest.php b/core/modules/layout_builder/tests/src/Kernel/TranslatableFieldTest.php index d22d7ea28..23d1dd103 100644 --- a/core/modules/layout_builder/tests/src/Kernel/TranslatableFieldTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/TranslatableFieldTest.php @@ -67,7 +67,7 @@ protected function setUp() { */ public function testSectionsClearedOnCreateTranslation() { $section_data = [ - new Section('layout_default', [], [ + new Section('layout_onecol', [], [ 'first-uuid' => new SectionComponent('first-uuid', 'content', ['id' => 'foo']), ]), ]; diff --git a/core/modules/layout_builder/tests/src/Unit/BlockComponentRenderArrayTest.php b/core/modules/layout_builder/tests/src/Unit/BlockComponentRenderArrayTest.php index 7b30433b0..117c76f7d 100644 --- a/core/modules/layout_builder/tests/src/Unit/BlockComponentRenderArrayTest.php +++ b/core/modules/layout_builder/tests/src/Unit/BlockComponentRenderArrayTest.php @@ -117,9 +117,6 @@ public function testOnBuildRender($refinable_dependent_access) { '#base_plugin_id' => 'block_plugin_id', '#derivative_plugin_id' => NULL, 'content' => $block_content, - '#attributes' => [ - 'data-layout-content-preview-placeholder-label' => $placeholder_label, - ], ]; $expected_cache = $expected_build + [ @@ -192,9 +189,6 @@ public function testOnBuildRenderWithoutPreviewFallbackString($refinable_depende '#base_plugin_id' => 'block_plugin_id', '#derivative_plugin_id' => NULL, 'content' => $block_content, - '#attributes' => [ - 'data-layout-content-preview-placeholder-label' => $placeholder_label, - ], ]; $expected_cache = $expected_build + [ diff --git a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php index f36b8fbc9..87580ad49 100644 --- a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php @@ -2,13 +2,17 @@ namespace Drupal\Tests\layout_builder\Unit; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityType; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\ContextInterface; +use Drupal\Core\Plugin\Context\EntityContextDefinition; +use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; use Drupal\layout_builder\Entity\SampleEntityGeneratorInterface; use Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage; @@ -32,9 +36,9 @@ class DefaultsSectionStorageTest extends UnitTestCase { protected $plugin; /** - * The entity manager. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; @@ -67,6 +71,17 @@ protected function setUp() { * @covers ::setThirdPartySetting */ public function testThirdPartySettings() { + $this->entityTypeManager->getDefinition('entity_view_display')->willReturn(new EntityType(['id' => 'entity_view_display'])); + + $container = new ContainerBuilder(); + $container->set('typed_data_manager', $this->prophesize(TypedDataManagerInterface::class)->reveal()); + $container->set('entity_type.manager', $this->entityTypeManager->reveal()); + \Drupal::setContainer($container); + + $this->plugin->getPluginDefinition() + ->addContextDefinition('display', EntityContextDefinition::fromEntityTypeId('entity_view_display')) + ->addContextDefinition('view_mode', new ContextDefinition('string')); + // Set an initial value on the section list. $section_list = $this->prophesize(LayoutEntityDisplayInterface::class); @@ -164,7 +179,7 @@ public function testGetSectionListFromId($success, $expected_entity_id, $value) } if (!$success) { - $this->setExpectedException(\InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); } $result = $this->plugin->getSectionListFromId($value); diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreRepositoryTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreRepositoryTest.php index fb46e608a..6269c7afa 100644 --- a/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreRepositoryTest.php +++ b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreRepositoryTest.php @@ -77,7 +77,8 @@ public function testGetInvalidEntry() { $repository = new LayoutTempstoreRepository($tempstore_factory->reveal()); - $this->setExpectedException(\UnexpectedValueException::class, 'The entry with storage type "my_storage_type" and ID "my_storage_id" is invalid'); + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('The entry with storage type "my_storage_type" and ID "my_storage_id" is invalid'); $repository->get($section_storage->reveal()); } diff --git a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php index 293a55c0a..eb03ae2e7 100644 --- a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php @@ -149,7 +149,7 @@ public function testGetSectionListFromId($success, $expected_entity_type_id, $id } if (!$success) { - $this->setExpectedException(\InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); } $result = $this->plugin->getSectionListFromId($id); diff --git a/core/modules/layout_builder/tests/src/Unit/SectionRenderTest.php b/core/modules/layout_builder/tests/src/Unit/SectionRenderTest.php index 574c00d49..5ef68d475 100644 --- a/core/modules/layout_builder/tests/src/Unit/SectionRenderTest.php +++ b/core/modules/layout_builder/tests/src/Unit/SectionRenderTest.php @@ -110,9 +110,6 @@ public function testToRenderArray() { '#base_plugin_id' => 'block_plugin_id', '#derivative_plugin_id' => NULL, 'content' => $block_content, - '#attributes' => [ - 'data-layout-content-preview-placeholder-label' => $placeholder_label, - ], '#cache' => [ 'contexts' => [], 'tags' => [], @@ -252,9 +249,6 @@ public function testContextAwareBlock() { '#base_plugin_id' => 'block_plugin_id', '#derivative_plugin_id' => NULL, 'content' => $block_content, - '#attributes' => [ - 'data-layout-content-preview-placeholder-label' => $placeholder_label, - ], '#cache' => [ 'contexts' => [], 'tags' => [], @@ -296,7 +290,8 @@ public function testContextAwareBlock() { * @covers ::toRenderArray */ public function testToRenderArrayMissingPluginId() { - $this->setExpectedException(PluginException::class, 'No plugin ID specified for component with "some_uuid" UUID'); + $this->expectException(PluginException::class); + $this->expectExceptionMessage('No plugin ID specified for component with "some_uuid" UUID'); (new Section('layout_onecol', [], [new SectionComponent('some_uuid', 'content')]))->toRenderArray(); } diff --git a/core/modules/layout_builder/tests/src/Unit/SectionTest.php b/core/modules/layout_builder/tests/src/Unit/SectionTest.php index 232e1719c..576eba7e1 100644 --- a/core/modules/layout_builder/tests/src/Unit/SectionTest.php +++ b/core/modules/layout_builder/tests/src/Unit/SectionTest.php @@ -59,7 +59,8 @@ public function testGetComponents() { * @covers ::getComponent */ public function testGetComponentInvalidUuid() { - $this->setExpectedException(\InvalidArgumentException::class, 'Invalid UUID "invalid-uuid"'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid UUID "invalid-uuid"'); $this->section->getComponent('invalid-uuid'); } @@ -122,7 +123,8 @@ public function testInsertAfterComponent() { * @covers ::insertAfterComponent */ public function testInsertAfterComponentValidUuidRegionMismatch() { - $this->setExpectedException(\InvalidArgumentException::class, 'Invalid preceding UUID "existing-uuid"'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid preceding UUID "existing-uuid"'); $this->section->insertAfterComponent('existing-uuid', new SectionComponent('new-uuid', 'ordered-region')); } @@ -130,7 +132,8 @@ public function testInsertAfterComponentValidUuidRegionMismatch() { * @covers ::insertAfterComponent */ public function testInsertAfterComponentInvalidUuid() { - $this->setExpectedException(\InvalidArgumentException::class, 'Invalid preceding UUID "invalid-uuid"'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid preceding UUID "invalid-uuid"'); $this->section->insertAfterComponent('invalid-uuid', new SectionComponent('new-uuid', 'ordered-region')); } @@ -169,7 +172,8 @@ public function testInsertComponentAppend() { * @covers ::insertComponent */ public function testInsertComponentInvalidDelta() { - $this->setExpectedException(\OutOfBoundsException::class, 'Invalid delta "7" for the "new-uuid" component'); + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage('Invalid delta "7" for the "new-uuid" component'); $this->section->insertComponent(7, new SectionComponent('new-uuid', 'ordered-region')); } diff --git a/core/modules/layout_discovery/layout_discovery.info.yml b/core/modules/layout_discovery/layout_discovery.info.yml index cc19c7d86..d0c8d3aff 100644 --- a/core/modules/layout_discovery/layout_discovery.info.yml +++ b/core/modules/layout_discovery/layout_discovery.info.yml @@ -2,11 +2,5 @@ name: 'Layout Discovery' type: module description: 'Provides a way for modules or themes to register layouts.' package: Core -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/layout_discovery/tests/themes/test_layout_theme/test_layout_theme.info.yml b/core/modules/layout_discovery/tests/themes/test_layout_theme/test_layout_theme.info.yml index 55503c213..021d43fd2 100644 --- a/core/modules/layout_discovery/tests/themes/test_layout_theme/test_layout_theme.info.yml +++ b/core/modules/layout_discovery/tests/themes/test_layout_theme/test_layout_theme.info.yml @@ -1,12 +1,6 @@ name: 'Test layout theme' type: theme description: 'Theme for testing a theme-provided layout' -# version: VERSION +version: VERSION base theme: classy -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +core: 8.x diff --git a/core/modules/link/link.info.yml b/core/modules/link/link.info.yml index 011e838d1..c3f8f9c94 100644 --- a/core/modules/link/link.info.yml +++ b/core/modules/link/link.info.yml @@ -1,14 +1,8 @@ name: Link type: module description: 'Provides a simple link field type.' -# core: 8.x +core: 8.x package: Field types -# version: VERSION +version: VERSION dependencies: - drupal:field - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/link/link.module b/core/modules/link/link.module index 0f5299498..fafa734f7 100644 --- a/core/modules/link/link.module +++ b/core/modules/link/link.module @@ -5,6 +5,7 @@ * Defines simple link field types. */ +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\Core\Routing\RouteMatchInterface; @@ -62,5 +63,5 @@ function link_theme() { * - url: A \Drupal\Core\Url object. */ function template_preprocess_link_formatter_link_separate(&$variables) { - $variables['link'] = \Drupal::l($variables['url_title'], $variables['url']); + $variables['link'] = Link::fromTextAndUrl($variables['url_title'], $variables['url'])->toString(); } diff --git a/core/modules/link/migrations/state/link.migrate_drupal.yml b/core/modules/link/migrations/state/link.migrate_drupal.yml new file mode 100644 index 000000000..4352ab4c7 --- /dev/null +++ b/core/modules/link/migrations/state/link.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + link: link + 7: + link: link diff --git a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php index ee688dd75..373c3f03a 100644 --- a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php +++ b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php @@ -190,12 +190,7 @@ public function setValue($values, $notify = TRUE) { // this, options must not be passed as a string anymore. if (is_string($values['options'])) { @trigger_error('Support for passing options as a serialized string is deprecated in 8.7.0 and will be removed before Drupal 9.0.0. Pass them as an array instead. See https://www.drupal.org/node/2961643.', E_USER_DEPRECATED); - if (version_compare(PHP_VERSION, '7.0.0', '>=')) { - $values['options'] = unserialize($values['options'], ['allowed_classes' => FALSE]); - } - else { - $values['options'] = unserialize($values['options']); - } + $values['options'] = unserialize($values['options'], ['allowed_classes' => FALSE]); } parent::setValue($values, $notify); } diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php index db00fedc9..278e8010a 100644 --- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php @@ -79,6 +79,9 @@ protected static function getUriAsDisplayableString($uri) { $displayable_string = EntityAutocomplete::getEntityLabels([$entity]); } } + elseif ($scheme === 'route') { + $displayable_string = ltrim($displayable_string, 'route:'); + } return $displayable_string; } @@ -111,6 +114,10 @@ protected static function getUserEnteredStringAsUri($string) { // https://www.drupal.org/node/2423093. $uri = 'entity:node/' . $entity_id; } + // Support linking to nothing. + elseif (in_array($string, ['', ''], TRUE)) { + $uri = 'route:' . $string; + } // Detect a schemeless string, map to 'internal:' URI. elseif (!empty($string) && parse_url($string, PHP_URL_SCHEME) === NULL) { // @todo '' is valid input for BC reasons, may be removed by @@ -209,12 +216,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen // element prefix and description. if (!$this->supportsExternalLinks()) { $element['uri']['#field_prefix'] = rtrim(Url::fromRoute('', [], ['absolute' => TRUE])->toString(), '/'); - $element['uri']['#description'] = $this->t('This must be an internal path such as %add-node. You can also start typing the title of a piece of content to select it. Enter %front to link to the front page.', ['%add-node' => '/node/add', '%front' => '']); + $element['uri']['#description'] = $this->t('This must be an internal path such as %add-node. You can also start typing the title of a piece of content to select it. Enter %front to link to the front page. Enter %nolink to display link text only.', ['%add-node' => '/node/add', '%front' => '', '%nolink' => '']); } // If the field is configured to allow both internal and external links, // show a useful description. elseif ($this->supportsExternalLinks() && $this->supportsInternalLinks()) { - $element['uri']['#description'] = $this->t('Start typing the title of a piece of content to select it. You can also enter an internal path such as %add-node or an external URL such as %url. Enter %front to link to the front page.', ['%front' => '', '%add-node' => '/node/add', '%url' => 'http://example.com']); + $element['uri']['#description'] = $this->t('Start typing the title of a piece of content to select it. You can also enter an internal path such as %add-node or an external URL such as %url. Enter %front to link to the front page. Enter %nolink to display link text only.', ['%front' => '', '%add-node' => '/node/add', '%url' => 'http://example.com', '%nolink' => '']); } // If the field is configured to allow only external links, show a useful // description. diff --git a/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php b/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php index 1a823f550..3ee4b650e 100644 --- a/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php +++ b/core/modules/link/src/Plugin/migrate/cckfield/LinkField.php @@ -18,7 +18,7 @@ * destination_module = "link" * ) * - * @deprecated in Drupal 8.3.x and will be removed in Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\link\Plugin\migrate\field\d6\LinkField instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php b/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php index a79640966..5e13b9552 100644 --- a/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php +++ b/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php @@ -22,7 +22,7 @@ * plugin with the exception that the plugin ID "link_field" is used in the * field type map. * - * @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\link\Plugin\migrate\field\d7\LinkField instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/link/src/Plugin/migrate/process/FieldLink.php b/core/modules/link/src/Plugin/migrate/process/FieldLink.php index 680049ce5..143b7ea09 100644 --- a/core/modules/link/src/Plugin/migrate/process/FieldLink.php +++ b/core/modules/link/src/Plugin/migrate/process/FieldLink.php @@ -73,7 +73,7 @@ protected function canonicalizeUri($uri) { // according to link module in Drupal 7. Every character between ¿ // and ÿ (except × × and ÷ ÷) with the addition of // Œ, œ and Ÿ. - // @see http://cgit.drupalcode.org/link/tree/link.module?h=7.x-1.5-beta2#n1382 + // @see https://git.drupalcode.org/project/link/blob/7.x-1.5-beta2/link.module#L1382 $link_ichars = '¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿŒœŸ'; // Pattern specific to internal links. @@ -119,7 +119,9 @@ public function transform($value, MigrateExecutableInterface $migrate_executable $attributes = unserialize($attributes); } - if (!$attributes) { + // In rare cases Drupal 6/7 link attributes are triple serialized. To avoid + // further problems with them we set them to an empty array in this case. + if (!is_array($attributes)) { $attributes = []; } diff --git a/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php b/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php index d9dc70097..84cac57e3 100644 --- a/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php +++ b/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php @@ -11,7 +11,7 @@ * id = "d6_cck_link" * ) * - * @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\link\Plugin\migrate\process\FieldLink instead. */ class CckLink extends FieldLink {} diff --git a/core/modules/link/src/Plugin/migrate/process/d6/FieldLink.php b/core/modules/link/src/Plugin/migrate/process/d6/FieldLink.php index 19c5e35ef..0a27254fe 100644 --- a/core/modules/link/src/Plugin/migrate/process/d6/FieldLink.php +++ b/core/modules/link/src/Plugin/migrate/process/d6/FieldLink.php @@ -11,7 +11,7 @@ * id = "d6_field_link" * ) * - * @deprecated in Drupal 8.4.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\link\Plugin\migrate\process\FieldLink instead. */ class FieldLink extends GeneralPurposeFieldLink {} diff --git a/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml b/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml index f3e19d5e3..ce07b8f95 100644 --- a/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml +++ b/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml @@ -1,14 +1,8 @@ name: Link test base field description: Tests link field as an optional base field type: module -# core: 8.x +core: 8.x hidden: true dependencies: - drupal:link - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/link/tests/modules/link_test_views/link_test_views.info.yml b/core/modules/link/tests/modules/link_test_views/link_test_views.info.yml index 8dbc09ce2..e40719e06 100644 --- a/core/modules/link/tests/modules/link_test_views/link_test_views.info.yml +++ b/core/modules/link/tests/modules/link_test_views/link_test_views.info.yml @@ -2,15 +2,9 @@ name: 'Link test views' type: module description: 'Provides default views for views link tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:node - drupal:views - drupal:link - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/link/tests/modules/link_test_views/test_views/views.view.test_link_tokens.yml b/core/modules/link/tests/modules/link_test_views/test_views/views.view.test_link_tokens.yml index 8f3ee26ea..975561599 100644 --- a/core/modules/link/tests/modules/link_test_views/test_views/views.view.test_link_tokens.yml +++ b/core/modules/link/tests/modules/link_test_views/test_views/views.view.test_link_tokens.yml @@ -14,7 +14,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/link/tests/src/Functional/LinkFieldTest.php b/core/modules/link/tests/src/Functional/LinkFieldTest.php index 76be35e77..fda83fd56 100644 --- a/core/modules/link/tests/src/Functional/LinkFieldTest.php +++ b/core/modules/link/tests/src/Functional/LinkFieldTest.php @@ -4,6 +4,8 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; +use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; @@ -11,6 +13,7 @@ use Drupal\node\NodeInterface; use Drupal\Tests\BrowserTestBase; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\Tests\Traits\Core\PathAliasTestTrait; /** * Tests link field widgets and formatters. @@ -19,6 +22,8 @@ */ class LinkFieldTest extends BrowserTestBase { + use PathAliasTestTrait; + /** * Modules to enable. * @@ -31,6 +36,11 @@ class LinkFieldTest extends BrowserTestBase { 'link_test_base_field', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A field to use in this test class. * @@ -76,7 +86,9 @@ public function testURLValidation() { ], ]); $this->field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $display_repository->getFormDisplay('entity_test', 'entity_test') ->setComponent($field_name, [ 'type' => 'link_default', 'settings' => [ @@ -84,7 +96,7 @@ public function testURLValidation() { ], ]) ->save(); - entity_get_display('entity_test', 'entity_test', 'full') + $display_repository->getViewDisplay('entity_test', 'entity_test', 'full') ->setComponent($field_name, [ 'type' => 'link', ]) @@ -96,7 +108,7 @@ public function testURLValidation() { $this->assertRaw('placeholder="http://example.com"'); // Create a path alias. - \Drupal::service('path.alias_storage')->save('/admin', '/a/path/alias'); + $this->createPathAlias('/admin', '/a/path/alias'); // Create a node to test the link widget. $node = $this->drupalCreateNode(); @@ -131,6 +143,11 @@ public function testURLValidation() { '#example' => '<front>#example', '?example=llama' => '<front>?example=llama', + // Text-only links. + '' => '<nolink>', + 'route:' => '<nolink>', + '' => '<none>', + // Query string and fragment. '?example=llama' => '?example=llama', '#example' => '#example', @@ -248,7 +265,9 @@ public function testLinkTitle() { ], ]); $this->field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $display_repository->getFormDisplay('entity_test', 'entity_test') ->setComponent($field_name, [ 'type' => 'link_default', 'settings' => [ @@ -257,7 +276,7 @@ public function testLinkTitle() { ], ]) ->save(); - entity_get_display('entity_test', 'entity_test', 'full') + $display_repository->getViewDisplay('entity_test', 'entity_test', 'full') ->setComponent($field_name, [ 'type' => 'link', 'label' => 'hidden', @@ -332,7 +351,7 @@ public function testLinkTitle() { $this->assertText(t('entity_test @id has been created.', ['@id' => $id])); $output = $this->renderTestEntity($id); - $expected_link = (string) \Drupal::l($value, Url::fromUri($value)); + $expected_link = (string) Link::fromTextAndUrl($value, Url::fromUri($value))->toString(); $this->assertContains($expected_link, $output); // Verify that a link with text is rendered using the link text. @@ -344,7 +363,7 @@ public function testLinkTitle() { $this->assertText(t('entity_test @id has been updated.', ['@id' => $id])); $output = $this->renderTestEntity($id); - $expected_link = (string) \Drupal::l($title, Url::fromUri($value)); + $expected_link = (string) Link::fromTextAndUrl($title, Url::fromUri($value))->toString(); $this->assertContains($expected_link, $output); } @@ -370,7 +389,9 @@ public function testLinkFormatter() { 'link_type' => LinkItemInterface::LINK_GENERIC, ], ])->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $display_repository->getFormDisplay('entity_test', 'entity_test') ->setComponent($field_name, [ 'type' => 'link_default', ]) @@ -379,7 +400,7 @@ public function testLinkFormatter() { 'type' => 'link', 'label' => 'hidden', ]; - entity_get_display('entity_test', 'entity_test', 'full') + $display_repository->getViewDisplay('entity_test', 'entity_test', 'full') ->setComponent($field_name, $display_options) ->save(); @@ -438,7 +459,7 @@ public function testLinkFormatter() { else { $display_options['settings'] = $new_value; } - entity_get_display('entity_test', 'entity_test', 'full') + $display_repository->getViewDisplay('entity_test', 'entity_test', 'full') ->setComponent($field_name, $display_options) ->save(); @@ -528,12 +549,14 @@ public function testLinkSeparateFormatter() { 'type' => 'link_separate', 'label' => 'hidden', ]; - entity_get_form_display('entity_test', 'entity_test', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $display_repository->getFormDisplay('entity_test', 'entity_test') ->setComponent($field_name, [ 'type' => 'link_default', ]) ->save(); - entity_get_display('entity_test', 'entity_test', 'full') + $display_repository->getViewDisplay('entity_test', 'entity_test', 'full') ->setComponent($field_name, $display_options) ->save(); @@ -572,7 +595,7 @@ public function testLinkSeparateFormatter() { foreach ($values as $new_value) { // Update the field formatter settings. $display_options['settings'] = [$setting => $new_value]; - entity_get_display('entity_test', 'entity_test', 'full') + $display_repository->getViewDisplay('entity_test', 'entity_test', 'full') ->setComponent($field_name, $display_options) ->save(); @@ -654,7 +677,7 @@ public function testLinkTypeOnLinkWidget() { ], ])->save(); - $this->container->get('entity.manager') + $this->container->get('entity_type.manager') ->getStorage('entity_form_display') ->load('entity_test.entity_test.default') ->setComponent($field_name, [ @@ -726,6 +749,73 @@ public function testEditNonNodeEntityLink() { $this->assertEquals($correct_link, $entity_test->get('field_link')->uri); } + /** + * Test and as link uri. + */ + public function testNoLinkUri() { + $field_name = mb_strtolower($this->randomMachineName()); + $this->fieldStorage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'entity_test', + 'type' => 'link', + 'cardinality' => 1, + ]); + $this->fieldStorage->save(); + FieldConfig::create([ + 'field_storage' => $this->fieldStorage, + 'label' => 'Read more about this entity', + 'bundle' => 'entity_test', + 'settings' => [ + 'title' => DRUPAL_OPTIONAL, + 'link_type' => LinkItemInterface::LINK_INTERNAL, + ], + ])->save(); + + $this->container->get('entity_type.manager') + ->getStorage('entity_form_display') + ->load('entity_test.entity_test.default') + ->setComponent($field_name, [ + 'type' => 'link_default', + ]) + ->save(); + + EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'full', + 'status' => TRUE, + ])->setComponent($field_name, [ + 'type' => 'link', + ]) + ->save(); + + // Test a link with uri. + $edit = [ + "{$field_name}[0][title]" => 'Title, no link', + "{$field_name}[0][uri]" => '', + ]; + + $this->drupalPostForm('/entity_test/add', $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match); + $id = $match[1]; + $output = $this->renderTestEntity($id); + $expected_link = (string) $this->container->get('link_generator')->generate('Title, no link', Url::fromUri('route:')); + $this->assertContains($expected_link, $output); + + // Test a link with uri. + $edit = [ + "{$field_name}[0][title]" => 'Title, none', + "{$field_name}[0][uri]" => '', + ]; + + $this->drupalPostForm('/entity_test/add', $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match); + $id = $match[1]; + $output = $this->renderTestEntity($id); + $expected_link = (string) $this->container->get('link_generator')->generate('Title, none', Url::fromUri('route:')); + $this->assertContains($expected_link, $output); + } + /** * Renders a test_entity and returns the output. * @@ -742,10 +832,11 @@ public function testEditNonNodeEntityLink() { */ protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) { if ($reset) { - $this->container->get('entity.manager')->getStorage('entity_test')->resetCache([$id]); + $this->container->get('entity_type.manager')->getStorage('entity_test')->resetCache([$id]); } $entity = EntityTest::load($id); - $display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), $view_mode); + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay($entity->getEntityTypeId(), $entity->bundle(), $view_mode); $content = $display->build($entity); $output = \Drupal::service('renderer')->renderRoot($content); $output = (string) $output; diff --git a/core/modules/link/tests/src/Functional/LinkFieldUITest.php b/core/modules/link/tests/src/Functional/LinkFieldUITest.php index 69a47b18b..65e4125dc 100644 --- a/core/modules/link/tests/src/Functional/LinkFieldUITest.php +++ b/core/modules/link/tests/src/Functional/LinkFieldUITest.php @@ -27,6 +27,11 @@ class LinkFieldUITest extends BrowserTestBase { */ public static $modules = ['node', 'link', 'field_ui', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A user that can edit content types. * @@ -197,7 +202,7 @@ public function runFieldUIItem($cardinality, $link_type, $title, $label, $field_ $expected_help_texts = [ LinkItemInterface::LINK_EXTERNAL => 'This must be an external URL such as http://example.com.', - LinkItemInterface::LINK_GENERIC => 'You can also enter an internal path such as /node/add or an external URL such as http://example.com. Enter <front> to link to the front page.', + LinkItemInterface::LINK_GENERIC => 'You can also enter an internal path such as /node/add or an external URL such as http://example.com. Enter <front> to link to the front page. Enter <nolink> to display link text only', LinkItemInterface::LINK_INTERNAL => rtrim(Url::fromRoute('', [], ['absolute' => TRUE])->toString(), '/'), ]; diff --git a/core/modules/link/tests/src/Functional/Views/LinkViewsTokensTest.php b/core/modules/link/tests/src/Functional/Views/LinkViewsTokensTest.php index b1cce75d0..4b9f0e932 100644 --- a/core/modules/link/tests/src/Functional/Views/LinkViewsTokensTest.php +++ b/core/modules/link/tests/src/Functional/Views/LinkViewsTokensTest.php @@ -21,6 +21,11 @@ class LinkViewsTokensTest extends ViewTestBase { */ public static $modules = ['link_test_views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * diff --git a/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php b/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php index 66209eff6..9d6b21666 100644 --- a/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php +++ b/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php @@ -97,7 +97,8 @@ public function testLinkDeserialization() { $json = json_decode($this->serializer->serialize($entity, 'json'), TRUE); $json['field_test'][0]['options'] = 'string data'; $serialized = json_encode($json, TRUE); - $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "options" properties of the "field_test" field (field item class: Drupal\link\Plugin\Field\FieldType\LinkItem).'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "options" properties of the "field_test" field (field item class: Drupal\link\Plugin\Field\FieldType\LinkItem).'); $this->serializer->deserialize($serialized, EntityTest::class, 'json'); } diff --git a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php index c2290bc4d..e7aa0e0d8 100644 --- a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkAccessConstraintValidatorTest.php @@ -30,7 +30,7 @@ class LinkAccessConstraintValidatorTest extends UnitTestCase { * @dataProvider providerValidate */ public function testValidate($value, $user, $valid) { - $context = $this->getMock(ExecutionContextInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); if ($valid) { $context->expects($this->never()) @@ -75,13 +75,13 @@ public function providerValidate() { ->method('access') ->willReturn($case['url_access']); // Mock a link object that returns the URL object. - $link = $this->getMock('Drupal\link\LinkItemInterface'); + $link = $this->createMock('Drupal\link\LinkItemInterface'); $link->expects($this->any()) ->method('getUrl') ->willReturn($url); // Mock a user object that returns a boolean indicating user access to all // links. - $user = $this->getMock('Drupal\Core\Session\AccountProxyInterface'); + $user = $this->createMock('Drupal\Core\Session\AccountProxyInterface'); $user->expects($this->any()) ->method('hasPermission') ->with($this->equalTo('link to any page')) diff --git a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php index 0de416f01..805690b8b 100644 --- a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkExternalProtocolsConstraintValidatorTest.php @@ -20,7 +20,7 @@ class LinkExternalProtocolsConstraintValidatorTest extends UnitTestCase { * @dataProvider providerValidate */ public function testValidate($value, $valid) { - $context = $this->getMock(ExecutionContextInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); if ($valid) { $context->expects($this->never()) @@ -57,7 +57,7 @@ public function providerValidate() { foreach ($data as &$single_data) { $url = Url::fromUri($single_data[0]); - $link = $this->getMock('Drupal\link\LinkItemInterface'); + $link = $this->createMock('Drupal\link\LinkItemInterface'); $link->expects($this->any()) ->method('getUrl') ->willReturn($url); @@ -73,12 +73,12 @@ public function providerValidate() { * @see \Drupal\Core\Url::fromUri */ public function testValidateWithMalformedUri() { - $link = $this->getMock('Drupal\link\LinkItemInterface'); + $link = $this->createMock('Drupal\link\LinkItemInterface'); $link->expects($this->any()) ->method('getUrl') ->willThrowException(new \InvalidArgumentException()); - $context = $this->getMock(ExecutionContextInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); $context->expects($this->never()) ->method('addViolation'); @@ -93,12 +93,12 @@ public function testValidateWithMalformedUri() { * @covers ::validate */ public function testValidateIgnoresInternalUrls() { - $link = $this->getMock('Drupal\link\LinkItemInterface'); + $link = $this->createMock('Drupal\link\LinkItemInterface'); $link->expects($this->any()) ->method('getUrl') ->willReturn(Url::fromRoute('example.test')); - $context = $this->getMock(ExecutionContextInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); $context->expects($this->never()) ->method('addViolation'); diff --git a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php index 2842df848..852069c84 100644 --- a/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidatorTest.php @@ -20,7 +20,7 @@ class LinkNotExistingInternalConstraintValidatorTest extends UnitTestCase { * @dataProvider providerValidate */ public function testValidate($value, $valid) { - $context = $this->getMock(ExecutionContextInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); if ($valid) { $context->expects($this->never()) @@ -50,7 +50,7 @@ public function providerValidate() { // Existing routed URL. $url = Url::fromRoute('example.existing_route'); - $url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); + $url_generator = $this->createMock('Drupal\Core\Routing\UrlGeneratorInterface'); $url_generator->expects($this->any()) ->method('generateFromRoute') ->with('example.existing_route', [], []) @@ -62,7 +62,7 @@ public function providerValidate() { // Not existing routed URL. $url = Url::fromRoute('example.not_existing_route'); - $url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); + $url_generator = $this->createMock('Drupal\Core\Routing\UrlGeneratorInterface'); $url_generator->expects($this->any()) ->method('generateFromRoute') ->with('example.not_existing_route', [], []) @@ -72,7 +72,7 @@ public function providerValidate() { $data[] = [$url, FALSE]; foreach ($data as &$single_data) { - $link = $this->getMock('Drupal\link\LinkItemInterface'); + $link = $this->createMock('Drupal\link\LinkItemInterface'); $link->expects($this->any()) ->method('getUrl') ->willReturn($single_data[0]); @@ -89,12 +89,12 @@ public function providerValidate() { * @see \Drupal\Core\Url::fromUri */ public function testValidateWithMalformedUri() { - $link = $this->getMock('Drupal\link\LinkItemInterface'); + $link = $this->createMock('Drupal\link\LinkItemInterface'); $link->expects($this->any()) ->method('getUrl') ->willThrowException(new \InvalidArgumentException()); - $context = $this->getMock(ExecutionContextInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); $context->expects($this->never()) ->method('addViolation'); diff --git a/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php b/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php index f765f6af3..18456aa86 100644 --- a/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/migrate/process/FieldLinkTest.php @@ -19,12 +19,12 @@ class FieldLinkTest extends UnitTestCase { * @dataProvider canonicalizeUriDataProvider */ public function testCanonicalizeUri($url, $expected, $configuration = []) { - $link_plugin = new FieldLink($configuration, '', [], $this->getMock(MigrationInterface::class)); + $link_plugin = new FieldLink($configuration, '', [], $this->createMock(MigrationInterface::class)); $transformed = $link_plugin->transform([ 'url' => $url, 'title' => '', 'attributes' => serialize([]), - ], $this->getMock(MigrateExecutableInterface::class), $this->getMockBuilder(Row::class)->disableOriginalConstructor()->getMock(), NULL); + ], $this->createMock(MigrateExecutableInterface::class), $this->getMockBuilder(Row::class)->disableOriginalConstructor()->getMock(), NULL); $this->assertEquals($expected, $transformed['uri']); } @@ -89,4 +89,27 @@ public function canonicalizeUriDataProvider() { ]; } + /** + * Test the attributes that are deeply serialized are discarded. + */ + public function testCanonicalizeUriSerialized() { + $link_plugin = new FieldLink([], '', [], $this->createMock(MigrationInterface::class)); + $migrate_executable = $this->createMock(MigrateExecutableInterface::class); + $row = new Row(); + + $transformed = $link_plugin->transform([ + 'url' => '', + 'title' => '', + 'attributes' => serialize(serialize(['not too deep'])), + ], $migrate_executable, $row, NULL); + $this->assertEquals(['not too deep'], $transformed['options']['attributes']); + + $transformed = $link_plugin->transform([ + 'url' => '', + 'title' => '', + 'attributes' => serialize(serialize(serialize(['too deep']))), + ], $migrate_executable, $row, NULL); + $this->assertEmpty($transformed['options']['attributes']); + } + } diff --git a/core/modules/link/tests/src/Unit/Plugin/migrate/process/d6/FieldLinkTest.php b/core/modules/link/tests/src/Unit/Plugin/migrate/process/d6/FieldLinkTest.php index 29d849f7d..2cb126d3e 100644 --- a/core/modules/link/tests/src/Unit/Plugin/migrate/process/d6/FieldLinkTest.php +++ b/core/modules/link/tests/src/Unit/Plugin/migrate/process/d6/FieldLinkTest.php @@ -17,12 +17,12 @@ class FieldLinkTest extends UnitTestCase { * @dataProvider canonicalizeUriDataProvider */ public function testCanonicalizeUri($url, $expected) { - $link_plugin = new FieldLink([], '', [], $this->getMock('\Drupal\migrate\Plugin\MigrationInterface')); + $link_plugin = new FieldLink([], '', [], $this->createMock('\Drupal\migrate\Plugin\MigrationInterface')); $transformed = $link_plugin->transform([ 'url' => $url, 'title' => '', 'attributes' => serialize([]), - ], $this->getMock('\Drupal\migrate\MigrateExecutableInterface'), $this->getMockBuilder('\Drupal\migrate\Row')->disableOriginalConstructor()->getMock(), NULL); + ], $this->createMock('\Drupal\migrate\MigrateExecutableInterface'), $this->getMockBuilder('\Drupal\migrate\Row')->disableOriginalConstructor()->getMock(), NULL); $this->assertEquals($expected, $transformed['uri']); } diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc index dc8575e34..4829363b6 100644 --- a/core/modules/locale/locale.batch.inc +++ b/core/modules/locale/locale.batch.inc @@ -5,6 +5,7 @@ * Batch process to check the availability of remote or local po files. */ +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; use GuzzleHttp\Exception\RequestException; use Psr\Http\Message\RequestInterface; @@ -293,7 +294,7 @@ function locale_translation_http_check($uri) { * File object if download was successful. FALSE on failure. */ function locale_translation_download_source($source_file, $directory = 'temporary://') { - if ($uri = system_retrieve_file($source_file->uri, $directory, FALSE, FILE_EXISTS_REPLACE)) { + if ($uri = system_retrieve_file($source_file->uri, $directory, FALSE, FileSystemInterface::EXISTS_REPLACE)) { $file = clone($source_file); $file->type = LOCALE_TRANSLATION_LOCAL; $file->uri = $uri; diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index 593ff3143..37c452209 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -97,7 +97,10 @@ function locale_translate_get_interface_translation_files(array $projects = [], // {project}-{version}.{langcode}.po. // Only files of known projects and languages will be returned. $directory = \Drupal::config('locale.settings')->get('translation.path'); - $result = file_scan_directory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', ['recurse' => FALSE]); + $result = []; + if (is_dir($directory)) { + $result = \Drupal::service('file_system')->scanDirectory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', ['recurse' => FALSE]); + } foreach ($result as $file) { // Update the file object with project name and version from the file name. diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc index f6b79f42e..317b45adb 100644 --- a/core/modules/locale/locale.compare.inc +++ b/core/modules/locale/locale.compare.inc @@ -69,7 +69,7 @@ function locale_translation_build_projects() { $data += [ 'name' => $name, 'version' => isset($data['info']['version']) ? $data['info']['version'] : '', - 'core' => isset($data['info']['core']) ? $data['info']['core'] : \Drupal::CORE_COMPATIBILITY, + 'core' => 'all', // A project can provide the path and filename pattern to download the // gettext file. Use the default if not. 'server_pattern' => isset($data['info']['interface translation server pattern']) && $data['info']['interface translation server pattern'] ? $data['info']['interface translation server pattern'] : $default_server['pattern'], @@ -103,7 +103,7 @@ function locale_translation_project_list() { 'interface translation project', 'interface translation server pattern', ]; - $module_data = _locale_translation_prepare_project_list(system_rebuild_module_data(), 'module'); + $module_data = _locale_translation_prepare_project_list(\Drupal::service('extension.list.module')->getList(), 'module'); $theme_data = _locale_translation_prepare_project_list(\Drupal::service('theme_handler')->rebuildThemeData(), 'theme'); $project_info = new ProjectInfo(); $project_info->processInfoList($projects, $module_data, 'module', TRUE, $additional_whitelist); diff --git a/core/modules/locale/locale.info.yml b/core/modules/locale/locale.info.yml index eed7d4bbe..1db8704e7 100644 --- a/core/modules/locale/locale.info.yml +++ b/core/modules/locale/locale.info.yml @@ -3,14 +3,8 @@ type: module description: 'Translates the built-in user interface.' configure: locale.translate_page package: Multilingual -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:language - drupal:file - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 13eeb2265..649f737d8 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -7,6 +7,7 @@ use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Link; use Drupal\Core\Url; /** @@ -32,10 +33,12 @@ function locale_uninstall() { if (is_dir($locale_js_directory)) { $locale_javascripts = \Drupal::state()->get('locale.translation.javascript') ?: []; + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); foreach ($locale_javascripts as $langcode => $file_suffix) { if (!empty($file_suffix)) { try { - \Drupal::service('file_system')->delete($locale_js_directory . '/' . $langcode . '_' . $file_suffix . '.js'); + $file_system->delete($locale_js_directory . '/' . $langcode . '_' . $file_suffix . '.js'); } catch (FileException $e) { // Ignore and continue. @@ -43,8 +46,10 @@ function locale_uninstall() { } } // Delete the JavaScript translations directory if empty. - if (!file_scan_directory($locale_js_directory, '/.*/')) { - \Drupal::service('file_system')->rmdir($locale_js_directory); + if (is_dir($locale_js_directory)) { + if (!$file_system->scanDirectory($locale_js_directory, '/.*/')) { + $file_system->rmdir($locale_js_directory); + } } } @@ -268,7 +273,7 @@ function locale_requirements($phase) { if ($available_updates) { $requirements['locale_translation'] = [ 'title' => t('Translation update status'), - 'value' => \Drupal::l(t('Updates available'), new Url('locale.translate_status')), + 'value' => Link::fromTextAndUrl(t('Updates available'), Url::fromRoute('locale.translate_status'))->toString(), 'severity' => REQUIREMENT_WARNING, 'description' => t('Updates available for: @languages. See the Available translation updates page for more information.', ['@languages' => implode(', ', $available_updates), ':updates' => Url::fromRoute('locale.translate_status')->toString()]), ]; @@ -293,7 +298,7 @@ function locale_requirements($phase) { else { $requirements['locale_translation'] = [ 'title' => t('Translation update status'), - 'value' => \Drupal::l(t('Can not determine status'), new Url('locale.translate_status')), + 'value' => Link::fromTextAndUrl(t('Can not determine status'), Url::fromRoute('locale.translate_status'))->toString(), 'severity' => REQUIREMENT_WARNING, 'description' => t('No translation status is available. See the Available translation updates page for more information.', [':updates' => Url::fromRoute('locale.translate_status')->toString()]), ]; @@ -323,3 +328,10 @@ function locale_update_8500() { ->save(); } } + +/** + * Clear Locale project storage to use new 'all' instead of 8.x in URLs. + */ +function locale_update_8800() { + \Drupal::service('locale.project')->deleteAll(); +} diff --git a/core/modules/locale/locale.libraries.yml b/core/modules/locale/locale.libraries.yml index e01d0d6bd..6cdbc5db3 100644 --- a/core/modules/locale/locale.libraries.yml +++ b/core/modules/locale/locale.libraries.yml @@ -19,6 +19,7 @@ drupal.locale.datepicker: - core/jquery - core/drupal - core/drupalSettings + deprecated: The "%library_id%" asset library is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. See https://www.drupal.org/node/3081864 translations: # No sensible version can be specified, since the translations may change at diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 94f2f379d..40cd9d249 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -17,6 +17,8 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\Xss; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Installer\InstallerKernel; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Form\FormStateInterface; @@ -407,7 +409,7 @@ function locale_system_update(array $components) { // Skip running the translation imports if in the installer, // because it would break out of the installer flow. We have // built-in support for translation imports in the installer. - if (!drupal_installation_attempted() && locale_translatable_language_list()) { + if (!InstallerKernel::installationAttempted() && locale_translatable_language_list()) { if (\Drupal::config('locale.settings')->get('translation.import_enabled')) { module_load_include('compare.inc', 'locale'); @@ -647,16 +649,14 @@ function locale_form_language_admin_overview_form_alter(&$form, FormStateInterfa 'ratio' => 0, ]; if (!$language->isLocked() && locale_is_translatable($langcode)) { - $form['languages'][$langcode]['locale_statistics'] = [ - '#markup' => \Drupal::l( - t('@translated/@total (@ratio%)', [ - '@translated' => $stats[$langcode]['translated'], - '@total' => $total_strings, - '@ratio' => $stats[$langcode]['ratio'], - ]), - new Url('locale.translate_page', [], ['query' => ['langcode' => $langcode]]) - ), - ]; + $form['languages'][$langcode]['locale_statistics'] = Link::fromTextAndUrl( + t('@translated/@total (@ratio%)', [ + '@translated' => $stats[$langcode]['translated'], + '@total' => $total_strings, + '@ratio' => $stats[$langcode]['ratio'], + ]), + Url::fromRoute('locale.translate_page', [], ['query' => ['langcode' => $langcode]]) + )->toRenderable(); } else { $form['languages'][$langcode]['locale_statistics'] = [ @@ -820,7 +820,7 @@ function locale_translation_get_file_history() { if (empty($history)) { // Get file history from the database. - $result = db_query('SELECT project, langcode, filename, version, uri, timestamp, last_checked FROM {locale_file}'); + $result = \Drupal::database()->query('SELECT project, langcode, filename, version, uri, timestamp, last_checked FROM {locale_file}'); foreach ($result as $file) { $file->type = $file->timestamp ? LOCALE_TRANSLATION_CURRENT : ''; $history[$file->project][$file->langcode] = $file; diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index 9b8b4ee68..0f7a5ea40 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -5,6 +5,7 @@ * Interface translation summary, editing and deletion user interfaces. */ +use Drupal\Core\Link; use Drupal\Core\Url; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -13,7 +14,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; * * Manually checks the translation status without the use of cron. * - * @deprecated in Drupal 8.5.0 and will be removed before 9.0.0. It is unused by + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. It is unused by * Drupal core. Duplicate this function in your own extension if you need its * behavior. * @@ -77,5 +78,5 @@ function template_preprocess_locale_translation_last_check(array &$variables) { $last = $variables['last']; $variables['last_checked'] = ($last != NULL); $variables['time'] = \Drupal::service('date.formatter')->formatTimeDiffSince($last); - $variables['link'] = \Drupal::l(t('Check manually'), new Url('locale.check_translation', [], ['query' => \Drupal::destination()->getAsArray()])); + $variables['link'] = Link::fromTextAndUrl(t('Check manually'), Url::fromRoute('locale.check_translation', [], ['query' => \Drupal::destination()->getAsArray()]))->toString(); } diff --git a/core/modules/locale/locale.routing.yml b/core/modules/locale/locale.routing.yml index 593e1ff67..9f59d0730 100644 --- a/core/modules/locale/locale.routing.yml +++ b/core/modules/locale/locale.routing.yml @@ -1,7 +1,7 @@ locale.settings: path: '/admin/config/regional/translate/settings' defaults: - _form: 'Drupal\locale\Form\LocaleSettingsForm' + _form: '\Drupal\locale\Form\LocaleSettingsForm' _title: 'Interface translation settings' requirements: _permission: 'translate interface' @@ -9,7 +9,7 @@ locale.settings: locale.check_translation: path: '/admin/reports/translations/check' defaults: - _controller: 'Drupal\locale\Controller\LocaleController::checkTranslation' + _controller: '\Drupal\locale\Controller\LocaleController::checkTranslation' requirements: _permission: 'translate interface' diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc index 1b1cdb761..2672d5c38 100644 --- a/core/modules/locale/locale.translation.inc +++ b/core/modules/locale/locale.translation.inc @@ -5,6 +5,8 @@ * Common API for interface translation. */ +use Drupal\Core\StreamWrapper\StreamWrapperManager; + /** * Comparison result of source files timestamps. * @@ -173,11 +175,13 @@ function locale_translation_source_check_file($source) { $directory = $source_file->directory; $filename = '/' . preg_quote($source_file->filename) . '$/'; - if ($files = file_scan_directory($directory, $filename, ['key' => 'name', 'recurse' => FALSE])) { - $file = current($files); - $source_file->uri = $file->uri; - $source_file->timestamp = filemtime($file->uri); - return $source_file; + if (is_dir($directory)) { + if ($files = \Drupal::service('file_system')->scanDirectory($directory, $filename, ['key' => 'name', 'recurse' => FALSE])) { + $file = current($files); + $source_file->uri = $file->uri; + $source_file->timestamp = filemtime($file->uri); + return $source_file; + } } } return FALSE; @@ -376,7 +380,7 @@ function locale_cron_fill_queue() { * TRUE if the $uri is a remote file. */ function _locale_translation_file_is_remote($uri) { - $scheme = file_uri_scheme($uri); + $scheme = StreamWrapperManager::getScheme($uri); if ($scheme) { return !\Drupal::service('file_system')->realpath($scheme . '://'); } diff --git a/core/modules/locale/migrations/state/locale.migrate_drupal.yml b/core/modules/locale/migrations/state/locale.migrate_drupal.yml new file mode 100644 index 000000000..5d4e806b8 --- /dev/null +++ b/core/modules/locale/migrations/state/locale.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + locale: locale + 7: + locale: locale diff --git a/core/modules/locale/src/EventSubscriber/LocaleTranslationCacheTag.php b/core/modules/locale/src/EventSubscriber/LocaleTranslationCacheTag.php index 0add8e9d3..496809790 100644 --- a/core/modules/locale/src/EventSubscriber/LocaleTranslationCacheTag.php +++ b/core/modules/locale/src/EventSubscriber/LocaleTranslationCacheTag.php @@ -32,7 +32,7 @@ public function __construct(CacheTagsInvalidatorInterface $cache_tags_invalidato * Invalidate cache tags whenever a string is translated. */ public function saveTranslation() { - $this->cacheTagsInvalidator->invalidateTags(['rendered', 'locale']); + $this->cacheTagsInvalidator->invalidateTags(['rendered', 'locale', 'library_info']); } /** diff --git a/core/modules/locale/src/Form/LocaleSettingsForm.php b/core/modules/locale/src/Form/LocaleSettingsForm.php index 78945ebc2..05a47b20c 100644 --- a/core/modules/locale/src/Form/LocaleSettingsForm.php +++ b/core/modules/locale/src/Form/LocaleSettingsForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; /** * Configure locale settings for this site. @@ -41,14 +42,14 @@ public function buildForm(array $form, FormStateInterface $form_state) { '7' => $this->t('Weekly'), '30' => $this->t('Monthly'), ], - '#description' => $this->t('Select how frequently you want to check for new interface translations for your currently installed modules and themes. Check updates now.', [':url' => $this->url('locale.check_translation')]), + '#description' => $this->t('Select how frequently you want to check for new interface translations for your currently installed modules and themes. Check updates now.', [':url' => Url::fromRoute('locale.check_translation')->toString()]), ]; if ($directory = $config->get('translation.path')) { - $description = $this->t('Translation files are stored locally in the %path directory. You can change this directory on the File system configuration page.', ['%path' => $directory, ':url' => $this->url('system.file_system_settings')]); + $description = $this->t('Translation files are stored locally in the %path directory. You can change this directory on the File system configuration page.', ['%path' => $directory, ':url' => Url::fromRoute('system.file_system_settings')->toString()]); } else { - $description = $this->t('Translation files will not be stored locally. Change the Interface translation directory on the File system configuration page.', [':url' => $this->url('system.file_system_settings')]); + $description = $this->t('Translation files will not be stored locally. Change the Interface translation directory on the File system configuration page.', [':url' => Url::fromRoute('system.file_system_settings')->toString()]); } $form['#translation_directory'] = $directory; $form['use_source'] = [ @@ -93,7 +94,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); if (empty($form['#translation_directory']) && $form_state->getValue('use_source') == LOCALE_TRANSLATION_USE_SOURCE_LOCAL) { - $form_state->setErrorByName('use_source', $this->t('You have selected local translation source, but no Interface translation directory was configured.', [':url' => $this->url('system.file_system_settings')])); + $form_state->setErrorByName('use_source', $this->t('You have selected local translation source, but no Interface translation directory was configured.', [':url' => Url::fromRoute('system.file_system_settings')->toString()])); } } diff --git a/core/modules/locale/src/Form/TranslationStatusForm.php b/core/modules/locale/src/Form/TranslationStatusForm.php index bf87567e8..a6d914afe 100644 --- a/core/modules/locale/src/Form/TranslationStatusForm.php +++ b/core/modules/locale/src/Form/TranslationStatusForm.php @@ -6,6 +6,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\State\StateInterface; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -130,7 +131,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { if (!$languages) { $empty = $this->t('No translatable languages available. Add a language first.', [ - ':add_language' => $this->url('entity.configurable_language.collection'), + ':add_language' => Url::fromRoute('entity.configurable_language.collection')->toString(), ]); } elseif ($status) { @@ -138,7 +139,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { } else { $empty = $this->t('No translation status available. Check manually.', [ - ':check' => $this->url('locale.check_translation'), + ':check' => Url::fromRoute('locale.check_translation')->toString(), ]); } diff --git a/core/modules/locale/src/LocaleConfigSubscriber.php b/core/modules/locale/src/LocaleConfigSubscriber.php index d06cb34de..289e838e0 100644 --- a/core/modules/locale/src/LocaleConfigSubscriber.php +++ b/core/modules/locale/src/LocaleConfigSubscriber.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\ConfigEvents; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\StorableConfigBase; +use Drupal\Core\Installer\InstallerKernel; use Drupal\language\Config\LanguageConfigOverrideCrudEvent; use Drupal\language\Config\LanguageConfigOverrideEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -78,7 +79,7 @@ public static function getSubscribedEvents() { public function onConfigSave(ConfigCrudEvent $event) { // Only attempt to feed back configuration translation changes to locale if // the update itself was not initiated by locale data changes. - if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) { + if (!InstallerKernel::installationAttempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) { $config = $event->getConfig(); $langcode = $config->get('langcode') ?: 'en'; $this->updateLocaleStorage($config, $langcode); @@ -94,7 +95,7 @@ public function onConfigSave(ConfigCrudEvent $event) { public function onOverrideChange(LanguageConfigOverrideCrudEvent $event) { // Only attempt to feed back configuration override changes to locale if // the update itself was not initiated by locale data changes. - if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) { + if (!InstallerKernel::installationAttempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) { $translation_config = $event->getLanguageConfigOverride(); $langcode = $translation_config->getLangcode(); $reference_config = $this->configFactory->getEditable($translation_config->getName())->get(); @@ -148,8 +149,8 @@ protected function processTranslatableData($name, array $config, array $translat continue; } if (is_array($item)) { - $reference_config = isset($reference_config[$key]) ? $reference_config[$key] : []; - $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config); + $reference_config_item = isset($reference_config[$key]) ? $reference_config[$key] : []; + $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config_item); } else { $this->saveCustomizedTranslation($name, $item->getUntranslatedString(), $item->getOption('context'), $config[$key], $langcode); diff --git a/core/modules/locale/src/StringStorageInterface.php b/core/modules/locale/src/StringStorageInterface.php index 18c5edc2b..51dd7d5e4 100644 --- a/core/modules/locale/src/StringStorageInterface.php +++ b/core/modules/locale/src/StringStorageInterface.php @@ -101,7 +101,7 @@ public function findTranslation(array $conditions); * @param \Drupal\locale\StringInterface $string * The string object. * - * @return \Drupal\locale\StringStorageInterface + * @return $this * The called object. * * @throws \Drupal\locale\StringStorageException @@ -115,7 +115,7 @@ public function save($string); * @param \Drupal\locale\StringInterface $string * The string object. * - * @return \Drupal\locale\StringStorageInterface + * @return $this * The called object. * * @throws \Drupal\locale\StringStorageException diff --git a/core/modules/locale/src/TranslationString.php b/core/modules/locale/src/TranslationString.php index 63f4543cb..c32d23f2d 100644 --- a/core/modules/locale/src/TranslationString.php +++ b/core/modules/locale/src/TranslationString.php @@ -58,7 +58,7 @@ public function __construct($values = []) { * @param bool $customized * (optional) Whether the string is customized or not. Defaults to TRUE. * - * @return \Drupal\locale\TranslationString + * @return $this * The called object. */ public function setCustomized($customized = TRUE) { diff --git a/core/modules/locale/tests/modules/early_translation_test/early_translation_test.info.yml b/core/modules/locale/tests/modules/early_translation_test/early_translation_test.info.yml index a0482383e..848708557 100644 --- a/core/modules/locale/tests/modules/early_translation_test/early_translation_test.info.yml +++ b/core/modules/locale/tests/modules/early_translation_test/early_translation_test.info.yml @@ -1,12 +1,6 @@ name: 'Early translation test' type: module description: 'Support module for testing early bootstrap getting of annotations with translations.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation_multiple.yml b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation_multiple.yml new file mode 100644 index 000000000..1be6abaea --- /dev/null +++ b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation_multiple.yml @@ -0,0 +1,8 @@ +test: English test +test_multiple: + string: 'A string' + another_string: 'Another string' + test_after_multiple: false +test_after_multiple: + string: 'After a string' + another_string: 'After another string' diff --git a/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml b/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml index 958ce1b9c..1f8ef08de 100644 --- a/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml +++ b/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml @@ -19,3 +19,40 @@ locale_test.translation: label: 'Test' # See \Drupal\Tests\locale\Kernel\LocaleConfigSubscriberTest translatable: true + +locale_test.translation_multiple: + type: config_object + label: 'translation settings' + mapping: + test: + type: string + label: 'Test' + # See \Drupal\Tests\locale\Kernel\LocaleConfigSubscriberTest + translatable: true + test_multiple: + type: mapping + label: 'Multiple settings' + mapping: + string: + type: string + label: 'A string' + translatable: true + another_string: + type: string + label: 'Another string' + translatable: true + test_after_multiple: + type: boolean + label: 'Test after multiple' + test_after_multiple: + type: mapping + label: 'Test after multiple settings' + mapping: + string: + type: string + label: 'A string' + translatable: true + another_string: + type: string + label: 'Another string' + translatable: true diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.info.yml b/core/modules/locale/tests/modules/locale_test/locale_test.info.yml index b9bc24f4a..ca4211d41 100644 --- a/core/modules/locale/tests/modules/locale_test/locale_test.info.yml +++ b/core/modules/locale/tests/modules/locale_test/locale_test.info.yml @@ -2,14 +2,8 @@ name: 'Locale test' type: module description: 'Support module for locale module testing.' package: Testing -# version: '1.2' -# core: 8.x +version: '1.2' +core: 8.x hidden: true 'interface translation project': locale_test 'interface translation server pattern': core/modules/locale/test/test.%language.po - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.module b/core/modules/locale/tests/modules/locale_test/locale_test.module index 268829689..4a0a78644 100644 --- a/core/modules/locale/tests/modules/locale_test/locale_test.module +++ b/core/modules/locale/tests/modules/locale_test/locale_test.module @@ -26,6 +26,15 @@ function locale_test_system_info_alter(&$info, Extension $file, $type) { $info['hidden'] = FALSE; } } + + // Alter the name and the core version of the project. This should not affect + // the locale project information. + if (\Drupal::state()->get('locale.test_system_info_alter_name_core')) { + if ($file->getName() == 'locale_test') { + $info['core'] = '8.6.7'; + $info['name'] = 'locale_test_alter'; + } + } } /** diff --git a/core/modules/locale/tests/modules/locale_test_development_release/locale_test_development_release.info.yml b/core/modules/locale/tests/modules/locale_test_development_release/locale_test_development_release.info.yml index b486fe8d0..fd1481ca1 100644 --- a/core/modules/locale/tests/modules/locale_test_development_release/locale_test_development_release.info.yml +++ b/core/modules/locale/tests/modules/locale_test_development_release/locale_test_development_release.info.yml @@ -2,12 +2,6 @@ name: 'Locale Test Development Release' type: module description: 'Helper module to test the behaviour when the core verison is a development release.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x hidden: true - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.info.yml b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.info.yml index aabf8f1a8..d703b0d25 100644 --- a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.info.yml +++ b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.info.yml @@ -2,14 +2,8 @@ name: 'Locale test translate' type: module description: 'Translation test module for locale module testing.' package: Testing -# version: '1.3' -# core: 8.x +version: '1.3' +core: 8.x hidden: true 'interface translation project': locale_test_translate 'interface translation server pattern': core/modules/locale/tests/test.%language.po - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php index 058d9ddf8..64dcb705d 100644 --- a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php @@ -20,6 +20,11 @@ class LocaleConfigTranslationImportTest extends BrowserTestBase { */ public static $modules = ['language', 'locale_test_translate']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationTest.php b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationTest.php index f3217691d..eeb22ef3d 100644 --- a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationTest.php @@ -26,6 +26,11 @@ class LocaleConfigTranslationTest extends BrowserTestBase { */ public static $modules = ['locale', 'contact', 'contact_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -65,7 +70,7 @@ public function testConfigTranslation() { // Check that the maintenance message exists and create translation for it. $source = '@site is currently under maintenance. We should be back shortly. Thank you for your patience.'; $string = $this->storage->findString(['source' => $source, 'context' => '', 'type' => 'configuration']); - $this->assertTrue($string, 'Configuration strings have been created upon installation.'); + $this->assertNotEmpty($string, 'Configuration strings have been created upon installation.'); // Translate using the UI so configuration is refreshed. $message = $this->randomMachineName(20); @@ -90,7 +95,7 @@ public function testConfigTranslation() { // Check default medium date format exists and create a translation for it. $string = $this->storage->findString(['source' => 'D, m/d/Y - H:i', 'context' => 'PHP date format', 'type' => 'configuration']); - $this->assertTrue($string, 'Configuration date formats have been created upon installation.'); + $this->assertNotEmpty($string, 'Configuration date formats have been created upon installation.'); // Translate using the UI so configuration is refreshed. $search = [ @@ -117,14 +122,14 @@ public function testConfigTranslation() { // Assert strings from image module config are not available. $string = $this->storage->findString(['source' => 'Medium (220×220)', 'context' => '', 'type' => 'configuration']); - $this->assertFalse($string, 'Configuration strings have been created upon installation.'); + $this->assertNull($string, 'Configuration strings have been created upon installation.'); // Enable the image module. $this->drupalPostForm('admin/modules', ['modules[image][enable]' => "1"], t('Install')); $this->rebuildContainer(); $string = $this->storage->findString(['source' => 'Medium (220×220)', 'context' => '', 'type' => 'configuration']); - $this->assertTrue($string, 'Configuration strings have been created upon installation.'); + $this->assertNotEmpty($string, 'Configuration strings have been created upon installation.'); $locations = $string->getLocations(); $this->assertTrue(isset($locations['configuration']) && isset($locations['configuration']['image.style.medium']), 'Configuration string has been created with the right location'); @@ -133,7 +138,7 @@ public function testConfigTranslation() { $this->assertEqual(count($translations), 1); $translation = reset($translations); $this->assertEqual($translation->source, $string->source); - $this->assertTrue(empty($translation->translation)); + $this->assertEmpty($translation->translation); // Translate using the UI so configuration is refreshed. $image_style_label = $this->randomMachineName(20); @@ -224,22 +229,22 @@ protected function assertNodeConfig($required, $optional) { $string = $this->storage->findString(['source' => 'Make content sticky', 'context' => '', 'type' => 'configuration']); if ($required) { $this->assertFalse($this->config('system.action.node_make_sticky_action')->isNew()); - $this->assertTrue($string, 'Node action text can be found with node module.'); + $this->assertNotEmpty($string, 'Node action text can be found with node module.'); } else { $this->assertTrue($this->config('system.action.node_make_sticky_action')->isNew()); - $this->assertFalse($string, 'Node action text can not be found without node module.'); + $this->assertNull($string, 'Node action text can not be found without node module.'); } // Check the optional default configuration in node module. $string = $this->storage->findString(['source' => 'No front page content has been created yet.
Follow the User Guide to start building your site.', 'context' => '', 'type' => 'configuration']); if ($optional) { $this->assertFalse($this->config('views.view.frontpage')->isNew()); - $this->assertTrue($string, 'Node view text can be found with node and views modules.'); + $this->assertNotEmpty($string, 'Node view text can be found with node and views modules.'); } else { $this->assertTrue($this->config('views.view.frontpage')->isNew()); - $this->assertFalse($string, 'Node view text can not be found without node and/or views modules.'); + $this->assertNull($string, 'Node view text can not be found without node and/or views modules.'); } } diff --git a/core/modules/locale/tests/src/Functional/LocaleContentTest.php b/core/modules/locale/tests/src/Functional/LocaleContentTest.php index e7955503a..9ac40754f 100644 --- a/core/modules/locale/tests/src/Functional/LocaleContentTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleContentTest.php @@ -21,6 +21,11 @@ class LocaleContentTest extends BrowserTestBase { */ public static $modules = ['node', 'locale']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Verifies that machine name fields are always LTR. */ diff --git a/core/modules/locale/tests/src/Functional/LocaleExportTest.php b/core/modules/locale/tests/src/Functional/LocaleExportTest.php index 3d0d1f416..9869fb70a 100644 --- a/core/modules/locale/tests/src/Functional/LocaleExportTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleExportTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\locale\Functional; +use Drupal\Core\File\FileSystemInterface; use Drupal\Tests\BrowserTestBase; /** @@ -18,6 +19,11 @@ class LocaleExportTest extends BrowserTestBase { */ public static $modules = ['locale']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user able to create languages and export translations. */ @@ -33,23 +39,24 @@ protected function setUp() { $this->drupalLogin($this->adminUser); // Copy test po files to the translations directory. - \Drupal::service('file_system')->copy(__DIR__ . '/../../../tests/test.de.po', 'translations://', FILE_EXISTS_REPLACE); - \Drupal::service('file_system')->copy(__DIR__ . '/../../../tests/test.xx.po', 'translations://', FILE_EXISTS_REPLACE); + \Drupal::service('file_system')->copy(__DIR__ . '/../../../tests/test.de.po', 'translations://', FileSystemInterface::EXISTS_REPLACE); + \Drupal::service('file_system')->copy(__DIR__ . '/../../../tests/test.xx.po', 'translations://', FileSystemInterface::EXISTS_REPLACE); } /** * Test exportation of translations. */ public function testExportTranslation() { + $file_system = \Drupal::service('file_system'); // First import some known translations. // This will also automatically add the 'fr' language. - $name = \Drupal::service('file_system')->tempnam('temporary://', "po_") . '.po'; + $name = $file_system->tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $this->getPoFile()); $this->drupalPostForm('admin/config/regional/translate/import', [ 'langcode' => 'fr', 'files[file]' => $name, ], t('Import')); - drupal_unlink($name); + $file_system->unlink($name); // Get the French translations. $this->drupalPostForm('admin/config/regional/translate/export', [ @@ -62,14 +69,14 @@ public function testExportTranslation() { $this->assertRaw('msgstr "lundi"', 'French translations present in exported file.'); // Import some more French translations which will be marked as customized. - $name = \Drupal::service('file_system')->tempnam('temporary://', "po2_") . '.po'; + $name = $file_system->tempnam('temporary://', "po2_") . '.po'; file_put_contents($name, $this->getCustomPoFile()); $this->drupalPostForm('admin/config/regional/translate/import', [ 'langcode' => 'fr', 'files[file]' => $name, 'customized' => 1, ], t('Import')); - drupal_unlink($name); + $file_system->unlink($name); // Create string without translation in the locales_source table. $this->container diff --git a/core/modules/locale/tests/src/Functional/LocaleFileSystemFormTest.php b/core/modules/locale/tests/src/Functional/LocaleFileSystemFormTest.php index c38da43d7..6a866af51 100644 --- a/core/modules/locale/tests/src/Functional/LocaleFileSystemFormTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleFileSystemFormTest.php @@ -18,6 +18,11 @@ class LocaleFileSystemFormTest extends BrowserTestBase { */ public static $modules = ['system']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/locale/tests/src/Functional/LocaleImportFunctionalTest.php b/core/modules/locale/tests/src/Functional/LocaleImportFunctionalTest.php index b8701b6c3..9342c6ed8 100644 --- a/core/modules/locale/tests/src/Functional/LocaleImportFunctionalTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleImportFunctionalTest.php @@ -2,7 +2,10 @@ namespace Drupal\Tests\locale\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; +use Drupal\Core\Database\Database; +use Drupal\Core\File\FileSystemInterface; use Drupal\Tests\BrowserTestBase; use Drupal\Core\Language\LanguageInterface; @@ -20,6 +23,11 @@ class LocaleImportFunctionalTest extends BrowserTestBase { */ public static $modules = ['locale', 'dblog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user able to create languages and import translations. * @@ -44,8 +52,8 @@ protected function setUp() { // Copy test po files to the translations directory. /** @var \Drupal\Core\File\FileSystemInterface $file_system */ $file_system = \Drupal::service('file_system'); - $file_system->copy(__DIR__ . '/../../../tests/test.de.po', 'translations://', FILE_EXISTS_REPLACE); - $file_system->copy(__DIR__ . '/../../../tests/test.xx.po', 'translations://', FILE_EXISTS_REPLACE); + $file_system->copy(__DIR__ . '/../../../tests/test.de.po', 'translations://', FileSystemInterface::EXISTS_REPLACE); + $file_system->copy(__DIR__ . '/../../../tests/test.xx.po', 'translations://', FileSystemInterface::EXISTS_REPLACE); $this->adminUser = $this->drupalCreateUser(['administer languages', 'translate interface', 'access administration pages']); $this->adminUserAccessSiteReports = $this->drupalCreateUser(['administer languages', 'translate interface', 'access administration pages', 'access site reports']); @@ -185,7 +193,7 @@ public function testStandalonePoFile() { // The database should now contain 6 customized strings (two imported // strings are not translated). - $count = db_query('SELECT COUNT(*) FROM {locales_target} WHERE customized = :custom', [':custom' => 1])->fetchField(); + $count = Database::getConnection()->query('SELECT COUNT(*) FROM {locales_target} WHERE customized = :custom', [':custom' => 1])->fetchField(); $this->assertEqual($count, 6, 'Customized translations successfully imported.'); // Try importing a .po file with overriding strings, and ensure existing @@ -309,7 +317,7 @@ public function testConfigPoFile() { $locale_storage = $this->container->get('locale.storage'); foreach ($config_strings as $config_string) { $string = $locale_storage->findString(['source' => $config_string[0], 'context' => '', 'type' => 'configuration']); - $this->assertTrue($string, 'Configuration strings have been created upon installation.'); + $this->assertNotEmpty($string, 'Configuration strings have been created upon installation.'); } // Import a .po file to translate. @@ -325,7 +333,7 @@ public function testConfigPoFile() { 'translation' => 'all', ]; $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter')); - $this->assertText($config_string[1], format_string('Translation of @string found.', ['@string' => $config_string[0]])); + $this->assertText($config_string[1], new FormattableMarkup('Translation of @string found.', ['@string' => $config_string[0]])); } // Test that translations got recorded in the config system. @@ -374,11 +382,12 @@ public function testCreatedLanguageTranslation() { * (optional) Additional options to pass to the translation import form. */ public function importPoFile($contents, array $options = []) { - $name = \Drupal::service('file_system')->tempnam('temporary://', "po_") . '.po'; + $file_system = \Drupal::service('file_system'); + $name = $file_system->tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPostForm('admin/config/regional/translate/import', $options, t('Import')); - drupal_unlink($name); + $file_system->unlink($name); } /** diff --git a/core/modules/locale/tests/src/Functional/LocaleJavascriptTranslationTest.php b/core/modules/locale/tests/src/Functional/LocaleJavascriptTranslationTest.php index 2219fa466..087052974 100644 --- a/core/modules/locale/tests/src/Functional/LocaleJavascriptTranslationTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleJavascriptTranslationTest.php @@ -21,6 +21,11 @@ class LocaleJavascriptTranslationTest extends BrowserTestBase { */ public static $modules = ['locale', 'locale_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + public function testFileParsing() { // This test is for ensuring that the regular expression in @@ -147,7 +152,7 @@ public function testLocaleTranslationJsDependencies() { $string = $strings[0]; $this->drupalPostForm(NULL, ['string' => 'Show description'], t('Filter')); - $edit = ['strings[' . $string->lid . '][translations][0]' => $this->randomString(16)]; + $edit = ['strings[' . $string->lid . '][translations][0]' => 'Mostrar descripcion']; $this->drupalPostForm(NULL, $edit, t('Save translations')); // Calculate the filename of the JS including the translations. @@ -155,6 +160,8 @@ public function testLocaleTranslationJsDependencies() { $js_filename = $prefix . '_' . $js_translation_files[$prefix] . '.js'; $content = $this->getSession()->getPage()->getContent(); + $this->assertRaw('core/misc/drupal.js'); + $this->assertRaw($js_filename); // Assert translations JS is included before drupal.js. $this->assertTrue(strpos($content, $js_filename) < strpos($content, 'core/misc/drupal.js'), 'Translations are included before Drupal.t.'); } diff --git a/core/modules/locale/tests/src/Functional/LocaleLibraryAlterTest.php b/core/modules/locale/tests/src/Functional/LocaleLibraryAlterTest.php deleted file mode 100644 index 5425fc5f4..000000000 --- a/core/modules/locale/tests/src/Functional/LocaleLibraryAlterTest.php +++ /dev/null @@ -1,36 +0,0 @@ -setLibraries(['core/jquery.ui.datepicker']); - $js_assets = $this->container->get('asset.resolver')->getJsAssets($assets, FALSE)[1]; - $this->assertTrue(array_key_exists('core/modules/locale/locale.datepicker.js', $js_assets), 'locale.datepicker.js added to scripts.'); - } - -} diff --git a/core/modules/locale/tests/src/Functional/LocaleLocaleLookupTest.php b/core/modules/locale/tests/src/Functional/LocaleLocaleLookupTest.php index 3dd4eadb5..02edcb767 100644 --- a/core/modules/locale/tests/src/Functional/LocaleLocaleLookupTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleLocaleLookupTest.php @@ -20,6 +20,11 @@ class LocaleLocaleLookupTest extends BrowserTestBase { */ public static $modules = ['locale', 'locale_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/locale/tests/src/Functional/LocaleNonInteractiveDevInstallTest.php b/core/modules/locale/tests/src/Functional/LocaleNonInteractiveDevInstallTest.php index 080616af9..6798f5a9a 100644 --- a/core/modules/locale/tests/src/Functional/LocaleNonInteractiveDevInstallTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleNonInteractiveDevInstallTest.php @@ -9,6 +9,11 @@ */ class LocaleNonInteractiveDevInstallTest extends LocaleNonInteractiveInstallTest { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/locale/tests/src/Functional/LocaleNonInteractiveInstallTest.php b/core/modules/locale/tests/src/Functional/LocaleNonInteractiveInstallTest.php index ee12a6713..ea3c1b5ab 100644 --- a/core/modules/locale/tests/src/Functional/LocaleNonInteractiveInstallTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleNonInteractiveInstallTest.php @@ -11,6 +11,11 @@ */ class LocaleNonInteractiveInstallTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Gets the version string to use in the translation file. * diff --git a/core/modules/locale/tests/src/Functional/LocalePathTest.php b/core/modules/locale/tests/src/Functional/LocalePathTest.php index 16cc26865..036b95747 100644 --- a/core/modules/locale/tests/src/Functional/LocalePathTest.php +++ b/core/modules/locale/tests/src/Functional/LocalePathTest.php @@ -5,14 +5,18 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\Traits\Core\PathAliasTestTrait; /** * Tests you can configure a language for individual URL aliases. * * @group locale + * @group path */ class LocalePathTest extends BrowserTestBase { + use PathAliasTestTrait; + /** * Modules to enable. * @@ -20,6 +24,11 @@ class LocalePathTest extends BrowserTestBase { */ public static $modules = ['node', 'locale', 'path', 'views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -69,18 +78,18 @@ public function testPathLanguageConfiguration() { $path = 'admin/config/search/path/add'; $english_path = $this->randomMachineName(8); $edit = [ - 'source' => '/node/' . $node->id(), - 'alias' => '/' . $english_path, - 'langcode' => 'en', + 'path[0][value]' => '/node/' . $node->id(), + 'alias[0][value]' => '/' . $english_path, + 'langcode[0][value]' => 'en', ]; $this->drupalPostForm($path, $edit, t('Save')); // Create a path alias in new custom language. $custom_language_path = $this->randomMachineName(8); $edit = [ - 'source' => '/node/' . $node->id(), - 'alias' => '/' . $custom_language_path, - 'langcode' => $langcode, + 'path[0][value]' => '/node/' . $node->id(), + 'alias[0][value]' => '/' . $custom_language_path, + 'langcode[0][value]' => $langcode, ]; $this->drupalPostForm($path, $edit, t('Save')); @@ -96,39 +105,24 @@ public function testPathLanguageConfiguration() { $custom_path = $this->randomMachineName(8); // Check priority of language for alias by source path. - $edit = [ - 'source' => '/node/' . $node->id(), - 'alias' => '/' . $custom_path, - 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, - ]; - $this->container->get('path.alias_storage')->save($edit['source'], $edit['alias'], $edit['langcode']); - $lookup_path = $this->container->get('path.alias_manager')->getAliasByPath('/node/' . $node->id(), 'en'); + $path_alias = $this->createPathAlias('/node/' . $node->id(), '/' . $custom_path, LanguageInterface::LANGCODE_NOT_SPECIFIED); + $lookup_path = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $node->id(), 'en'); $this->assertEqual('/' . $english_path, $lookup_path, 'English language alias has priority.'); // Same check for language 'xx'. - $lookup_path = $this->container->get('path.alias_manager')->getAliasByPath('/node/' . $node->id(), $prefix); + $lookup_path = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $node->id(), $prefix); $this->assertEqual('/' . $custom_language_path, $lookup_path, 'Custom language alias has priority.'); - $this->container->get('path.alias_storage')->delete($edit); + $path_alias->delete(); // Create language nodes to check priority of aliases. $first_node = $this->drupalCreateNode(['type' => 'page', 'promote' => 1, 'langcode' => 'en']); $second_node = $this->drupalCreateNode(['type' => 'page', 'promote' => 1, 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED]); // Assign a custom path alias to the first node with the English language. - $edit = [ - 'source' => '/node/' . $first_node->id(), - 'alias' => '/' . $custom_path, - 'langcode' => $first_node->language()->getId(), - ]; - $this->container->get('path.alias_storage')->save($edit['source'], $edit['alias'], $edit['langcode']); + $this->createPathAlias('/node/' . $first_node->id(), '/' . $custom_path, $first_node->language()->getId()); // Assign a custom path alias to second node with // LanguageInterface::LANGCODE_NOT_SPECIFIED. - $edit = [ - 'source' => '/node/' . $second_node->id(), - 'alias' => '/' . $custom_path, - 'langcode' => $second_node->language()->getId(), - ]; - $this->container->get('path.alias_storage')->save($edit['source'], $edit['alias'], $edit['langcode']); + $this->createPathAlias('/node/' . $second_node->id(), '/' . $custom_path, $second_node->language()->getId()); // Test that both node titles link to our path alias. $this->drupalGet('admin/content'); diff --git a/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php b/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php index 6af46e532..b85e737a0 100644 --- a/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php +++ b/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\locale\Functional; use Drupal\Component\Gettext\PoItem; +use Drupal\Core\Database\Database; use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\Tests\BrowserTestBase; @@ -27,6 +28,11 @@ class LocalePluralFormatTest extends BrowserTestBase { */ public static $modules = ['locale']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -187,7 +193,7 @@ public function testPluralEditDateFormatter() { // not save our source string for performance optimization if we do not ask // specifically for a language. \Drupal::translation()->formatPlural(1, '1 second', '@count seconds', [], ['langcode' => 'fr'])->render(); - $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", [':source' => "1 second" . PoItem::DELIMITER . "@count seconds"])->fetchField(); + $lid = Database::getConnection()->query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", [':source' => "1 second" . PoItem::DELIMITER . "@count seconds"])->fetchField(); // Look up editing page for this plural string and check fields. $search = [ 'string' => '1 second', @@ -270,8 +276,9 @@ public function testPluralEditExport() { $this->assertText('@count sata'); $this->assertText('@count sati'); + $connection = Database::getConnection(); // Edit langcode hr translations and see if that took effect. - $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", [':source' => "1 hour" . PoItem::DELIMITER . "@count hours"])->fetchField(); + $lid = $connection->query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", [':source' => "1 hour" . PoItem::DELIMITER . "@count hours"])->fetchField(); $edit = [ "strings[$lid][translations][1]" => '@count sata edited', ]; @@ -297,7 +304,7 @@ public function testPluralEditExport() { // not save our source string for performance optimization if we do not ask // specifically for a language. \Drupal::translation()->formatPlural(1, '1 day', '@count days', [], ['langcode' => 'fr'])->render(); - $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", [':source' => "1 day" . PoItem::DELIMITER . "@count days"])->fetchField(); + $lid = $connection->query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", [':source' => "1 day" . PoItem::DELIMITER . "@count days"])->fetchField(); // Look up editing page for this plural string and check fields. $search = [ 'string' => '1 day', @@ -352,11 +359,12 @@ public function testPluralEditExport() { * Additional options to pass to the translation import form. */ public function importPoFile($contents, array $options = []) { - $name = \Drupal::service('file_system')->tempnam('temporary://', "po_") . '.po'; + $file_system = \Drupal::service('file_system'); + $name = $file_system->tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPostForm('admin/config/regional/translate/import', $options, t('Import')); - drupal_unlink($name); + $file_system->unlink($name); } /** diff --git a/core/modules/locale/tests/src/Functional/LocaleTranslateStringTourTest.php b/core/modules/locale/tests/src/Functional/LocaleTranslateStringTourTest.php index ffd5bc7b0..19db1b38e 100644 --- a/core/modules/locale/tests/src/Functional/LocaleTranslateStringTourTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleTranslateStringTourTest.php @@ -25,6 +25,11 @@ class LocaleTranslateStringTourTest extends TourTestBase { */ public static $modules = ['locale', 'tour']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/locale/tests/src/Functional/LocaleTranslatedSchemaDefinitionTest.php b/core/modules/locale/tests/src/Functional/LocaleTranslatedSchemaDefinitionTest.php index ba5afb933..71186fa0c 100644 --- a/core/modules/locale/tests/src/Functional/LocaleTranslatedSchemaDefinitionTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleTranslatedSchemaDefinitionTest.php @@ -22,6 +22,11 @@ class LocaleTranslatedSchemaDefinitionTest extends BrowserTestBase { */ public static $modules = ['language', 'locale', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -53,7 +58,7 @@ public function testTranslatedSchemaDefinition() { ])->save(); // Ensure that the field is translated when access through the API. - $this->assertEqual('Translated Revision ID', \Drupal::entityManager()->getBaseFieldDefinitions('node')['vid']->getLabel()); + $this->assertEqual('Translated Revision ID', \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('node')['vid']->getLabel()); // Assert there are no updates. $this->assertFalse(\Drupal::service('entity.definition_update_manager')->needsUpdates()); diff --git a/core/modules/locale/tests/src/Functional/LocaleTranslationDownloadTest.php b/core/modules/locale/tests/src/Functional/LocaleTranslationDownloadTest.php index a6f43d46b..7b727518a 100644 --- a/core/modules/locale/tests/src/Functional/LocaleTranslationDownloadTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleTranslationDownloadTest.php @@ -20,6 +20,11 @@ class LocaleTranslationDownloadTest extends LocaleUpdateBase { */ protected $translationsStream; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -49,7 +54,7 @@ public function testUpdateImportSourceRemote() { ], $this->translationsStream); $url = \Drupal::service('url_generator')->generateFromRoute('', [], ['absolute' => TRUE]); - $uri = $url . PublicStream::basePath() . '/remote/8.x/contrib_module_one/contrib_module_one-8.x-1.1.de._po'; + $uri = $url . PublicStream::basePath() . '/remote/all/contrib_module_one/contrib_module_one-8.x-1.1.de._po'; $source_file = (object) [ 'uri' => $uri, ]; diff --git a/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php b/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php index d9aa561d6..f8fe9c6db 100644 --- a/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php @@ -24,6 +24,11 @@ class LocaleTranslationUiTest extends BrowserTestBase { */ public static $modules = ['locale']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Enable interface translation to English. */ diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php b/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php index 63ca5e762..2e2d82b63 100644 --- a/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php +++ b/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php @@ -91,7 +91,7 @@ protected function addLanguage($langcode) { $edit = ['predefined_langcode' => $langcode]; $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); $this->container->get('language_manager')->reset(); - $this->assertTrue(\Drupal::languageManager()->getLanguage($langcode), new FormattableMarkup('Language %langcode added.', ['%langcode' => $langcode])); + $this->assertNotEmpty(\Drupal::languageManager()->getLanguage($langcode), new FormattableMarkup('Language %langcode added.', ['%langcode' => $langcode])); } /** @@ -194,9 +194,9 @@ protected function setTranslationFiles() { // Add a number of files to the local file system to serve as remote // translation server and match the project definitions set in // locale_test_locale_translation_projects_alter(). - $this->makePoFile('remote/8.x/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, $translations_one); - $this->makePoFile('remote/8.x/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampOld, $translations_two); - $this->makePoFile('remote/8.x/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three); + $this->makePoFile('remote/all/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, $translations_one); + $this->makePoFile('remote/all/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampOld, $translations_two); + $this->makePoFile('remote/all/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three); // Add a number of files to the local file system to serve as local // translation files and match the project definitions set in @@ -301,9 +301,9 @@ protected function setCurrentTranslations() { * (optional) A message to display with the assertion. */ protected function assertTranslation($source, $translation, $langcode, $message = '') { - $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', [':source' => $source, ':langcode' => $langcode])->fetchField(); + $db_translation = Database::getConnection()->query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', [':source' => $source, ':langcode' => $langcode])->fetchField(); $db_translation = $db_translation == FALSE ? '' : $db_translation; - $this->assertEqual($translation, $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', ['%source' => $source, '%language' => $langcode])); + $this->assertEqual($translation, $db_translation, $message ? $message : new FormattableMarkup('Correct translation of %source (%language)', ['%source' => $source, '%language' => $langcode])); } } diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php b/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php index dd167439f..34ee9dc36 100644 --- a/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php @@ -16,6 +16,11 @@ class LocaleUpdateCronTest extends LocaleUpdateBase { protected $batchOutput = []; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateDevelopmentReleaseTest.php b/core/modules/locale/tests/src/Functional/LocaleUpdateDevelopmentReleaseTest.php index 5c972d1ef..592119840 100644 --- a/core/modules/locale/tests/src/Functional/LocaleUpdateDevelopmentReleaseTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleUpdateDevelopmentReleaseTest.php @@ -13,6 +13,11 @@ class LocaleUpdateDevelopmentReleaseTest extends BrowserTestBase { public static $modules = ['locale', 'locale_test_development_release']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); module_load_include('compare.inc', 'locale'); diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateInterfaceTest.php b/core/modules/locale/tests/src/Functional/LocaleUpdateInterfaceTest.php index dcf1ba08c..516d5b638 100644 --- a/core/modules/locale/tests/src/Functional/LocaleUpdateInterfaceTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleUpdateInterfaceTest.php @@ -19,6 +19,11 @@ class LocaleUpdateInterfaceTest extends LocaleUpdateBase { */ public static $modules = ['locale_test_translate']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -117,7 +122,7 @@ public function testInterface() { '@date' => $this->container->get('date.formatter')->format(REQUEST_TIME, 'html_date'), ]), 'Core translation update'); $update_button = $this->xpath('//input[@type="submit"][@value="' . t('Update translations') . '"]'); - $this->assertTrue($update_button, 'Update translations button'); + $this->assertNotEmpty($update_button, 'Update translations button'); } } diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php b/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php index f357075bf..d6818a8f1 100644 --- a/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\locale\Functional; +use Drupal\Core\Database\Database; use Drupal\Core\Language\LanguageInterface; /** @@ -11,6 +12,11 @@ */ class LocaleUpdateTest extends LocaleUpdateBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -346,14 +352,15 @@ public function testEnableLanguage() { $this->assertTranslation('Extraday', 'extra dag', 'nl'); // Check if the language data is added to the database. - $result = db_query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField(); - $this->assertTrue($result, 'Files added to file history'); + $connection = Database::getConnection(); + $result = $connection->query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField(); + $this->assertNotEmpty($result, 'Files added to file history'); // Remove a language. $this->drupalPostForm('admin/config/regional/language/delete/nl', [], t('Delete')); // Check if the language data is removed from the database. - $result = db_query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField(); + $result = $connection->query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField(); $this->assertFalse($result, 'Files removed from file history'); // Check that the Dutch translation is gone. diff --git a/core/modules/locale/tests/src/Kernel/LocaleBuildTest.php b/core/modules/locale/tests/src/Kernel/LocaleBuildTest.php new file mode 100644 index 000000000..3156acbda --- /dev/null +++ b/core/modules/locale/tests/src/Kernel/LocaleBuildTest.php @@ -0,0 +1,64 @@ +container->get('module_handler')->loadInclude('locale', 'compare.inc'); + /** @var \Drupal\Core\Extension\ExtensionList $module_list */ + $module_list = \Drupal::service('extension.list.module'); + + // Make the test modules look like a normal custom module. I.e. make the + // modules not hidden. locale_test_system_info_alter() modifies the project + // info of the locale_test and locale_test_translate modules. + \Drupal::state()->set('locale.test_system_info_alter', TRUE); + + // Confirm the project name and core value before the module is altered. + $projects = locale_translation_build_projects(); + $this->assertSame('locale_test', $projects['locale_test']->name); + $this->assertSame('all', $projects['locale_test']->core); + + $projects['locale_test']->langcode = 'de'; + $this->assertSame('/all/locale_test/locale_test-1.2.de.po', locale_translation_build_server_pattern($projects['locale_test'], '/%core/%project/%project-%version.%language.po')); + + // Alter both the name and core value of the project. + \Drupal::state()->set('locale.test_system_info_alter_name_core', TRUE); + drupal_static_reset('locale_translation_project_list'); + $module_list->reset(); + + // Confirm the name and core value are changed in $module->info. + $module = $module_list->get('locale_test'); + $this->assertSame('locale_test_alter', $module->info['name']); + $this->assertSame('8.6.7', $module->info['core']); + $this->assertSame('locale_test', $module->getName()); + + // Confirm the name and core value are not changed in the project. + $projects = locale_translation_build_projects(); + $this->assertSame('locale_test', $projects['locale_test']->name); + $this->assertSame('all', $projects['locale_test']->core); + + $projects['locale_test']->langcode = 'de'; + $this->assertSame('/all/locale_test/locale_test-1.2.de.po', locale_translation_build_server_pattern($projects['locale_test'], '/%core/%project/%project-%version.%language.po')); + } + +} diff --git a/core/modules/locale/tests/src/Kernel/LocaleConfigSubscriberTest.php b/core/modules/locale/tests/src/Kernel/LocaleConfigSubscriberTest.php index 67fdb1a97..caa9a734f 100644 --- a/core/modules/locale/tests/src/Kernel/LocaleConfigSubscriberTest.php +++ b/core/modules/locale/tests/src/Kernel/LocaleConfigSubscriberTest.php @@ -99,6 +99,7 @@ protected function setUpLocale() { // Set up the locale database the same way we have in the config samples. $this->setUpNoTranslation('locale_test.no_translation', 'test', 'Test', 'de'); $this->setUpTranslation('locale_test.translation', 'test', 'English test', 'German test', 'de'); + $this->setUpTranslation('locale_test.translation_multiple', 'test', 'English test', 'German test', 'de'); } /** @@ -111,6 +112,23 @@ public function testCreateTranslation() { $this->assertTranslation($config_name, 'Test (German)', 'de'); } + /** + * Tests creating translations configuration with multi value settings. + */ + public function testCreateTranslationMultiValue() { + $config_name = 'locale_test.translation_multiple'; + + $this->saveLanguageOverride($config_name, 'test_multiple', ['string' => 'String (German)', 'another_string' => 'Another string (German)'], 'de'); + $this->saveLanguageOverride($config_name, 'test_after_multiple', ['string' => 'After string (German)', 'another_string' => 'After another string (German)'], 'de'); + $strings = $this->stringStorage->getTranslations([ + 'type' => 'configuration', + 'name' => $config_name, + 'language' => 'de', + 'translated' => TRUE, + ]); + $this->assertCount(5, $strings); + } + /** * Tests importing community translations of shipped configuration. */ @@ -245,7 +263,7 @@ protected function setUpTranslation($config_name, $key, $source, $translation, $ * The configuration name. * @param string $key * The configuration key. - * @param string $value + * @param string|array $value * The configuration value to save. * @param string $langcode * The language code. @@ -446,7 +464,7 @@ protected function assertNoTranslation($config_name, $langcode) { * * @param string $config_name * The configuration name. - * @param string $translation + * @param string|array $translation * The translation. * @param string $langcode * The language code. diff --git a/core/modules/locale/tests/src/Kernel/LocaleLibraryAlterTest.php b/core/modules/locale/tests/src/Kernel/LocaleLibraryAlterTest.php new file mode 100644 index 000000000..3e227e7fc --- /dev/null +++ b/core/modules/locale/tests/src/Kernel/LocaleLibraryAlterTest.php @@ -0,0 +1,39 @@ +installSchema('locale', [ + 'locales_location', + 'locales_source', + ]); + + $assets = new AttachedAssets(); + $assets->setLibraries(['core/jquery.ui.datepicker']); + $js_assets = $this->container->get('asset.resolver')->getJsAssets($assets, FALSE)[1]; + $this->assertArrayHasKey('core/modules/locale/locale.datepicker.js', $js_assets); + } + +} diff --git a/core/modules/locale/tests/src/Kernel/LocaleStringTest.php b/core/modules/locale/tests/src/Kernel/LocaleStringTest.php index b0e440006..885b3c526 100644 --- a/core/modules/locale/tests/src/Kernel/LocaleStringTest.php +++ b/core/modules/locale/tests/src/Kernel/LocaleStringTest.php @@ -53,7 +53,7 @@ protected function setUp() { public function testStringCrudApi() { // Create source string. $source = $this->buildSourceString()->save(); - $this->assertTrue($source->lid); + $this->assertNotEmpty($source->lid); // Load strings by lid and source. $string1 = $this->storage->findString(['lid' => $source->lid]); @@ -61,7 +61,7 @@ public function testStringCrudApi() { $string2 = $this->storage->findString(['source' => $source->source, 'context' => $source->context]); $this->assertEquals($source, $string2); $string3 = $this->storage->findString(['source' => $source->source, 'context' => '']); - $this->assertFalse($string3); + $this->assertNull($string3); // Check version handling and updating. $this->assertEquals('none', $source->version); @@ -96,9 +96,9 @@ public function testStringCrudApi() { $source->delete(); $string = $this->storage->findString(['lid' => $lid]); - $this->assertFalse($string); + $this->assertNull($string); $deleted = $search = $this->storage->getTranslations(['lid' => $lid]); - $this->assertFalse($deleted); + $this->assertEmpty($deleted); // Tests that locations of different types and arbitrary lengths can be // added to a source string. Too long locations will be cut off. diff --git a/core/modules/locale/tests/src/Unit/LocaleLookupTest.php b/core/modules/locale/tests/src/Unit/LocaleLookupTest.php index 458bafa60..944137fd5 100644 --- a/core/modules/locale/tests/src/Unit/LocaleLookupTest.php +++ b/core/modules/locale/tests/src/Unit/LocaleLookupTest.php @@ -18,42 +18,42 @@ class LocaleLookupTest extends UnitTestCase { /** * A mocked storage to use when instantiating LocaleTranslation objects. * - * @var \Drupal\locale\StringStorageInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\locale\StringStorageInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $storage; /** * A mocked cache object. * - * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $cache; /** * A mocked lock object. * - * @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $lock; /** * A mocked user object built from AccountInterface. * - * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Session\AccountInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $user; /** * A mocked config factory built with UnitTestCase::getConfigFactoryStub(). * - * @var \Drupal\Core\Config\ConfigFactory|\PHPUnit_Framework_MockObject_MockBuilder + * @var \Drupal\Core\Config\ConfigFactory|\PHPUnit\Framework\MockObject\MockBuilder */ protected $configFactory; /** * A mocked language manager built from LanguageManagerInterface. * - * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $languageManager; @@ -68,20 +68,20 @@ class LocaleLookupTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->storage = $this->getMock('Drupal\locale\StringStorageInterface'); - $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); - $this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface'); + $this->storage = $this->createMock('Drupal\locale\StringStorageInterface'); + $this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface'); $this->lock->expects($this->never()) ->method($this->anything()); - $this->user = $this->getMock('Drupal\Core\Session\AccountInterface'); + $this->user = $this->createMock('Drupal\Core\Session\AccountInterface'); $this->user->expects($this->any()) ->method('getRoles') ->will($this->returnValue(['anonymous'])); $this->configFactory = $this->getConfigFactoryStub(['locale.settings' => ['cache_strings' => FALSE]]); - $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->languageManager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface'); $this->requestStack = new RequestStack(); $container = new ContainerBuilder(); @@ -243,7 +243,7 @@ public function testResolveCacheMissWithPersist() { * @covers ::resolveCacheMiss */ public function testResolveCacheMissNoTranslation() { - $string = $this->getMock('Drupal\locale\StringInterface'); + $string = $this->createMock('Drupal\locale\StringInterface'); $string->expects($this->once()) ->method('addLocation') ->will($this->returnSelf()); diff --git a/core/modules/locale/tests/src/Unit/LocaleTranslationTest.php b/core/modules/locale/tests/src/Unit/LocaleTranslationTest.php index c4a3f1e74..47c4cca8b 100644 --- a/core/modules/locale/tests/src/Unit/LocaleTranslationTest.php +++ b/core/modules/locale/tests/src/Unit/LocaleTranslationTest.php @@ -15,14 +15,14 @@ class LocaleTranslationTest extends UnitTestCase { /** * A mocked storage to use when instantiating LocaleTranslation objects. * - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit\Framework\MockObject\MockObject */ protected $storage; /** * A mocked language manager built from LanguageManagerInterface. * - * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $languageManager; @@ -37,10 +37,10 @@ class LocaleTranslationTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->storage = $this->getMock('Drupal\locale\StringStorageInterface'); - $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); - $this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface'); - $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->storage = $this->createMock('Drupal\locale\StringStorageInterface'); + $this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface'); + $this->languageManager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface'); $this->requestStack = new RequestStack(); } diff --git a/core/modules/locale/tests/src/Unit/StringBaseTest.php b/core/modules/locale/tests/src/Unit/StringBaseTest.php index 7436c7007..99bb0377a 100644 --- a/core/modules/locale/tests/src/Unit/StringBaseTest.php +++ b/core/modules/locale/tests/src/Unit/StringBaseTest.php @@ -17,7 +17,8 @@ class StringBaseTest extends UnitTestCase { */ public function testSaveWithoutStorage() { $string = new SourceString(['source' => 'test']); - $this->setExpectedException(StringStorageException::class, 'The string cannot be saved because its not bound to a storage: test'); + $this->expectException(StringStorageException::class); + $this->expectExceptionMessage('The string cannot be saved because its not bound to a storage: test'); $string->save(); } @@ -26,7 +27,8 @@ public function testSaveWithoutStorage() { */ public function testDeleteWithoutStorage() { $string = new SourceString(['lid' => 1, 'source' => 'test']); - $this->setExpectedException(StringStorageException::class, 'The string cannot be deleted because its not bound to a storage: test'); + $this->expectException(StringStorageException::class); + $this->expectExceptionMessage('The string cannot be deleted because its not bound to a storage: test'); $string->delete(); } diff --git a/core/modules/media/config/optional/views.view.media.yml b/core/modules/media/config/optional/views.view.media.yml index 7cac70aba..584b5f504 100644 --- a/core/modules/media/config/optional/views.view.media.yml +++ b/core/modules/media/config/optional/views.view.media.yml @@ -1,6 +1,8 @@ langcode: en status: true dependencies: + config: + - image.style.thumbnail module: - image - media @@ -8,11 +10,10 @@ dependencies: id: media label: Media module: views -description: '' +description: 'Find and manage media.' tag: '' base_table: media_field_data base_field: mid -core: 8.x display: default: display_plugin: default @@ -639,6 +640,8 @@ display: authenticated: authenticated anonymous: '0' administrator: '0' + operator_limit_selection: false + operator_list: { } is_grouped: false group_info: label: '' @@ -680,6 +683,8 @@ display: anonymous: '0' administrator: '0' reduce: false + operator_limit_selection: false + operator_list: { } is_grouped: false group_info: label: '' @@ -718,6 +723,8 @@ display: multiple: false remember_roles: authenticated: authenticated + operator_limit_selection: false + operator_list: { } is_grouped: true group_info: label: 'Published status' @@ -741,6 +748,45 @@ display: plugin_id: boolean entity_type: media entity_field: status + status_extra: + id: status_extra + table: media_field_data + field: status_extra + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + operator_limit_selection: false + operator_list: { } + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: media + plugin_id: media_status langcode: id: langcode table: media_field_data @@ -767,6 +813,8 @@ display: anonymous: '0' administrator: '0' reduce: false + operator_limit_selection: false + operator_list: { } is_grouped: false group_info: label: '' @@ -823,6 +871,7 @@ display: - 'languages:language_interface' - url - url.query_args + - user - user.permissions tags: { } media_page_list: @@ -850,5 +899,6 @@ display: - 'languages:language_interface' - url - url.query_args + - user - user.permissions tags: { } diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml index d5d8e5c7a..a419dd866 100644 --- a/core/modules/media/config/schema/media.schema.yml +++ b/core/modules/media/config/schema/media.schema.yml @@ -104,3 +104,23 @@ media.source.field_aware: source_field: type: string label: 'Source field' + +filter_settings.media_embed: + type: filter + label: 'Media Embed' + mapping: + default_view_mode: + type: string + label: 'The view mode that is used by default' + allowed_media_types: + type: sequence + label: 'Media types selectable in the Media Library' + sequence: + type: string + label: 'Media type' + allowed_view_modes: + type: sequence + label: 'View modes selectable in the "Edit media" dialog' + sequence: + type: string + label: 'View mode' diff --git a/core/modules/media/css/filter.caption.css b/core/modules/media/css/filter.caption.css new file mode 100644 index 000000000..a92505c30 --- /dev/null +++ b/core/modules/media/css/filter.caption.css @@ -0,0 +1,10 @@ +/** + * @file + * Caption filter: default styling for displaying Media Embed captions. + */ + +.caption .media .field, +.caption .media .field * { + float: none; + margin: unset; +} diff --git a/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css new file mode 100644 index 000000000..82923ff63 --- /dev/null +++ b/core/modules/media/css/plugins/drupalmedia/ckeditor.drupalmedia.css @@ -0,0 +1,41 @@ +/** + * @file + * Media embed: overrides to make focus styles and alignment work in CKEditor. + */ + +/** + * Allow the drupal-media element's width to collapse to the size of its + * contents so that the outline has no extra white space (margin). This + * emulates the image2 plugin's styles inherited by the drupallink CKEditor + * plugin. + */ +drupal-media { + display: inline-block; +} + +/** + * For center alignment, take advantage of drupal-media's inline-block + * display and center it as if it were text. + */ +.cke_widget_drupalmedia.align-center { + text-align: center; +} + +/** + * Fix positioning without delete button. Can be removed with this issue: + * @see https://www.drupal.org/project/drupal/issues/3074859 + */ +drupal-media .media-library-item__edit { + right: 10px; +} + +/** + * Allow alignment to display in CKEditor. + */ +drupal-media[data-align=left], +drupal-media[data-align=right] { + display: inline; +} +drupal-media[data-align=center] { + display: flex; +} diff --git a/core/modules/media/js/media_embed_ckeditor.theme.es6.js b/core/modules/media/js/media_embed_ckeditor.theme.es6.js new file mode 100644 index 000000000..477d62f8e --- /dev/null +++ b/core/modules/media/js/media_embed_ckeditor.theme.es6.js @@ -0,0 +1,30 @@ +/** + * @file + * Theme elements for the Media Embed CKEditor plugin. + */ + +(Drupal => { + /** + * Themes the error displayed when the media embed preview fails. + * + * @return {string} + * A string representing a DOM fragment. + * + * @see media-embed-error.html.twig + */ + Drupal.theme.mediaEmbedPreviewError = () => + `
${Drupal.t( + 'An error occurred while trying to preview the media. Please save your work and reload this page.', + )}
`; + + /** + * Themes the edit button for a media embed. + * + * @return {string} + * An HTML string to insert in the CKEditor. + */ + Drupal.theme.mediaEmbedEditButton = () => + ``; +})(Drupal); diff --git a/core/modules/media/js/media_embed_ckeditor.theme.js b/core/modules/media/js/media_embed_ckeditor.theme.js new file mode 100644 index 000000000..53d706e3b --- /dev/null +++ b/core/modules/media/js/media_embed_ckeditor.theme.js @@ -0,0 +1,16 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function (Drupal) { + Drupal.theme.mediaEmbedPreviewError = function () { + return '
' + Drupal.t('An error occurred while trying to preview the media. Please save your work and reload this page.') + '
'; + }; + + Drupal.theme.mediaEmbedEditButton = function () { + return ''; + }; +})(Drupal); \ No newline at end of file diff --git a/core/modules/media/js/plugins/drupalmedia/plugin.es6.js b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js new file mode 100644 index 000000000..ef7ea3f9b --- /dev/null +++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js @@ -0,0 +1,495 @@ +/** + * @file + * Drupal Media embed plugin. + */ + +(function(jQuery, Drupal, CKEDITOR) { + /** + * Gets the focused widget, if of the type specific for this plugin. + * + * @param {CKEDITOR.editor} editor + * A CKEditor instance. + * + * @return {?CKEDITOR.plugins.widget} + * The focused drupalmedia widget instance, or null. + */ + function getFocusedWidget(editor) { + const widget = editor.widgets.focused; + + if (widget && widget.name === 'drupalmedia') { + return widget; + } + return null; + } + + /** + * Makes embedded items linkable by integrating with the drupallink plugin. + * + * @param {CKEDITOR.editor} editor + * A CKEditor instance. + */ + function linkCommandIntegrator(editor) { + if (!editor.plugins.drupallink) { + return; + } + + CKEDITOR.plugins.drupallink.registerLinkableWidget('drupalmedia'); + + editor.getCommand('drupalunlink').on('exec', function(evt) { + const widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + widget.setData('link', null); + + this.refresh(editor, editor.elementPath()); + + evt.cancel(); + }); + + editor.getCommand('drupalunlink').on('refresh', function(evt) { + const widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + this.setState( + widget.data.link ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + ); + + evt.cancel(); + }); + + // Register context menu items for editing link. + if (editor.contextMenu) { + editor.contextMenu.addListener(() => { + const widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + if (widget.data.link) { + return { + link: CKEDITOR.TRISTATE_OFF, + unlink: CKEDITOR.TRISTATE_OFF, + }; + } + return {}; + }); + } + } + + CKEDITOR.plugins.add('drupalmedia', { + requires: 'widget', + + beforeInit(editor) { + // Configure CKEditor DTD for custom drupal-media element. + // @see https://www.drupal.org/node/2448449#comment-9717735 + const { dtd } = CKEDITOR; + // Allow text within the drupal-media tag. + dtd['drupal-media'] = { '#': 1 }; + // Register drupal-media element as an allowed child in each tag that can + // contain a div element and as an allowed child of the a tag. + Object.keys(dtd).forEach(tagName => { + if (dtd[tagName].div) { + dtd[tagName]['drupal-media'] = 1; + } + }); + dtd.a['drupal-media'] = 1; + + editor.widgets.add('drupalmedia', { + allowedContent: { + 'drupal-media': { + attributes: { + '!data-entity-type': true, + '!data-entity-uuid': true, + 'data-align': true, + 'data-caption': true, + alt: true, + title: true, + }, + classes: {}, + }, + }, + // Minimum HTML which is required by this widget to work. + // This does not use the object format used above, but a + // CKEDITOR.style instance, because requiredContent does not support + // the object format. + // @see https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_filter_contentRule.html + requiredContent: new CKEDITOR.style({ + element: 'drupal-media', + attributes: { + 'data-entity-type': '', + 'data-entity-uuid': '', + }, + }), + + pathName: Drupal.t('Embedded media'), + + editables: { + caption: { + selector: 'figcaption', + allowedContent: 'a[!href]; em strong cite code br', + pathName: Drupal.t('Caption'), + }, + }, + + getLabel() { + if (this.data.label) { + return this.data.label; + } + return Drupal.t('Embedded media'); + }, + + upcast(element, data) { + const { attributes } = element; + // This matches the behavior of the corresponding server-side text filter plugin. + if ( + element.name !== 'drupal-media' || + attributes['data-entity-type'] !== 'media' || + attributes['data-entity-uuid'] === undefined + ) { + return; + } + data.attributes = CKEDITOR.tools.copy(attributes); + data.hasCaption = data.attributes.hasOwnProperty('data-caption'); + // Add space to the empty caption to allow the server-side text + // filter to render a caption, allowing the placeholder-rendering + // CSS to work. + if (data.hasCaption && data.attributes['data-caption'] === '') { + data.attributes['data-caption'] = ' '; + } + data.label = null; + data.link = null; + if (element.parent.name === 'a') { + data.link = CKEDITOR.tools.copy(element.parent.attributes); + // Omit CKEditor-internal attributes. + Object.keys(element.parent.attributes).forEach(attrName => { + if (attrName.indexOf('data-cke-') !== -1) { + delete data.link[attrName]; + } + }); + } + // @see media_field_widget_form_alter() + const hostEntityLangcode = document + .getElementById(editor.name) + .getAttribute('data-media-embed-host-entity-langcode'); + if (hostEntityLangcode) { + data.hostEntityLangcode = hostEntityLangcode; + } + return element; + }, + + destroy() { + this._tearDownDynamicEditables(); + }, + + data(event) { + // Only run during changes. + if (this.oldData) { + // The server-side text filter plugin treats both an empty + // `data-caption` attribute and a non-existing one the same: it + // does not render a caption. But in the CKEditor Widget, we need + // to be able to show an empty caption with placeholder text using + // CSS even when technically there is no `data-caption` attribute + // value yet. That's why this CKEditor Widget has an independent + // `hasCaption` boolean (which is not an attribute) to know when + // to generate a non-empty `data-caption` attribute when the + // content creator has enabled caption: this makes the server-side + // text filter render a caption, allowing the placeholder-rendering + // CSS to work. + // @see core/modules/filter/css/filter.caption.css + // @see ckeditor_ckeditor_css_alter() + if (!this.data.hasCaption && this.oldData.hasCaption) { + delete this.data.attributes['data-caption']; + } else if ( + this.data.hasCaption && + !this.data.attributes['data-caption'] + ) { + this.data.attributes['data-caption'] = ' '; + } + } + + if (this._previewNeedsServerSideUpdate()) { + editor.fire('lockSnapshot'); + this._tearDownDynamicEditables(); + + this._loadPreview(widget => { + widget._setUpDynamicEditables(); + widget._setUpEditButton(); + editor.fire('unlockSnapshot'); + }); + } + + // Remove old attributes from drupal-media element within the widget. + if (this.oldData) { + Object.keys(this.oldData.attributes).forEach(attrName => { + this.element.removeAttribute(attrName); + }); + } + // Add attributes to drupal-media element within the widget. + this.element.setAttributes(this.data.attributes); + + // Track the previous state to allow checking if preview needs + // server side update. + this.oldData = CKEDITOR.tools.clone(this.data); + }, + + downcast() { + const downcastElement = new CKEDITOR.htmlParser.element( + 'drupal-media', + this.data.attributes, + ); + if (this.data.link) { + const link = new CKEDITOR.htmlParser.element('a', this.data.link); + link.add(downcastElement); + return link; + } + return downcastElement; + }, + + _setUpDynamicEditables() { + // Now that the caption is available in the DOM, make it editable. + if (this.initEditable('caption', this.definition.editables.caption)) { + const captionEditable = this.editables.caption; + // @see core/modules/filter/css/filter.caption.css + // @see ckeditor_ckeditor_css_alter() + captionEditable.setAttribute( + 'data-placeholder', + Drupal.t('Enter caption here'), + ); + // Ensure that any changes made to the caption are persisted in the + // widget's data-caption attribute. + this.captionObserver = new MutationObserver(() => { + const mediaAttributes = CKEDITOR.tools.clone( + this.data.attributes, + ); + mediaAttributes['data-caption'] = captionEditable.getData(); + this.setData('attributes', mediaAttributes); + }); + this.captionObserver.observe(captionEditable.$, { + characterData: true, + attributes: true, + childList: true, + subtree: true, + }); + // Some browsers will add a
tag to a newly created DOM element + // with no content. Remove this
if it is the only thing in the + // caption. Our placeholder support requires the element to be + // entirely empty. + // @see core/modules/filter/css/filter.caption.css + // @see core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js + if ( + captionEditable.$.childNodes.length === 1 && + captionEditable.$.childNodes.item(0).nodeName === 'BR' + ) { + captionEditable.$.removeChild( + captionEditable.$.childNodes.item(0), + ); + } + } + }, + + /** + * Injects HTML for edit button into the preview that was just loaded. + */ + _setUpEditButton() { + // No buttons for missing media. + if (this.element.findOne('.media-embed-error')) { + return; + } + + /** + * Determines if a node is an element node. + * + * @param {CKEDITOR.dom.node} n + * A DOM node to evaluate. + * + * @return {bool} + * Returns true if node is an element node and not a non-element + * node (such as NODE_TEXT, NODE_COMMENT, NODE_DOCUMENT or + * NODE_DOCUMENT_FRAGMENT). + * + * @see https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#property-NODE_ELEMENT + */ + const isElementNode = function(n) { + return n.type === CKEDITOR.NODE_ELEMENT; + }; + + // Find the actual embedded media in the DOM. + const embeddedMediaContainer = this.data.hasCaption + ? this.element.findOne('figure') + : this.element; + let embeddedMedia = embeddedMediaContainer.getFirst(isElementNode); + // If there is a link, the top-level element is the `a` tag, and the + // embedded media will be within the `a` tag. + if (this.data.link) { + embeddedMedia = embeddedMedia.getFirst(isElementNode); + } + // To allow the edit button to be absolutely positioned, the parent + // element must be positioned relative. + embeddedMedia.setStyle('position', 'relative'); + + const editButton = CKEDITOR.dom.element.createFromHtml( + Drupal.theme('mediaEmbedEditButton'), + ); + embeddedMedia.getFirst().insertBeforeMe(editButton); + + // Make the edit button do things. + const widget = this; + this.element + .findOne('.media-library-item__edit') + .on('click', event => { + const saveCallback = function(values) { + event.cancel(); + editor.fire('saveSnapshot'); + if (values.hasOwnProperty('attributes')) { + // Combine the dialog attributes with the widget attributes. + // This copies the properties from widget.data.attributes to + // values.attributes. (Properties already present + // in values.attributes are not overwritten.) + CKEDITOR.tools.extend( + values.attributes, + widget.data.attributes, + ); + // Allow the dialog to delete attributes by setting them + // to `false` or `none`. For example: `alt`. + Object.keys(values.attributes).forEach(prop => { + if ( + values.attributes[prop] === false || + (prop === 'data-align' && + values.attributes[prop] === 'none') + ) { + delete values.attributes[prop]; + } + }); + } + widget.setData({ + attributes: values.attributes, + hasCaption: !!values.hasCaption, + }); + editor.fire('saveSnapshot'); + }; + + Drupal.ckeditor.openDialog( + editor, + Drupal.url( + `editor/dialog/media/${editor.config.drupal.format}`, + ), + widget.data, + saveCallback, + {}, + ); + }); + + // Allow opening the dialog with the return key or the space bar + // by triggering a click event when a keydown event occurs on + // the edit button. + this.element + .findOne('.media-library-item__edit') + .on('keydown', event => { + // The character code for the return key. + const returnKey = 13; + // The character code for the space bar. + const spaceBar = 32; + if (typeof event.data !== 'undefined') { + const keypress = event.data.getKey(); + if (keypress === returnKey || keypress === spaceBar) { + // Clicks the edit button that triggered the 'keydown' + // event. + event.sender.$.click(); + } + // Stop propagation to keep the return key from + // adding a line break. + event.data.$.stopPropagation(); + event.data.$.stopImmediatePropagation(); + } + }); + }, + + _tearDownDynamicEditables() { + // If we are watching for changes to the caption, stop doing that. + if (this.captionObserver) { + this.captionObserver.disconnect(); + } + }, + + /** + * Determines if the preview needs to be re-rendered by the server. + * + * @return {boolean} + * Returns true if the data hashes differ. + */ + _previewNeedsServerSideUpdate() { + // When the widget is first loading, it of course needs to still get a preview! + if (!this.ready) { + return true; + } + + return this._hashData(this.oldData) !== this._hashData(this.data); + }, + + /** + * Computes a hash of the data that can only be previewed by the server. + * + * @return {string} + */ + _hashData(data) { + const dataToHash = CKEDITOR.tools.clone(data); + // The caption does not need rendering. + delete dataToHash.attributes['data-caption']; + // The media entity's label is server-side data and cannot be + // modified by the content author. + delete dataToHash.label; + // Changed link destinations do not affect the visual preview. + if (dataToHash.link) { + delete dataToHash.link.href; + } + return JSON.stringify(dataToHash); + }, + + /** + * Loads an media embed preview and runs a callback after insertion. + * + * Note the absence of caching, that's because this uses a GET request (which is cacheable) and the server takes + * special care to make the responses privately cacheable (i.e. per session) in the browser. + * + * @see \Drupal\media\Controller\MediaFilterController::preview() + * + * @param {function} callback + * A callback function that will be called after the preview has + * loaded. Receives the widget instance. + */ + _loadPreview(callback) { + jQuery.get({ + url: Drupal.url(`media/${editor.config.drupal.format}/preview`), + data: { + text: this.downcast().getOuterHtml(), + uuid: this.data.attributes['data-entity-uuid'], + }, + dataType: 'html', + success: (previewHtml, textStatus, jqXhr) => { + this.element.setHtml(previewHtml); + this.setData( + 'label', + jqXhr.getResponseHeader('Drupal-Media-Label'), + ); + callback(this); + }, + error: () => { + this.element.setHtml(Drupal.theme('mediaEmbedPreviewError')); + }, + }); + }, + }); + }, + + afterInit(editor) { + linkCommandIntegrator(editor); + }, + }); +})(jQuery, Drupal, CKEDITOR); diff --git a/core/modules/media/js/plugins/drupalmedia/plugin.js b/core/modules/media/js/plugins/drupalmedia/plugin.js new file mode 100644 index 000000000..7061bf814 --- /dev/null +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js @@ -0,0 +1,333 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function (jQuery, Drupal, CKEDITOR) { + function getFocusedWidget(editor) { + var widget = editor.widgets.focused; + + if (widget && widget.name === 'drupalmedia') { + return widget; + } + return null; + } + + function linkCommandIntegrator(editor) { + if (!editor.plugins.drupallink) { + return; + } + + CKEDITOR.plugins.drupallink.registerLinkableWidget('drupalmedia'); + + editor.getCommand('drupalunlink').on('exec', function (evt) { + var widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + widget.setData('link', null); + + this.refresh(editor, editor.elementPath()); + + evt.cancel(); + }); + + editor.getCommand('drupalunlink').on('refresh', function (evt) { + var widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + this.setState(widget.data.link ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED); + + evt.cancel(); + }); + + if (editor.contextMenu) { + editor.contextMenu.addListener(function () { + var widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + if (widget.data.link) { + return { + link: CKEDITOR.TRISTATE_OFF, + unlink: CKEDITOR.TRISTATE_OFF + }; + } + return {}; + }); + } + } + + CKEDITOR.plugins.add('drupalmedia', { + requires: 'widget', + + beforeInit: function beforeInit(editor) { + var dtd = CKEDITOR.dtd; + + dtd['drupal-media'] = { '#': 1 }; + + Object.keys(dtd).forEach(function (tagName) { + if (dtd[tagName].div) { + dtd[tagName]['drupal-media'] = 1; + } + }); + dtd.a['drupal-media'] = 1; + + editor.widgets.add('drupalmedia', { + allowedContent: { + 'drupal-media': { + attributes: { + '!data-entity-type': true, + '!data-entity-uuid': true, + 'data-align': true, + 'data-caption': true, + alt: true, + title: true + }, + classes: {} + } + }, + + requiredContent: new CKEDITOR.style({ + element: 'drupal-media', + attributes: { + 'data-entity-type': '', + 'data-entity-uuid': '' + } + }), + + pathName: Drupal.t('Embedded media'), + + editables: { + caption: { + selector: 'figcaption', + allowedContent: 'a[!href]; em strong cite code br', + pathName: Drupal.t('Caption') + } + }, + + getLabel: function getLabel() { + if (this.data.label) { + return this.data.label; + } + return Drupal.t('Embedded media'); + }, + upcast: function upcast(element, data) { + var attributes = element.attributes; + + if (element.name !== 'drupal-media' || attributes['data-entity-type'] !== 'media' || attributes['data-entity-uuid'] === undefined) { + return; + } + data.attributes = CKEDITOR.tools.copy(attributes); + data.hasCaption = data.attributes.hasOwnProperty('data-caption'); + + if (data.hasCaption && data.attributes['data-caption'] === '') { + data.attributes['data-caption'] = ' '; + } + data.label = null; + data.link = null; + if (element.parent.name === 'a') { + data.link = CKEDITOR.tools.copy(element.parent.attributes); + + Object.keys(element.parent.attributes).forEach(function (attrName) { + if (attrName.indexOf('data-cke-') !== -1) { + delete data.link[attrName]; + } + }); + } + + var hostEntityLangcode = document.getElementById(editor.name).getAttribute('data-media-embed-host-entity-langcode'); + if (hostEntityLangcode) { + data.hostEntityLangcode = hostEntityLangcode; + } + return element; + }, + destroy: function destroy() { + this._tearDownDynamicEditables(); + }, + data: function data(event) { + var _this = this; + + if (this.oldData) { + if (!this.data.hasCaption && this.oldData.hasCaption) { + delete this.data.attributes['data-caption']; + } else if (this.data.hasCaption && !this.data.attributes['data-caption']) { + this.data.attributes['data-caption'] = ' '; + } + } + + if (this._previewNeedsServerSideUpdate()) { + editor.fire('lockSnapshot'); + this._tearDownDynamicEditables(); + + this._loadPreview(function (widget) { + widget._setUpDynamicEditables(); + widget._setUpEditButton(); + editor.fire('unlockSnapshot'); + }); + } + + if (this.oldData) { + Object.keys(this.oldData.attributes).forEach(function (attrName) { + _this.element.removeAttribute(attrName); + }); + } + + this.element.setAttributes(this.data.attributes); + + this.oldData = CKEDITOR.tools.clone(this.data); + }, + downcast: function downcast() { + var downcastElement = new CKEDITOR.htmlParser.element('drupal-media', this.data.attributes); + if (this.data.link) { + var link = new CKEDITOR.htmlParser.element('a', this.data.link); + link.add(downcastElement); + return link; + } + return downcastElement; + }, + _setUpDynamicEditables: function _setUpDynamicEditables() { + var _this2 = this; + + if (this.initEditable('caption', this.definition.editables.caption)) { + var captionEditable = this.editables.caption; + + captionEditable.setAttribute('data-placeholder', Drupal.t('Enter caption here')); + + this.captionObserver = new MutationObserver(function () { + var mediaAttributes = CKEDITOR.tools.clone(_this2.data.attributes); + mediaAttributes['data-caption'] = captionEditable.getData(); + _this2.setData('attributes', mediaAttributes); + }); + this.captionObserver.observe(captionEditable.$, { + characterData: true, + attributes: true, + childList: true, + subtree: true + }); + + if (captionEditable.$.childNodes.length === 1 && captionEditable.$.childNodes.item(0).nodeName === 'BR') { + captionEditable.$.removeChild(captionEditable.$.childNodes.item(0)); + } + } + }, + _setUpEditButton: function _setUpEditButton() { + if (this.element.findOne('.media-embed-error')) { + return; + } + + var isElementNode = function isElementNode(n) { + return n.type === CKEDITOR.NODE_ELEMENT; + }; + + var embeddedMediaContainer = this.data.hasCaption ? this.element.findOne('figure') : this.element; + var embeddedMedia = embeddedMediaContainer.getFirst(isElementNode); + + if (this.data.link) { + embeddedMedia = embeddedMedia.getFirst(isElementNode); + } + + embeddedMedia.setStyle('position', 'relative'); + + var editButton = CKEDITOR.dom.element.createFromHtml(Drupal.theme('mediaEmbedEditButton')); + embeddedMedia.getFirst().insertBeforeMe(editButton); + + var widget = this; + this.element.findOne('.media-library-item__edit').on('click', function (event) { + var saveCallback = function saveCallback(values) { + event.cancel(); + editor.fire('saveSnapshot'); + if (values.hasOwnProperty('attributes')) { + CKEDITOR.tools.extend(values.attributes, widget.data.attributes); + + Object.keys(values.attributes).forEach(function (prop) { + if (values.attributes[prop] === false || prop === 'data-align' && values.attributes[prop] === 'none') { + delete values.attributes[prop]; + } + }); + } + widget.setData({ + attributes: values.attributes, + hasCaption: !!values.hasCaption + }); + editor.fire('saveSnapshot'); + }; + + Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/media/' + editor.config.drupal.format), widget.data, saveCallback, {}); + }); + + this.element.findOne('.media-library-item__edit').on('keydown', function (event) { + var returnKey = 13; + + var spaceBar = 32; + if (typeof event.data !== 'undefined') { + var keypress = event.data.getKey(); + if (keypress === returnKey || keypress === spaceBar) { + event.sender.$.click(); + } + + event.data.$.stopPropagation(); + event.data.$.stopImmediatePropagation(); + } + }); + }, + _tearDownDynamicEditables: function _tearDownDynamicEditables() { + if (this.captionObserver) { + this.captionObserver.disconnect(); + } + }, + _previewNeedsServerSideUpdate: function _previewNeedsServerSideUpdate() { + if (!this.ready) { + return true; + } + + return this._hashData(this.oldData) !== this._hashData(this.data); + }, + _hashData: function _hashData(data) { + var dataToHash = CKEDITOR.tools.clone(data); + + delete dataToHash.attributes['data-caption']; + + delete dataToHash.label; + + if (dataToHash.link) { + delete dataToHash.link.href; + } + return JSON.stringify(dataToHash); + }, + _loadPreview: function _loadPreview(callback) { + var _this3 = this; + + jQuery.get({ + url: Drupal.url('media/' + editor.config.drupal.format + '/preview'), + data: { + text: this.downcast().getOuterHtml(), + uuid: this.data.attributes['data-entity-uuid'] + }, + dataType: 'html', + success: function success(previewHtml, textStatus, jqXhr) { + _this3.element.setHtml(previewHtml); + _this3.setData('label', jqXhr.getResponseHeader('Drupal-Media-Label')); + callback(_this3); + }, + error: function error() { + _this3.element.setHtml(Drupal.theme('mediaEmbedPreviewError')); + } + }); + } + }); + }, + afterInit: function afterInit(editor) { + linkCommandIntegrator(editor); + } + }); +})(jQuery, Drupal, CKEDITOR); \ No newline at end of file diff --git a/core/modules/media/media.info.yml b/core/modules/media/media.info.yml index 86d5baed2..4766c0e52 100644 --- a/core/modules/media/media.info.yml +++ b/core/modules/media/media.info.yml @@ -2,16 +2,10 @@ name: Media description: 'Manages the creation, configuration, and display of media items.' type: module package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:file - drupal:image - drupal:user configure: media.settings - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/media/media.install b/core/modules/media/media.install index 5bf11c2ce..83b6a35c2 100644 --- a/core/modules/media/media.install +++ b/core/modules/media/media.install @@ -9,10 +9,13 @@ use Drupal\Core\Entity\Sql\SqlEntityStorageInterface; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; +use Drupal\media\Entity\MediaType; use Drupal\media\MediaTypeInterface; use Drupal\media\Plugin\media\Source\OEmbedInterface; use Drupal\user\Entity\Role; use Drupal\user\RoleInterface; +use Drupal\image\Plugin\Field\FieldType\ImageItem; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Implements hook_install(). @@ -24,7 +27,7 @@ function media_install() { $file_system = \Drupal::service('file_system'); $file_system->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); - $files = file_scan_directory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/'); + $files = $file_system->scanDirectory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/'); foreach ($files as $file) { // When reinstalling the media module we don't want to copy the icons when // they already exist. The icons could be replaced (by a contrib module or @@ -77,7 +80,7 @@ function media_requirements($phase) { // Prevent installation if the 1.x branch of the contrib module is enabled. if (\Drupal::moduleHandler()->moduleExists('media_entity')) { - $info = system_get_info('module', 'media_entity'); + $info = \Drupal::service('extension.list.module')->getExtensionInfo('media_entity'); if (version_compare($info['version'], '8.x-2') < 0) { $requirements['media_module_incompatibility'] = [ 'title' => t('Media'), @@ -118,6 +121,55 @@ function media_requirements($phase) { ]; } } + + // When a new media type with an image source is created we're configuring + // the default entity view display using the 'large' image style. + // Unfortunately, if a site builder has deleted the 'large' image style, + // we need some other image style to use, but at this point, we can't + // really know the site builder's intentions. So rather than do something + // surprising, we're leaving the embedded media without an image style and + // adding a warning that the site builder might want to add an image style. + // @see Drupal\media\Plugin\media\Source\Image::prepareViewDisplay + $module_handler = \Drupal::service('module_handler'); + foreach (MediaType::loadMultiple() as $type) { + // Load the default display. + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('media', $type->id()); + + $source_field_definition = $type->getSource()->getSourceFieldDefinition($type); + if (!is_a($source_field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE)) { + continue; + } + + $component = $display->getComponent($source_field_definition->getName()); + if (empty($component) || $component['type'] !== 'image' || !empty($component['settings']['image_style'])) { + continue; + } + + $action_item = ''; + if ($module_handler->moduleExists('field_ui') && \Drupal::currentUser()->hasPermission('administer media display')) { + $url = Url::fromRoute('entity.entity_view_display.media.default', [ + 'media_type' => $type->id(), + ])->toString(); + $action_item = new TranslatableMarkup('If you would like to change this, add an image style to the %field_name field.', + [ + '%field_name' => $source_field_definition->label(), + ':display' => $url, + ]); + } + $requirements['media_default_image_style_' . $type->id()] = [ + 'title' => t('Media'), + 'description' => new TranslatableMarkup('The default display for the %type media type is not currently using an image style on the %field_name field. Not using an image style can lead to much larger file downloads. @action_item', + [ + '%field_name' => $source_field_definition->label(), + '@action_item' => $action_item, + '%type' => $type->label(), + ] + ), + 'severity' => REQUIREMENT_WARNING, + ]; + } + } return $requirements; diff --git a/core/modules/media/media.libraries.yml b/core/modules/media/media.libraries.yml index 612318636..efc9dd1ad 100644 --- a/core/modules/media/media.libraries.yml +++ b/core/modules/media/media.libraries.yml @@ -23,3 +23,18 @@ oembed.frame: css: component: css/oembed.frame.css: {} + +filter.caption: + version: VERSION + css: + component: + css/filter.caption.css: {} + dependencies: + - filter/caption + +media_embed_ckeditor_theme: + version: VERSION + js: + js/media_embed_ckeditor.theme.js: {} + dependencies: + - core/drupal diff --git a/core/modules/media/media.module b/core/modules/media/media.module index 48ef64777..116c6f50a 100644 --- a/core/modules/media/media.module +++ b/core/modules/media/media.module @@ -17,6 +17,7 @@ use Drupal\Core\Template\Attribute; use Drupal\Core\Url; use Drupal\field\FieldConfigInterface; use Drupal\media\Plugin\media\Source\OEmbedInterface; +use Drupal\views\ViewExecutable; /** * Implements hook_help(). @@ -80,6 +81,12 @@ function media_theme() { 'placeholder_token' => '', ], ], + 'media_embed_error' => [ + 'variables' => [ + 'message' => NULL, + 'attributes' => [], + ], + ], ]; } @@ -359,3 +366,163 @@ function media_entity_type_alter(array &$entity_types) { $entity_type->setLinkTemplate('canonical', '/media/{media}'); } } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function media_form_filter_format_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Add an additional validate callback so we can ensure the order of filters + // is correct. + $form['#validate'][] = 'media_filter_format_edit_form_validate'; +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function media_form_filter_format_add_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + // Add an additional validate callback so we can ensure the order of filters + // is correct. + $form['#validate'][] = 'media_filter_format_edit_form_validate'; +} + +/** + * Validate callback to ensure filter order and allowed_html are compatible. + */ +function media_filter_format_edit_form_validate($form, FormStateInterface $form_state) { + if ($form_state->getTriggeringElement()['#name'] !== 'op') { + return; + } + + $allowed_html_path = [ + 'filters', + 'filter_html', + 'settings', + 'allowed_html', + ]; + + $filter_html_settings_path = [ + 'filters', + 'filter_html', + 'settings', + ]; + + $filter_html_enabled = $form_state->getValue([ + 'filters', + 'filter_html', + 'status', + ]); + + $media_embed_enabled = $form_state->getValue([ + 'filters', + 'media_embed', + 'status', + ]); + + if (!$media_embed_enabled) { + return; + } + + $get_filter_label = function ($filter_plugin_id) use ($form) { + return (string) $form['filters']['order'][$filter_plugin_id]['filter']['#markup']; + }; + + if ($filter_html_enabled && $allowed_html = $form_state->getValue($allowed_html_path)) { + /** @var \Drupal\filter\Entity\FilterFormat $filter_format */ + $filter_format = $form_state->getFormObject()->getEntity(); + + $filter_html = clone $filter_format->filters()->get('filter_html'); + $filter_html->setConfiguration(['settings' => $form_state->getValue($filter_html_settings_path)]); + $restrictions = $filter_html->getHTMLRestrictions(); + $allowed = $restrictions['allowed']; + + // Require `` HTML tag if filter_html is enabled. + if (!isset($allowed['drupal-media'])) { + $form_state->setError($form['filters']['settings']['filter_html']['allowed_html'], t('The %media-embed-filter-label filter requires <drupal-media> among the allowed HTML tags.', [ + '%media-embed-filter-label' => $get_filter_label('media_embed'), + ])); + } + else { + $required_attributes = [ + 'data-entity-type', + 'data-entity-uuid', + ]; + + // If there are no attributes, the allowed item is set to FALSE, + // otherwise, it is set to an array. + if ($allowed['drupal-media'] === FALSE) { + $missing_attributes = $required_attributes; + } + else { + $missing_attributes = array_diff($required_attributes, array_keys($allowed['drupal-media'])); + } + + if ($missing_attributes) { + $form_state->setError($form['filters']['settings']['filter_html']['allowed_html'], t('The <drupal-media> tag in the allowed HTML tags is missing the following attributes: %list.', [ + '%list' => implode(', ', $missing_attributes), + ])); + } + } + } + + $filters = $form_state->getValue('filters'); + + // The "media_embed" filter must run after "filter_align", "filter_caption", + // and "filter_html_image_secure". + $precedents = [ + 'filter_align', + 'filter_caption', + 'filter_html_image_secure', + ]; + + $error_filters = []; + foreach ($precedents as $filter_name) { + // A filter that should run before media embed filter. + $precedent = $filters[$filter_name]; + + if (empty($precedent['status']) || !isset($precedent['weight'])) { + continue; + } + + if ($precedent['weight'] >= $filters['media_embed']['weight']) { + $error_filters[$filter_name] = $get_filter_label($filter_name); + } + } + + if (!empty($error_filters)) { + $error_message = \Drupal::translation()->formatPlural( + count($error_filters), + 'The %media-embed-filter-label filter needs to be placed after the %filter filter.', + 'The %media-embed-filter-label filter needs to be placed after the following filters: %filters.', + [ + '%media-embed-filter-label' => $get_filter_label('media_embed'), + '%filter' => reset($error_filters), + '%filters' => implode(', ', $error_filters), + ] + ); + + $form_state->setErrorByName('filters', $error_message); + } +} + +/** + * Implements hook_field_widget_form_alter(). + */ +function media_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) { + // Add an attribute so that "drupalmedia" CKEditor plugin can pass the host + // entity's language to EditorMediaDialog, allowing it to present entities + // in the same language. + if (!empty($element['#type']) && $element['#type'] == 'text_format') { + $element['#attributes']['data-media-embed-host-entity-langcode'] = $context['items']->getLangcode(); + } +} + +/** + * Implements hook_views_query_substitutions(). + */ +function media_views_query_substitutions(ViewExecutable $view) { + $account = \Drupal::currentUser(); + return [ + '***VIEW_OWN_UNPUBLISHED_MEDIA***' => (int) $account->hasPermission('view own unpublished media'), + '***ADMINISTER_MEDIA***' => (int) $account->hasPermission('administer media'), + ]; +} diff --git a/core/modules/media/media.post_update.php b/core/modules/media/media.post_update.php index f2a6c375f..3bae4b448 100644 --- a/core/modules/media/media.post_update.php +++ b/core/modules/media/media.post_update.php @@ -5,6 +5,9 @@ * Post update functions for Media. */ +use Drupal\user\RoleInterface; +use Drupal\views\Views; + /** * Clear caches due to changes in local tasks and action links. */ @@ -28,3 +31,69 @@ function media_post_update_enable_standalone_url() { $config->set('standalone_url', TRUE)->save(TRUE); } } + +/** + * Add a status extra filter to the media view default display. + */ +function media_post_update_add_status_extra_filter() { + $view = Views::getView('media'); + + if (!$view) { + return; + } + + // Fetch the filters from the default display and add the new 'status_extra' + // filter if it does not yet exist. + $default_display = $view->getDisplay(); + $filters = $default_display->getOption('filters'); + + if (!isset($filters['status_extra'])) { + $filters['status_extra'] = [ + 'group_info' => [ + 'widget' => 'select', + 'group_items' => [], + 'multiple' => FALSE, + 'description' => '', + 'default_group_multiple' => [], + 'default_group' => 'All', + 'label' => '', + 'identifier' => '', + 'optional' => TRUE, + 'remember' => FALSE, + ], + 'group' => 1, + 'relationship' => 'none', + 'exposed' => FALSE, + 'expose' => [ + 'use_operator' => FALSE, + 'remember' => FALSE, + 'operator_id' => '', + 'multiple' => FALSE, + 'description' => '', + 'required' => FALSE, + 'label' => '', + 'operator_limit_selection' => FALSE, + 'operator' => '', + 'identifier' => '', + 'operator_list' => [], + 'remember_roles' => [RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID], + ], + 'entity_type' => 'media', + 'value' => '', + 'field' => 'status_extra', + 'is_grouped' => FALSE, + 'admin_label' => '', + 'operator' => '=', + 'table' => 'media_field_data', + 'plugin_id' => 'media_status', + 'id' => 'status_extra', + 'group_type' => 'group', + ]; + $default_display->setOption('filters', $filters); + $view->save(); + + return t("The 'Published status or admin user' filter was added to the %label view.", [ + '%label' => $view->storage->label(), + ]); + } +} diff --git a/core/modules/media/media.routing.yml b/core/modules/media/media.routing.yml index a9b634c89..918071361 100644 --- a/core/modules/media/media.routing.yml +++ b/core/modules/media/media.routing.yml @@ -39,3 +39,21 @@ media.settings: _title: 'Media settings' requirements: _permission: 'administer media' + +media.filter.preview: + path: '/media/{filter_format}/preview' + defaults: + _controller: '\Drupal\media\Controller\MediaFilterController::preview' + methods: [GET] + requirements: + _entity_access: 'filter_format.use' + _custom_access: '\Drupal\media\Controller\MediaFilterController::formatUsesMediaEmbedFilter' + +editor.media_dialog: + path: '/editor/dialog/media/{editor}' + defaults: + _form: '\Drupal\media\Form\EditorMediaDialog' + _title: 'Edit media' + methods: [POST] + requirements: + _entity_access: 'editor.use' diff --git a/core/modules/media/src/Controller/MediaFilterController.php b/core/modules/media/src/Controller/MediaFilterController.php new file mode 100644 index 000000000..4b9ea6bfe --- /dev/null +++ b/core/modules/media/src/Controller/MediaFilterController.php @@ -0,0 +1,143 @@ +renderer = $renderer; + $this->mediaStorage = $media_storage; + $this->entityRepository = $entity_repository; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('renderer'), + $container->get('entity_type.manager')->getStorage('media'), + $container->get('entity.repository') + ); + } + + /** + * Returns a HTML response containing a preview of the text after filtering. + * + * Applies all of the given text format's filters, not just the `media_embed` + * filter, because for example `filter_align` and `filter_caption` may apply + * to it as well. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * @param \Drupal\filter\FilterFormatInterface $filter_format + * The text format. + * + * @return \Symfony\Component\HttpFoundation\Response + * The filtered text. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * Throws an exception if 'text' parameter is not found in the query + * string. + * + * @see \Drupal\editor\EditorController::getUntransformedText + */ + public function preview(Request $request, FilterFormatInterface $filter_format) { + $text = $request->query->get('text'); + $uuid = $request->query->get('uuid'); + if ($text == '' || $uuid == '') { + throw new NotFoundHttpException(); + } + + $build = [ + '#type' => 'processed_text', + '#text' => $text, + '#format' => $filter_format->id(), + ]; + $html = $this->renderer->renderPlain($build); + + // Load the media item so we can embed the label in the response, for use + // in an ARIA label. + $headers = []; + if ($media = $this->entityRepository->loadEntityByUuid('media', $uuid)) { + $headers['Drupal-Media-Label'] = $this->entityRepository->getTranslationFromContext($media)->label(); + } + + // Note that we intentionally do not use: + // - \Drupal\Core\Cache\CacheableResponse because caching it on the server + // side is wasteful, hence there is no need for cacheability metadata. + // - \Drupal\Core\Render\HtmlResponse because there is no need for + // attachments nor cacheability metadata. + return (new Response($html, 200, $headers)) + // Do not allow any intermediary to cache the response, only the end user. + ->setPrivate() + // Allow the end user to cache it for up to 5 minutes. + ->setMaxAge(300); + } + + /** + * Checks access based on media_embed filter status on the text format. + * + * @param \Drupal\filter\FilterFormatInterface $filter_format + * The text format for which to check access. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function formatUsesMediaEmbedFilter(FilterFormatInterface $filter_format) { + $filters = $filter_format->filters(); + return AccessResult::allowedIf($filters->has('media_embed') && $filters->get('media_embed')->status) + ->addCacheableDependency($filter_format); + } + +} diff --git a/core/modules/media/src/Controller/OEmbedIframeController.php b/core/modules/media/src/Controller/OEmbedIframeController.php index a00114556..d5a879186 100644 --- a/core/modules/media/src/Controller/OEmbedIframeController.php +++ b/core/modules/media/src/Controller/OEmbedIframeController.php @@ -29,8 +29,9 @@ * of an iframe. * * @internal - * This is an internal part of the oEmbed system and should only be used by - * oEmbed-related code in Drupal core. + * This is an internal part of the media system in Drupal core and may be + * subject to change in minor releases. This class should not be + * instantiated or extended by external code. */ class OEmbedIframeController implements ContainerInjectionInterface { @@ -125,7 +126,7 @@ public function render(Request $request) { // Hash the URL and max dimensions, and ensure it is equal to the hash // parameter passed in the query string. $hash = $this->iFrameUrlHelper->getHash($url, $max_width, $max_height); - if (!Crypt::hashEquals($hash, $request->query->get('hash', ''))) { + if (!hash_equals($hash, $request->query->get('hash', ''))) { throw new AccessDeniedHttpException('This resource is not available'); } diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php index 2617980ee..55572560b 100644 --- a/core/modules/media/src/Entity/Media.php +++ b/core/modules/media/src/Entity/Media.php @@ -40,7 +40,6 @@ * "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm", * "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm", * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\media\MediaViewsData", * "route_provider" = { * "html" = "Drupal\media\Routing\MediaRouteProvider", diff --git a/core/modules/media/src/Form/EditorMediaDialog.php b/core/modules/media/src/Form/EditorMediaDialog.php new file mode 100644 index 000000000..450c7157a --- /dev/null +++ b/core/modules/media/src/Form/EditorMediaDialog.php @@ -0,0 +1,329 @@ +entityRepository = $entity_repository; + $this->entityDisplayRepository = $entity_display_repository; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.repository'), + $container->get('entity_display.repository') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'editor_media_dialog'; + } + + /** + * {@inheritdoc} + * + * @param \Drupal\editor\Entity\Editor $editor + * The text editor to which this dialog corresponds. + */ + public function buildForm(array $form, FormStateInterface $form_state, EditorInterface $editor = NULL) { + // This form is special, in that the default values do not come from the + // server side, but from the client side, from a text editor. We must cache + // this data in form state, because when the form is rebuilt, we will be + // receiving values from the form, instead of the values from the text + // editor. If we don't cache it, this data will be lost. By convention, + // the data that the text editor sends to any dialog is in the + // 'editor_object' key. + if (isset($form_state->getUserInput()['editor_object'])) { + $editor_object = $form_state->getUserInput()['editor_object']; + // The data that the text editor sends to any dialog is in + // the 'editor_object' key. + // @see core/modules/ckeditor/js/ckeditor.es6.js + $media_embed_element = $editor_object['attributes']; + $form_state->set('media_embed_element', $media_embed_element); + $has_caption = $editor_object['hasCaption']; + $form_state + ->set('hasCaption', $has_caption) + ->setCached(TRUE); + } + else { + // Retrieve the user input from form state. + $media_embed_element = $form_state->get('media_embed_element'); + $has_caption = $form_state->get('hasCaption'); + } + + $form['#tree'] = TRUE; + $form['#attached']['library'][] = 'editor/drupal.editor.dialog'; + $form['#prefix'] = '
'; + $form['#suffix'] = '
'; + + $filters = $editor->getFilterFormat()->filters(); + $filter_html = $filters->get('filter_html'); + $filter_align = $filters->get('filter_align'); + $filter_caption = $filters->get('filter_caption'); + $media_embed_filter = $filters->get('media_embed'); + + $allowed_attributes = []; + if ($filter_html->status) { + $restrictions = $filter_html->getHTMLRestrictions(); + $allowed_attributes = $restrictions['allowed']['drupal-media']; + } + + $media = $this->entityRepository->loadEntityByUuid('media', $media_embed_element['data-entity-uuid']); + + if ($image_field_name = $this->getMediaImageSourceFieldName($media)) { + // We'll want the alt text from the same language as the host. + if (!empty($editor_object['hostEntityLangcode']) && $media->hasTranslation($editor_object['hostEntityLangcode'])) { + $media = $media->getTranslation($editor_object['hostEntityLangcode']); + } + $settings = $media->{$image_field_name}->getItemDefinition()->getSettings(); + $alt = isset($media_embed_element['alt']) ? $media_embed_element['alt'] : NULL; + $form['alt'] = [ + '#type' => 'textfield', + '#title' => $this->t('Alternate text'), + '#default_value' => $alt, + '#description' => $this->t('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility.'), + '#required_error' => $this->t('Alternative text is required.
(Only in rare cases should this be left empty. To create empty alternative text, enter "" — two double quotes without any content).'), + '#maxlength' => 2048, + '#placeholder' => $media->{$image_field_name}->alt, + '#parents' => ['attributes', 'alt'], + '#access' => !empty($settings['alt_field']) && ($filter_html->status === FALSE || !empty($allowed_attributes['alt'])), + ]; + } + + // When Drupal core's filter_align is being used, the text editor offers the + // ability to change the alignment. + $form['align'] = [ + '#title' => $this->t('Align'), + '#type' => 'radios', + '#options' => [ + 'none' => $this->t('None'), + 'left' => $this->t('Left'), + 'center' => $this->t('Center'), + 'right' => $this->t('Right'), + ], + '#default_value' => empty($media_embed_element['data-align']) ? 'none' : $media_embed_element['data-align'], + '#attributes' => ['class' => ['container-inline']], + '#parents' => ['attributes', 'data-align'], + '#access' => $filter_align->status && ($filter_html->status === FALSE || !empty($allowed_attributes['data-align'])), + ]; + + // When Drupal core's filter_caption is being used, the text editor offers + // the ability to in-place edit the media's caption: show a toggle. + $form['caption'] = [ + '#title' => $this->t('Caption'), + '#type' => 'checkbox', + '#default_value' => $has_caption === 'true', + '#parents' => ['hasCaption'], + '#access' => $filter_caption->status && ($filter_html->status === FALSE || !empty($allowed_attributes['data-caption'])), + ]; + + $view_mode_options = array_intersect_key($this->entityDisplayRepository->getViewModeOptions('media'), $media_embed_filter->settings['allowed_view_modes']); + $default_view_mode = static::getViewModeDefaultValue($view_mode_options, $media_embed_filter, $media_embed_element['data-view-mode']); + + $form['view_mode'] = [ + '#title' => $this->t("Display"), + '#type' => 'select', + '#options' => $view_mode_options, + '#default_value' => $default_view_mode, + '#parents' => ['attributes', 'data-view-mode'], + '#access' => count($view_mode_options) >= 2, + ]; + + // Store the default from the MediaEmbed filter, so that if the selected + // view mode matches the default, we can drop the 'data-view-mode' + // attribute. + $form_state->set('filter_default_view_mode', $media_embed_filter->settings['default_view_mode']); + + if ((empty($form['alt']) || $form['alt']['#access'] === FALSE) && $form['align']['#access'] === FALSE && $form['caption']['#access'] === FALSE && $form['view_mode']['#access'] === FALSE) { + $format = $editor->getFilterFormat(); + $warning = $this->t('There is nothing to configure for this media.'); + $form['no_access_notice'] = ['#markup' => $warning]; + if ($format->access('update')) { + $tparams = [ + '@warning' => $warning, + '@edit_url' => $format->toUrl('edit-form')->toString(), + '%format' => $format->label(), + ]; + $form['no_access_notice']['#markup'] = $this->t('@warning Edit the text format %format to modify the attributes that can be overridden.', $tparams); + } + } + + $form['actions'] = [ + '#type' => 'actions', + ]; + $form['actions']['save_modal'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + // No regular submit-handler. This form only works via JavaScript. + '#submit' => [], + '#ajax' => [ + 'callback' => '::submitForm', + 'event' => 'click', + ], + // Prevent this hidden element from being tabbable. + '#attributes' => [ + 'tabindex' => -1, + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + + // When the `alt` attribute is set to two double quotes, transform it to the + // empty string: two double quotes signify "empty alt attribute". See above. + if (trim($form_state->getValue(['attributes', 'alt'], '')) === '""') { + $form_state->setValue(['attributes', 'alt'], '""'); + } + + // The `alt` attribute is optional: if it isn't set, the default value + // simply will not be overridden. It's important to set it to FALSE + // instead of unsetting the value. This way we explicitly inform + // the client side about the new value. + if ($form_state->hasValue(['attributes', 'alt']) && trim($form_state->getValue(['attributes', 'alt'])) === '') { + $form_state->setValue(['attributes', 'alt'], FALSE); + } + + // If the selected view mode matches the default on the filter, remove the + // attribute. + if (!empty($form_state->get('filter_default_view_mode')) && $form_state->getValue(['attributes', 'data-view-mode']) === $form_state->get('filter_default_view_mode')) { + $form_state->setValue(['attributes', 'data-view-mode'], FALSE); + } + + if ($form_state->getErrors()) { + unset($form['#prefix'], $form['#suffix']); + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -10, + ]; + $response->addCommand(new HtmlCommand('#editor-media-dialog-form', $form)); + } + else { + // Only send back the relevant values. + $values = [ + 'hasCaption' => $form_state->getValue('hasCaption'), + 'attributes' => $form_state->getValue('attributes'), + ]; + $response->addCommand(new EditorDialogSave($values)); + $response->addCommand(new CloseModalDialogCommand()); + } + + return $response; + } + + /** + * Gets the default value for the view mode form element. + * + * @param array $view_mode_options + * The array of options for the view mode form element. + * @param \Drupal\filter\Plugin\FilterInterface $media_embed_filter + * The media embed filter. + * @param string $media_element_view_mode_attribute + * The data-view-mode attribute on the element. + * + * @return string|null + * The default value for the view mode form element. + */ + public static function getViewModeDefaultValue(array $view_mode_options, FilterInterface $media_embed_filter, $media_element_view_mode_attribute) { + // The select element won't display without at least two options, so if + // that's the case, just return NULL. + if (count($view_mode_options) < 2) { + return NULL; + } + + $filter_default_view_mode = $media_embed_filter->settings['default_view_mode']; + + // If the current media embed ($media_embed_element) has a set view mode, + // we want to use that as the default in the select form element, + // otherwise we'll want to use the default for all embedded media. + if (!empty($media_element_view_mode_attribute) && array_key_exists($media_element_view_mode_attribute, $view_mode_options)) { + return $media_element_view_mode_attribute; + } + elseif (array_key_exists($filter_default_view_mode, $view_mode_options)) { + return $filter_default_view_mode; + } + + return NULL; + } + + /** + * Gets the name of an image media item's source field. + * + * @param \Drupal\media\MediaInterface $media + * The media item being embedded. + * + * @return string|null + * The name of the image source field configured for the media item, or + * NULL if the source field is not an image field. + */ + protected function getMediaImageSourceFieldName(MediaInterface $media) { + $field_definition = $media->getSource() + ->getSourceFieldDefinition($media->bundle->entity); + $item_class = $field_definition->getItemDefinition()->getClass(); + if (is_a($item_class, ImageItem::class, TRUE)) { + return $field_definition->getName(); + } + return NULL; + } + +} diff --git a/core/modules/media/src/Form/MediaDeleteMultipleConfirmForm.php b/core/modules/media/src/Form/MediaDeleteMultipleConfirmForm.php index 6437753cd..8952b7815 100644 --- a/core/modules/media/src/Form/MediaDeleteMultipleConfirmForm.php +++ b/core/modules/media/src/Form/MediaDeleteMultipleConfirmForm.php @@ -13,7 +13,7 @@ /** * Provides a confirmation form to delete multiple media items at once. * - * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. * This route is not used in Drupal core. As an internal API, it may also be * removed in a minor release. If you are using it, copy the class * and the related "entity.media.multiple_delete_confirm" route to your diff --git a/core/modules/media/src/MediaInterface.php b/core/modules/media/src/MediaInterface.php index 912bf97f3..3aeaef56a 100644 --- a/core/modules/media/src/MediaInterface.php +++ b/core/modules/media/src/MediaInterface.php @@ -51,7 +51,7 @@ public function getCreatedTime(); * @param int $timestamp * The media creation timestamp. * - * @return \Drupal\media\MediaInterface + * @return $this * The called media item. */ public function setCreatedTime($timestamp); diff --git a/core/modules/media/src/MediaSourceBase.php b/core/modules/media/src/MediaSourceBase.php index c01b9946e..5da82a644 100644 --- a/core/modules/media/src/MediaSourceBase.php +++ b/core/modules/media/src/MediaSourceBase.php @@ -339,7 +339,9 @@ public function getSourceFieldValue(MediaInterface $media) { * {@inheritdoc} */ public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) { - $display->setComponent($this->getSourceFieldDefinition($type)->getName()); + $display->setComponent($this->getSourceFieldDefinition($type)->getName(), [ + 'label' => 'visually_hidden', + ]); } /** diff --git a/core/modules/media/src/MediaTypeForm.php b/core/modules/media/src/MediaTypeForm.php index f2aea499c..289fc8aad 100644 --- a/core/modules/media/src/MediaTypeForm.php +++ b/core/modules/media/src/MediaTypeForm.php @@ -5,6 +5,7 @@ use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\ReplaceCommand; +use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Field\BaseFieldDefinition; @@ -35,6 +36,13 @@ class MediaTypeForm extends EntityForm { */ protected $entityFieldManager; + /** + * Entity display repository service. + * + * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface + */ + protected $entityDisplayRepository; + /** * Constructs a new class instance. * @@ -42,10 +50,13 @@ class MediaTypeForm extends EntityForm { * Media source plugin manager. * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager * Entity field manager service. + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entityDisplayRepository + * Entity display repository service. */ - public function __construct(PluginManagerInterface $source_manager, EntityFieldManagerInterface $entity_field_manager) { + public function __construct(PluginManagerInterface $source_manager, EntityFieldManagerInterface $entity_field_manager, EntityDisplayRepositoryInterface $entityDisplayRepository) { $this->sourceManager = $source_manager; $this->entityFieldManager = $entity_field_manager; + $this->entityDisplayRepository = $entityDisplayRepository; } /** @@ -54,7 +65,8 @@ public function __construct(PluginManagerInterface $source_manager, EntityFieldM public static function create(ContainerInterface $container) { return new static( $container->get('plugin.manager.media.source'), - $container->get('entity_field.manager') + $container->get('entity_field.manager'), + $container->get('entity_display.repository') ); } @@ -345,16 +357,17 @@ public function save(array $form, FormStateInterface $form_state) { // Add the new field to the default form and view displays for this // media type. if ($source_field->isDisplayConfigurable('form')) { - // @todo Replace entity_get_form_display() when #2367933 is done. - // https://www.drupal.org/node/2872159. - $display = entity_get_form_display('media', $media_type->id(), 'default'); + $display = $this->entityDisplayRepository->getFormDisplay('media', $media_type->id()); $source->prepareFormDisplay($media_type, $display); $display->save(); } if ($source_field->isDisplayConfigurable('view')) { - // @todo Replace entity_get_display() when #2367933 is done. - // https://www.drupal.org/node/2872159. - $display = entity_get_display('media', $media_type->id(), 'default'); + $display = $this->entityDisplayRepository->getViewDisplay('media', $media_type->id()); + + // Remove all default components. + foreach (array_keys($display->getComponents()) as $name) { + $display->removeComponent($name); + } $source->prepareViewDisplay($media_type, $display); $display->save(); } diff --git a/core/modules/media/src/MediaViewsData.php b/core/modules/media/src/MediaViewsData.php index 7a038cf16..393548be8 100644 --- a/core/modules/media/src/MediaViewsData.php +++ b/core/modules/media/src/MediaViewsData.php @@ -18,6 +18,16 @@ public function getViewsData() { $data['media_field_data']['table']['wizard_id'] = 'media'; $data['media_field_revision']['table']['wizard_id'] = 'media_revision'; + $data['media_field_data']['status_extra'] = [ + 'title' => $this->t('Published status or admin user'), + 'help' => $this->t('Filters out unpublished media if the current user cannot view it.'), + 'filter' => [ + 'field' => 'status', + 'id' => 'media_status', + 'label' => $this->t('Published status or admin user'), + ], + ]; + return $data; } diff --git a/core/modules/media/src/OEmbed/UrlResolver.php b/core/modules/media/src/OEmbed/UrlResolver.php index 5990fc4c7..599bdcc8b 100644 --- a/core/modules/media/src/OEmbed/UrlResolver.php +++ b/core/modules/media/src/OEmbed/UrlResolver.php @@ -86,16 +86,13 @@ public function __construct(ProviderRepositoryInterface $providers, ResourceFetc * * @return string|bool * URL of the oEmbed endpoint, or FALSE if the discovery was unsuccessful. - * - * @throws \Drupal\media\OEmbed\ResourceException - * If the resource cannot be retrieved. */ protected function discoverResourceUrl($url) { try { $response = $this->httpClient->get($url); } catch (RequestException $e) { - throw new ResourceException('Could not fetch oEmbed resource.', $url, [], $e); + return FALSE; } $document = Html::load((string) $response->getBody()); @@ -176,7 +173,7 @@ public function getResourceUrl($url, $max_width = NULL, $max_height = NULL) { // provide extra parameters in the query string. For example, Instagram also // supports the 'omitscript' parameter. $this->moduleHandler->alter('oembed_resource_url', $parsed_url, $provider); - $resource_url = $parsed_url['path'] . '?' . UrlHelper::buildQuery($parsed_url['query']); + $resource_url = $parsed_url['path'] . '?' . rawurldecode(UrlHelper::buildQuery($parsed_url['query'])); $this->urlCache[$url] = $resource_url; $this->cacheSet($cache_id, $resource_url); diff --git a/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php new file mode 100644 index 000000000..c3aff900d --- /dev/null +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php @@ -0,0 +1,130 @@ +moduleExtensionList = $extension_list_module; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('extension.list.module') + ); + } + + /** + * {@inheritdoc} + */ + public function isInternal() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(Editor $editor) { + return []; + } + + /** + * {@inheritdoc} + */ + public function getLibraries(Editor $editor) { + return [ + 'core/jquery', + 'core/drupal', + 'core/drupal.ajax', + 'media/media_embed_ckeditor_theme', + ]; + } + + /** + * {@inheritdoc} + */ + public function getFile() { + return $this->moduleExtensionList->getPath('media') . '/js/plugins/drupalmedia/plugin.js'; + } + + /** + * {@inheritdoc} + */ + public function getConfig(Editor $editor) { + return []; + } + + /** + * {@inheritdoc} + */ + public function isEnabled(Editor $editor) { + if (!$editor->hasAssociatedFilterFormat()) { + return FALSE; + } + + // Automatically enable this plugin if the text format associated with this + // text editor uses the media_embed filter. + $filters = $editor->getFilterFormat()->filters(); + return $filters->has('media_embed') && $filters->get('media_embed')->status; + } + + /** + * {@inheritdoc} + * + * @todo Improve this in https://www.drupal.org/project/drupal/issues/3072063 + */ + public function getCssFiles(Editor $editor) { + return [ + $this->moduleExtensionList->getPath('media') . '/css/plugins/drupalmedia/ckeditor.drupalmedia.css', + $this->moduleExtensionList->getPath('system') . '/css/components/hidden.module.css', + ]; + } + +} diff --git a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php index 65d601eb6..c08c84f40 100644 --- a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php @@ -229,6 +229,13 @@ public function viewElements(FieldItemListInterface $items, $langcode) { ], ]; + // An empty title attribute will disable title inheritance, so only + // add it if the resource has a title. + $title = $resource->getTitle(); + if ($title) { + $element[$delta]['#attributes']['title'] = $title; + } + CacheableMetadata::createFromObject($resource) ->addCacheTags($this->config->getCacheTags()) ->applyTo($element[$delta]); diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbed.php b/core/modules/media/src/Plugin/Filter/MediaEmbed.php new file mode 100644 index 000000000..b97b1f13c --- /dev/null +++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php @@ -0,0 +1,535 @@ +<drupal-media>. If used in conjunction with the 'Align/Caption' filters, make sure this filter is configured to run after them."), + * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE, + * settings = { + * "default_view_mode" = "default", + * "allowed_view_modes" = {}, + * "allowed_media_types" = {}, + * }, + * weight = 100, + * ) + * + * @internal + */ +class MediaEmbed extends FilterBase implements ContainerFactoryPluginInterface, TrustedCallbackInterface { + + /** + * The entity repository. + * + * @var \Drupal\Core\Entity\EntityRepositoryInterface + */ + protected $entityRepository; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity display repository. + * + * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface + */ + protected $entityDisplayRepository; + + /** + * The entity type bundle info service. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $entityTypeBundleInfo; + + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * The logger factory. + * + * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected $loggerFactory; + + /** + * An array of counters for the recursive rendering protection. + * + * Each counter takes into account all the relevant information about the + * field and the referenced entity that is being rendered. + * + * @var array + * + * @see \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter::$recursiveRenderDepth + */ + protected static $recursiveRenderDepth = []; + + /** + * Constructs a MediaEmbed object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository + * The entity repository. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository + * The entity display repository. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info + * The entity type bundle info service. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger factory. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityRepositoryInterface $entity_repository, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository, EntityTypeBundleInfoInterface $bundle_info, RendererInterface $renderer, LoggerChannelFactoryInterface $logger_factory) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->entityRepository = $entity_repository; + $this->entityTypeManager = $entity_type_manager; + $this->entityDisplayRepository = $entity_display_repository; + $this->entityTypeBundleInfo = $bundle_info; + $this->renderer = $renderer; + $this->loggerFactory = $logger_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.repository'), + $container->get('entity_type.manager'), + $container->get('entity_display.repository'), + $container->get('entity_type.bundle.info'), + $container->get('renderer'), + $container->get('logger.factory') + ); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $view_mode_options = $this->entityDisplayRepository->getViewModeOptions('media'); + + $form['default_view_mode'] = [ + '#type' => 'select', + '#options' => $view_mode_options, + '#title' => $this->t('Default view mode'), + '#default_value' => $this->settings['default_view_mode'], + '#description' => $this->t('The view mode that an embedded media item should be displayed in by default. This can be overridden using the data-view-mode attribute.'), + ]; + + $bundles = $this->entityTypeBundleInfo->getBundleInfo('media'); + $bundle_options = array_map(function ($item) { + return $item['label']; + }, $bundles); + $form['allowed_media_types'] = [ + '#title' => $this->t('Media types selectable in the Media Library'), + '#type' => 'checkboxes', + '#options' => $bundle_options, + '#default_value' => $this->settings['allowed_media_types'], + '#description' => $this->t('If none are selected, all will be allowed.'), + '#element_validate' => [[static::class, 'validateOptions']], + ]; + + $form['allowed_view_modes'] = [ + '#title' => $this->t("View modes selectable in the 'Edit media' dialog"), + '#type' => 'checkboxes', + '#options' => $view_mode_options, + '#default_value' => $this->settings['allowed_view_modes'], + '#description' => $this->t("If two or more view modes are selected, users will be able to update the view mode that an embedded media item should be displayed in after it has been embedded. If less than two view modes are selected, media will be embedded using the default view mode and no view mode options will appear after a media item has been embedded."), + '#element_validate' => [[static::class, 'validateOptions']], + ]; + + return $form; + } + + /** + * Form element validation handler. + * + * @param array $element + * The allowed_view_modes form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + */ + public static function validateOptions(array &$element, FormStateInterface $form_state) { + // Filters the #value property so only selected values appear in the + // config. + $form_state->setValueForElement($element, array_filter($element['#value'])); + } + + /** + * Builds the render array for the given media entity in the given langcode. + * + * @param \Drupal\media\MediaInterface $media + * A media entity to render. + * @param string $view_mode + * The view mode to render it in. + * @param string $langcode + * Language code in which the media entity should be rendered. + * + * @return array + * A render array. + */ + protected function renderMedia(MediaInterface $media, $view_mode, $langcode) { + // Due to render caching and delayed calls, filtering happens later + // in the rendering process through a '#pre_render' callback, so we + // need to generate a counter for the media entity that is being embedded. + // @see \Drupal\filter\Element\ProcessedText::preRenderText() + $recursive_render_id = $media->uuid(); + if (isset(static::$recursiveRenderDepth[$recursive_render_id])) { + static::$recursiveRenderDepth[$recursive_render_id]++; + } + else { + static::$recursiveRenderDepth[$recursive_render_id] = 1; + } + // Protect ourselves from recursive rendering: return an empty render array. + if (static::$recursiveRenderDepth[$recursive_render_id] > EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT) { + $this->loggerFactory->get('media')->error('During rendering of embedded media: recursive rendering detected for %entity_id. Aborting rendering.', [ + '%entity_id' => $media->id(), + ]); + return []; + } + + $build = $this->entityTypeManager + ->getViewBuilder('media') + ->view($media, $view_mode, $langcode); + + // Allows other modules to treat embedded media items differently. + // @see quickedit_entity_view_alter() + $build['#embed'] = TRUE; + + // There are a few concerns when rendering an embedded media entity: + // - entity access checking happens not during rendering but during routing, + // and therefore we have to do it explicitly here for the embedded entity. + $build['#access'] = $media->access('view', NULL, TRUE); + // - caching an embedded media entity separately is unnecessary; the host + // entity is already render cached. + unset($build['#cache']['keys']); + // - Contextual Links do not make sense for embedded entities; we only allow + // the host entity to be contextually managed. + $build['#pre_render'][] = static::class . '::disableContextualLinks'; + // - default styling may break captioned media embeds; attach asset library + // to ensure captions behave as intended. Do not set this at the root + // level of the render array, otherwise it will be attached always, + // instead of only when #access allows this media to be viewed and hence + // only when media is actually rendered. + $build[':media_embed']['#attached']['library'][] = 'media/filter.caption'; + + return $build; + } + + /** + * Builds the render array for the indicator when media cannot be loaded. + * + * @return array + * A render array. + */ + protected function renderMissingMediaIndicator() { + return [ + '#theme' => 'media_embed_error', + '#message' => $this->t('The referenced media source is missing and needs to be re-embedded.'), + ]; + } + + /** + * {@inheritdoc} + */ + public function process($text, $langcode) { + $result = new FilterProcessResult($text); + + if (stristr($text, 'query('//drupal-media[@data-entity-type="media" and normalize-space(@data-entity-uuid)!=""]') as $node) { + /** @var \DOMElement $node */ + $uuid = $node->getAttribute('data-entity-uuid'); + $view_mode_id = $node->getAttribute('data-view-mode') ?: $this->settings['default_view_mode']; + + // Delete the consumed attributes. + $node->removeAttribute('data-entity-type'); + $node->removeAttribute('data-entity-uuid'); + $node->removeAttribute('data-view-mode'); + + $media = $this->entityRepository->loadEntityByUuid('media', $uuid); + assert($media === NULL || $media instanceof MediaInterface); + if (!$media) { + $this->loggerFactory->get('media')->error('During rendering of embedded media: the media item with UUID "@uuid" does not exist.', ['@uuid' => $uuid]); + } + else { + $media = $this->entityRepository->getTranslationFromContext($media, $langcode); + $media = clone $media; + $this->applyPerEmbedMediaOverrides($node, $media); + } + + $view_mode = NULL; + if ($view_mode_id !== EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE) { + $view_mode = $this->entityRepository->loadEntityByConfigTarget('entity_view_mode', "media.$view_mode_id"); + if (!$view_mode) { + $this->loggerFactory->get('media')->error('During rendering of embedded media: the view mode "@view-mode-id" does not exist.', ['@view-mode-id' => $view_mode_id]); + } + } + + $build = $media && ($view_mode || $view_mode_id === EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE) + ? $this->renderMedia($media, $view_mode_id, $langcode) + : $this->renderMissingMediaIndicator(); + + if (empty($build['#attributes']['class'])) { + $build['#attributes']['class'] = []; + } + // Any attributes not consumed by the filter should be carried over to the + // rendered embedded entity. For example, `data-align` and `data-caption` + // should be carried over, so that even when embedded media goes missing, + // at least the caption and visual structure won't get lost. + foreach ($node->attributes as $attribute) { + if ($attribute->nodeName == 'class') { + // We don't want to overwrite the existing CSS class of the embedded + // media (or if the media entity can't be loaded, the missing media + // indicator). But, we need to merge in CSS classes added by other + // filters, such as filter_align, in order for those filters to work + // properly. + $build['#attributes']['class'] = array_unique(array_merge($build['#attributes']['class'], explode(' ', $attribute->nodeValue))); + } + else { + $build['#attributes'][$attribute->nodeName] = $attribute->nodeValue; + } + } + + $this->renderIntoDomNode($build, $node, $result); + } + + $result->setProcessedText(Html::serialize($dom)); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + if ($long) { + return $this->t(' +

You can embed media items:

+
    +
  • Choose which media item to embed: <drupal-media data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" />
  • +
  • Optionally also choose a view mode: data-view-mode="tiny_embed", otherwise the default view mode is used.
  • +
  • The data-entity-type="media" attribute is required for consistency.
  • +
'); + } + else { + return $this->t('You can embed media items (using the <drupal-media> tag).'); + } + } + + /** + * Renders the given render array into the given DOM node. + * + * @param array $build + * The render array to render in isolation. + * @param \DOMNode $node + * The DOM node to render into. + * @param \Drupal\filter\FilterProcessResult $result + * The accumulated result of filter processing, updated with the metadata + * bubbled during rendering. + */ + protected function renderIntoDomNode(array $build, \DOMNode $node, FilterProcessResult &$result) { + // We need to render the embedded entity: + // - without replacing placeholders, so that the placeholders are + // only replaced at the last possible moment. Hence we cannot use + // either renderPlain() or renderRoot(), so we must use render(). + // - without bubbling beyond this filter, because filters must + // ensure that the bubbleable metadata for the changes they make + // when filtering text makes it onto the FilterProcessResult + // object that they return ($result). To prevent that bubbling, we + // must wrap the call to render() in a render context. + $markup = $this->renderer->executeInRenderContext(new RenderContext(), function () use (&$build) { + return $this->renderer->render($build); + }); + $result = $result->merge(BubbleableMetadata::createFromRenderArray($build)); + static::replaceNodeContent($node, $markup); + } + + /** + * Replaces the contents of a DOMNode. + * + * @param \DOMNode $node + * A DOMNode object. + * @param string $content + * The text or HTML that will replace the contents of $node. + */ + protected static function replaceNodeContent(\DOMNode &$node, $content) { + if (strlen($content)) { + // Load the content into a new DOMDocument and retrieve the DOM nodes. + $replacement_nodes = Html::load($content)->getElementsByTagName('body') + ->item(0) + ->childNodes; + } + else { + $replacement_nodes = [$node->ownerDocument->createTextNode('')]; + } + + foreach ($replacement_nodes as $replacement_node) { + // Import the replacement node from the new DOMDocument into the original + // one, importing also the child nodes of the replacement node. + $replacement_node = $node->ownerDocument->importNode($replacement_node, TRUE); + $node->parentNode->insertBefore($replacement_node, $node); + } + $node->parentNode->removeChild($node); + } + + /** + * Disables Contextual Links for the embedded media by removing its property. + * + * @param array $build + * The render array for the embedded media. + * + * @return array + * The updated render array. + * + * @see \Drupal\Core\Entity\EntityViewBuilder::addContextualLinks() + */ + public static function disableContextualLinks(array $build) { + unset($build['#contextual_links']); + return $build; + } + + /** + * Applies attribute-based per-media embed overrides of media information. + * + * Currently, this only supports overriding an image media source's `alt` and + * `title`. Support for more overrides may be added in the future. + * + * @param \DOMElement $node + * The HTML tag whose attributes may contain overrides, and if such + * attributes are applied, they will be considered consumed and will + * therefore be removed from the HTML. + * @param \Drupal\media\MediaInterface $media + * The media entity to apply attribute-based overrides to, if any. + * + * @see \Drupal\media\Plugin\media\Source\Image + */ + protected function applyPerEmbedMediaOverrides(\DOMElement $node, MediaInterface $media) { + if ($image_field = $this->getMediaImageSourceField($media)) { + $settings = $media->{$image_field}->getItemDefinition()->getSettings(); + + if (!empty($settings['alt_field']) && $node->hasAttribute('alt')) { + // Allow the display of the image without an alt tag in special cases. + // Since setting the value in the EditorMediaDialog to an empty string + // restores the default value, this allows special cases where the alt + // text should not be set to the default value, but should be + // explicitly empty instead so it can be ignored by assistive + // technologies, such as screen readers. + if ($node->getAttribute('alt') === '""') { + $node->setAttribute('alt', NULL); + } + $media->{$image_field}->alt = $node->getAttribute('alt'); + // All media entities have a thumbnail. In the case of image media, it + // is conceivable that a particular view mode chooses to display the + // thumbnail instead of the image field itself since the thumbnail + // simply shows a smaller version of the actual media. So we must update + // its `alt` too. Because its `alt` already is inherited from the image + // field's `alt` at entity save time. + // @see \Drupal\media\Plugin\media\Source\Image::getMetadata() + $media->thumbnail->alt = $node->getAttribute('alt'); + // Delete the consumed attribute. + $node->removeAttribute('alt'); + } + + if (!empty($settings['title_field']) && $node->hasAttribute('title')) { + // See above, the explanations for `alt` also apply to `title`. + $media->{$image_field}->title = $node->getAttribute('title'); + $media->thumbnail->title = $node->getAttribute('title'); + // Delete the consumed attribute. + $node->removeAttribute('title'); + } + } + } + + /** + * Get image field from source config. + * + * @param \Drupal\media\MediaInterface $media + * A media entity. + * + * @return string|null + * String of image field name. + */ + protected function getMediaImageSourceField(MediaInterface $media) { + $field_definition = $media->getSource() + ->getSourceFieldDefinition($media->bundle->entity); + $item_class = $field_definition->getItemDefinition()->getClass(); + if ($item_class == ImageItem::class || is_subclass_of($item_class, ImageItem::class)) { + return $field_definition->getName(); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['disableContextualLinks']; + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + $dependencies = []; + // Combine the view modes from both config parameters. + $view_modes = $this->settings['allowed_view_modes'] + [$this->settings['default_view_mode']]; + $view_modes = array_unique(array_values($view_modes)); + $dependencies += ['config' => []]; + $storage = $this->entityTypeManager->getStorage('entity_view_mode'); + foreach ($view_modes as $view_mode) { + if ($entity_view_mode = $storage->load('media.' . $view_mode)) { + $dependencies[$entity_view_mode->getConfigDependencyKey()][] = $entity_view_mode->getConfigDependencyName(); + } + } + return $dependencies; + } + +} diff --git a/core/modules/media/src/Plugin/media/Source/AudioFile.php b/core/modules/media/src/Plugin/media/Source/AudioFile.php index 22291434e..1e2159308 100644 --- a/core/modules/media/src/Plugin/media/Source/AudioFile.php +++ b/core/modules/media/src/Plugin/media/Source/AudioFile.php @@ -33,6 +33,7 @@ public function createSourceField(MediaTypeInterface $type) { public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) { $display->setComponent($this->getSourceFieldDefinition($type)->getName(), [ 'type' => 'file_audio', + 'label' => 'visually_hidden', ]); } diff --git a/core/modules/media/src/Plugin/media/Source/Image.php b/core/modules/media/src/Plugin/media/Source/Image.php index 34b565c05..be37177f2 100644 --- a/core/modules/media/src/Plugin/media/Source/Image.php +++ b/core/modules/media/src/Plugin/media/Source/Image.php @@ -3,6 +3,7 @@ namespace Drupal\media\Plugin\media\Source; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; @@ -161,4 +162,24 @@ public function createSourceField(MediaTypeInterface $type) { return $field->set('settings', $settings); } + /** + * {@inheritdoc} + */ + public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) { + parent::prepareViewDisplay($type, $display); + + // Use the `large` image style and do not link the image to anything. + // This will prevent the out-of-the-box configuration from outputting very + // large raw images. If the `large` image style has been deleted, do not + // set an image style. + $field_name = $this->getSourceFieldDefinition($type)->getName(); + $component = $display->getComponent($field_name); + $component['settings']['image_link'] = ''; + $component['settings']['image_style'] = ''; + if ($this->entityTypeManager->getStorage('image_style')->load('large')) { + $component['settings']['image_style'] = 'large'; + } + $display->setComponent($field_name, $component); + } + } diff --git a/core/modules/media/src/Plugin/media/Source/OEmbed.php b/core/modules/media/src/Plugin/media/Source/OEmbed.php index afc626448..9fbea0b16 100644 --- a/core/modules/media/src/Plugin/media/Source/OEmbed.php +++ b/core/modules/media/src/Plugin/media/Source/OEmbed.php @@ -342,7 +342,11 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s */ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { $thumbnails_directory = $form_state->getValue('thumbnails_directory'); - if (!file_valid_uri($thumbnails_directory)) { + + /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */ + $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager'); + + if (!$stream_wrapper_manager->isValidUri($thumbnails_directory)) { $form_state->setErrorByName('thumbnails_directory', $this->t('@path is not a valid path.', [ '@path' => $thumbnails_directory, ])); @@ -438,6 +442,7 @@ public function getSourceFieldConstraints() { public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) { $display->setComponent($this->getSourceFieldDefinition($type)->getName(), [ 'type' => 'oembed', + 'label' => 'visually_hidden', ]); } diff --git a/core/modules/media/src/Plugin/media/Source/VideoFile.php b/core/modules/media/src/Plugin/media/Source/VideoFile.php index 214d36c41..ebcf2f474 100644 --- a/core/modules/media/src/Plugin/media/Source/VideoFile.php +++ b/core/modules/media/src/Plugin/media/Source/VideoFile.php @@ -33,6 +33,7 @@ public function createSourceField(MediaTypeInterface $type) { public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) { $display->setComponent($this->getSourceFieldDefinition($type)->getName(), [ 'type' => 'file_video', + 'label' => 'visually_hidden', ]); } diff --git a/core/modules/media/src/Plugin/views/filter/Status.php b/core/modules/media/src/Plugin/views/filter/Status.php new file mode 100644 index 000000000..0ac00a0fd --- /dev/null +++ b/core/modules/media/src/Plugin/views/filter/Status.php @@ -0,0 +1,55 @@ +ensureMyTable(); + $snippet = "$table.status = 1 OR ($table.uid = ***CURRENT_USER*** AND ***CURRENT_USER*** <> 0 AND ***VIEW_OWN_UNPUBLISHED_MEDIA*** = 1) OR ***ADMINISTER_MEDIA*** = 1"; + if ($this->moduleHandler->moduleExists('content_moderation')) { + $snippet .= ' OR ***VIEW_ANY_UNPUBLISHED_NODES*** = 1'; + } + $this->query->addWhereExpression($this->options['group'], $snippet); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + $contexts[] = 'user'; + return $contexts; + } + +} diff --git a/core/modules/media/templates/media-embed-error.html.twig b/core/modules/media/templates/media-embed-error.html.twig new file mode 100644 index 000000000..7da2c509e --- /dev/null +++ b/core/modules/media/templates/media-embed-error.html.twig @@ -0,0 +1,20 @@ +{# +/** + * @file + * Default theme implementation for a missing media error. + * + * Available variables + * - message: The message text. + * - attributes: HTML attributes for the containing element. + * + * When a response from the back end can't be returned, a related error message + * is displayed from JavaScript. + * + * @see Drupal.theme.mediaEmbedPreviewError + * + * @ingroup themeable + */ +#} + + {{ message }} +
diff --git a/core/modules/media/tests/fixtures/oembed/providers.json b/core/modules/media/tests/fixtures/oembed/providers.json index e618ec40f..3ff7de23c 100644 --- a/core/modules/media/tests/fixtures/oembed/providers.json +++ b/core/modules/media/tests/fixtures/oembed/providers.json @@ -57,5 +57,20 @@ "discovery": true } ] + }, + { + "provider_name": "YouTube", + "provider_url": "https://www.youtube.com/", + "endpoints": [ + { + "schemes": [ + "https://*.youtube.com/watch*", + "https://*.youtube.com/v/*\"", + "https://youtu.be/*" + ], + "url": "https://www.youtube.com/oembed", + "discovery": true + } + ] } ] diff --git a/core/modules/media/tests/fixtures/oembed/video_vimeo-no-title.html b/core/modules/media/tests/fixtures/oembed/video_vimeo-no-title.html new file mode 100644 index 000000000..210151fa3 --- /dev/null +++ b/core/modules/media/tests/fixtures/oembed/video_vimeo-no-title.html @@ -0,0 +1,8 @@ + + + + + + + diff --git a/core/modules/media/tests/fixtures/oembed/video_vimeo-no-title.json b/core/modules/media/tests/fixtures/oembed/video_vimeo-no-title.json new file mode 100644 index 000000000..a4e905685 --- /dev/null +++ b/core/modules/media/tests/fixtures/oembed/video_vimeo-no-title.json @@ -0,0 +1,16 @@ +{ + "type": "video", + "version": "1.0", + "provider_name": "Vimeo", + "provider_url": "https:\/\/vimeo.com\/", + "title": "", + "author_name": "Tendenci - The Open Source AMS", + "author_url": "https:\/\/vimeo.com\/schipul", + "html": "", + "width": 480, + "height": 360, + "description": "Special thanks to Tendenci, formerly Schipul for sponsoring this video with training, equipment and time. The open source way. All creative however was self directed by the individuals - A. Hughes (www.schipul.com\/ahughes) featuring QCait (www.schipul.com\/qcait) - Hands On Drupal\n\nDrupal is a free software package that allows an individual or a community of users to easily publish, manage and organize a wide variety of content on a website.\n\nNeed a little Drupal help or just want to geek out with us? Visit our www.schipul.com\/drupal for more info - we'd love to connect!\n\nGo here for Drupal Common Terms and Suggested Modules : http:\/\/schipul.com\/en\/helpfiles\/v\/229", + "thumbnail_url": "internal:\/core\/misc\/druplicon.png", + "thumbnail_width": 295, + "thumbnail_height": 221 +} diff --git a/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.info.yml b/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.info.yml new file mode 100644 index 000000000..7a7255f04 --- /dev/null +++ b/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.info.yml @@ -0,0 +1,8 @@ +name: Media CKEditor plugin test +description: 'Provides functionality to test the Media Embed CKEditor integration.' +type: module +package: Testing +version: VERSION +core: 8.x +dependencies: + - drupal:media diff --git a/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.module b/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.module new file mode 100644 index 000000000..cd1069f6c --- /dev/null +++ b/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.module @@ -0,0 +1,24 @@ +getActiveTheme()->getName(); +} + +/** + * Implements hook_preprocess_HOOK(). + */ +function media_test_ckeditor_preprocess_media_embed_error(&$variables) { + $variables['attributes']['class'][] = 'this-error-message-is-themeable'; +} diff --git a/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.services.yml b/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.services.yml new file mode 100644 index 000000000..4d6eee8c8 --- /dev/null +++ b/core/modules/media/tests/modules/media_test_ckeditor/media_test_ckeditor.services.yml @@ -0,0 +1,5 @@ +services: + media_test_ckeditor.route_subscriber: + class: Drupal\media_test_ckeditor\Routing\RouteSubscriber + tags: + - { name: event_subscriber } diff --git a/core/modules/media/tests/modules/media_test_ckeditor/src/Controller/TestMediaFilterController.php b/core/modules/media/tests/modules/media_test_ckeditor/src/Controller/TestMediaFilterController.php new file mode 100644 index 000000000..94893d94e --- /dev/null +++ b/core/modules/media/tests/modules/media_test_ckeditor/src/Controller/TestMediaFilterController.php @@ -0,0 +1,25 @@ +get('test_media_filter_controller_throw_error', FALSE)) { + throw new NotFoundHttpException(); + } + return parent::preview($request, $filter_format); + } + +} diff --git a/core/modules/media/tests/modules/media_test_ckeditor/src/Routing/RouteSubscriber.php b/core/modules/media/tests/modules/media_test_ckeditor/src/Routing/RouteSubscriber.php new file mode 100644 index 000000000..027f497b4 --- /dev/null +++ b/core/modules/media/tests/modules/media_test_ckeditor/src/Routing/RouteSubscriber.php @@ -0,0 +1,22 @@ +get('media.filter.preview')) { + $route->setDefault('_controller', '\Drupal\media_test_ckeditor\Controller\TestMediaFilterController::preview'); + } + } + +} diff --git a/core/modules/media/tests/modules/media_test_filter/media_test_filter.info.yml b/core/modules/media/tests/modules/media_test_filter/media_test_filter.info.yml new file mode 100644 index 000000000..5c4c7838d --- /dev/null +++ b/core/modules/media/tests/modules/media_test_filter/media_test_filter.info.yml @@ -0,0 +1,8 @@ +name: Media Filter test +description: 'Provides functionality to test the Media Embed filter.' +type: module +package: Testing +version: VERSION +core: 8.x +dependencies: + - drupal:media diff --git a/core/modules/media/tests/modules/media_test_filter/media_test_filter.module b/core/modules/media/tests/modules/media_test_filter/media_test_filter.module new file mode 100644 index 000000000..0600144e7 --- /dev/null +++ b/core/modules/media/tests/modules/media_test_filter/media_test_filter.module @@ -0,0 +1,32 @@ +addCacheTags(['_media_test_filter_access:' . $entity->getEntityTypeId() . ':' . $entity->id()]); +} + +/** + * Implements hook_entity_view_alter(). + */ +function media_test_filter_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) { + $build['#attributes']['data-media-embed-test-view-mode'] = $display->getMode(); +} + +/** + * Implements hook_preprocess_HOOK(). + */ +function media_test_filter_preprocess_media_embed_error(&$variables) { + $variables['attributes']['class'][] = 'this-error-message-is-themeable'; +} diff --git a/core/modules/media/tests/modules/media_test_oembed/media_test_oembed.info.yml b/core/modules/media/tests/modules/media_test_oembed/media_test_oembed.info.yml index 64857ba62..2ad5d2fad 100644 --- a/core/modules/media/tests/modules/media_test_oembed/media_test_oembed.info.yml +++ b/core/modules/media/tests/modules/media_test_oembed/media_test_oembed.info.yml @@ -2,13 +2,7 @@ name: Media oEmbed test description: 'Provides functionality to mimic an oEmbed provider.' type: module package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:media - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/media/tests/modules/media_test_oembed/src/Controller/ResourceController.php b/core/modules/media/tests/modules/media_test_oembed/src/Controller/ResourceController.php index ca401e720..ab58e2f96 100644 --- a/core/modules/media/tests/modules/media_test_oembed/src/Controller/ResourceController.php +++ b/core/modules/media/tests/modules/media_test_oembed/src/Controller/ResourceController.php @@ -11,22 +11,27 @@ class ResourceController { /** - * Returns the contents of an oEmbed resource fixture. + * Creates an oEmbed resource response. * * @param \Symfony\Component\HttpFoundation\Request $request * The request. * * @return \Symfony\Component\HttpFoundation\Response - * The JSON response. + * The oEmbed resource response. */ public function get(Request $request) { $asset_url = $request->query->get('url'); $resources = \Drupal::state()->get(static::class, []); - $content = file_get_contents($resources[$asset_url]); - $response = new Response($content); - $response->headers->set('Content-Type', 'application/json'); + if ($resources[$asset_url] === 404) { + $response = new Response('Not Found', 404); + } + else { + $content = file_get_contents($resources[$asset_url]); + $response = new Response($content); + $response->headers->set('Content-Type', 'application/json'); + } return $response; } @@ -45,4 +50,16 @@ public static function setResourceUrl($asset_url, $resource_path) { \Drupal::state()->set(static::class, $resources); } + /** + * Maps an asset URL to a 404 response. + * + * @param string $asset_url + * The asset URL. + */ + public static function setResource404($asset_url) { + $resources = \Drupal::state()->get(static::class, []); + $resources[$asset_url] = 404; + \Drupal::state()->set(static::class, $resources); + } + } diff --git a/core/modules/media/tests/modules/media_test_source/media_test_source.info.yml b/core/modules/media/tests/modules/media_test_source/media_test_source.info.yml index e661547c8..c0a873619 100644 --- a/core/modules/media/tests/modules/media_test_source/media_test_source.info.yml +++ b/core/modules/media/tests/modules/media_test_source/media_test_source.info.yml @@ -1,12 +1,6 @@ name: 'Test media source' type: module description: 'Provides test media source to test configuration forms.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/media/tests/modules/media_test_type/media_test_type.info.yml b/core/modules/media/tests/modules/media_test_type/media_test_type.info.yml index ff10d8d90..5e81ed0ac 100644 --- a/core/modules/media/tests/modules/media_test_type/media_test_type.info.yml +++ b/core/modules/media/tests/modules/media_test_type/media_test_type.info.yml @@ -1,15 +1,9 @@ name: 'Media test type' type: module description: 'Provides test type for a media item.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION +version: VERSION dependencies: - drupal:media - drupal:media_test_source - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml index 2c212b824..eb71b614c 100644 --- a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml +++ b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: media_field_data base_field: mid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/media/tests/modules/media_test_views/media_test_views.info.yml b/core/modules/media/tests/modules/media_test_views/media_test_views.info.yml index b919e58a7..ee833fc39 100644 --- a/core/modules/media/tests/modules/media_test_views/media_test_views.info.yml +++ b/core/modules/media/tests/modules/media_test_views/media_test_views.info.yml @@ -2,14 +2,8 @@ name: 'Media test views' type: module description: 'Provides default views for views media tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:media - drupal:views - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php b/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php index eecc9cde5..b4e576761 100644 --- a/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php +++ b/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php @@ -27,6 +27,11 @@ class OEmbedFormatterTest extends MediaFunctionalTestBase { 'media_test_oembed', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -58,20 +63,35 @@ public function providerRender() { [ 'iframe' => [ 'src' => '/media/oembed?url=https%3A//vimeo.com/7073899', - 'width' => 480, - 'height' => 360, + 'width' => '480', + 'height' => '360', + 'title' => 'Drupal Rap Video - Schipulcon09', ], ], ], 'Vimeo video, resized' => [ 'https://vimeo.com/7073899', 'video_vimeo.json?maxwidth=100&maxheight=100', - ['max_width' => 100, 'max_height' => 100], + ['max_width' => '100', 'max_height' => '100'], + [ + 'iframe' => [ + 'src' => '/media/oembed?url=https%3A//vimeo.com/7073899', + 'width' => '100', + 'height' => '100', + 'title' => 'Drupal Rap Video - Schipulcon09', + ], + ], + ], + 'Vimeo video, no title' => [ + 'https://vimeo.com/7073899', + 'video_vimeo-no-title.json', + [], [ 'iframe' => [ 'src' => '/media/oembed?url=https%3A//vimeo.com/7073899', - 'width' => 100, - 'height' => 100, + 'width' => '480', + 'height' => '360', + 'title' => NULL, ], ], ], @@ -82,8 +102,8 @@ public function providerRender() { [ 'iframe' => [ 'src' => '/media/oembed?url=https%3A//twitter.com/drupaldevdays/status/935643039741202432', - 'width' => 550, - 'height' => 360, + 'width' => '550', + 'height' => '360', ], ], ], @@ -94,8 +114,8 @@ public function providerRender() { [ 'img' => [ 'src' => '/core/misc/druplicon.png', - 'width' => 88, - 'height' => 100, + 'width' => '88', + 'height' => '100', ], ], ], @@ -171,8 +191,14 @@ public function testRender($url, $resource_url, array $formatter_settings, array $assert = $this->assertSession(); $assert->statusCodeEquals(200); foreach ($selectors as $selector => $attributes) { + $element = $assert->elementExists('css', $selector); foreach ($attributes as $attribute => $value) { - $assert->elementAttributeContains('css', $selector, $attribute, $value); + if (isset($value)) { + $this->assertContains($value, $element->getAttribute($attribute)); + } + else { + $this->assertFalse($element->hasAttribute($attribute)); + } } } } diff --git a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php index 2b92c73be..6f990b664 100644 --- a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php +++ b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php @@ -22,6 +22,11 @@ class MediaHalJsonAnonTest extends MediaResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonBasicAuthTest.php b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonBasicAuthTest.php index 73d37b377..92019bf75 100644 --- a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonBasicAuthTest.php +++ b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class MediaHalJsonBasicAuthTest extends MediaHalJsonAnonTest { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonCookieTest.php b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonCookieTest.php index 798cf39a2..16a76ec71 100644 --- a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonCookieTest.php +++ b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonCookieTest.php @@ -16,4 +16,9 @@ class MediaHalJsonCookieTest extends MediaHalJsonAnonTest { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonAnonTest.php b/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonAnonTest.php index 6a5611f9c..4618e492c 100644 --- a/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonAnonTest.php +++ b/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonAnonTest.php @@ -17,6 +17,11 @@ class MediaTypeHalJsonAnonTest extends MediaTypeResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonBasicAuthTest.php b/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonBasicAuthTest.php index 0ef13e995..3fc0aaba1 100644 --- a/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonBasicAuthTest.php +++ b/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class MediaTypeHalJsonBasicAuthTest extends MediaTypeResourceTestBase { */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonCookieTest.php b/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonCookieTest.php index c66b499ef..62de7a48b 100644 --- a/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonCookieTest.php +++ b/core/modules/media/tests/src/Functional/Hal/MediaTypeHalJsonCookieTest.php @@ -17,6 +17,11 @@ class MediaTypeHalJsonCookieTest extends MediaTypeResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/MediaAccessTest.php b/core/modules/media/tests/src/Functional/MediaAccessTest.php index 926686d13..c50f57330 100644 --- a/core/modules/media/tests/src/Functional/MediaAccessTest.php +++ b/core/modules/media/tests/src/Functional/MediaAccessTest.php @@ -26,6 +26,11 @@ class MediaAccessTest extends MediaFunctionalTestBase { 'media_test_source', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -178,7 +183,7 @@ public function testMediaAccess() { $this->drupalGet('admin/content'); $assert_session->linkByHrefExists('/admin/content/media'); $this->clickLink('Media'); - $this->assertCacheContext('user.permissions'); + $this->assertCacheContext('user'); $assert_session->statusCodeEquals(200); $assert_session->elementExists('css', '.view-media'); $assert_session->pageTextContains($this->loggedInUser->getDisplayName()); @@ -366,7 +371,7 @@ public function testReferencedRendering() { ]); $media_parent->save(); - entity_get_display('media', $media_type->id(), 'full') + \Drupal::service('entity_display.repository')->getViewDisplay('media', $media_type->id(), 'full') ->set('content', []) ->setComponent('title', ['type' => 'string']) ->setComponent('field_reference', [ diff --git a/core/modules/media/tests/src/Functional/MediaBulkFormTest.php b/core/modules/media/tests/src/Functional/MediaBulkFormTest.php index fb9caf1fd..5bb70a2a8 100644 --- a/core/modules/media/tests/src/Functional/MediaBulkFormTest.php +++ b/core/modules/media/tests/src/Functional/MediaBulkFormTest.php @@ -19,6 +19,11 @@ class MediaBulkFormTest extends MediaFunctionalTestBase { */ public static $modules = ['media_test_views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The test media type. * diff --git a/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php b/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php index a2c3530ab..eb3402fc8 100644 --- a/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php +++ b/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php @@ -24,6 +24,11 @@ class MediaCacheTagsTest extends EntityWithUriCacheTagsTestBase { 'media_test_source', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/MediaContextualLinksTest.php b/core/modules/media/tests/src/Functional/MediaContextualLinksTest.php index 5eeaa95ce..35c1224c7 100644 --- a/core/modules/media/tests/src/Functional/MediaContextualLinksTest.php +++ b/core/modules/media/tests/src/Functional/MediaContextualLinksTest.php @@ -18,6 +18,11 @@ class MediaContextualLinksTest extends MediaFunctionalTestBase { 'contextual', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests contextual links. */ diff --git a/core/modules/media/tests/src/Functional/MediaFunctionalTestCreateMediaTypeTrait.php b/core/modules/media/tests/src/Functional/MediaFunctionalTestCreateMediaTypeTrait.php index 265671b3d..047075a6d 100644 --- a/core/modules/media/tests/src/Functional/MediaFunctionalTestCreateMediaTypeTrait.php +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestCreateMediaTypeTrait.php @@ -9,7 +9,7 @@ /** * Trait with helpers for Media functional tests. * - * @deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\media\Traits\MediaTypeCreationTrait instead. * * @see https://www.drupal.org/node/2981614 diff --git a/core/modules/media/tests/src/Functional/MediaInstallTest.php b/core/modules/media/tests/src/Functional/MediaInstallTest.php index 010f07698..0811322fb 100644 --- a/core/modules/media/tests/src/Functional/MediaInstallTest.php +++ b/core/modules/media/tests/src/Functional/MediaInstallTest.php @@ -16,6 +16,11 @@ class MediaInstallTest extends BrowserTestBase { */ public static $modules = ['media']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/MediaOverviewPageTest.php b/core/modules/media/tests/src/Functional/MediaOverviewPageTest.php index a95515280..d4f82eaea 100644 --- a/core/modules/media/tests/src/Functional/MediaOverviewPageTest.php +++ b/core/modules/media/tests/src/Functional/MediaOverviewPageTest.php @@ -13,6 +13,11 @@ */ class MediaOverviewPageTest extends MediaFunctionalTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -32,7 +37,7 @@ public function testMediaOverviewPage() { $assert_session->statusCodeEquals(403); $role = Role::load(RoleInterface::AUTHENTICATED_ID); $this->grantPermissions($role, ['access media overview']); - $this->drupalGet('/admin/content/media'); + $this->getSession()->reload(); $assert_session->statusCodeEquals(200); $assert_session->titleEquals('Media | Drupal'); $assert_session->fieldExists('Media name'); @@ -70,64 +75,56 @@ public function testMediaOverviewPage() { 'name' => 'Media 2', 'uid' => $this->adminUser->id(), 'status' => FALSE, + 'changed' => time() - 50, ]); $media2->save(); $media3 = Media::create([ 'bundle' => $media_type1->id(), 'name' => 'Media 3', 'uid' => $this->nonAdminUser->id(), + 'changed' => time() - 100, ]); $media3->save(); - // Verify the view is now correctly populated. + // Verify the view is now correctly populated. The non-admin user can only + // view published media. $this->grantPermissions($role, [ 'view media', 'update any media', 'delete any media', ]); - $this->drupalGet('/admin/content/media'); + $this->getSession()->reload(); $row1 = $assert_session->elementExists('css', 'table tbody tr:nth-child(1)'); $row2 = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)'); - $row3 = $assert_session->elementExists('css', 'table tbody tr:nth-child(3)'); // Media thumbnails. $assert_session->elementExists('css', 'td.views-field-thumbnail__target-id img', $row1); $assert_session->elementExists('css', 'td.views-field-thumbnail__target-id img', $row2); - $assert_session->elementExists('css', 'td.views-field-thumbnail__target-id img', $row3); // Media names. $name1 = $assert_session->elementExists('css', 'td.views-field-name a', $row1); $this->assertSame($media1->label(), $name1->getText()); $name2 = $assert_session->elementExists('css', 'td.views-field-name a', $row2); - $this->assertSame($media2->label(), $name2->getText()); - $name3 = $assert_session->elementExists('css', 'td.views-field-name a', $row3); - $this->assertSame($media3->label(), $name3->getText()); + $this->assertSame($media3->label(), $name2->getText()); $assert_session->linkByHrefExists('/media/' . $media1->id()); - $assert_session->linkByHrefExists('/media/' . $media2->id()); $assert_session->linkByHrefExists('/media/' . $media3->id()); // Media types. $type_element1 = $assert_session->elementExists('css', 'td.views-field-bundle', $row1); $this->assertSame($media_type1->label(), $type_element1->getText()); $type_element2 = $assert_session->elementExists('css', 'td.views-field-bundle', $row2); - $this->assertSame($media_type2->label(), $type_element2->getText()); - $type_element3 = $assert_session->elementExists('css', 'td.views-field-bundle', $row3); - $this->assertSame($media_type1->label(), $type_element3->getText()); + $this->assertSame($media_type1->label(), $type_element2->getText()); // Media authors. $author_element1 = $assert_session->elementExists('css', 'td.views-field-uid', $row1); $this->assertSame($this->adminUser->getDisplayName(), $author_element1->getText()); - $author_element2 = $assert_session->elementExists('css', 'td.views-field-uid', $row2); - $this->assertSame($this->adminUser->getDisplayName(), $author_element2->getText()); - $author_element3 = $assert_session->elementExists('css', 'td.views-field-uid', $row3); + $author_element3 = $assert_session->elementExists('css', 'td.views-field-uid', $row2); $this->assertSame($this->nonAdminUser->getDisplayName(), $author_element3->getText()); // Media publishing status. $status_element1 = $assert_session->elementExists('css', 'td.views-field-status', $row1); $this->assertSame('Published', $status_element1->getText()); - $status_element2 = $assert_session->elementExists('css', 'td.views-field-status', $row2); - $this->assertSame('Unpublished', $status_element2->getText()); - $status_element3 = $assert_session->elementExists('css', 'td.views-field-status', $row3); + $status_element3 = $assert_session->elementExists('css', 'td.views-field-status', $row2); $this->assertSame('Published', $status_element3->getText()); // Timestamp. @@ -142,6 +139,36 @@ public function testMediaOverviewPage() { $delete_link1 = $assert_session->elementExists('css', 'td.views-field-operations li.delete a', $row1); $this->assertSame('Delete', $delete_link1->getText()); $assert_session->linkByHrefExists('/media/' . $media1->id() . '/delete'); + + // Make the user the owner of the unpublished media item and assert the + // media item is only visible with the 'view own unpublished media' + // permission. + $media2->setOwner($this->nonAdminUser)->save(); + $this->getSession()->reload(); + $assert_session->pageTextNotContains($media2->label()); + $role->grantPermission('view own unpublished media')->save(); + $this->getSession()->reload(); + $row = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)'); + $name = $assert_session->elementExists('css', 'td.views-field-name a', $row); + $this->assertSame($media2->label(), $name->getText()); + $status_element = $assert_session->elementExists('css', 'td.views-field-status', $row); + $this->assertSame('Unpublished', $status_element->getText()); + + // Assert the admin user can always view all media. + $this->drupalLogin($this->adminUser); + $this->drupalGet('/admin/content/media'); + $row1 = $assert_session->elementExists('css', 'table tbody tr:nth-child(1)'); + $row2 = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)'); + $row3 = $assert_session->elementExists('css', 'table tbody tr:nth-child(3)'); + $name1 = $assert_session->elementExists('css', 'td.views-field-name a', $row1); + $this->assertSame($media1->label(), $name1->getText()); + $name2 = $assert_session->elementExists('css', 'td.views-field-name a', $row2); + $this->assertSame($media2->label(), $name2->getText()); + $name3 = $assert_session->elementExists('css', 'td.views-field-name a', $row3); + $this->assertSame($media3->label(), $name3->getText()); + $assert_session->linkByHrefExists('/media/' . $media1->id()); + $assert_session->linkByHrefExists('/media/' . $media2->id()); + $assert_session->linkByHrefExists('/media/' . $media3->id()); } } diff --git a/core/modules/media/tests/src/Functional/MediaRevisionTest.php b/core/modules/media/tests/src/Functional/MediaRevisionTest.php index 8291d2fcf..6ec57bea2 100644 --- a/core/modules/media/tests/src/Functional/MediaRevisionTest.php +++ b/core/modules/media/tests/src/Functional/MediaRevisionTest.php @@ -15,6 +15,11 @@ */ class MediaRevisionTest extends MediaFunctionalTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Checks media revision operations. */ @@ -22,7 +27,7 @@ public function testRevisions() { $assert = $this->assertSession(); /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $media_storage */ - $media_storage = $this->container->get('entity.manager')->getStorage('media'); + $media_storage = $this->container->get('entity_type.manager')->getStorage('media'); // Create a media type and media item. $media_type = $this->createMediaType('test'); @@ -80,10 +85,10 @@ public function testFileMediaRevision() { $uri = 'temporary://foo.txt'; file_put_contents($uri, $this->randomString(128)); - $this->createMediaType('file', ['id' => 'file', 'new_revision' => TRUE]); + $this->createMediaType('file', ['id' => 'document', 'new_revision' => TRUE]); // Create a media item. - $this->drupalGet('/media/add/file'); + $this->drupalGet('/media/add/document'); $page = $this->getSession()->getPage(); $page->fillField('Name', 'Foobar'); $page->attachFileToField('File', $this->container->get('file_system')->realpath($uri)); diff --git a/core/modules/media/tests/src/Functional/MediaSettingsTest.php b/core/modules/media/tests/src/Functional/MediaSettingsTest.php index ba25392f5..0a5a00b2f 100644 --- a/core/modules/media/tests/src/Functional/MediaSettingsTest.php +++ b/core/modules/media/tests/src/Functional/MediaSettingsTest.php @@ -9,6 +9,11 @@ */ class MediaSettingsTest extends MediaFunctionalTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/MediaSourceFileTest.php b/core/modules/media/tests/src/Functional/MediaSourceFileTest.php index fad13dc81..55fb9276f 100644 --- a/core/modules/media/tests/src/Functional/MediaSourceFileTest.php +++ b/core/modules/media/tests/src/Functional/MediaSourceFileTest.php @@ -11,6 +11,11 @@ */ class MediaSourceFileTest extends MediaFunctionalTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test that it's possible to change the allowed file extensions. */ diff --git a/core/modules/media/tests/src/Functional/MediaTemplateSuggestionsTest.php b/core/modules/media/tests/src/Functional/MediaTemplateSuggestionsTest.php index 916eb7cfc..eef7ae34c 100644 --- a/core/modules/media/tests/src/Functional/MediaTemplateSuggestionsTest.php +++ b/core/modules/media/tests/src/Functional/MediaTemplateSuggestionsTest.php @@ -18,6 +18,11 @@ class MediaTemplateSuggestionsTest extends MediaFunctionalTestBase { */ public static $modules = ['media']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests template suggestions from media_theme_suggestions_media(). */ diff --git a/core/modules/media/tests/src/Functional/MediaTranslationUITest.php b/core/modules/media/tests/src/Functional/MediaTranslationUITest.php index 167d76a30..9ac587a5d 100644 --- a/core/modules/media/tests/src/Functional/MediaTranslationUITest.php +++ b/core/modules/media/tests/src/Functional/MediaTranslationUITest.php @@ -14,6 +14,11 @@ class MediaTranslationUITest extends ContentTranslationUITestBase { use MediaTypeCreationTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/MediaTypeCreationTest.php b/core/modules/media/tests/src/Functional/MediaTypeCreationTest.php index 7de1cd827..2ced87cf4 100644 --- a/core/modules/media/tests/src/Functional/MediaTypeCreationTest.php +++ b/core/modules/media/tests/src/Functional/MediaTypeCreationTest.php @@ -18,6 +18,11 @@ class MediaTypeCreationTest extends MediaFunctionalTestBase { 'media_test_source', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the media type creation form with only the mandatory options. */ diff --git a/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php index 130baa416..42220d2e1 100644 --- a/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php @@ -25,6 +25,11 @@ class MediaUiFunctionalTest extends MediaFunctionalTestBase { 'media_test_source', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -135,7 +140,7 @@ public function testMediaWithOnlyOneMediaType() { $assert_session->pageTextContains('This action cannot be undone'); $page->pressButton('Delete'); $media_id = \Drupal::entityQuery('media')->execute(); - $this->assertFalse($media_id); + $this->assertEmpty($media_id); } /** diff --git a/core/modules/media/tests/src/Functional/ProviderRepositoryTest.php b/core/modules/media/tests/src/Functional/ProviderRepositoryTest.php index 43a71053a..8c583e4ba 100644 --- a/core/modules/media/tests/src/Functional/ProviderRepositoryTest.php +++ b/core/modules/media/tests/src/Functional/ProviderRepositoryTest.php @@ -13,6 +13,11 @@ */ class ProviderRepositoryTest extends MediaFunctionalTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that provider discovery fails if the provider database is empty. * @@ -29,7 +34,8 @@ public function testEmptyProviderList($content) { $client->method('request')->withAnyParameters()->willReturn($response->reveal()); $this->container->set('http_client', $client); - $this->setExpectedException(ProviderException::class, 'Remote oEmbed providers database returned invalid or empty list.'); + $this->expectException(ProviderException::class); + $this->expectExceptionMessage('Remote oEmbed providers database returned invalid or empty list.'); $this->container->get('media.oembed.provider_repository')->getAll(); } @@ -62,7 +68,8 @@ public function testNonExistingProviderDatabase($providers_url, $exception_messa ->set('oembed_providers_url', $providers_url) ->save(); - $this->setExpectedException(ProviderException::class, $exception_message); + $this->expectException(ProviderException::class); + $this->expectExceptionMessage($exception_message); $this->container->get('media.oembed.provider_repository')->getAll(); } diff --git a/core/modules/media/tests/src/Functional/ResourceFetcherTest.php b/core/modules/media/tests/src/Functional/ResourceFetcherTest.php index e10ef2e20..7aed9b062 100644 --- a/core/modules/media/tests/src/Functional/ResourceFetcherTest.php +++ b/core/modules/media/tests/src/Functional/ResourceFetcherTest.php @@ -16,6 +16,11 @@ class ResourceFetcherTest extends MediaFunctionalTestBase { use OEmbedTestTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Rest/MediaJsonAnonTest.php b/core/modules/media/tests/src/Functional/Rest/MediaJsonAnonTest.php index 0aae16a00..2576c5551 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaJsonAnonTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaJsonAnonTest.php @@ -21,4 +21,9 @@ class MediaJsonAnonTest extends MediaResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Rest/MediaJsonBasicAuthTest.php b/core/modules/media/tests/src/Functional/Rest/MediaJsonBasicAuthTest.php index b124487d4..c65a3b2fd 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaJsonBasicAuthTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class MediaJsonBasicAuthTest extends MediaResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Rest/MediaJsonCookieTest.php b/core/modules/media/tests/src/Functional/Rest/MediaJsonCookieTest.php index 9c6786f13..09dd24502 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaJsonCookieTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaJsonCookieTest.php @@ -26,4 +26,9 @@ class MediaJsonCookieTest extends MediaResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonAnonTest.php b/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonAnonTest.php index 6ee494de5..0f5066898 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonAnonTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonAnonTest.php @@ -21,4 +21,9 @@ class MediaTypeJsonAnonTest extends MediaTypeResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonBasicAuthTest.php b/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonBasicAuthTest.php index 84b1a01c8..640ad42ef 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonBasicAuthTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class MediaTypeJsonBasicAuthTest extends MediaTypeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonCookieTest.php b/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonCookieTest.php index 90c27b2d5..9a3d459de 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonCookieTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaTypeJsonCookieTest.php @@ -26,4 +26,9 @@ class MediaTypeJsonCookieTest extends MediaTypeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlAnonTest.php b/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlAnonTest.php index 083d80a1c..cd6bc82c4 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlAnonTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlAnonTest.php @@ -23,4 +23,9 @@ class MediaTypeXmlAnonTest extends MediaTypeResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlBasicAuthTest.php b/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlBasicAuthTest.php index 95de21197..6bd780977 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlBasicAuthTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class MediaTypeXmlBasicAuthTest extends MediaTypeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlCookieTest.php b/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlCookieTest.php index 3b6a53d96..c28c72311 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlCookieTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaTypeXmlCookieTest.php @@ -28,4 +28,9 @@ class MediaTypeXmlCookieTest extends MediaTypeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Rest/MediaXmlAnonTest.php b/core/modules/media/tests/src/Functional/Rest/MediaXmlAnonTest.php index 787794996..baafbcb3d 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaXmlAnonTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaXmlAnonTest.php @@ -23,4 +23,9 @@ class MediaXmlAnonTest extends MediaResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Rest/MediaXmlBasicAuthTest.php b/core/modules/media/tests/src/Functional/Rest/MediaXmlBasicAuthTest.php index 971e75f42..9d41af515 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaXmlBasicAuthTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class MediaXmlBasicAuthTest extends MediaResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/media/tests/src/Functional/Rest/MediaXmlCookieTest.php b/core/modules/media/tests/src/Functional/Rest/MediaXmlCookieTest.php index f67f7b4f8..16a36458a 100644 --- a/core/modules/media/tests/src/Functional/Rest/MediaXmlCookieTest.php +++ b/core/modules/media/tests/src/Functional/Rest/MediaXmlCookieTest.php @@ -28,4 +28,9 @@ class MediaXmlCookieTest extends MediaResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php b/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php index ee97fe5cc..7e6c925bb 100644 --- a/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php +++ b/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php @@ -114,4 +114,25 @@ public function testEnableStandaloneUrl() { $this->assertSession()->statusCodeEquals(200); } + /** + * Tests that the status extra filter is added to the media view. + * + * @see media_post_update_add_status_extra_filter() + */ + public function testMediaViewStatusExtraFilter() { + $config = $this->config('views.view.media'); + $this->assertNull($config->get('display.default.display_options.filters.status_extra')); + + $this->runUpdates(); + + $config = $this->config('views.view.media'); + $filter = $config->get('display.default.display_options.filters.status_extra'); + $this->assertInternalType('array', $filter); + $this->assertSame('status_extra', $filter['field']); + $this->assertSame('media', $filter['entity_type']); + $this->assertSame('media_status', $filter['plugin_id']); + $this->assertSame('status_extra', $filter['id']); + $this->assertFalse($filter['exposed']); + } + } diff --git a/core/modules/media/tests/src/Functional/UrlResolverTest.php b/core/modules/media/tests/src/Functional/UrlResolverTest.php index 1dfe5d6ab..507362581 100644 --- a/core/modules/media/tests/src/Functional/UrlResolverTest.php +++ b/core/modules/media/tests/src/Functional/UrlResolverTest.php @@ -15,6 +15,11 @@ class UrlResolverTest extends MediaFunctionalTestBase { use OEmbedTestTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -35,15 +40,15 @@ public function providerEndpointMatching() { return [ 'match by endpoint: Twitter' => [ 'https://twitter.com/Dries/status/999985431595880448', - 'https://publish.twitter.com/oembed?url=https%3A//twitter.com/Dries/status/999985431595880448', + 'https://publish.twitter.com/oembed?url=https://twitter.com/Dries/status/999985431595880448', ], 'match by endpoint: Vimeo' => [ 'https://vimeo.com/14782834', - 'https://vimeo.com/api/oembed.json?url=https%3A//vimeo.com/14782834', + 'https://vimeo.com/api/oembed.json?url=https://vimeo.com/14782834', ], 'match by endpoint: CollegeHumor' => [ 'http://www.collegehumor.com/video/40002870/lets-not-get-a-drink-sometime', - 'http://www.collegehumor.com/oembed.json?url=http%3A//www.collegehumor.com/video/40002870/lets-not-get-a-drink-sometime', + 'http://www.collegehumor.com/oembed.json?url=http://www.collegehumor.com/video/40002870/lets-not-get-a-drink-sometime', ], ]; } diff --git a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php new file mode 100644 index 000000000..1a0e7201d --- /dev/null +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php @@ -0,0 +1,1551 @@ + 'test_format', + 'name' => 'Test format', + 'filters' => [ + 'filter_align' => ['status' => TRUE], + 'filter_caption' => ['status' => TRUE], + 'media_embed' => ['status' => TRUE], + ], + ])->save(); + Editor::create([ + 'editor' => 'ckeditor', + 'format' => 'test_format', + 'settings' => [ + 'toolbar' => [ + 'rows' => [ + [ + [ + 'name' => 'All the things', + 'items' => [ + 'Source', + 'Bold', + 'Italic', + 'DrupalLink', + 'DrupalUnlink', + 'DrupalImage', + ], + ], + ], + ], + ], + ], + ])->save(); + + // Note that media_install() grants 'view media' to all users by default. + $this->adminUser = $this->drupalCreateUser([ + 'use text format test_format', + 'bypass node access', + ]); + + // Create a sample media entity to be embedded. + $this->createMediaType('image', ['id' => 'image']); + File::create([ + 'uri' => $this->getTestFiles('image')[0]->uri, + ])->save(); + $this->media = Media::create([ + 'bundle' => 'image', + 'name' => 'Screaming hairy armadillo', + 'field_media_image' => [ + [ + 'target_id' => 1, + 'alt' => 'default alt', + 'title' => 'default title', + ], + ], + ]); + $this->media->save(); + + // Create a sample host entity to embed media in. + $this->drupalCreateContentType(['type' => 'blog']); + $this->host = $this->createNode([ + 'type' => 'blog', + 'title' => 'Animals with strange names', + 'body' => [ + 'value' => '', + 'format' => 'test_format', + ], + ]); + $this->host->save(); + + $this->drupalLogin($this->adminUser); + } + + /** + * Tests that only tags are processed. + * + * @see \Drupal\Tests\media\Kernel\MediaEmbedFilterTest::testOnlyDrupalMediaTagProcessed() + */ + public function testOnlyDrupalMediaTagProcessed() { + $original_value = $this->host->body->value; + $this->host->body->value = str_replace('drupal-media', 'p', $original_value); + $this->host->save(); + + // Assert that `

` is not upcast into a CKEditor Widget. + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $assert_session = $this->assertSession(); + $this->assertEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000)); + $assert_session->elementNotExists('css', 'figure'); + + $this->host->body->value = $original_value; + $this->host->save(); + + // Assert that `` is upcast into a CKEditor Widget. + $this->getSession()->reload(); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]')); + $assert_session->elementExists('css', 'figure'); + } + + /** + * Tests that failed media embed preview requests inform the end user. + */ + public function testErrorMessages() { + // Assert that a request to the `media.filter.preview` route that does not + // result in a 200 response (due to server error or network error) is + // handled in the JavaScript by displaying the expected error message. + // @see core/modules/media/js/media_embed_ckeditor.theme.js + // @see core/modules/media/js/plugins/drupalmedia/plugin.js + $this->container->get('state')->set('test_media_filter_controller_throw_error', TRUE); + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $assert_session = $this->assertSession(); + $this->assertEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000)); + $assert_session->elementNotExists('css', 'figure'); + $this->assertNotEmpty($assert_session->waitForText('An error occurred while trying to preview the media. Please save your work and reload this page.')); + // Now assert that the error doesn't appear when the override to force an + // error is removed. + $this->container->get('state')->set('test_media_filter_controller_throw_error', FALSE); + $this->getSession()->reload(); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]')); + + // There's a second kind of error message that comes from the back end + // that happens when the media uuid can't be converted to a media preview. + // In this case, the error will appear in a the themable + // media-embed-error.html template. We have a hook altering the css + // classes to test the twi template is working properly and picking up our + // extra class. + // @see \Drupal\media\Plugin\Filter\MediaEmbed::renderMissingMediaIndicator() + // @see core/modules/media/templates/media-embed-error.html.twig + // @see media_test_ckeditor_preprocess_media_embed_error() + $original_value = $this->host->body->value; + $this->host->body->value = str_replace($this->media->uuid(), 'invalid_uuid', $original_value); + $this->host->save(); + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElement('css', 'drupal-media figure.caption-drupal-media .this-error-message-is-themeable')); + + // Test when using the classy theme, an additional class is added in + // classy/templates/content/media-embed-error.html.twig. + $this->assertTrue($this->container->get('theme_installer')->install(['classy'])); + $this->config('system.theme') + ->set('default', 'classy') + ->save(); + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElement('css', 'drupal-media figure.caption-drupal-media .this-error-message-is-themeable.media-embed-error--missing-source')); + $assert_session->responseContains('classy/css/components/media-embed-error.css'); + + // Test that restoring a valid UUID results in the media embed preview + // displaying. + $this->host->body->value = $original_value; + $this->host->save(); + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]')); + $assert_session->elementNotExists('css', 'drupal-media figure.caption-drupal-media .this-error-message-is-themeable'); + } + + /** + * The CKEditor Widget must load a preview generated using the default theme. + */ + public function testPreviewUsesDefaultThemeAndIsClientCacheable() { + // Make the node edit form use the admin theme, like on most Drupal sites. + $this->config('node.settings') + ->set('use_admin_theme', TRUE) + ->save(); + $this->container->get('router.builder')->rebuild(); + + // Allow the test user to view the admin theme. + $this->adminUser->addRole($this->drupalCreateRole(['view the administration theme'])); + $this->adminUser->save(); + + // Configure a different default and admin theme, like on most Drupal sites. + $this->config('system.theme') + ->set('default', 'stable') + ->set('admin', 'classy') + ->save(); + + // Assert that when looking at an embedded entity in the CKEditor Widget, + // the preview is generated using the default theme, not the admin theme. + // @see media_test_ckeditor_entity_view_alter() + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $assert_session = $this->assertSession(); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]')); + $element = $assert_session->elementExists('css', '[data-media-embed-test-active-theme]'); + $this->assertSame('stable', $element->getAttribute('data-media-embed-test-active-theme')); + // Assert that the first preview request transferred >500 B over the wire. + // Then toggle source mode on and off. This causes the CKEditor widget to be + // destroyed and then reconstructed. Assert that during this reconstruction, + // a second request is sent. This second request should have transferred 0 + // bytes: the browser should have cached the response, thus resulting in a + // much better user experience. + $this->assertGreaterThan(500, $this->getLastPreviewRequestTransferSize()); + $this->pressEditorButton('source'); + $this->assertNotEmpty($assert_session->waitForElement('css', 'textarea.cke_source')); + $this->pressEditorButton('source'); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]')); + $this->assertSame(0, $this->getLastPreviewRequestTransferSize()); + } + + /** + * Tests caption editing in the CKEditor widget. + */ + public function testEditableCaption() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + // Test that setting caption to blank string doesn't break 'Edit media' + // button. + $original_value = $this->host->body->value; + $this->host->body->value = str_replace('data-caption="baz"', 'data-caption=""', $original_value); + $this->host->save(); + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForButton('Edit media')); + // Test `aria-label` attribute appears on the widget wrapper. + $assert_session->elementExists('css', '.cke_widget_drupalmedia[aria-label="Screaming hairy armadillo"]'); + $assert_session->elementContains('css', 'figcaption', ''); + $assert_session->elementAttributeContains('css', 'figcaption', 'data-placeholder', 'Enter caption here'); + // Test if you leave the caption blank, but change another attribute, + // such as the alt text, the editable caption is still there and the edit + // button still exists. + $this->fillFieldInMetadataDialogAndSubmit('attributes[alt]', 'Mama, life had just begun'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'drupal-media img[alt*="Mama, life had just begun"]')); + $assert_session->buttonExists('Edit media'); + $assert_session->elementContains('css', 'figcaption', ''); + $assert_session->elementAttributeContains('css', 'figcaption', 'data-placeholder', 'Enter caption here'); + + // Restore caption in saved body value. + $original_value = $this->host->body->value; + $this->host->body->value = str_replace('data-caption=""', 'data-caption="baz"', $original_value); + $this->host->save(); + + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + // Assert that figcaption element exists within the drupal-media element. + $this->assertNotEmpty($figcaption = $assert_session->waitForElement('css', 'drupal-media figcaption')); + $this->assertSame('baz', $figcaption->getHtml()); + + // Test that disabling the caption in the metadata dialog removes it + // from the drupal-media element. + $this->openMetadataDialogWithKeyPress(static::SPACE_BAR); + $page->uncheckField('hasCaption'); + $this->submitDialog(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($drupal_media = $assert_session->waitForElementVisible('css', 'drupal-media')); + + // Wait for element to update without figcaption. + $result = $page->waitFor(10, function () use ($drupal_media) { + return empty($drupal_media->find('css', 'figcaption')); + }); + // Will be true if no figcaption exists within the drupal-media element. + $this->assertTrue($result); + + // Test that enabling the caption in the metadata dialog adds an editable + // caption to the embedded media. + $this->openMetadataDialogWithKeyPress(static::SPACE_BAR); + $page->checkField('hasCaption'); + $this->submitDialog(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($drupal_media = $assert_session->waitForElementVisible('css', 'drupal-media figcaption')); + + // Type into the widget's caption element. + $this->assertNotEmpty($assert_session->waitForElement('css', 'figcaption')); + $this->setCaption('Caught in a landslide! No escape from reality!'); + $this->getSession()->switchToIFrame('ckeditor'); + $assert_session->elementExists('css', 'figcaption > em'); + $assert_session->elementExists('css', 'figcaption > strong')->click(); + + // Select the element and unbold it. + $this->clickPathLinkByTitleAttribute("strong element"); + $this->pressEditorButton('bold'); + $this->getSession()->switchToIFrame('ckeditor'); + $assert_session->elementExists('css', 'figcaption > em'); + $assert_session->elementNotExists('css', 'figcaption > strong'); + + // Select the element and unitalicize it. + $assert_session->elementExists('css', 'figcaption > em')->click(); + $this->clickPathLinkByTitleAttribute("em element"); + $this->pressEditorButton('italic'); + + // The "source" button should reveal the HTML source in a state matching + // what is shown in the CKEditor widget. + $this->pressEditorButton('source'); + $source = $assert_session->elementExists('css', 'textarea.cke_source'); + $value = $source->getValue(); + $dom = Html::load($value); + $xpath = new \DOMXPath($dom); + $drupal_media = $xpath->query('//drupal-media')[0]; + $this->assertSame('Caught in a landslide! No escape from reality!', $drupal_media->getAttribute('data-caption')); + + // Change the caption by modifying the HTML source directly. When exiting + // "source" mode, this should be respected. + $poor_boy_text = "I'm just a poor boy, I need no sympathy!"; + $drupal_media->setAttribute("data-caption", $poor_boy_text); + $source->setValue(Html::serialize($dom)); + $this->pressEditorButton('source'); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $figcaption = $assert_session->waitForElement('css', 'figcaption'); + $this->assertNotEmpty($figcaption); + $this->assertSame($poor_boy_text, $figcaption->getHtml()); + + // Select the element that we just set in "source" mode. This + // proves that it was indeed rendered by the CKEditor widget. + $strong = $figcaption->find('css', 'strong'); + $this->assertNotEmpty($strong); + $strong->click(); + $this->pressEditorButton('bold'); + + // Insert a link into the caption. + $this->clickPathLinkByTitleAttribute("Caption element"); + $this->pressEditorButton('drupallink'); + $field = $assert_session->waitForElementVisible('xpath', '//input[@name="attributes[href]"]'); + $this->assertNotEmpty($field); + $field->setValue('https://www.drupal.org'); + $assert_session->elementExists('css', 'button.form-submit')->press(); + + // Wait for the live preview in the CKEditor widget to finish loading, then + // edit the link; no `data-cke-saved-href` attribute should exist on it. + $this->getSession()->switchToIFrame('ckeditor'); + $figcaption = $assert_session->waitForElement('css', 'figcaption'); + $page = $this->getSession()->getPage(); + // Wait for AJAX refresh. + $page->waitFor(10, function () use ($figcaption) { + return $figcaption->find('xpath', '//a[@href="https://www.drupal.org"]'); + }); + $assert_session->elementExists('css', 'a', $figcaption)->click(); + $this->clickPathLinkByTitleAttribute("a element"); + $this->pressEditorButton('drupallink'); + $field = $assert_session->waitForElementVisible('xpath', '//input[@name="attributes[href]"]'); + $this->assertNotEmpty($field); + $field->setValue('https://www.drupal.org/project/drupal'); + $assert_session->elementExists('css', 'button.form-submit')->press(); + $this->getSession()->switchToIFrame('ckeditor'); + $figcaption = $assert_session->waitForElement('css', 'figcaption'); + $page = $this->getSession()->getPage(); + // Wait for AJAX refresh. + $page->waitFor(10, function () use ($figcaption) { + return $figcaption->find('xpath', '//a[@href="https://www.drupal.org/project/drupal"]'); + }); + $this->pressEditorButton('source'); + $source = $assert_session->elementExists('css', "textarea.cke_source"); + $value = $source->getValue(); + $this->assertContains('https://www.drupal.org/project/drupal', $value); + $this->assertNotContains('data-cke-saved-href', $value); + + // Save the entity. + $assert_session->buttonExists('Save')->press(); + + // Verify the saved entity when viewed also contains the captioned media. + $link = $assert_session->elementExists('css', 'figcaption > a'); + $this->assertSame('https://www.drupal.org/project/drupal', $link->getAttribute('href')); + $this->assertSame("I'm just a poor boy, I need no sympathy!", $link->getText()); + + // Edit it again, type a different caption in the widget. + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'figcaption')); + $this->setCaption('Scaramouch, Scaramouch, will you do the Fandango?'); + + // Erase the caption in the CKEditor Widget, verify the

still + // exists and contains placeholder text, then type something else. + $this->setCaption(''); + $this->getSession()->switchToIFrame('ckeditor'); + $assert_session->elementContains('css', 'figcaption', ''); + $assert_session->elementAttributeContains('css', 'figcaption', 'data-placeholder', 'Enter caption here'); + $this->setCaption('Fin.'); + $this->getSession()->switchToIFrame('ckeditor'); + $assert_session->elementContains('css', 'figcaption', 'Fin.'); + } + + /** + * Test the EditorMediaDialog's form elements' #access logic. + */ + public function testDialogAccess() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + $this->drupalGet($this->host->toUrl('edit-form')); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + + // Enable `filter_html` without "alt", "data-align" or "data-caption" + // attributes added to the drupal-media tag. + $allowed_html = "
{% endif %} diff --git a/core/modules/media_library/templates/media-library-item.html.twig b/core/modules/media_library/templates/media-library-item.html.twig new file mode 100644 index 000000000..a765d04c1 --- /dev/null +++ b/core/modules/media_library/templates/media-library-item.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file + * Default theme implementation of a media library item. + * + * This is used when displaying selected media items, either in the field + * widget or in the "Additional selected media" area when adding new + * media items in the media library modal dialog. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - content: The content of the media library item, plus any additional + * fields or elements surrounding it. + * + * @see template_preprocess_media_library_item() + * + * @ingroup themeable + */ +#} + + {{ content }} +
diff --git a/core/modules/media_library/templates/media-library-wrapper.html.twig b/core/modules/media_library/templates/media-library-wrapper.html.twig new file mode 100644 index 000000000..344637cd6 --- /dev/null +++ b/core/modules/media_library/templates/media-library-wrapper.html.twig @@ -0,0 +1,21 @@ +{# +/** + * @file + * Default theme implementation of a container used to wrap the media library's + * modal dialog interface. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - menu: The menu of availble media types to choose from. + * - content: The form to add new media items, followed by the grid or table of + * existing media items to choose from. + * + * @see template_preprocess_media_library_wrapper() + * + * @ingroup themeable + */ +#} + + {{ menu }} + {{ content }} +
diff --git a/core/modules/media_library/tests/fixtures/update/drupal-8.7.2-media_library_installed.php b/core/modules/media_library/tests/fixtures/update/drupal-8.7.2-media_library_installed.php new file mode 100644 index 000000000..f4db99ea7 --- /dev/null +++ b/core/modules/media_library/tests/fixtures/update/drupal-8.7.2-media_library_installed.php @@ -0,0 +1,120 @@ +merge('key_value') + ->fields([ + 'value' => 'i:8000;', + 'name' => 'media_library', + 'collection' => 'system.schema', + ]) + ->condition('collection', 'system.schema') + ->condition('name', 'media_library') + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['media_library'] = 0; +$connection->update('config') + ->fields([ + 'data' => serialize($extensions), + 'collection' => '', + 'name' => 'core.extension', + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); + +// Insert media library config objects. +$connection->insert('config') +->fields(array( + 'collection', + 'name', + 'data', +)) +->values(array( + 'collection' => '', + 'name' => 'core.entity_form_display.media.file.media_library', + 'data' => 'a:11:{s:4:"uuid";s:36:"86ab9619-c970-4416-971d-e5c8614b3368";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"config";a:3:{i:0;s:41:"core.entity_form_mode.media.media_library";i:1;s:39:"field.field.media.file.field_media_file";i:2;s:15:"media.type.file";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"RFmywOcuem167havmD4VLgBTO1Swq9hyA-_f5aYTi8c";}s:2:"id";s:24:"media.file.media_library";s:16:"targetEntityType";s:5:"media";s:6:"bundle";s:4:"file";s:4:"mode";s:13:"media_library";s:7:"content";a:1:{s:4:"name";a:5:{s:4:"type";s:16:"string_textfield";s:6:"weight";i:0;s:6:"region";s:7:"content";s:8:"settings";a:2:{s:4:"size";i:60;s:11:"placeholder";s:0:"";}s:20:"third_party_settings";a:0:{}}}s:6:"hidden";a:5:{s:7:"created";b:1;s:16:"field_media_file";b:1;s:4:"path";b:1;s:6:"status";b:1;s:3:"uid";b:1;}}', +)) +->values(array( + 'collection' => '', + 'name' => 'core.entity_form_display.media.image.media_library', + 'data' => 'a:11:{s:4:"uuid";s:36:"2bbea060-3cd8-4881-a3aa-c898d6619b16";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:4:{i:0;s:41:"core.entity_form_mode.media.media_library";i:1;s:41:"field.field.media.image.field_media_image";i:2;s:21:"image.style.thumbnail";i:3;s:16:"media.type.image";}s:6:"module";a:1:{i:0;s:5:"image";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"PlyfyVZfALLkP7nbxLpaVKIDUWRioZghWpFDv0_rJ68";}s:2:"id";s:25:"media.image.media_library";s:16:"targetEntityType";s:5:"media";s:6:"bundle";s:5:"image";s:4:"mode";s:13:"media_library";s:7:"content";a:2:{s:17:"field_media_image";a:5:{s:4:"type";s:11:"image_image";s:6:"weight";i:1;s:6:"region";s:7:"content";s:8:"settings";a:2:{s:18:"progress_indicator";s:8:"throbber";s:19:"preview_image_style";s:9:"thumbnail";}s:20:"third_party_settings";a:0:{}}s:4:"name";a:5:{s:4:"type";s:16:"string_textfield";s:6:"weight";i:0;s:6:"region";s:7:"content";s:8:"settings";a:2:{s:4:"size";i:60;s:11:"placeholder";s:0:"";}s:20:"third_party_settings";a:0:{}}}s:6:"hidden";a:4:{s:7:"created";b:1;s:4:"path";b:1;s:6:"status";b:1;s:3:"uid";b:1;}}', +)) +->values(array( + 'collection' => '', + 'name' => 'core.entity_view_display.media.file.media_library', + 'data' => 'a:11:{s:4:"uuid";s:36:"67e6d857-8ecb-49f5-95e1-6b1c4306c31f";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:4:{i:0;s:41:"core.entity_view_mode.media.media_library";i:1;s:39:"field.field.media.file.field_media_file";i:2;s:21:"image.style.thumbnail";i:3;s:15:"media.type.file";}s:6:"module";a:1:{i:0;s:5:"image";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"vhAK2lCOWK2paUpJawj7yiSLFO9wwsx6WE8_oDmvbwU";}s:2:"id";s:24:"media.file.media_library";s:16:"targetEntityType";s:5:"media";s:6:"bundle";s:4:"file";s:4:"mode";s:13:"media_library";s:7:"content";a:1:{s:9:"thumbnail";a:6:{s:4:"type";s:5:"image";s:6:"weight";i:0;s:6:"region";s:7:"content";s:5:"label";s:6:"hidden";s:8:"settings";a:2:{s:11:"image_style";s:9:"thumbnail";s:10:"image_link";s:0:"";}s:20:"third_party_settings";a:0:{}}}s:6:"hidden";a:4:{s:7:"created";b:1;s:16:"field_media_file";b:1;s:4:"name";b:1;s:3:"uid";b:1;}}', +)) +->values(array( + 'collection' => '', + 'name' => 'core.entity_view_display.media.image.media_library', + 'data' => 'a:11:{s:4:"uuid";s:36:"277ca98b-2ada-4251-ad69-aa73e72d60fe";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:4:{i:0;s:41:"core.entity_view_mode.media.media_library";i:1;s:41:"field.field.media.image.field_media_image";i:2;s:18:"image.style.medium";i:3;s:16:"media.type.image";}s:6:"module";a:1:{i:0;s:5:"image";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"PaGXvzRcL9eII--JV4eCVfObjrNo0l-u1dB_WJtB9ig";}s:2:"id";s:25:"media.image.media_library";s:16:"targetEntityType";s:5:"media";s:6:"bundle";s:5:"image";s:4:"mode";s:13:"media_library";s:7:"content";a:1:{s:9:"thumbnail";a:6:{s:4:"type";s:5:"image";s:6:"weight";i:0;s:6:"region";s:7:"content";s:5:"label";s:6:"hidden";s:8:"settings";a:2:{s:11:"image_style";s:6:"medium";s:10:"image_link";s:0:"";}s:20:"third_party_settings";a:0:{}}}s:6:"hidden";a:4:{s:7:"created";b:1;s:17:"field_media_image";b:1;s:4:"name";b:1;s:3:"uid";b:1;}}', +)) +->values(array( + 'collection' => '', + 'name' => 'core.entity_view_mode.media.media_library', + 'data' => 'a:9:{s:4:"uuid";s:36:"20b2f1f7-a864-4d41-a15f-32f66789f73d";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:8:"enforced";a:1:{s:6:"module";a:1:{i:0;s:13:"media_library";}}s:6:"module";a:1:{i:0;s:5:"media";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"pkq0uj-IoqEQRBOP_ddUDV0ZJ-dKQ_fLcppsEDF2UO8";}s:2:"id";s:19:"media.media_library";s:5:"label";s:13:"Media library";s:16:"targetEntityType";s:5:"media";s:5:"cache";b:1;}', +)) +->values(array( + 'collection' => '', + 'name' => 'views.view.media_library', + 'data' => 'a:14:{s:4:"uuid";s:36:"e42c9697-889f-41f8-a752-4d91aa8997a7";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:3:{s:6:"config";a:2:{i:0;s:41:"core.entity_view_mode.media.media_library";i:1;s:25:"image.style.media_library";}s:8:"enforced";a:1:{s:6:"module";a:1:{i:0;s:13:"media_library";}}s:6:"module";a:4:{i:0;s:5:"image";i:1;s:5:"media";i:2;s:13:"media_library";i:3;s:4:"user";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"WM0EWBXTR1beBFa860NA1kUdffL9bM1548gUS9DS5fg";}s:2:"id";s:13:"media_library";s:5:"label";s:13:"Media library";s:6:"module";s:5:"views";s:11:"description";s:0:"";s:3:"tag";s:0:"";s:10:"base_table";s:16:"media_field_data";s:10:"base_field";s:3:"mid";s:4:"core";s:3:"8.x";s:7:"display";a:4:{s:7:"default";a:6:{s:14:"display_plugin";s:7:"default";s:2:"id";s:7:"default";s:13:"display_title";s:6:"Master";s:8:"position";i:0;s:15:"display_options";a:18:{s:6:"access";a:2:{s:4:"type";s:4:"perm";s:7:"options";a:1:{s:4:"perm";s:21:"access media overview";}}s:5:"cache";a:2:{s:4:"type";s:3:"tag";s:7:"options";a:0:{}}s:5:"query";a:2:{s:4:"type";s:11:"views_query";s:7:"options";a:5:{s:19:"disable_sql_rewrite";b:0;s:8:"distinct";b:0;s:7:"replica";b:0;s:13:"query_comment";s:0:"";s:10:"query_tags";a:0:{}}}s:12:"exposed_form";a:2:{s:4:"type";s:5:"basic";s:7:"options";a:7:{s:13:"submit_button";s:13:"Apply filters";s:12:"reset_button";b:0;s:18:"reset_button_label";s:5:"Reset";s:19:"exposed_sorts_label";s:7:"Sort by";s:17:"expose_sort_order";b:0;s:14:"sort_asc_label";s:3:"Asc";s:15:"sort_desc_label";s:4:"Desc";}}s:5:"pager";a:2:{s:4:"type";s:4:"mini";s:7:"options";a:6:{s:14:"items_per_page";i:24;s:6:"offset";i:0;s:2:"id";i:0;s:11:"total_pages";N;s:6:"expose";a:7:{s:14:"items_per_page";b:0;s:20:"items_per_page_label";s:14:"Items per page";s:22:"items_per_page_options";s:13:"6, 12, 24, 48";s:26:"items_per_page_options_all";b:0;s:32:"items_per_page_options_all_label";s:7:"- All -";s:6:"offset";b:0;s:12:"offset_label";s:6:"Offset";}s:4:"tags";a:2:{s:8:"previous";s:6:"‹‹";s:4:"next";s:6:"››";}}}s:5:"style";a:2:{s:4:"type";s:7:"default";s:7:"options";a:3:{s:8:"grouping";a:0:{}s:9:"row_class";s:84:"media-library-item media-library-item--grid js-media-library-item js-click-to-select";s:17:"default_row_class";b:1;}}s:3:"row";a:2:{s:4:"type";s:6:"fields";s:7:"options";a:4:{s:22:"default_field_elements";b:1;s:6:"inline";a:0:{}s:9:"separator";s:0:"";s:10:"hide_empty";b:0;}}s:6:"fields";a:2:{s:15:"media_bulk_form";a:26:{s:2:"id";s:15:"media_bulk_form";s:5:"table";s:5:"media";s:5:"field";s:15:"media_bulk_form";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"js-click-to-select-checkbox";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:12:"action_title";s:6:"Action";s:15:"include_exclude";s:7:"exclude";s:16:"selected_actions";a:0:{}s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:9:"bulk_form";}s:15:"rendered_entity";a:24:{s:2:"id";s:15:"rendered_entity";s:5:"table";s:5:"media";s:5:"field";s:15:"rendered_entity";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"media-library-item__content";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:9:"view_mode";s:13:"media_library";s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:15:"rendered_entity";}}s:7:"filters";a:3:{s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:0:"";s:5:"label";s:17:"Publishing status";s:11:"description";N;s:12:"use_operator";b:0;s:8:"operator";s:9:"status_op";s:10:"identifier";s:6:"status";s:8:"required";b:1;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:1;s:10:"group_info";a:10:{s:5:"label";s:9:"Published";s:11:"description";s:0:"";s:10:"identifier";s:6:"status";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:2:{i:1;a:3:{s:5:"title";s:9:"Published";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";}i:2;a:3:{s:5:"title";s:11:"Unpublished";s:8:"operator";s:1:"=";s:5:"value";s:1:"0";}}}s:9:"plugin_id";s:7:"boolean";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";}s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:4:"Name";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:4:"name";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}s:6:"bundle";a:16:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:2:"in";s:5:"value";a:0:{}s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:13:{s:11:"operator_id";s:9:"bundle_op";s:5:"label";s:10:"Media type";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:9:"bundle_op";s:10:"identifier";s:4:"type";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:6:"reduce";b:0;s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:10:"Media type";s:11:"description";N;s:10:"identifier";s:6:"bundle";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:3:{i:1;a:0:{}i:2;a:0:{}i:3;a:0:{}}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"bundle";}}s:5:"sorts";a:3:{s:7:"created";a:13:{s:2:"id";s:7:"created";s:5:"table";s:16:"media_field_data";s:5:"field";s:7:"created";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"order";s:4:"DESC";s:7:"exposed";b:1;s:6:"expose";a:1:{s:5:"label";s:12:"Newest first";}s:11:"granularity";s:6:"second";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:7:"created";s:9:"plugin_id";s:4:"date";}s:4:"name";a:12:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"order";s:3:"ASC";s:7:"exposed";b:1;s:6:"expose";a:1:{s:5:"label";s:10:"Name (A-Z)";}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:8:"standard";}s:6:"name_1";a:12:{s:2:"id";s:6:"name_1";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"order";s:4:"DESC";s:7:"exposed";b:1;s:6:"expose";a:1:{s:5:"label";s:10:"Name (Z-A)";}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:8:"standard";}}s:5:"title";s:5:"Media";s:6:"header";a:0:{}s:6:"footer";a:0:{}s:5:"empty";a:1:{s:16:"area_text_custom";a:10:{s:2:"id";s:16:"area_text_custom";s:5:"table";s:5:"views";s:5:"field";s:16:"area_text_custom";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"empty";b:1;s:8:"tokenize";b:0;s:7:"content";s:19:"No media available.";s:9:"plugin_id";s:11:"text_custom";}}s:13:"relationships";a:0:{}s:17:"display_extenders";a:0:{}s:8:"use_ajax";b:1;s:9:"css_class";s:40:"media-library-view js-media-library-view";}s:14:"cache_metadata";a:3:{s:7:"max-age";i:0;s:8:"contexts";a:5:{i:0;s:28:"languages:language_interface";i:1;s:3:"url";i:2;s:14:"url.query_args";i:3;s:22:"url.query_args:sort_by";i:4;s:16:"user.permissions";}s:4:"tags";a:0:{}}}s:4:"page";a:6:{s:14:"display_plugin";s:4:"page";s:2:"id";s:4:"page";s:13:"display_title";s:4:"Page";s:8:"position";i:1;s:15:"display_options";a:3:{s:17:"display_extenders";a:0:{}s:4:"path";s:19:"admin/content/media";s:4:"menu";a:8:{s:4:"type";s:3:"tab";s:5:"title";s:5:"Media";s:11:"description";s:49:"Allows users to browse and administer media items";s:8:"expanded";b:0;s:6:"parent";s:20:"system.admin_content";s:6:"weight";i:5;s:7:"context";s:1:"0";s:9:"menu_name";s:5:"admin";}}s:14:"cache_metadata";a:3:{s:7:"max-age";i:0;s:8:"contexts";a:5:{i:0;s:28:"languages:language_interface";i:1;s:3:"url";i:2;s:14:"url.query_args";i:3;s:22:"url.query_args:sort_by";i:4;s:16:"user.permissions";}s:4:"tags";a:0:{}}}s:6:"widget";a:6:{s:14:"display_plugin";s:4:"page";s:2:"id";s:6:"widget";s:13:"display_title";s:6:"Widget";s:8:"position";i:2;s:15:"display_options";a:11:{s:17:"display_extenders";a:0:{}s:4:"path";s:26:"admin/content/media-widget";s:6:"fields";a:2:{s:15:"rendered_entity";a:24:{s:2:"id";s:15:"rendered_entity";s:5:"table";s:5:"media";s:5:"field";s:15:"rendered_entity";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"media-library-item__content";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:9:"view_mode";s:13:"media_library";s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:15:"rendered_entity";}s:25:"media_library_select_form";a:23:{s:2:"id";s:25:"media_library_select_form";s:5:"table";s:5:"media";s:5:"field";s:25:"media_library_select_form";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:27:"js-click-to-select-checkbox";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:25:"media_library_select_form";}}s:8:"defaults";a:7:{s:6:"fields";b:0;s:6:"access";b:0;s:7:"filters";b:0;s:13:"filter_groups";b:0;s:9:"arguments";b:0;s:6:"header";b:0;s:9:"css_class";b:0;}s:19:"display_description";s:0:"";s:6:"access";a:2:{s:4:"type";s:4:"perm";s:7:"options";a:1:{s:4:"perm";s:10:"view media";}}s:7:"filters";a:2:{s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:0;s:6:"expose";a:12:{s:11:"operator_id";s:0:"";s:5:"label";s:0:"";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:0:"";s:10:"identifier";s:0:"";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";s:9:"plugin_id";s:7:"boolean";}s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:4:"Name";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:4:"name";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}}s:13:"filter_groups";a:2:{s:8:"operator";s:3:"AND";s:6:"groups";a:1:{i:1;s:3:"AND";}}s:9:"arguments";a:1:{s:6:"bundle";a:27:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:14:"default_action";s:6:"ignore";s:9:"exception";a:3:{s:5:"value";s:3:"all";s:12:"title_enable";b:0;s:5:"title";s:3:"All";}s:12:"title_enable";b:0;s:5:"title";s:0:"";s:21:"default_argument_type";s:5:"fixed";s:24:"default_argument_options";a:1:{s:8:"argument";s:0:"";}s:25:"default_argument_skip_url";b:0;s:15:"summary_options";a:4:{s:9:"base_path";s:0:"";s:5:"count";b:1;s:14:"items_per_page";i:24;s:8:"override";b:0;}s:7:"summary";a:3:{s:10:"sort_order";s:3:"asc";s:17:"number_of_records";i:0;s:6:"format";s:15:"default_summary";}s:18:"specify_validation";b:0;s:8:"validate";a:2:{s:4:"type";s:4:"none";s:4:"fail";s:9:"not found";}s:16:"validate_options";a:0:{}s:8:"glossary";b:0;s:5:"limit";i:0;s:4:"case";s:4:"none";s:9:"path_case";s:4:"none";s:14:"transform_dash";b:0;s:12:"break_phrase";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"string";}}s:6:"header";a:2:{s:17:"display_link_grid";a:7:{s:2:"id";s:17:"display_link_grid";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:6:"widget";s:5:"label";s:4:"Grid";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}s:18:"display_link_table";a:7:{s:2:"id";s:18:"display_link_table";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:12:"widget_table";s:5:"label";s:5:"Table";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}}s:9:"css_class";s:67:"media-library-view js-media-library-view media-library-view--widget";}s:14:"cache_metadata";a:3:{s:7:"max-age";i:-1;s:8:"contexts";a:5:{i:0;s:28:"languages:language_interface";i:1;s:3:"url";i:2;s:14:"url.query_args";i:3;s:22:"url.query_args:sort_by";i:4;s:16:"user.permissions";}s:4:"tags";a:0:{}}}s:12:"widget_table";a:6:{s:14:"display_plugin";s:4:"page";s:2:"id";s:12:"widget_table";s:13:"display_title";s:14:"Widget (table)";s:8:"position";i:3;s:15:"display_options";a:12:{s:17:"display_extenders";a:0:{}s:4:"path";s:32:"admin/content/media-widget-table";s:5:"style";a:2:{s:4:"type";s:5:"table";s:7:"options";a:2:{s:9:"row_class";s:85:"media-library-item media-library-item--table js-media-library-item js-click-to-select";s:17:"default_row_class";b:1;}}s:8:"defaults";a:9:{s:5:"style";b:0;s:3:"row";b:0;s:6:"fields";b:0;s:6:"access";b:0;s:7:"filters";b:0;s:13:"filter_groups";b:0;s:9:"arguments";b:0;s:6:"header";b:0;s:9:"css_class";b:0;}s:3:"row";a:1:{s:4:"type";s:6:"fields";}s:6:"fields";a:5:{s:25:"media_library_select_form";a:9:{s:2:"id";s:25:"media_library_select_form";s:5:"label";s:0:"";s:5:"table";s:5:"media";s:5:"field";s:25:"media_library_select_form";s:12:"relationship";s:4:"none";s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:25:"media_library_select_form";s:21:"element_wrapper_class";s:27:"js-click-to-select-checkbox";s:13:"element_class";s:0:"";}s:20:"thumbnail__target_id";a:10:{s:2:"id";s:20:"thumbnail__target_id";s:5:"label";s:9:"Thumbnail";s:5:"table";s:16:"media_field_data";s:5:"field";s:20:"thumbnail__target_id";s:12:"relationship";s:4:"none";s:4:"type";s:5:"image";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:9:"thumbnail";s:9:"plugin_id";s:5:"field";s:8:"settings";a:2:{s:11:"image_style";s:13:"media_library";s:10:"image_link";s:0:"";}}s:4:"name";a:10:{s:2:"id";s:4:"name";s:5:"label";s:4:"Name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:4:"type";s:6:"string";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:5:"field";s:8:"settings";a:1:{s:14:"link_to_entity";b:0;}}s:3:"uid";a:10:{s:2:"id";s:3:"uid";s:5:"label";s:6:"Author";s:5:"table";s:20:"media_field_revision";s:5:"field";s:3:"uid";s:12:"relationship";s:4:"none";s:4:"type";s:22:"entity_reference_label";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:3:"uid";s:9:"plugin_id";s:5:"field";s:8:"settings";a:1:{s:4:"link";b:1;}}s:7:"changed";a:10:{s:2:"id";s:7:"changed";s:5:"label";s:7:"Updated";s:5:"table";s:16:"media_field_data";s:5:"field";s:7:"changed";s:12:"relationship";s:4:"none";s:4:"type";s:9:"timestamp";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:7:"changed";s:9:"plugin_id";s:5:"field";s:8:"settings";a:3:{s:11:"date_format";s:5:"short";s:18:"custom_date_format";s:0:"";s:8:"timezone";s:0:"";}}}s:6:"access";a:2:{s:4:"type";s:4:"perm";s:7:"options";a:1:{s:4:"perm";s:10:"view media";}}s:7:"filters";a:2:{s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:0;s:6:"expose";a:12:{s:11:"operator_id";s:0:"";s:5:"label";s:0:"";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:0:"";s:10:"identifier";s:0:"";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";s:9:"plugin_id";s:7:"boolean";}s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:4:"Name";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:4:"name";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}}s:13:"filter_groups";a:2:{s:8:"operator";s:3:"AND";s:6:"groups";a:1:{i:1;s:3:"AND";}}s:9:"arguments";a:1:{s:6:"bundle";a:27:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:14:"default_action";s:6:"ignore";s:9:"exception";a:3:{s:5:"value";s:3:"all";s:12:"title_enable";b:0;s:5:"title";s:3:"All";}s:12:"title_enable";b:0;s:5:"title";s:0:"";s:21:"default_argument_type";s:5:"fixed";s:24:"default_argument_options";a:1:{s:8:"argument";s:0:"";}s:25:"default_argument_skip_url";b:0;s:15:"summary_options";a:4:{s:9:"base_path";s:0:"";s:5:"count";b:1;s:14:"items_per_page";i:24;s:8:"override";b:0;}s:7:"summary";a:3:{s:10:"sort_order";s:3:"asc";s:17:"number_of_records";i:0;s:6:"format";s:15:"default_summary";}s:18:"specify_validation";b:0;s:8:"validate";a:2:{s:4:"type";s:4:"none";s:4:"fail";s:9:"not found";}s:16:"validate_options";a:0:{}s:8:"glossary";b:0;s:5:"limit";i:0;s:4:"case";s:4:"none";s:9:"path_case";s:4:"none";s:14:"transform_dash";b:0;s:12:"break_phrase";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"string";}}s:6:"header";a:2:{s:17:"display_link_grid";a:7:{s:2:"id";s:17:"display_link_grid";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:6:"widget";s:5:"label";s:4:"Grid";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}s:18:"display_link_table";a:7:{s:2:"id";s:18:"display_link_table";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:12:"widget_table";s:5:"label";s:5:"Table";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}}s:9:"css_class";s:67:"media-library-view js-media-library-view media-library-view--widget";}s:14:"cache_metadata";a:3:{s:7:"max-age";i:-1;s:8:"contexts";a:6:{i:0;s:26:"languages:language_content";i:1;s:28:"languages:language_interface";i:2;s:3:"url";i:3;s:14:"url.query_args";i:4;s:22:"url.query_args:sort_by";i:5;s:16:"user.permissions";}s:4:"tags";a:0:{}}}}}', +)) +->execute(); + +// Insert 'media' view, as modified by media_library_install(). +$connection->merge('config') + ->fields([ + 'data' => 'a:14:{s:4:"uuid";s:36:"668e914f-4d0b-4712-957c-8f3e9459caf5";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:1:{i:0;s:21:"image.style.thumbnail";}s:6:"module";a:3:{i:0;s:5:"image";i:1;s:5:"media";i:2;s:4:"user";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"rRzAWaXpJGYA2gPG6BmGxy8gfFd4srCc-LQY3JB6tR8";}s:2:"id";s:5:"media";s:5:"label";s:5:"Media";s:6:"module";s:5:"views";s:11:"description";s:0:"";s:3:"tag";s:0:"";s:10:"base_table";s:16:"media_field_data";s:10:"base_field";s:3:"mid";s:4:"core";s:3:"8.x";s:7:"display";a:2:{s:7:"default";a:6:{s:14:"display_plugin";s:7:"default";s:2:"id";s:7:"default";s:13:"display_title";s:6:"Master";s:8:"position";i:0;s:15:"display_options";a:17:{s:6:"access";a:2:{s:4:"type";s:4:"perm";s:7:"options";a:1:{s:4:"perm";s:21:"access media overview";}}s:5:"cache";a:2:{s:4:"type";s:3:"tag";s:7:"options";a:0:{}}s:5:"query";a:2:{s:4:"type";s:11:"views_query";s:7:"options";a:5:{s:19:"disable_sql_rewrite";b:0;s:8:"distinct";b:0;s:7:"replica";b:0;s:13:"query_comment";s:0:"";s:10:"query_tags";a:0:{}}}s:12:"exposed_form";a:2:{s:4:"type";s:5:"basic";s:7:"options";a:7:{s:13:"submit_button";s:6:"Filter";s:12:"reset_button";b:0;s:18:"reset_button_label";s:5:"Reset";s:19:"exposed_sorts_label";s:7:"Sort by";s:17:"expose_sort_order";b:1;s:14:"sort_asc_label";s:3:"Asc";s:15:"sort_desc_label";s:4:"Desc";}}s:5:"pager";a:2:{s:4:"type";s:4:"full";s:7:"options";a:7:{s:14:"items_per_page";i:50;s:6:"offset";i:0;s:2:"id";i:0;s:11:"total_pages";N;s:6:"expose";a:7:{s:14:"items_per_page";b:0;s:20:"items_per_page_label";s:14:"Items per page";s:22:"items_per_page_options";s:13:"5, 10, 25, 50";s:26:"items_per_page_options_all";b:0;s:32:"items_per_page_options_all_label";s:7:"- All -";s:6:"offset";b:0;s:12:"offset_label";s:6:"Offset";}s:4:"tags";a:4:{s:8:"previous";s:12:"‹ Previous";s:4:"next";s:8:"Next ›";s:5:"first";s:8:"« First";s:4:"last";s:7:"Last »";}s:8:"quantity";i:9;}}s:5:"style";a:2:{s:4:"type";s:5:"table";s:7:"options";a:12:{s:8:"grouping";a:0:{}s:9:"row_class";s:0:"";s:17:"default_row_class";b:1;s:8:"override";b:1;s:6:"sticky";b:0;s:7:"caption";s:0:"";s:7:"summary";s:0:"";s:11:"description";s:0:"";s:7:"columns";a:6:{s:4:"name";s:4:"name";s:6:"bundle";s:6:"bundle";s:7:"changed";s:7:"changed";s:3:"uid";s:3:"uid";s:6:"status";s:6:"status";s:20:"thumbnail__target_id";s:20:"thumbnail__target_id";}s:4:"info";a:6:{s:4:"name";a:6:{s:8:"sortable";b:1;s:18:"default_sort_order";s:3:"asc";s:5:"align";s:0:"";s:9:"separator";s:0:"";s:12:"empty_column";b:0;s:10:"responsive";s:0:"";}s:6:"bundle";a:6:{s:8:"sortable";b:1;s:18:"default_sort_order";s:3:"asc";s:5:"align";s:0:"";s:9:"separator";s:0:"";s:12:"empty_column";b:0;s:10:"responsive";s:0:"";}s:7:"changed";a:6:{s:8:"sortable";b:1;s:18:"default_sort_order";s:4:"desc";s:5:"align";s:0:"";s:9:"separator";s:0:"";s:12:"empty_column";b:0;s:10:"responsive";s:0:"";}s:3:"uid";a:6:{s:8:"sortable";b:0;s:18:"default_sort_order";s:3:"asc";s:5:"align";s:0:"";s:9:"separator";s:0:"";s:12:"empty_column";b:0;s:10:"responsive";s:0:"";}s:6:"status";a:6:{s:8:"sortable";b:1;s:18:"default_sort_order";s:3:"asc";s:5:"align";s:0:"";s:9:"separator";s:0:"";s:12:"empty_column";b:0;s:10:"responsive";s:0:"";}s:20:"thumbnail__target_id";a:6:{s:8:"sortable";b:0;s:18:"default_sort_order";s:3:"asc";s:5:"align";s:0:"";s:9:"separator";s:0:"";s:12:"empty_column";b:0;s:10:"responsive";s:0:"";}}s:7:"default";s:7:"changed";s:11:"empty_table";b:1;}}s:3:"row";a:1:{s:4:"type";s:6:"fields";}s:6:"fields";a:8:{s:15:"media_bulk_form";a:26:{s:2:"id";s:15:"media_bulk_form";s:5:"table";s:5:"media";s:5:"field";s:15:"media_bulk_form";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:12:"action_title";s:6:"Action";s:15:"include_exclude";s:7:"exclude";s:16:"selected_actions";a:0:{}s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:9:"bulk_form";}s:20:"thumbnail__target_id";a:37:{s:2:"id";s:20:"thumbnail__target_id";s:5:"table";s:16:"media_field_data";s:5:"field";s:20:"thumbnail__target_id";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:9:"Thumbnail";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:9:"target_id";s:4:"type";s:5:"image";s:8:"settings";a:2:{s:11:"image_style";s:9:"thumbnail";s:10:"image_link";s:0:"";}s:12:"group_column";s:0:"";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:9:"thumbnail";s:9:"plugin_id";s:5:"field";}s:4:"name";a:37:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:5:"media";s:5:"alter";a:8:{s:10:"alter_text";b:0;s:9:"make_link";b:0;s:8:"absolute";b:0;s:4:"trim";b:0;s:13:"word_boundary";b:0;s:8:"ellipsis";b:0;s:10:"strip_tags";b:0;s:4:"html";b:0;}s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:8:"settings";a:1:{s:14:"link_to_entity";b:1;}s:9:"plugin_id";s:5:"field";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:10:"Media name";s:7:"exclude";b:0;s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:5:"value";s:4:"type";s:6:"string";s:12:"group_column";s:5:"value";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;}s:6:"bundle";a:37:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:4:"Type";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:9:"target_id";s:4:"type";s:22:"entity_reference_label";s:8:"settings";a:1:{s:4:"link";b:0;}s:12:"group_column";s:9:"target_id";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:5:"field";}s:3:"uid";a:37:{s:2:"id";s:3:"uid";s:5:"table";s:16:"media_field_data";s:5:"field";s:3:"uid";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:6:"Author";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:9:"target_id";s:4:"type";s:22:"entity_reference_label";s:8:"settings";a:1:{s:4:"link";b:1;}s:12:"group_column";s:9:"target_id";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:3:"uid";s:9:"plugin_id";s:5:"field";}s:6:"status";a:37:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:6:"Status";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:5:"value";s:4:"type";s:7:"boolean";s:8:"settings";a:3:{s:6:"format";s:6:"custom";s:18:"format_custom_true";s:9:"Published";s:19:"format_custom_false";s:11:"Unpublished";}s:12:"group_column";s:5:"value";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";s:9:"plugin_id";s:5:"field";}s:7:"changed";a:37:{s:2:"id";s:7:"changed";s:5:"table";s:16:"media_field_data";s:5:"field";s:7:"changed";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:7:"Updated";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:5:"value";s:4:"type";s:9:"timestamp";s:8:"settings";a:3:{s:11:"date_format";s:5:"short";s:18:"custom_date_format";s:0:"";s:8:"timezone";s:0:"";}s:12:"group_column";s:5:"value";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:7:"changed";s:9:"plugin_id";s:5:"field";}s:10:"operations";a:24:{s:2:"id";s:10:"operations";s:5:"table";s:5:"media";s:5:"field";s:10:"operations";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:10:"Operations";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:11:"destination";b:1;s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:17:"entity_operations";}}s:7:"filters";a:4:{s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:10:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:10:"Media name";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:4:"name";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}s:6:"bundle";a:16:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:2:"in";s:5:"value";a:0:{}s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:11:{s:11:"operator_id";s:9:"bundle_op";s:5:"label";s:4:"Type";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:9:"bundle_op";s:10:"identifier";s:4:"type";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:6:"reduce";b:0;}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"bundle";}s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:10:{s:11:"operator_id";s:0:"";s:5:"label";s:4:"True";s:11:"description";N;s:12:"use_operator";b:0;s:8:"operator";s:9:"status_op";s:10:"identifier";s:6:"status";s:8:"required";b:1;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}}s:10:"is_grouped";b:1;s:10:"group_info";a:10:{s:5:"label";s:16:"Published status";s:11:"description";s:0:"";s:10:"identifier";s:6:"status";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:2:{i:1;a:3:{s:5:"title";s:9:"Published";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";}i:2;a:3:{s:5:"title";s:11:"Unpublished";s:8:"operator";s:1:"=";s:5:"value";s:1:"0";}}}s:9:"plugin_id";s:7:"boolean";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";}s:8:"langcode";a:16:{s:2:"id";s:8:"langcode";s:5:"table";s:16:"media_field_data";s:5:"field";s:8:"langcode";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:2:"in";s:5:"value";a:0:{}s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:11:{s:11:"operator_id";s:11:"langcode_op";s:5:"label";s:8:"Language";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:11:"langcode_op";s:10:"identifier";s:8:"langcode";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:6:"reduce";b:0;}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:8:"langcode";s:9:"plugin_id";s:8:"language";}}s:5:"sorts";a:1:{s:7:"created";a:13:{s:2:"id";s:7:"created";s:5:"table";s:16:"media_field_data";s:5:"field";s:7:"created";s:5:"order";s:4:"DESC";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:7:"created";s:9:"plugin_id";s:4:"date";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:7:"exposed";b:0;s:6:"expose";a:1:{s:5:"label";s:0:"";}s:11:"granularity";s:6:"second";}}s:5:"title";s:5:"Media";s:6:"header";a:0:{}s:6:"footer";a:0:{}s:5:"empty";a:1:{s:16:"area_text_custom";a:10:{s:2:"id";s:16:"area_text_custom";s:5:"table";s:5:"views";s:5:"field";s:16:"area_text_custom";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"empty";b:1;s:8:"tokenize";b:0;s:7:"content";s:19:"No media available.";s:9:"plugin_id";s:11:"text_custom";}}s:13:"relationships";a:0:{}s:9:"arguments";a:0:{}s:17:"display_extenders";a:0:{}}s:14:"cache_metadata";a:3:{s:7:"max-age";i:0;s:8:"contexts";a:5:{i:0;s:26:"languages:language_content";i:1;s:28:"languages:language_interface";i:2;s:3:"url";i:3;s:14:"url.query_args";i:4;s:16:"user.permissions";}s:4:"tags";a:0:{}}}s:15:"media_page_list";a:6:{s:14:"display_plugin";s:4:"page";s:2:"id";s:15:"media_page_list";s:13:"display_title";s:5:"Media";s:8:"position";i:1;s:15:"display_options";a:3:{s:17:"display_extenders";a:0:{}s:4:"path";s:25:"admin/content/media-table";s:19:"display_description";s:0:"";}s:14:"cache_metadata";a:3:{s:7:"max-age";i:0;s:8:"contexts";a:5:{i:0;s:26:"languages:language_content";i:1;s:28:"languages:language_interface";i:2;s:3:"url";i:3;s:14:"url.query_args";i:4;s:16:"user.permissions";}s:4:"tags";a:0:{}}}}}', + 'name' => 'views.view.media', + 'collection' => '', + ]) + ->condition('collection', '') + ->condition('name', 'views.view.media') + ->execute(); + +// Insert media library key_value entries. +$connection->insert('key_value') +->fields(array( + 'collection', + 'name', + 'value', +)) +->values(array( + 'collection' => 'config.entity.key_store.entity_view_display', + 'name' => 'uuid:67e6d857-8ecb-49f5-95e1-6b1c4306c31f', + 'value' => 'a:1:{i:0;s:49:"core.entity_view_display.media.file.media_library";}', +)) +->values(array( + 'collection' => 'config.entity.key_store.entity_view_display', + 'name' => 'uuid:277ca98b-2ada-4251-ad69-aa73e72d60fe', + 'value' => 'a:1:{i:0;s:50:"core.entity_view_display.media.image.media_library";}', +)) +->values(array( + 'collection' => 'config.entity.key_store.entity_view_mode', + 'name' => 'uuid:20b2f1f7-a864-4d41-a15f-32f66789f73d', + 'value' => 'a:1:{i:0;s:41:"core.entity_view_mode.media.media_library";}', +)) +->values(array( + 'collection' => 'config.entity.key_store.view', + 'name' => 'uuid:3bc9cf0f-cb66-4dbe-8d7e-862cb85e5932', + 'value' => 'a:1:{i:0;s:24:"views.view.media_library";}', +)) +->execute(); diff --git a/core/modules/media_library/tests/fixtures/update/drupal-8.8.x-media_library-update-views-classnames-3049943.php b/core/modules/media_library/tests/fixtures/update/drupal-8.8.x-media_library-update-views-classnames-3049943.php new file mode 100644 index 000000000..c91f8010f --- /dev/null +++ b/core/modules/media_library/tests/fixtures/update/drupal-8.8.x-media_library-update-views-classnames-3049943.php @@ -0,0 +1,54 @@ +merge('key_value') + ->fields([ + 'value' => 'i:8000;', + 'name' => 'media_library', + 'collection' => 'system.schema', + ]) + ->condition('collection', 'system.schema') + ->condition('name', 'media_library') + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['media_library'] = 0; +$connection->update('config') + ->fields([ + 'data' => serialize($extensions), + 'collection' => '', + 'name' => 'core.extension', + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); + +$connection->insert('config') + ->fields(array( + 'collection', + 'name', + 'data', + )) + ->values(array( + 'collection' => '', + 'name' => 'views.view.media_library', + 'data' => 'a:14:{s:4:"uuid";s:36:"f57329d9-bc3a-43c4-90a8-ace75fcc2e22";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:3:{s:6:"config";a:2:{i:0;s:41:"core.entity_view_mode.media.media_library";i:1;s:25:"image.style.media_library";}s:8:"enforced";a:1:{s:6:"module";a:1:{i:0;s:13:"media_library";}}s:6:"module";a:4:{i:0;s:5:"image";i:1;s:5:"media";i:2;s:13:"media_library";i:3;s:4:"user";}}s:5:"_core";a:1:{s:19:"default_config_hash";s:43:"AWji7uYzNsxcFVzYkTki4iTCAT2cn2s7FLMu64brtQo";}s:2:"id";s:13:"media_library";s:5:"label";s:13:"Media library";s:6:"module";s:5:"views";s:11:"description";s:22:"Find and manage media.";s:3:"tag";s:0:"";s:10:"base_table";s:16:"media_field_data";s:10:"base_field";s:3:"mid";s:4:"core";s:3:"8.x";s:7:"display";a:4:{s:7:"default";a:6:{s:14:"display_plugin";s:7:"default";s:2:"id";s:7:"default";s:13:"display_title";s:6:"Master";s:8:"position";i:0;s:15:"display_options";a:18:{s:6:"access";a:2:{s:4:"type";s:4:"perm";s:7:"options";a:1:{s:4:"perm";s:21:"access media overview";}}s:5:"cache";a:2:{s:4:"type";s:3:"tag";s:7:"options";a:0:{}}s:5:"query";a:2:{s:4:"type";s:11:"views_query";s:7:"options";a:5:{s:19:"disable_sql_rewrite";b:0;s:8:"distinct";b:0;s:7:"replica";b:0;s:13:"query_comment";s:0:"";s:10:"query_tags";a:0:{}}}s:12:"exposed_form";a:2:{s:4:"type";s:5:"basic";s:7:"options";a:7:{s:13:"submit_button";s:13:"Apply filters";s:12:"reset_button";b:0;s:18:"reset_button_label";s:5:"Reset";s:19:"exposed_sorts_label";s:7:"Sort by";s:17:"expose_sort_order";b:0;s:14:"sort_asc_label";s:3:"Asc";s:15:"sort_desc_label";s:4:"Desc";}}s:5:"pager";a:2:{s:4:"type";s:4:"mini";s:7:"options";a:6:{s:14:"items_per_page";i:24;s:6:"offset";i:0;s:2:"id";i:0;s:11:"total_pages";N;s:6:"expose";a:7:{s:14:"items_per_page";b:0;s:20:"items_per_page_label";s:14:"Items per page";s:22:"items_per_page_options";s:13:"6, 12, 24, 48";s:26:"items_per_page_options_all";b:0;s:32:"items_per_page_options_all_label";s:7:"- All -";s:6:"offset";b:0;s:12:"offset_label";s:6:"Offset";}s:4:"tags";a:2:{s:8:"previous";s:6:"‹‹";s:4:"next";s:6:"››";}}}s:5:"style";a:2:{s:4:"type";s:7:"default";s:7:"options";a:3:{s:8:"grouping";a:0:{}s:9:"row_class";s:84:"media-library-item media-library-item--grid js-media-library-item js-click-to-select";s:17:"default_row_class";b:1;}}s:3:"row";a:2:{s:4:"type";s:6:"fields";s:7:"options";a:4:{s:22:"default_field_elements";b:1;s:6:"inline";a:0:{}s:9:"separator";s:0:"";s:10:"hide_empty";b:0;}}s:6:"fields";a:2:{s:15:"media_bulk_form";a:26:{s:2:"id";s:15:"media_bulk_form";s:5:"table";s:5:"media";s:5:"field";s:15:"media_bulk_form";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"js-click-to-select-checkbox";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:12:"action_title";s:6:"Action";s:15:"include_exclude";s:7:"exclude";s:16:"selected_actions";a:0:{}s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:9:"bulk_form";}s:15:"rendered_entity";a:24:{s:2:"id";s:15:"rendered_entity";s:5:"table";s:5:"media";s:5:"field";s:15:"rendered_entity";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"media-library-item__content";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:9:"view_mode";s:13:"media_library";s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:15:"rendered_entity";}}s:7:"filters";a:4:{s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:0:"";s:5:"label";s:17:"Publishing status";s:11:"description";N;s:12:"use_operator";b:0;s:8:"operator";s:9:"status_op";s:10:"identifier";s:6:"status";s:8:"required";b:1;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:1;s:10:"group_info";a:10:{s:5:"label";s:9:"Published";s:11:"description";s:0:"";s:10:"identifier";s:6:"status";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:2:{i:1;a:3:{s:5:"title";s:9:"Published";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";}i:2;a:3:{s:5:"title";s:11:"Unpublished";s:8:"operator";s:1:"=";s:5:"value";s:1:"0";}}}s:9:"plugin_id";s:7:"boolean";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";}s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:4:"Name";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:4:"name";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}s:6:"bundle";a:16:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:2:"in";s:5:"value";a:0:{}s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:13:{s:11:"operator_id";s:9:"bundle_op";s:5:"label";s:10:"Media type";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:9:"bundle_op";s:10:"identifier";s:4:"type";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:6:"reduce";b:0;s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:10:"Media type";s:11:"description";N;s:10:"identifier";s:6:"bundle";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:3:{i:1;a:0:{}i:2;a:0:{}i:3;a:0:{}}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"bundle";}s:12:"status_extra";a:15:{s:2:"id";s:12:"status_extra";s:5:"table";s:16:"media_field_data";s:5:"field";s:12:"status_extra";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:0;s:6:"expose";a:12:{s:11:"operator_id";s:0:"";s:5:"label";s:0:"";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:0:"";s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}s:10:"identifier";s:0:"";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:12:"media_status";}}s:5:"sorts";a:3:{s:7:"created";a:13:{s:2:"id";s:7:"created";s:5:"table";s:16:"media_field_data";s:5:"field";s:7:"created";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"order";s:4:"DESC";s:7:"exposed";b:1;s:6:"expose";a:1:{s:5:"label";s:12:"Newest first";}s:11:"granularity";s:6:"second";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:7:"created";s:9:"plugin_id";s:4:"date";}s:4:"name";a:12:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"order";s:3:"ASC";s:7:"exposed";b:1;s:6:"expose";a:1:{s:5:"label";s:10:"Name (A-Z)";}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:8:"standard";}s:6:"name_1";a:12:{s:2:"id";s:6:"name_1";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"order";s:4:"DESC";s:7:"exposed";b:1;s:6:"expose";a:1:{s:5:"label";s:10:"Name (Z-A)";}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:8:"standard";}}s:5:"title";s:5:"Media";s:6:"header";a:0:{}s:6:"footer";a:0:{}s:5:"empty";a:1:{s:16:"area_text_custom";a:10:{s:2:"id";s:16:"area_text_custom";s:5:"table";s:5:"views";s:5:"field";s:16:"area_text_custom";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"empty";b:1;s:8:"tokenize";b:0;s:7:"content";s:19:"No media available.";s:9:"plugin_id";s:11:"text_custom";}}s:13:"relationships";a:0:{}s:17:"display_extenders";a:0:{}s:8:"use_ajax";b:1;s:9:"css_class";s:40:"media-library-view js-media-library-view";}s:14:"cache_metadata";a:3:{s:7:"max-age";i:0;s:8:"contexts";a:6:{i:0;s:28:"languages:language_interface";i:1;s:3:"url";i:2;s:14:"url.query_args";i:3;s:22:"url.query_args:sort_by";i:4;s:4:"user";i:5;s:16:"user.permissions";}s:4:"tags";a:0:{}}}s:4:"page";a:6:{s:14:"display_plugin";s:4:"page";s:2:"id";s:4:"page";s:13:"display_title";s:4:"Page";s:8:"position";i:1;s:15:"display_options";a:5:{s:17:"display_extenders";a:0:{}s:4:"path";s:19:"admin/content/media";s:4:"menu";a:8:{s:4:"type";s:3:"tab";s:5:"title";s:5:"Media";s:11:"description";s:49:"Allows users to browse and administer media items";s:8:"expanded";b:0;s:6:"parent";s:20:"system.admin_content";s:6:"weight";i:5;s:7:"context";s:1:"0";s:9:"menu_name";s:5:"admin";}s:6:"fields";a:5:{s:15:"media_bulk_form";a:26:{s:2:"id";s:15:"media_bulk_form";s:5:"table";s:5:"media";s:5:"field";s:15:"media_bulk_form";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"js-click-to-select-checkbox";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:12:"action_title";s:6:"Action";s:15:"include_exclude";s:7:"exclude";s:16:"selected_actions";a:0:{}s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:9:"bulk_form";}s:4:"name";a:37:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:1;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:5:"value";s:4:"type";s:6:"string";s:8:"settings";a:1:{s:14:"link_to_entity";b:0;}s:12:"group_column";s:5:"value";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:5:"field";}s:10:"edit_media";a:26:{s:2:"id";s:10:"edit_media";s:5:"table";s:5:"media";s:5:"field";s:10:"edit_media";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:1;s:4:"text";s:15:"Edit {{ name }}";s:9:"make_link";b:1;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:15:"Edit {{ name }}";s:3:"rel";s:0:"";s:10:"link_class";s:24:"media-library-item__edit";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:1:"0";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:0;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:4:"text";s:4:"Edit";s:18:"output_url_as_text";b:0;s:8:"absolute";b:0;s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:16:"entity_link_edit";}s:12:"delete_media";a:26:{s:2:"id";s:12:"delete_media";s:5:"table";s:5:"media";s:5:"field";s:12:"delete_media";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:1;s:4:"text";s:17:"Delete {{ name }}";s:9:"make_link";b:1;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:17:"Delete {{ name }}";s:3:"rel";s:0:"";s:10:"link_class";s:26:"media-library-item__remove";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:1:"0";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:0;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:4:"text";s:6:"Delete";s:18:"output_url_as_text";b:0;s:8:"absolute";b:0;s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:18:"entity_link_delete";}s:15:"rendered_entity";a:24:{s:2:"id";s:15:"rendered_entity";s:5:"table";s:5:"media";s:5:"field";s:15:"rendered_entity";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"media-library-item__content";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:9:"view_mode";s:13:"media_library";s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:15:"rendered_entity";}}s:8:"defaults";a:1:{s:6:"fields";b:0;}}s:14:"cache_metadata";a:3:{s:7:"max-age";i:0;s:8:"contexts";a:7:{i:0;s:26:"languages:language_content";i:1;s:28:"languages:language_interface";i:2;s:3:"url";i:3;s:14:"url.query_args";i:4;s:22:"url.query_args:sort_by";i:5;s:4:"user";i:6;s:16:"user.permissions";}s:4:"tags";a:0:{}}}s:6:"widget";a:6:{s:14:"display_plugin";s:4:"page";s:2:"id";s:6:"widget";s:13:"display_title";s:6:"Widget";s:8:"position";i:2;s:15:"display_options";a:11:{s:17:"display_extenders";a:0:{}s:4:"path";s:26:"admin/content/media-widget";s:6:"fields";a:2:{s:15:"rendered_entity";a:24:{s:2:"id";s:15:"rendered_entity";s:5:"table";s:5:"media";s:5:"field";s:15:"rendered_entity";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:27:"media-library-item__content";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:9:"view_mode";s:13:"media_library";s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:15:"rendered_entity";}s:25:"media_library_select_form";a:23:{s:2:"id";s:25:"media_library_select_form";s:5:"table";s:5:"media";s:5:"field";s:25:"media_library_select_form";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:27:"js-click-to-select-checkbox";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:25:"media_library_select_form";}}s:8:"defaults";a:7:{s:6:"fields";b:0;s:6:"access";b:0;s:7:"filters";b:0;s:13:"filter_groups";b:0;s:9:"arguments";b:0;s:6:"header";b:0;s:9:"css_class";b:0;}s:19:"display_description";s:0:"";s:6:"access";a:2:{s:4:"type";s:4:"perm";s:7:"options";a:1:{s:4:"perm";s:10:"view media";}}s:7:"filters";a:2:{s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:0;s:6:"expose";a:12:{s:11:"operator_id";s:0:"";s:5:"label";s:0:"";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:0:"";s:10:"identifier";s:0:"";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";s:9:"plugin_id";s:7:"boolean";}s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:4:"Name";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:4:"name";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}}s:13:"filter_groups";a:2:{s:8:"operator";s:3:"AND";s:6:"groups";a:1:{i:1;s:3:"AND";}}s:9:"arguments";a:1:{s:6:"bundle";a:27:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:14:"default_action";s:6:"ignore";s:9:"exception";a:3:{s:5:"value";s:3:"all";s:12:"title_enable";b:0;s:5:"title";s:3:"All";}s:12:"title_enable";b:0;s:5:"title";s:0:"";s:21:"default_argument_type";s:5:"fixed";s:24:"default_argument_options";a:1:{s:8:"argument";s:0:"";}s:25:"default_argument_skip_url";b:0;s:15:"summary_options";a:4:{s:9:"base_path";s:0:"";s:5:"count";b:1;s:14:"items_per_page";i:24;s:8:"override";b:0;}s:7:"summary";a:3:{s:10:"sort_order";s:3:"asc";s:17:"number_of_records";i:0;s:6:"format";s:15:"default_summary";}s:18:"specify_validation";b:0;s:8:"validate";a:2:{s:4:"type";s:4:"none";s:4:"fail";s:9:"not found";}s:16:"validate_options";a:0:{}s:8:"glossary";b:0;s:5:"limit";i:0;s:4:"case";s:4:"none";s:9:"path_case";s:4:"none";s:14:"transform_dash";b:0;s:12:"break_phrase";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"string";}}s:6:"header";a:2:{s:17:"display_link_grid";a:7:{s:2:"id";s:17:"display_link_grid";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:6:"widget";s:5:"label";s:4:"Grid";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}s:18:"display_link_table";a:7:{s:2:"id";s:18:"display_link_table";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:12:"widget_table";s:5:"label";s:5:"Table";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}}s:9:"css_class";s:67:"media-library-view js-media-library-view media-library-view--widget";}s:14:"cache_metadata";a:3:{s:7:"max-age";i:-1;s:8:"contexts";a:5:{i:0;s:28:"languages:language_interface";i:1;s:3:"url";i:2;s:14:"url.query_args";i:3;s:22:"url.query_args:sort_by";i:4;s:16:"user.permissions";}s:4:"tags";a:0:{}}}s:12:"widget_table";a:6:{s:14:"display_plugin";s:4:"page";s:2:"id";s:12:"widget_table";s:13:"display_title";s:14:"Widget (table)";s:8:"position";i:3;s:15:"display_options";a:12:{s:17:"display_extenders";a:0:{}s:4:"path";s:32:"admin/content/media-widget-table";s:5:"style";a:2:{s:4:"type";s:5:"table";s:7:"options";a:2:{s:9:"row_class";s:85:"media-library-item media-library-item--table js-media-library-item js-click-to-select";s:17:"default_row_class";b:1;}}s:8:"defaults";a:9:{s:5:"style";b:0;s:3:"row";b:0;s:6:"fields";b:0;s:6:"access";b:0;s:7:"filters";b:0;s:13:"filter_groups";b:0;s:9:"arguments";b:0;s:6:"header";b:0;s:9:"css_class";b:0;}s:3:"row";a:1:{s:4:"type";s:6:"fields";}s:6:"fields";a:5:{s:25:"media_library_select_form";a:9:{s:2:"id";s:25:"media_library_select_form";s:5:"label";s:0:"";s:5:"table";s:5:"media";s:5:"field";s:25:"media_library_select_form";s:12:"relationship";s:4:"none";s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:25:"media_library_select_form";s:21:"element_wrapper_class";s:27:"js-click-to-select-checkbox";s:13:"element_class";s:0:"";}s:20:"thumbnail__target_id";a:10:{s:2:"id";s:20:"thumbnail__target_id";s:5:"label";s:9:"Thumbnail";s:5:"table";s:16:"media_field_data";s:5:"field";s:20:"thumbnail__target_id";s:12:"relationship";s:4:"none";s:4:"type";s:5:"image";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:9:"thumbnail";s:9:"plugin_id";s:5:"field";s:8:"settings";a:2:{s:11:"image_style";s:13:"media_library";s:10:"image_link";s:0:"";}}s:4:"name";a:10:{s:2:"id";s:4:"name";s:5:"label";s:4:"Name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:4:"type";s:6:"string";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:5:"field";s:8:"settings";a:1:{s:14:"link_to_entity";b:0;}}s:3:"uid";a:10:{s:2:"id";s:3:"uid";s:5:"label";s:6:"Author";s:5:"table";s:20:"media_field_revision";s:5:"field";s:3:"uid";s:12:"relationship";s:4:"none";s:4:"type";s:22:"entity_reference_label";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:3:"uid";s:9:"plugin_id";s:5:"field";s:8:"settings";a:1:{s:4:"link";b:1;}}s:7:"changed";a:10:{s:2:"id";s:7:"changed";s:5:"label";s:7:"Updated";s:5:"table";s:16:"media_field_data";s:5:"field";s:7:"changed";s:12:"relationship";s:4:"none";s:4:"type";s:9:"timestamp";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:7:"changed";s:9:"plugin_id";s:5:"field";s:8:"settings";a:3:{s:11:"date_format";s:5:"short";s:18:"custom_date_format";s:0:"";s:8:"timezone";s:0:"";}}}s:6:"access";a:2:{s:4:"type";s:4:"perm";s:7:"options";a:1:{s:4:"perm";s:10:"view media";}}s:7:"filters";a:2:{s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:0;s:6:"expose";a:12:{s:11:"operator_id";s:0:"";s:5:"label";s:0:"";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:0:"";s:10:"identifier";s:0:"";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";s:9:"plugin_id";s:7:"boolean";}s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:4:"Name";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:4:"name";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:3:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";}s:24:"operator_limit_selection";b:0;s:13:"operator_list";a:0:{}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}}s:13:"filter_groups";a:2:{s:8:"operator";s:3:"AND";s:6:"groups";a:1:{i:1;s:3:"AND";}}s:9:"arguments";a:1:{s:6:"bundle";a:27:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:14:"default_action";s:6:"ignore";s:9:"exception";a:3:{s:5:"value";s:3:"all";s:12:"title_enable";b:0;s:5:"title";s:3:"All";}s:12:"title_enable";b:0;s:5:"title";s:0:"";s:21:"default_argument_type";s:5:"fixed";s:24:"default_argument_options";a:1:{s:8:"argument";s:0:"";}s:25:"default_argument_skip_url";b:0;s:15:"summary_options";a:4:{s:9:"base_path";s:0:"";s:5:"count";b:1;s:14:"items_per_page";i:24;s:8:"override";b:0;}s:7:"summary";a:3:{s:10:"sort_order";s:3:"asc";s:17:"number_of_records";i:0;s:6:"format";s:15:"default_summary";}s:18:"specify_validation";b:0;s:8:"validate";a:2:{s:4:"type";s:4:"none";s:4:"fail";s:9:"not found";}s:16:"validate_options";a:0:{}s:8:"glossary";b:0;s:5:"limit";i:0;s:4:"case";s:4:"none";s:9:"path_case";s:4:"none";s:14:"transform_dash";b:0;s:12:"break_phrase";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"string";}}s:6:"header";a:2:{s:17:"display_link_grid";a:7:{s:2:"id";s:17:"display_link_grid";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:6:"widget";s:5:"label";s:4:"Grid";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}s:18:"display_link_table";a:7:{s:2:"id";s:18:"display_link_table";s:5:"table";s:5:"views";s:5:"field";s:12:"display_link";s:10:"display_id";s:12:"widget_table";s:5:"label";s:5:"Table";s:9:"plugin_id";s:12:"display_link";s:5:"empty";b:1;}}s:9:"css_class";s:67:"media-library-view js-media-library-view media-library-view--widget";}s:14:"cache_metadata";a:3:{s:7:"max-age";i:-1;s:8:"contexts";a:6:{i:0;s:26:"languages:language_content";i:1;s:28:"languages:language_interface";i:2;s:3:"url";i:3;s:14:"url.query_args";i:4;s:22:"url.query_args:sort_by";i:5;s:16:"user.permissions";}s:4:"tags";a:0:{}}}}}', + )) + ->execute(); diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_five.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_five.default.yml index 8deaa648b..98206155d 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_five.default.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_five.default.yml @@ -51,6 +51,7 @@ content: weight: 5 settings: match_operator: CONTAINS + match_limit: 10 size: 60 placeholder: '' region: content diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml index 40d4d1bd3..5303563d9 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml @@ -62,6 +62,7 @@ content: weight: 5 settings: match_operator: CONTAINS + match_limit: 10 size: 60 placeholder: '' region: content diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml index 212daf818..2e07c3d21 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_one.default.yml @@ -36,6 +36,7 @@ content: weight: 5 settings: match_operator: CONTAINS + match_limit: 10 size: 60 placeholder: '' region: content diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml index ea7248e2a..c70d348c7 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml @@ -53,6 +53,7 @@ content: weight: 5 settings: match_operator: CONTAINS + match_limit: 10 size: 60 placeholder: '' region: content diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml index fabd13b02..acc954e82 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_two.default.yml @@ -36,6 +36,7 @@ content: weight: 5 settings: match_operator: CONTAINS + match_limit: 10 size: 60 placeholder: '' region: content diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.basic_page.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.basic_page.default.yml index 6df651848..0fd908e71 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.basic_page.default.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.basic_page.default.yml @@ -95,6 +95,7 @@ content: weight: 5 settings: match_operator: CONTAINS + match_limit: 10 size: 60 placeholder: '' region: content diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml index 0950c442a..32b9d973b 100644 --- a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml +++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml @@ -1,8 +1,8 @@ -name: 'Media library test' +name: 'Media Library test' type: module -description: 'Test module for Media library.' +description: 'Test module for Media Library.' package: Testing -# core: 8.x +core: 8.x dependencies: - drupal:image - drupal:media_library @@ -10,9 +10,3 @@ dependencies: - drupal:menu_ui - drupal:node - drupal:path - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.module b/core/modules/media_library/tests/modules/media_library_test/media_library_test.module new file mode 100644 index 000000000..6c001f977 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.module @@ -0,0 +1,29 @@ +getName() === 'field_media_no_access', 'Field access denied by test module'); +} + +/** + * Implements hook_entity_type_alter(). + */ +function media_library_test_entity_type_alter(array &$entity_types) { + if (isset($entity_types['node'])) { + $entity_types['node']->setFormClass('default', TestNodeFormOverride::class); + $entity_types['node']->setFormClass('edit', TestNodeFormOverride::class); + } +} diff --git a/core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php b/core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php new file mode 100644 index 000000000..d95c4cda6 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/src/Form/TestNodeFormOverride.php @@ -0,0 +1,24 @@ +getTriggeringElement(); + if (in_array('open_button', $triggering_element['#parents'], TRUE)) { + throw new \Exception('The media library widget open_button element should not trigger form submit.'); + } + parent::submitForm($form, $form_state); + } + +} diff --git a/core/modules/media_library/tests/modules/media_library_test_widget/config/schema/media_library_test_widget.schema.yml b/core/modules/media_library/tests/modules/media_library_test_widget/config/schema/media_library_test_widget.schema.yml new file mode 100644 index 000000000..81186253e --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test_widget/config/schema/media_library_test_widget.schema.yml @@ -0,0 +1,10 @@ +field.widget.settings.media_library_inception_widget: + type: mapping + label: 'Media library inception widget settings' + mapping: + media_types: + type: sequence + label: 'Allowed media types, in display order' + sequence: + type: string + label: 'Media type ID' diff --git a/core/modules/media_library/tests/modules/media_library_test_widget/media_library_test_widget.info.yml b/core/modules/media_library/tests/modules/media_library_test_widget/media_library_test_widget.info.yml new file mode 100644 index 000000000..fe6cac86a --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test_widget/media_library_test_widget.info.yml @@ -0,0 +1,9 @@ +name: 'Media Library test widget' +type: module +description: 'Test widget that has a nested media library widget' +package: Testing +core: 8.x +dependencies: + - drupal:image + - drupal:media_library + - drupal:media_test_source diff --git a/core/modules/media_library/tests/modules/media_library_test_widget/src/Plugin/Field/FieldWidget/MediaLibraryInceptionWidget.php b/core/modules/media_library/tests/modules/media_library_test_widget/src/Plugin/Field/FieldWidget/MediaLibraryInceptionWidget.php new file mode 100644 index 000000000..e5a030ecc --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test_widget/src/Plugin/Field/FieldWidget/MediaLibraryInceptionWidget.php @@ -0,0 +1,59 @@ +getFormObject()->getEntity(); + $input = $form_state->getUserInput(); + if (!empty($input['_triggering_element_name']) && strpos($input['_triggering_element_name'], 'media-library-update') !== FALSE) { + // This will validate a required field before an upload is completed. + $display = EntityFormDisplay::collectRenderDisplay($entity, 'edit'); + $display->extractFormValues($entity, $form, $form_state); + $display->validateFormValues($entity, $form, $form_state); + } + $form_value = $form_state->getValue($field_name); + if (!empty($form_value['media_library_selection'])) { + $entity->set($field_name, $form_value['media_library_selection']); + } + } + +} diff --git a/core/modules/media_library/tests/src/Functional/MediaLibraryDisplayModeTest.php b/core/modules/media_library/tests/src/Functional/MediaLibraryDisplayModeTest.php index d0c1801d8..7ebc2bb1b 100644 --- a/core/modules/media_library/tests/src/Functional/MediaLibraryDisplayModeTest.php +++ b/core/modules/media_library/tests/src/Functional/MediaLibraryDisplayModeTest.php @@ -12,7 +12,7 @@ use Drupal\media\Entity\MediaType; /** - * Tests that the Media library automatically configures form/view modes. + * Tests that the Media Library automatically configures form/view modes. * * @group media_library */ @@ -29,6 +29,11 @@ class MediaLibraryDisplayModeTest extends BrowserTestBase { 'system', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -46,7 +51,7 @@ protected function setUp() { } /** - * Tests that the Media library can automatically configure display modes. + * Tests that the Media Library can automatically configure display modes. */ public function testDisplayModes() { $this->createMediaType('file', [ diff --git a/core/modules/media_library/tests/src/Functional/SettingsFormTest.php b/core/modules/media_library/tests/src/Functional/SettingsFormTest.php new file mode 100644 index 000000000..4d802a868 --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/SettingsFormTest.php @@ -0,0 +1,48 @@ +drupalCreateUser([ + 'access administration pages', + 'administer media', + ]); + $this->drupalLogin($account); + + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + $this->drupalGet('/admin/config'); + $page->clickLink('Media Library settings'); + $page->checkField('Enable advanced UI'); + $page->pressButton('Save configuration'); + $assert_session->checkboxChecked('Enable advanced UI'); + $page->uncheckField('Enable advanced UI'); + $page->pressButton('Save configuration'); + $assert_session->checkboxNotChecked('Enable advanced UI'); + } + +} diff --git a/core/modules/media_library/tests/src/Functional/Update/MediaLibrarySetAdministrativePageToTableDisplayTest.php b/core/modules/media_library/tests/src/Functional/Update/MediaLibrarySetAdministrativePageToTableDisplayTest.php new file mode 100644 index 000000000..23ba0018e --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/Update/MediaLibrarySetAdministrativePageToTableDisplayTest.php @@ -0,0 +1,154 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../../../media/tests/fixtures/update/drupal-8.4.0-media_installed.php', + __DIR__ . '/../../../fixtures/update/drupal-8.7.2-media_library_installed.php', + ]; + } + + /** + * Tests that the update alters uncustomized path and menu settings. + */ + public function testUpdateWithoutCustomizations() { + /** @var \Drupal\views\ViewEntityInterface $view */ + $view = View::load('media'); + $display = $view->getDisplay('media_page_list'); + $this->assertSame('admin/content/media-table', $display['display_options']['path']); + $this->assertArrayNotHasKey('menu', $display['display_options']); + + $view = View::load('media_library'); + $display = $view->getDisplay('page'); + $this->assertSame('admin/content/media', $display['display_options']['path']); + $this->assertSame('tab', $display['display_options']['menu']['type']); + $this->assertSame('Media', $display['display_options']['menu']['title']); + + $this->runUpdates(); + + $view = View::load('media'); + $display = $view->getDisplay('media_page_list'); + $this->assertSame('admin/content/media', $display['display_options']['path']); + $this->assertArrayNotHasKey('menu', $display['display_options']); + + $view = View::load('media_library'); + $display = $view->getDisplay('page'); + $this->assertSame('admin/content/media-grid', $display['display_options']['path']); + $this->assertArrayNotHasKey('menu', $display['display_options']); + } + + /** + * Tests that the update does not alter a custom 'media' view path. + */ + public function testUpdateWithCustomizedMediaViewPath() { + /** @var \Drupal\views\ViewEntityInterface $view */ + $view = View::load('media'); + $display = &$view->getDisplay('media_page_list'); + $display['display_options']['path'] = 'admin/content/all-media'; + $view->save(); + + $this->runUpdates(); + + // The update should not have modified the path. + $view = View::load('media'); + $display = $view->getDisplay('media_page_list'); + $this->assertSame('admin/content/all-media', $display['display_options']['path']); + + $view = View::load('media_library'); + $display = $view->getDisplay('page'); + $this->assertSame('admin/content/media-grid', $display['display_options']['path']); + $this->assertArrayNotHasKey('menu', $display['display_options']); + } + + /** + * Tests that the update does not alter custom 'media' view menu settings. + */ + public function testUpdateWithCustomizedMediaViewMenuSettings() { + /** @var \Drupal\views\ViewEntityInterface $view */ + $view = View::load('media'); + $display = &$view->getDisplay('media_page_list'); + $display['display_options']['menu'] = [ + 'type' => 'normal', + 'title' => 'All media', + 'parent' => 'system.admin_structure', + ]; + $view->save(); + + $this->runUpdates(); + + // The update should not have modified the path. + $view = View::load('media'); + $display = $view->getDisplay('media_page_list'); + $this->assertSame('admin/content/media', $display['display_options']['path']); + $this->assertSame('normal', $display['display_options']['menu']['type']); + $this->assertSame('All media', $display['display_options']['menu']['title']); + $this->assertSame('system.admin_structure', $display['display_options']['menu']['parent']); + + $view = View::load('media_library'); + $display = $view->getDisplay('page'); + $this->assertSame('admin/content/media-grid', $display['display_options']['path']); + $this->assertArrayNotHasKey('menu', $display['display_options']); + } + + /** + * Tests that the update does not alter custom 'media' path and menu settings. + */ + public function testUpdateWithCustomizedMediaLibraryViewPath() { + /** @var \Drupal\views\ViewEntityInterface $view */ + $view = View::load('media_library'); + $display = &$view->getDisplay('page'); + $display['display_options']['path'] = 'admin/content/media-pretty'; + $view->save(); + + $this->runUpdates(); + + // The update should not have modified the path or menu settings. + $view = View::load('media_library'); + $display = $view->getDisplay('page'); + $this->assertSame('admin/content/media-pretty', $display['display_options']['path']); + $this->assertSame('tab', $display['display_options']['menu']['type']); + $this->assertSame('Media', $display['display_options']['menu']['title']); + } + + /** + * Tests that the update preserves custom 'media_library' menu settings. + */ + public function testUpdateWithCustomizedMediaLibraryMenuSettings() { + /** @var \Drupal\views\ViewEntityInterface $view */ + $view = View::load('media_library'); + $display = &$view->getDisplay('page'); + $display['display_options']['menu'] = [ + 'type' => 'normal', + 'title' => 'A treasure trove of interesting pictures', + 'parent' => 'system.admin_structure', + ]; + $view->save(); + + $this->runUpdates(); + + // The update should have changed the path but preserved the menu settings. + $view = View::load('media_library'); + $display = $view->getDisplay('page'); + $this->assertSame('admin/content/media-grid', $display['display_options']['path']); + $this->assertSame('normal', $display['display_options']['menu']['type']); + $this->assertSame('A treasure trove of interesting pictures', $display['display_options']['menu']['title']); + $this->assertSame('system.admin_structure', $display['display_options']['menu']['parent']); + } + +} diff --git a/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdate8704Test.php b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdate8704Test.php new file mode 100644 index 000000000..819f39cf1 --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdate8704Test.php @@ -0,0 +1,37 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../../../media/tests/fixtures/update/drupal-8.4.0-media_installed.php', + __DIR__ . '/../../../fixtures/update/drupal-8.7.2-media_library_installed.php', + ]; + } + + /** + * Tests that the update creates the media_library.settings config object. + */ + public function testUpdate() { + $this->assertNull($this->config('media_library.settings')->get('advanced_ui')); + $this->runUpdates(); + $this->assertTrue($this->config('media_library.settings')->get('advanced_ui')); + } + +} diff --git a/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateCheckboxClassesTest.php b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateCheckboxClassesTest.php new file mode 100644 index 000000000..6642e2960 --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateCheckboxClassesTest.php @@ -0,0 +1,83 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../../../media/tests/fixtures/update/drupal-8.4.0-media_installed.php', + __DIR__ . '/../../../fixtures/update/drupal-8.8.x-media_library-update-views-classnames-3049943.php', + ]; + } + + /** + * Tests that non js prefixes are added to checkboxes in the media view. + * + * @see media_library_post_update_update_8001_checkbox_classes() + */ + public function testAddNonPrefixedClasses() { + $view = Views::getView('media_library'); + + $display_items = [ + [ + 'display_id' => 'default', + 'option' => 'element_class', + 'field' => 'media_bulk_form', + ], + [ + 'display_id' => 'page', + 'option' => 'element_class', + 'field' => 'media_bulk_form', + ], + [ + 'display_id' => 'widget', + 'option' => 'element_wrapper_class', + 'field' => 'media_library_select_form', + ], + [ + 'display_id' => 'widget_table', + 'option' => 'element_wrapper_class', + 'field' => 'media_library_select_form', + ], + ]; + foreach ($display_items as $item) { + $display_id = $item['display_id']; + $option = $item['option']; + $field = $item['field']; + $display = $view->storage->getDisplay($display_id); + $classes_string = $display['display_options']['fields'][$field][$option]; + $classes = preg_split('/\s+/', $classes_string); + $this->assertContains('js-click-to-select-checkbox', $classes); + $this->assertNotContains('media-library-item__click-to-select-checkbox', $classes); + } + + $this->runUpdates(); + $view = Views::getView('media_library'); + + foreach ($display_items as $item) { + $display_id = $item['display_id']; + $option = $item['option']; + $field = $item['field']; + $display = $view->storage->getDisplay($display_id); + $classes_string = $display['display_options']['fields'][$field][$option]; + $classes = preg_split('/\s+/', $classes_string); + $this->assertContains('js-click-to-select-checkbox', $classes); + $this->assertContains('media-library-item__click-to-select-checkbox', $classes, "Class 'media-library-item__click-to-select-checkbox' not found in display: $display_id"); + } + } + +} diff --git a/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewLangcodeFiltersTest.php b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewLangcodeFiltersTest.php new file mode 100644 index 000000000..bec98b28f --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewLangcodeFiltersTest.php @@ -0,0 +1,82 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../../../media/tests/fixtures/update/drupal-8.4.0-media_installed.php', + __DIR__ . '/../../../fixtures/update/drupal-8.7.2-media_library_installed.php', + ]; + } + + /** + * Tests that the langcode filters are added to the media library view. + * + * @see media_library_post_update_add_langcode_filters() + */ + public function testMediaLibraryViewStatusExtraFilter() { + $config = $this->config('views.view.media_library'); + // We don't have any language filters yet for all displays. + $this->assertNull($config->get('display.default.display_options.filters.langcode')); + $this->assertNull($config->get('display.default.display_options.filters.default_langcode')); + $this->assertNull($config->get('display.widget.display_options.filters.langcode')); + $this->assertNull($config->get('display.widget.display_options.filters.default_langcode')); + $this->assertNull($config->get('display.widget_table.display_options.filters.langcode')); + $this->assertNull($config->get('display.widget_table.display_options.filters.default_langcode')); + // The rendering language should not be set for the displays. + $this->assertNull($config->get('display.default.display_options.rendering_language')); + $this->assertNull($config->get('display.widget.display_options.rendering_language')); + $this->assertNull($config->get('display.widget_table.display_options.rendering_language')); + + $this->runUpdates(); + + $config = $this->config('views.view.media_library'); + + // The update should add the langcode filter to the default display only. + $this->assertNull($config->get('display.widget.display_options.filters.langcode')); + $this->assertNull($config->get('display.widget_table.display_options.filters.langcode')); + $default_langcode_filter = $config->get('display.default.display_options.filters.langcode'); + $this->assertInternalType('array', $default_langcode_filter); + $this->assertSame('langcode', $default_langcode_filter['field']); + $this->assertSame('media', $default_langcode_filter['entity_type']); + $this->assertSame('language', $default_langcode_filter['plugin_id']); + $this->assertSame('langcode', $default_langcode_filter['id']); + $this->assertTrue($default_langcode_filter['exposed']); + + // The update should add the default_langcode filter to the widget displays + // only. + $this->assertNull($config->get('display.default.display_options.filters.default_langcode')); + foreach (['widget', 'widget_table'] as $display_id) { + $filter = $config->get('display.' . $display_id . '.display_options.filters.default_langcode'); + $this->assertInternalType('array', $filter); + $this->assertSame('default_langcode', $filter['field']); + $this->assertSame('media', $filter['entity_type']); + $this->assertSame('boolean', $filter['plugin_id']); + $this->assertSame('default_langcode', $filter['id']); + $this->assertFalse($filter['exposed']); + } + + // The default display should use the default rendering language, which is + // the language of the content. + $this->assertNull($config->get('display.default.display_options.rendering_language')); + // The rendering language of the row should be set to the interface + // language. + $this->assertSame('***LANGUAGE_language_interface***', $config->get('display.widget.display_options.rendering_language')); + $this->assertSame('***LANGUAGE_language_interface***', $config->get('display.widget_table.display_options.rendering_language')); + } + +} diff --git a/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewPageDisplayEditDeleteLinkTest.php b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewPageDisplayEditDeleteLinkTest.php new file mode 100644 index 000000000..38a30a8b6 --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewPageDisplayEditDeleteLinkTest.php @@ -0,0 +1,52 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../../../media/tests/fixtures/update/drupal-8.4.0-media_installed.php', + __DIR__ . '/../../../fixtures/update/drupal-8.7.2-media_library_installed.php', + ]; + } + + /** + * Tests that the media library view config is updated. + * + * @see media_library_update_8703() + */ + public function testMediaLibraryViewsConfig() { + $config = $this->config('views.view.media_library'); + $this->assertNull($config->get('display.page.display_options.defaults.fields')); + $this->assertNull($config->get('display.page.display_options.fields.name')); + $this->assertNull($config->get('display.page.display_options.fields.edit_media')); + $this->assertNull($config->get('display.page.display_options.fields.delete_media')); + + $this->runUpdates(); + + $config = $this->config('views.view.media_library'); + $this->assertFalse($config->get('display.page.display_options.defaults.fields')); + $this->assertSame('field', $config->get('display.page.display_options.fields.name.plugin_id')); + $this->assertSame('name', $config->get('display.page.display_options.fields.name.entity_field')); + $this->assertSame('entity_link_edit', $config->get('display.page.display_options.fields.edit_media.plugin_id')); + $this->assertSame('entity_link_delete', $config->get('display.page.display_options.fields.delete_media.plugin_id')); + // Check if the rendered entity is last in the field. + $fields = $config->get('display.page.display_options.fields'); + end($fields); + $this->assertSame('rendered_entity', key($fields)); + } + +} diff --git a/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewStatusExtraFilterTest.php b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewStatusExtraFilterTest.php new file mode 100644 index 000000000..29c42b8b9 --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdateViewStatusExtraFilterTest.php @@ -0,0 +1,47 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../../../media/tests/fixtures/update/drupal-8.4.0-media_installed.php', + __DIR__ . '/../../../fixtures/update/drupal-8.7.2-media_library_installed.php', + ]; + } + + /** + * Tests that the status extra filter is added to the media library view. + * + * @see media_library_post_update_add_status_extra_filter() + */ + public function testMediaLibraryViewStatusExtraFilter() { + $config = $this->config('views.view.media_library'); + $this->assertNull($config->get('display.default.display_options.filters.status_extra')); + + $this->runUpdates(); + + $config = $this->config('views.view.media_library'); + $filter = $config->get('display.default.display_options.filters.status_extra'); + $this->assertInternalType('array', $filter); + $this->assertSame('status_extra', $filter['field']); + $this->assertSame('media', $filter['entity_type']); + $this->assertSame('media_status', $filter['plugin_id']); + $this->assertSame('status_extra', $filter['id']); + $this->assertFalse($filter['exposed']); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php new file mode 100644 index 000000000..57a556fde --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php @@ -0,0 +1,330 @@ + 'test_format', + 'name' => 'Test format', + 'filters' => [ + 'media_embed' => ['status' => TRUE], + ], + ])->save(); + Editor::create([ + 'editor' => 'ckeditor', + 'format' => 'test_format', + 'settings' => [ + 'toolbar' => [ + 'rows' => [ + [ + [ + 'name' => 'Main', + 'items' => [ + 'Source', + 'Undo', + 'Redo', + ], + ], + ], + [ + [ + 'name' => 'Embeds', + 'items' => [ + 'DrupalMediaLibrary', + ], + ], + ], + ], + ], + ], + ])->save(); + + $this->drupalCreateContentType(['type' => 'blog']); + + // Note that media_install() grants 'view media' to all users by default. + $this->user = $this->drupalCreateUser([ + 'use text format test_format', + 'access media overview', + 'create blog content', + ]); + + // Create a media type that starts with the letter a, to test tab order. + $this->createMediaType('image', ['id' => 'arrakis', 'label' => 'Arrakis']); + + // Create a sample media entity to be embedded. + $this->createMediaType('image', ['id' => 'image', 'label' => 'Image']); + File::create([ + 'uri' => $this->getTestFiles('image')[0]->uri, + ])->save(); + $this->media = Media::create([ + 'bundle' => 'image', + 'name' => 'Fear is the mind-killer', + 'field_media_image' => [ + [ + 'target_id' => 1, + 'alt' => 'default alt', + 'title' => 'default title', + ], + ], + ]); + $this->media->save(); + + $arrakis_media = Media::create([ + 'bundle' => 'arrakis', + 'name' => 'Le baron Vladimir Harkonnen', + 'field_media_image' => [ + [ + 'target_id' => 1, + 'alt' => 'Il complote pour détruire le duc Leto', + 'title' => 'Il complote pour détruire le duc Leto', + ], + ], + ]); + $arrakis_media->save(); + + $this->drupalLogin($this->user); + } + + /** + * Tests validation that DrupalMediaLibrary requires media_embed filter. + */ + public function testConfigurationValidation() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + $admin_user = $this->drupalCreateUser([ + 'access administration pages', + 'administer site configuration', + 'administer filters', + ]); + $this->drupalLogin($admin_user); + $this->drupalGet('/admin/config/content/formats/manage/test_format'); + $page->uncheckField('filters[media_embed][status]'); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('The Embed media filter must be enabled to use the Insert from Media Library button.'); + $page->checkField('filters[media_embed][status]'); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('The text format Test format has been updated.'); + + // Now test adding a new format. + $this->drupalGet('/admin/config/content/formats/add'); + $page->fillField('name', 'Sulaco'); + // Wait for machine name to be filled in. + $this->assertNotEmpty($assert_session->waitForText('sulaco')); + $page->checkField('roles[authenticated]'); + $page->selectFieldOption('editor[editor]', 'ckeditor'); + + $targetSelector = 'ul.ckeditor-toolbar-group-buttons'; + $buttonSelector = 'li[data-drupal-ckeditor-button-name="DrupalMediaLibrary"]'; + $this->assertNotEmpty($target = $assert_session->waitForElementVisible('css', $targetSelector)); + $this->assertNotEmpty($button = $assert_session->elementExists('css', $buttonSelector)); + $this->sortableTo($buttonSelector, 'ul.ckeditor-available-buttons', $targetSelector); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('The Embed media filter must be enabled to use the Insert from Media Library button.'); + $page->checkField('filters[media_embed][status]'); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('Added text format Sulaco.'); + + // Test that when adding the DrupalMediaLibrary button to the editor the + // correct attributes are added to the tag in the Allowed + // HTML tags. + $this->drupalGet('/admin/config/content/formats/manage/sulaco'); + $page->checkField('filters[filter_html][status]'); + $expected = 'drupal-media data-entity-type data-entity-uuid data-view-mode data-align data-caption alt title'; + $allowed_html = $assert_session->fieldExists('filters[filter_html][settings][allowed_html]')->getValue(); + $this->assertContains($expected, $allowed_html); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('The text format Sulaco has been updated.'); + + // Test that the config form allows removing non-required attributes from + // the tag. + $this->drupalGet('/admin/config/content/formats/manage/sulaco'); + $allowed_html_field = $assert_session->fieldExists('filters[filter_html][settings][allowed_html]'); + $allowed_html = $allowed_html_field->getValue(); + $search = 'drupal-media data-entity-type data-entity-uuid data-view-mode data-align data-caption alt title'; + $replace = 'drupal-media data-entity-type data-entity-uuid'; + $allowed_html = str_replace($search, $replace, $allowed_html); + $page->clickLink('Limit allowed HTML tags and correct faulty HTML'); + $this->assertTrue($allowed_html_field->waitFor(10, function ($allowed_html_field) { + return $allowed_html_field->isVisible(); + })); + $allowed_html_field->setValue($allowed_html); + $page->pressButton('Save configuration'); + $assert_session->pageTextContains('The text format Sulaco has been updated.'); + } + + /** + * Tests using DrupalMediaLibrary button to embed media into CKEditor. + */ + public function testButton() { + $this->drupalGet('/node/add/blog'); + $this->waitForEditor(); + $this->pressEditorButton('drupalmedialibrary'); + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + $this->assertNotEmpty($assert_session->waitForId('drupal-modal')); + + // Test that the order is the order set in DrupalMediaLibrary::getConfig(). + $tabs = $page->findAll('css', '.media-library-menu__link'); + $expected_tab_order = [ + 'Show Image media (selected)', + 'Show Arrakis media', + ]; + foreach ($tabs as $key => $tab) { + $this->assertSame($expected_tab_order[$key], $tab->getText()); + } + + $assert_session->pageTextContains('0 of 1 item selected'); + $assert_session->elementExists('css', '.js-media-library-item')->click(); + $assert_session->pageTextContains('1 of 1 item selected'); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 2000)); + // @todo Inserting media embed should enable undo. + // @see https://www.drupal.org/project/drupal/issues/3073294 + $this->pressEditorButton('source'); + $value = $assert_session->elementExists('css', 'textarea.cke_source')->getValue(); + $dom = Html::load($value); + $xpath = new \DOMXPath($dom); + $drupal_media = $xpath->query('//drupal-media')[0]; + $expected_attributes = [ + 'data-entity-type' => 'media', + 'data-entity-uuid' => $this->media->uuid(), + 'data-align' => 'center', + ]; + foreach ($expected_attributes as $name => $expected) { + $this->assertSame($expected, $drupal_media->getAttribute($name)); + } + $this->pressEditorButton('source'); + // Why do we keep switching to the 'ckeditor' iframe? Because the buttons + // are in a separate iframe from the markup, so after calling + // ::pressEditorButton() (which switches to the button iframe), we'll need + // to switch back to the CKEditor iframe. + $this->assignNameToCkeditorIframe(); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 1000)); + $this->assertEditorButtonEnabled('undo'); + $this->pressEditorButton('undo'); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 1000)); + $this->assertEditorButtonDisabled('undo'); + $this->pressEditorButton('redo'); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 1000)); + $this->assertEditorButtonEnabled('undo'); + } + + /** + * Tests the allowed media types setting on the MediaEmbed filter. + */ + public function testAllowedMediaTypes() { + $test_cases = [ + 'all_media_types' => [], + 'only_image' => ['image' => 'image'], + 'only_arrakis' => ['arrakis' => 'arrakis'], + 'both_items_chedked' => [ + 'image' => 'image', + 'arrakis' => 'arrakis', + ], + ]; + + foreach ($test_cases as $allowed_media_types) { + // Update the filter format to set the allowed media types. + FilterFormat::load('test_format') + ->setFilterConfig('media_embed', [ + 'status' => TRUE, + 'settings' => [ + 'default_view_mode' => 'view_mode_1', + 'allowed_media_types' => $allowed_media_types, + 'allowed_view_modes' => [ + 'view_mode_1' => 'view_mode_1', + 'view_mode_2' => 'view_mode_2', + ], + ], + ])->save(); + + // Now test opening the media library from the CKEditor plugin, and + // verify the expected behavior. + $this->drupalGet('/node/add/blog'); + $this->waitForEditor(); + $this->pressEditorButton('drupalmedialibrary'); + $assert_session = $this->assertSession(); + $this->assertNotEmpty($assert_session->waitForId('media-library-wrapper')); + + if (empty($allowed_media_types) || count($allowed_media_types) === 2) { + $assert_session->elementExists('css', 'li.media-library-menu-image'); + $assert_session->elementExists('css', 'li.media-library-menu-arrakis'); + $assert_session->elementTextContains('css', '.media-library-item__name', 'Fear is the mind-killer'); + } + elseif (count($allowed_media_types) === 1 && !empty($allowed_media_types['image'])) { + // No tabs should appear if there's only one media type available. + $assert_session->elementNotExists('css', 'li.media-library-menu-image'); + $assert_session->elementNotExists('css', 'li.media-library-menu-arrakis'); + $assert_session->elementTextContains('css', '.media-library-item__name', 'Fear is the mind-killer'); + } + elseif (count($allowed_media_types) === 1 && !empty($allowed_media_types['arrakis'])) { + // No tabs should appear if there's only one media type available. + $assert_session->elementNotExists('css', 'li.media-library-menu-image'); + $assert_session->elementNotExists('css', 'li.media-library-menu-arrakis'); + $assert_session->elementTextContains('css', '.media-library-item__name', 'Le baron Vladimir Harkonnen'); + } + } + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/ContentModerationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/ContentModerationTest.php new file mode 100644 index 000000000..f78523518 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/ContentModerationTest.php @@ -0,0 +1,333 @@ +createMediaType('image', ['id' => 'image']); + $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + + // Create a media reference field on articles. + $this->createEntityReferenceField( + 'node', + 'article', + 'field_media', + 'Media', + 'media', + 'default', + ['target_bundles' => ['image']], + FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED + ); + // Add the media field to the form display. + $form_display = \Drupal::service('entity_display.repository')->getFormDisplay('node', 'article', 'default'); + $form_display->setComponent('field_media', [ + 'type' => 'media_library_widget', + ])->save(); + + // Configure the "Editorial" workflow to apply to image media. + $workflow = $this->createEditorialWorkflow(); + $workflow->getTypePlugin()->addEntityTypeAndBundle('media', 'image'); + $workflow->save(); + + $image = File::create([ + 'uri' => $this->getTestFiles('image')[0]->uri, + ]); + $image->setPermanent(); + $image->save(); + + // Create a draft, published and archived media item. + $draft_media = Media::create([ + 'name' => 'Hoglet', + 'bundle' => 'image', + 'field_media_image' => $image, + 'moderation_state' => 'draft', + ]); + $draft_media->save(); + $published_media = Media::create([ + 'name' => 'Panda', + 'bundle' => 'image', + 'field_media_image' => $image, + 'moderation_state' => 'published', + ]); + $published_media->save(); + $archived_media = Media::create([ + 'name' => 'Mammoth', + 'bundle' => 'image', + 'field_media_image' => $image, + 'moderation_state' => 'archived', + ]); + $archived_media->save(); + + // Create some users for our tests. We want to check with user 1, a media + // administrator with 'administer media' permissions, a user that has the + // 'view media' permissions, a user that can 'view media' and 'view own + // unpublished media', and a user that has 'view media' and 'view any + // unpublished content' permissions. + $this->userAdmin = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'access media overview', + 'edit own article content', + 'create article content', + 'administer media', + ]); + $this->userViewer = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'access media overview', + 'edit own article content', + 'create article content', + 'view media', + 'create media', + ]); + $this->userViewOwnUnpublished = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'access media overview', + 'edit own article content', + 'create article content', + 'view media', + 'view own unpublished media', + 'create media', + ]); + $this->userViewAnyUnpublished = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'access media overview', + 'edit own article content', + 'create article content', + 'view media', + 'create media', + 'view any unpublished content', + ]); + } + + /** + * Tests the media library widget only shows published media. + */ + public function testAdministrationPage() { + // User 1 should be able to see all media items. + $this->drupalLogin($this->rootUser); + $this->drupalGet('admin/content/media'); + $this->assertAllMedia(); + + // The media admin user should be able to see all media items. + $this->drupalLogin($this->userAdmin); + $this->drupalGet('admin/content/media'); + $this->assertAllMedia(); + + // The media viewer user should be able to see only published media items. + $this->drupalLogin($this->userViewer); + $this->drupalGet('admin/content/media'); + $this->assertOnlyPublishedMedia(); + + // The media viewer user that can also view its own unpublished media should + // also be able to see only published media items since it is not the owner + // of the created media items. + $this->drupalLogin($this->userViewOwnUnpublished); + $this->drupalGet('admin/content/media'); + $this->assertOnlyPublishedMedia(); + + // When content moderation is enabled, a media viewer that can view any + // unpublished content should be able to see all media. + // @see content_moderation_entity_access() + $this->drupalLogin($this->userViewAnyUnpublished); + $this->drupalGet('admin/content/media'); + $this->assertAllMedia(); + + // Assign all media to the user with the 'view own unpublished media' + // permission. + foreach (Media::loadMultiple() as $media) { + $media->setOwner($this->userViewOwnUnpublished); + $media->save(); + } + + // User 1 should still be able to see all media items. + $this->drupalLogin($this->rootUser); + $this->drupalGet('admin/content/media'); + $this->assertAllMedia(); + + // The media admin user should still be able to see all media items. + $this->drupalLogin($this->userAdmin); + $this->drupalGet('admin/content/media'); + $this->assertAllMedia(); + + // The media viewer user should still be able to see only published media + // items. + $this->drupalLogin($this->userViewer); + $this->drupalGet('admin/content/media'); + $this->assertOnlyPublishedMedia(); + + // The media viewer user that can also view its own unpublished media + // should now be able to see all media items since it is the owner of the + // created media items. + $this->drupalLogin($this->userViewOwnUnpublished); + $this->drupalGet('admin/content/media'); + $this->assertAllMedia(); + + // The media viewer that can view any unpublished content should still be + // able to see all media. + $this->drupalLogin($this->userViewAnyUnpublished); + $this->drupalGet('admin/content/media'); + $this->assertAllMedia(); + } + + /** + * Tests the media library widget only shows published media. + */ + public function testWidget() { + $assert_session = $this->assertSession(); + + // All users should only be able to see published media items. + $this->drupalLogin($this->rootUser); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userAdmin); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userViewer); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userViewOwnUnpublished); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userViewAnyUnpublished); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + + // After we change the owner to the user with 'view own unpublished media' + // permission, all users should still only be able to see published media. + foreach (Media::loadMultiple() as $media) { + $media->setOwner($this->userViewOwnUnpublished); + $media->save(); + } + + $this->drupalLogin($this->rootUser); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userAdmin); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userViewer); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userViewOwnUnpublished); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + $this->drupalLogin($this->userViewAnyUnpublished); + $this->drupalGet('node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->assertWaitOnAjaxRequest(); + $this->assertOnlyPublishedMedia(); + } + + /** + * Asserts all media items are visible. + */ + protected function assertAllMedia() { + $assert_session = $this->assertSession(); + $assert_session->pageTextContains('Hoglet'); + $assert_session->pageTextContains('Panda'); + $assert_session->pageTextContains('Mammoth'); + } + + /** + * Asserts only published media items are visible. + */ + protected function assertOnlyPublishedMedia() { + $assert_session = $this->assertSession(); + $assert_session->pageTextNotContains('Hoglet'); + $assert_session->pageTextContains('Panda'); + $assert_session->pageTextNotContains('Mammoth'); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/EmbeddedFormWidgetTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/EmbeddedFormWidgetTest.php new file mode 100644 index 000000000..eac555c41 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/EmbeddedFormWidgetTest.php @@ -0,0 +1,162 @@ +container->get('entity_display.repository'); + + FieldStorageConfig::create([ + 'field_name' => 'media_image_field', + 'entity_type' => 'node', + 'type' => 'entity_reference', + 'settings' => [ + 'target_type' => 'media', + 'required' => TRUE, + ], + ])->save(); + + FieldConfig::create([ + 'label' => 'A Media Image Field', + 'field_name' => 'media_image_field', + 'entity_type' => 'node', + 'bundle' => 'basic_page', + 'field_type' => 'entity_reference', + 'required' => TRUE, + 'settings' => [ + 'handler_settings' => [ + 'target_bundles' => [ + 'type_three' => 'type_three', + ], + ], + ], + ])->save(); + + $display_repository->getFormDisplay('node', 'basic_page') + ->setComponent('media_image_field', [ + 'type' => 'media_library_widget', + 'region' => 'content', + 'settings' => [ + 'media_types' => ['type_three'], + ], + ]) + ->save(); + + $this->config('media_library.settings') + ->set('advanced_ui', TRUE) + ->save(); + + $user = $this->drupalCreateUser([ + 'access content', + 'access media overview', + 'edit own basic_page content', + 'create basic_page content', + 'create media', + 'view media', + ]); + $this->drupalLogin($user); + } + + /** + * Test media inside another widget that validates too enthusiastically. + * + * @dataProvider insertionReselectionProvider + */ + public function testInsertionAndReselection($widget) { + $this->container + ->get('entity_display.repository') + ->getFormDisplay('node', 'basic_page') + ->setComponent('media_image_field', [ + 'type' => $widget, + 'region' => 'content', + 'settings' => [ + 'media_types' => ['type_three'], + ], + ]) + ->save(); + + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + foreach ($this->getTestFiles('image') as $image) { + $extension = pathinfo($image->filename, PATHINFO_EXTENSION); + if ($extension === 'jpg') { + $jpg_image = $image; + break; + } + } + + $this->drupalGet('node/add/basic_page'); + $wrapper = $assert_session->elementExists('css', '#media_image_field-media-library-wrapper'); + $wrapper->pressButton('Add media'); + $this->assertNotNull($assert_session->waitForText('Add or select media')); + $page->attachFileToField('Add file', $this->container->get('file_system')->realpath($jpg_image->uri)); + $this->assertNotNull($assert_session->waitForText('Alternative text')); + $page->fillField('Alternative text', $this->randomString()); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and insert'); + $first_item_locator = "(//div[@data-drupal-selector='edit-media-image-field-selection-0'])[1]"; + $this->assertNotNull($first_item = $assert_session->waitForElementVisible('xpath', $first_item_locator)); + $first_item->pressButton('Remove'); + $assert_session->waitForElementRemoved('xpath', $first_item_locator); + $page->waitFor(10, function () use ($wrapper) { + return $wrapper->hasButton('Add media'); + }); + // Test reinserting the same selection. + $wrapper->pressButton('Add media'); + $this->assertNotNull($assert_session->waitForText('Add or select media')); + $assert_session->elementExists('xpath', "(//div[contains(@class, 'media-library-item')])[1]")->click(); + $assert_session->checkboxChecked('media_library_select_form[0]'); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assertNotNull($assert_session->waitForElementVisible('xpath', $first_item_locator)); + } + + /** + * Data provider for ::testInsertionAndReselection(). + * + * @return array + * Test data. + */ + public function insertionReselectionProvider() { + return [ + 'using media_library_widget' => [ + 'widget' => 'media_library_widget', + ], + 'using media_library_inception_widget' => [ + 'widget' => 'media_library_inception_widget', + ], + ]; + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php new file mode 100644 index 000000000..4d8ee6060 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php @@ -0,0 +1,406 @@ +createMediaItems([ + 'type_one' => [ + 'Horse', + 'Bear', + 'Cat', + 'Dog', + ], + 'type_two' => [ + 'Crocodile', + 'Lizard', + 'Snake', + 'Turtle', + ], + ]); + + // Create a user who can use the Media library. + $user = $this->drupalCreateUser([ + 'access content', + 'create basic_page content', + 'edit own basic_page content', + 'view media', + 'create media', + 'administer node form display', + ]); + $this->drupalLogin($user); + } + + /** + * Tests that the Media library's widget works as expected. + */ + public function testWidget() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + // Assert that media widget instances are present. + $assert_session->pageTextContains('Unlimited media'); + $assert_session->pageTextContains('Twin media'); + $assert_session->pageTextContains('Single media type'); + $assert_session->pageTextContains('Empty types media'); + + // Assert generic media library elements. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert that the media type menu is available when more than 1 type is + // configured for the field. + $menu = $this->openMediaLibraryForField('field_unlimited_media'); + $this->assertTrue($menu->hasLink('Show Type One media (selected)')); + $this->assertFalse($menu->hasLink('Type Two')); + $this->assertTrue($menu->hasLink('Type Three')); + $this->assertFalse($menu->hasLink('Type Four')); + $this->switchToMediaType('Three'); + // Assert the active tab is set correctly. + $this->assertFalse($menu->hasLink('Show Type One media (selected)')); + $this->assertTrue($menu->hasLink('Show Type Three media (selected)')); + // Assert the focus is set to the first tabbable element when a vertical tab + // is clicked. + $this->assertJsCondition('jQuery("#media-library-content :tabbable:first").is(":focus")'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert that there are no links in the media library view. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->elementNotExists('css', '.media-library-item__name a'); + $assert_session->elementNotExists('css', '.view-media-library .media-library-item__edit'); + $assert_session->elementNotExists('css', '.view-media-library .media-library-item__remove'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert that the media type menu is available when the target_bundles + // setting for the entity reference field is null. All types should be + // allowed in this case. + $menu = $this->openMediaLibraryForField('field_null_types_media'); + + // Assert that the button to open the media library does not submit the + // parent form. We can do this by checking if the validation of the parent + // form is not triggered. + $assert_session->pageTextNotContains('Title field is required.'); + + $this->assertTrue($menu->hasLink('Type One')); + $this->assertTrue($menu->hasLink('Type Two')); + $this->assertTrue($menu->hasLink('Type Three')); + $this->assertTrue($menu->hasLink('Type Four')); + $this->assertTrue($menu->hasLink('Type Five')); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert that the media type menu is not available when only 1 type is + // configured for the field. + $this->openMediaLibraryForField('field_single_media_type', '#media-library-wrapper'); + $this->waitForElementTextContains('.media-library-selected-count', '0 of 1 item selected'); + + // Select a media item, assert the hidden selection field contains the ID of + // the selected item. + $this->selectMediaItem(0); + $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4'); + $this->assertSelectedMediaCount('1 of 1 item selected'); + $assert_session->elementNotExists('css', '.js-media-library-menu'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert the menu links can be sorted through the widget configuration. + $this->openMediaLibraryForField('field_twin_media'); + $links = $this->getTypesMenu()->findAll('css', 'a'); + $link_titles = []; + foreach ($links as $link) { + $link_titles[] = $link->getText(); + } + $expected_link_titles = ['Show Type Three media (selected)', 'Show Type One media', 'Show Type Two media', 'Show Type Four media']; + $this->assertSame($link_titles, $expected_link_titles); + $this->drupalGet('admin/structure/types/manage/basic_page/form-display'); + $assert_session->buttonExists('field_twin_media_settings_edit')->press(); + $this->assertElementExistsAfterWait('css', '#field-twin-media .tabledrag-toggle-weight')->press(); + $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_one][weight]')->selectOption(0); + $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_three][weight]')->selectOption(1); + $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_four][weight]')->selectOption(2); + $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_two][weight]')->selectOption(3); + $assert_session->buttonExists('Save')->press(); + + $this->drupalGet('node/add/basic_page'); + $this->openMediaLibraryForField('field_twin_media'); + $link_titles = array_map(function ($link) { + return $link->getText(); + }, $links); + $this->assertSame($link_titles, ['Show Type One media (selected)', 'Show Type Three media', 'Show Type Four media', 'Show Type Two media']); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert the announcements for media type navigation in the media library. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + $this->assertNotEmpty($assert_session->waitForText('Showing Type Three media.')); + $this->switchToMediaType('One'); + $this->assertNotEmpty($assert_session->waitForText('Showing Type One media.')); + // Assert the links can be triggered by via the spacebar. + $assert_session->elementExists('named', ['link', 'Type Three'])->keyPress(32); + $this->waitForText('Showing Type Three media.'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert media is only visible on the tab for the related media type. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextContains('Bear'); + $assert_session->pageTextNotContains('Turtle'); + $this->switchToMediaType('Three'); + $this->assertNotEmpty($assert_session->waitForText('Showing Type Three media.')); + $assert_session->elementExists('named', ['link', 'Show Type Three media (selected)']); + $assert_session->pageTextNotContains('Dog'); + $assert_session->pageTextNotContains('Bear'); + $assert_session->pageTextNotContains('Turtle'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert the exposed name filter of the view. + $this->openMediaLibraryForField('field_unlimited_media'); + $session = $this->getSession(); + $session->getPage()->fillField('Name', 'Dog'); + $session->getPage()->pressButton('Apply filters'); + $this->waitForText('Dog'); + $this->waitForNoText('Bear'); + $session->getPage()->fillField('Name', ''); + $session->getPage()->pressButton('Apply filters'); + $this->waitForText('Dog'); + $this->waitForText('Bear'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert adding a single media item and removing it. + $this->openMediaLibraryForField('field_twin_media'); + $this->selectMediaItem(0); + $this->pressInsertSelected('Added one media item.'); + // Assert the focus is set back on the open button of the media field. + $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":focus")'); + + // Assert that we can toggle the visibility of the weight inputs. + $wrapper = $assert_session->elementExists('css', '.field--name-field-twin-media'); + $wrapper->pressButton('Show media item weights'); + $assert_session->fieldExists('Weight', $wrapper)->click(); + $wrapper->pressButton('Hide media item weights'); + + // Remove the selected item. + $button = $assert_session->buttonExists('Remove', $wrapper); + $this->assertSame('Remove Dog', $button->getAttribute('aria-label')); + $button->press(); + $this->waitForText('Dog has been removed.'); + // Assert the focus is set back on the open button of the media field. + $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":focus")'); + + // Assert we can select the same media item twice. + $this->openMediaLibraryForField('field_twin_media'); + $page->checkField('Select Dog'); + $this->pressInsertSelected('Added one media item.'); + $this->openMediaLibraryForField('field_twin_media'); + $page->checkField('Select Dog'); + $this->pressInsertSelected('Added one media item.'); + + // Assert the same has been added twice and remove the items again. + $this->waitForElementsCount('css', '.field--name-field-twin-media [data-media-library-item-delta]', 2); + $assert_session->hiddenFieldValueEquals('field_twin_media[selection][0][target_id]', 4); + $assert_session->hiddenFieldValueEquals('field_twin_media[selection][1][target_id]', 4); + $wrapper->pressButton('Remove'); + $this->waitForText('Dog has been removed.'); + $wrapper->pressButton('Remove'); + $this->waitForText('Dog has been removed.'); + $result = $wrapper->waitFor(10, function ($wrapper) { + /** @var \Behat\Mink\Element\NodeElement $wrapper */ + return $wrapper->findButton('Remove') == NULL; + }); + $this->assertTrue($result); + + // Assert the selection is persistent in the media library modal, and + // the number of selected items is displayed correctly. + $this->openMediaLibraryForField('field_twin_media'); + // Assert the number of selected items is displayed correctly. + $this->assertSelectedMediaCount('0 of 2 items selected'); + // Select a media item, assert the hidden selection field contains the ID of + // the selected item. + $checkboxes = $this->getCheckboxes(); + $this->assertCount(4, $checkboxes); + $this->selectMediaItem(0, '1 of 2 items selected'); + $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4'); + // Select another item and assert the number of selected items is updated. + $this->selectMediaItem(1, '2 of 2 items selected'); + $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4,3'); + // Assert unselected items are disabled when the maximum allowed items are + // selected (cardinality for this field is 2). + $this->assertTrue($checkboxes[2]->hasAttribute('disabled')); + $this->assertTrue($checkboxes[3]->hasAttribute('disabled')); + // Assert the selected items are updated when deselecting an item. + $checkboxes[0]->click(); + $this->assertSelectedMediaCount('1 of 2 items selected'); + $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '3'); + // Assert deselected items are available again. + $this->assertFalse($checkboxes[2]->hasAttribute('disabled')); + $this->assertFalse($checkboxes[3]->hasAttribute('disabled')); + // The selection should be persisted when navigating to other media types in + // the modal. + $this->switchToMediaType('Three'); + $this->switchToMediaType('One'); + $selected_checkboxes = []; + foreach ($this->getCheckboxes() as $checkbox) { + if ($checkbox->isChecked()) { + $selected_checkboxes[] = $checkbox->getValue(); + } + } + $this->assertCount(1, $selected_checkboxes); + $assert_session->hiddenFieldValueEquals('media-library-modal-selection', implode(',', $selected_checkboxes)); + $this->assertSelectedMediaCount('1 of 2 items selected'); + // Add to selection from another type. + $this->switchToMediaType('Two'); + $checkboxes = $this->getCheckboxes(); + $this->assertCount(4, $checkboxes); + $this->selectMediaItem(0, '2 of 2 items selected'); + $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '3,8'); + // Assert unselected items are disabled when the maximum allowed items are + // selected (cardinality for this field is 2). + $this->assertFalse($checkboxes[0]->hasAttribute('disabled')); + $this->assertTrue($checkboxes[1]->hasAttribute('disabled')); + $this->assertTrue($checkboxes[2]->hasAttribute('disabled')); + $this->assertTrue($checkboxes[3]->hasAttribute('disabled')); + // Assert the checkboxes are also disabled on other pages. + $this->switchToMediaType('One'); + $this->assertTrue($checkboxes[0]->hasAttribute('disabled')); + $this->assertFalse($checkboxes[1]->hasAttribute('disabled')); + $this->assertTrue($checkboxes[2]->hasAttribute('disabled')); + $this->assertTrue($checkboxes[3]->hasAttribute('disabled')); + // Select the items. + $this->pressInsertSelected('Added 2 media items.'); + // Assert the open button is disabled. + $open_button = $this->assertElementExistsAfterWait('css', '.js-media-library-open-button[name^="field_twin_media"]'); + $this->assertTrue($open_button->hasAttribute('data-disabled-focus')); + $this->assertTrue($open_button->hasAttribute('disabled')); + $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":disabled")'); + + // Ensure that the selection completed successfully. + $assert_session->pageTextNotContains('Add or select media'); + $assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Dog'); + $assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Cat'); + $assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Turtle'); + $assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Snake'); + + // Remove "Cat" (happens to be the first remove button on the page). + $button = $assert_session->buttonExists('Remove', $wrapper); + $this->assertSame('Remove Cat', $button->getAttribute('aria-label')); + $button->press(); + $this->waitForText('Cat has been removed.'); + // Assert the focus is set to the wrapper of the other selected item. + $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper [data-media-library-item-delta]").is(":focus")'); + $assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Cat'); + $assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Turtle'); + // Assert the open button is no longer disabled. + $open_button = $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]'); + $this->assertFalse($open_button->hasAttribute('data-disabled-focus')); + $this->assertFalse($open_button->hasAttribute('disabled')); + $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":not(:disabled)")'); + + // Open the media library again and select another item. + $this->openMediaLibraryForField('field_twin_media'); + $this->selectMediaItem(0); + $this->pressInsertSelected('Added one media item.'); + $this->waitForElementTextContains('#field_twin_media-media-library-wrapper', 'Dog'); + $assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Cat'); + $assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Turtle'); + $assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Snake'); + // Assert the open button is disabled. + $this->assertTrue($assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]')->hasAttribute('data-disabled-focus')); + $this->assertTrue($assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]')->hasAttribute('disabled')); + $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":disabled")'); + + // Assert the selection is cleared when the modal is closed. + $this->openMediaLibraryForField('field_unlimited_media'); + $checkboxes = $this->getCheckboxes(); + $this->assertGreaterThanOrEqual(4, count($checkboxes)); + // Nothing is selected yet. + $this->assertFalse($checkboxes[0]->isChecked()); + $this->assertFalse($checkboxes[1]->isChecked()); + $this->assertFalse($checkboxes[2]->isChecked()); + $this->assertFalse($checkboxes[3]->isChecked()); + $this->assertSelectedMediaCount('0 items selected'); + // Select the first 2 items. + $checkboxes[0]->click(); + $this->assertSelectedMediaCount('1 item selected'); + $checkboxes[1]->click(); + $this->assertSelectedMediaCount('2 items selected'); + $this->assertTrue($checkboxes[0]->isChecked()); + $this->assertTrue($checkboxes[1]->isChecked()); + $this->assertFalse($checkboxes[2]->isChecked()); + $this->assertFalse($checkboxes[3]->isChecked()); + // Close the dialog, reopen it and assert not is selected again. + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + $this->openMediaLibraryForField('field_unlimited_media'); + $checkboxes = $this->getCheckboxes(); + $this->assertGreaterThanOrEqual(4, count($checkboxes)); + $this->assertFalse($checkboxes[0]->isChecked()); + $this->assertFalse($checkboxes[1]->isChecked()); + $this->assertFalse($checkboxes[2]->isChecked()); + $this->assertFalse($checkboxes[3]->isChecked()); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Finally, save the form. + $assert_session->elementExists('css', '.js-media-library-widget-toggle-weight')->click(); + $this->submitForm([ + 'title[0][value]' => 'My page', + 'field_twin_media[selection][0][weight]' => '3', + ], 'Save'); + $assert_session->pageTextContains('Basic Page My page has been created'); + // We removed this item earlier. + $assert_session->pageTextNotContains('Cat'); + // This item was never selected. + $assert_session->pageTextNotContains('Snake'); + // "Turtle" should come after "Dog", since we changed the weight. + $assert_session->elementExists('css', '.field--name-field-twin-media > .field__items > .field__item:last-child:contains("Turtle")'); + // Make sure everything that was selected shows up. + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextContains('Turtle'); + + // Re-edit the content and make a new selection. + $this->drupalGet('node/1/edit'); + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextNotContains('Cat'); + $assert_session->pageTextNotContains('Bear'); + $assert_session->pageTextNotContains('Horse'); + $assert_session->pageTextContains('Turtle'); + $assert_session->pageTextNotContains('Snake'); + $this->openMediaLibraryForField('field_unlimited_media'); + // Select all media items of type one (should also contain Dog, again). + $this->selectMediaItem(0); + $this->selectMediaItem(1); + $this->selectMediaItem(2); + $this->selectMediaItem(3); + $this->pressInsertSelected('Added 4 media items.'); + $this->waitForText('Dog'); + $assert_session->pageTextContains('Cat'); + $assert_session->pageTextContains('Bear'); + $assert_session->pageTextContains('Horse'); + $assert_session->pageTextContains('Turtle'); + $assert_session->pageTextNotContains('Snake'); + $this->submitForm([], 'Save'); + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextContains('Cat'); + $assert_session->pageTextContains('Bear'); + $assert_session->pageTextContains('Horse'); + $assert_session->pageTextContains('Turtle'); + $assert_session->pageTextNotContains('Snake'); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php new file mode 100644 index 000000000..9baa5b79a --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php @@ -0,0 +1,64 @@ +drupalCreateUser([ + 'access administration pages', + 'administer node fields', + 'administer node form display', + ]); + $this->drupalLogin($user); + $this->drupalCreateContentType(['type' => 'article']); + } + + /** + * Tests field UI integration for media library widget. + */ + public function testFieldUiIntegration() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + $user = $this->drupalCreateUser([ + 'access administration pages', + 'administer node fields', + 'administer node form display', + ]); + $this->drupalLogin($user); + + $this->drupalGet('/admin/structure/types/manage/article/fields/add-field'); + $page->selectFieldOption('new_storage_type', 'field_ui:entity_reference:media'); + $this->assertNotNull($assert_session->waitForField('label')); + $page->fillField('label', 'Shatner'); + $this->waitForText('field_shatner'); + $page->pressButton('Save and continue'); + $page->pressButton('Save field settings'); + $assert_session->pageTextNotContains('Undefined index: target_bundles'); + $this->waitForFieldExists('Type One')->check(); + $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_one]"][checked="checked"]'); + $page->checkField('settings[handler_settings][target_bundles][type_two]'); + $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_two]"][checked="checked"]'); + $page->checkField('settings[handler_settings][target_bundles][type_three]'); + $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_three]"][checked="checked"]'); + $page->pressButton('Save settings'); + $assert_session->pageTextContains('Saved Shatner configuration.'); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php deleted file mode 100644 index 6f79ed965..000000000 --- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php +++ /dev/null @@ -1,1369 +0,0 @@ -lockHttpClientToFixtures(); - - // Create a few example media items for use in selection. - $media = [ - 'type_one' => [ - 'Horse', - 'Bear', - 'Cat', - 'Dog', - ], - 'type_two' => [ - 'Crocodile', - 'Lizard', - 'Snake', - 'Turtle', - ], - ]; - - $time = time(); - foreach ($media as $type => $names) { - foreach ($names as $name) { - $entity = Media::create(['name' => $name, 'bundle' => $type]); - $source_field = $type === 'type_one' ? 'field_media_test' : 'field_media_test_1'; - $entity->setCreatedTime(++$time); - $entity->set($source_field, $name); - $entity->save(); - } - } - - // Create a user who can use the Media library. - $user = $this->drupalCreateUser([ - 'access administration pages', - 'access content', - 'access media overview', - 'edit own basic_page content', - 'create basic_page content', - 'create media', - 'delete any media', - 'view media', - 'administer node form display', - ]); - $this->drupalLogin($user); - $this->drupalPlaceBlock('local_tasks_block'); - $this->drupalPlaceBlock('local_actions_block'); - } - - /** - * Tests that the Media library's administration page works as expected. - */ - public function testAdministrationPage() { - $session = $this->getSession(); - $page = $session->getPage(); - $assert_session = $this->assertSession(); - - // Visit the administration page. - $this->drupalGet('admin/content/media'); - - // Verify that the "Add media" link is present. - $assert_session->linkExists('Add media'); - - // Verify that media from two separate types is present. - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextContains('Turtle'); - - // Test that users can filter by type. - $page->selectFieldOption('Media type', 'Type One'); - $page->pressButton('Apply filters'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Turtle'); - $page->selectFieldOption('Media type', 'Type Two'); - $page->pressButton('Apply filters'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Dog'); - $assert_session->pageTextContains('Turtle'); - - // Test that selecting elements as a part of bulk operations works. - $page->selectFieldOption('Media type', '- Any -'); - $page->pressButton('Apply filters'); - $assert_session->assertWaitOnAjaxRequest(); - // This tests that anchor tags clicked inside the preview are suppressed. - $this->getSession()->executeScript('jQuery(".js-click-to-select-trigger a")[4].click()'); - $this->submitForm([], 'Apply to selected items'); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Cat'); - $this->submitForm([], 'Delete'); - $assert_session->pageTextNotContains('Dog'); - $assert_session->pageTextContains('Cat'); - - // Test 'Select all media'. - $this->getSession()->getPage()->checkField('Select all media'); - $this->getSession()->getPage()->selectFieldOption('Action', 'media_delete_action'); - $this->submitForm([], 'Apply to selected items'); - $this->getSession()->getPage()->pressButton('Delete'); - - $assert_session->pageTextNotContains('Cat'); - $assert_session->pageTextNotContains('Turtle'); - $assert_session->pageTextNotContains('Snake'); - - // Test empty text. - $assert_session->pageTextContains('No media available.'); - - // Verify that the "Table" link is present, click it and check address. - $assert_session->linkExists('Table'); - $page->clickLink('Table'); - $assert_session->addressEquals('admin/content/media-table'); - // Verify that the "Add media" link is present. - $assert_session->linkExists('Add media'); - } - - /** - * Tests that the widget works as expected when media types are deleted. - */ - public function testWidgetWithoutMediaTypes() { - $assert_session = $this->assertSession(); - - $user = $this->drupalCreateUser([ - 'access administration pages', - 'access content', - 'create basic_page content', - 'create media', - 'view media', - ]); - $this->drupalLogin($user); - - $default_message = 'There are no allowed media types configured for this field. Please contact the site administrator.'; - - $this->drupalGet('node/add/basic_page'); - - // Assert a properly configured field does not show a message. - $assert_session->elementTextNotContains('css', '.field--name-field-twin-media', 'There are no allowed media types configured for this field.'); - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]'); - // Assert that the message is shown when the target_bundles setting for the - // entity reference field is an empty array. No types are allowed in this - // case. - $assert_session->elementTextContains('css', '.field--name-field-empty-types-media', $default_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_empty_types_media"]'); - // Assert that the message is not shown when the target_bundles setting for - // the entity reference field is null. All types are allowed in this case. - $assert_session->elementTextNotContains('css', '.field--name-field-null-types-media', 'There are no allowed media types configured for this field.'); - $assert_session->elementExists('css', '.media-library-open-button[name^="field_null_types_media"]'); - - // Delete all media and media types. - $entity_type_manager = \Drupal::entityTypeManager(); - $media_storage = $entity_type_manager->getStorage('media'); - $media_type_storage = $entity_type_manager->getStorage('media_type'); - $media_storage->delete($media_storage->loadMultiple()); - $media_type_storage->delete($media_type_storage->loadMultiple()); - - // Visit a node create page. - $this->drupalGet('node/add/basic_page'); - - // Assert a properly configured field now shows a message. - $assert_session->elementTextContains('css', '.field--name-field-twin-media', $default_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_twin_media"]'); - // Assert that the message is shown when the target_bundles setting for the - // entity reference field is an empty array. - $assert_session->elementTextContains('css', '.field--name-field-empty-types-media', $default_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_empty_types_media"]'); - // Assert that the message is shown when the target_bundles setting for - // the entity reference field is null. - $assert_session->elementTextContains('css', '.field--name-field-null-types-media', $default_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_null_types_media"]'); - - // Assert a different message is shown when the user is allowed to - // administer the fields. - $user = $this->drupalCreateUser([ - 'access administration pages', - 'access content', - 'create basic_page content', - 'view media', - 'administer node fields', - ]); - $this->drupalLogin($user); - - $route_bundle_params = FieldUI::getRouteBundleParameter(\Drupal::entityTypeManager()->getDefinition('node'), 'basic_page'); - - $field_twin_url = new Url('entity.field_config.node_field_edit_form', [ - 'field_config' => 'node.basic_page.field_twin_media', - ] + $route_bundle_params); - $field_twin_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; - - $field_empty_types_url = new Url('entity.field_config.node_field_edit_form', [ - 'field_config' => 'node.basic_page.field_empty_types_media', - ] + $route_bundle_params); - $field_empty_types_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; - - $field_null_types_url = new Url('entity.field_config.node_field_edit_form', [ - 'field_config' => 'node.basic_page.field_null_types_media', - ] + $route_bundle_params); - $field_null_types_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; - - // Visit a node create page. - $this->drupalGet('node/add/basic_page'); - - // Assert a properly configured field still shows a message. - $assert_session->elementContains('css', '.field--name-field-twin-media', $field_twin_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_twin_media"]'); - // Assert that the message is shown when the target_bundles setting for the - // entity reference field is an empty array. - $assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_empty_types_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_empty_types_media"]'); - // Assert that the message is shown when the target_bundles setting for the - // entity reference field is null. - $assert_session->elementContains('css', '.field--name-field-null-types-media', $field_null_types_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_null_types_media"]'); - - // Assert the messages are also shown in the default value section of the - // field edit form. - $this->drupalGet($field_empty_types_url); - $assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_empty_types_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_empty_types_media"]'); - $this->drupalGet($field_null_types_url); - $assert_session->elementContains('css', '.field--name-field-null-types-media', $field_null_types_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_null_types_media"]'); - - // Uninstall the Field UI and check if the link is removed from the message. - \Drupal::service('module_installer')->uninstall(['field_ui']); - - // Visit a node create page. - $this->drupalGet('node/add/basic_page'); - - $field_ui_uninstalled_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; - - // Assert the link is now longer part of the message. - $assert_session->elementNotExists('named', ['link', 'Edit the field settings']); - // Assert a properly configured field still shows a message. - $assert_session->elementContains('css', '.field--name-field-twin-media', $field_ui_uninstalled_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_twin_media"]'); - // Assert that the message is shown when the target_bundles setting for the - // entity reference field is an empty array. - $assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_ui_uninstalled_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_empty_types_media"]'); - // Assert that the message is shown when the target_bundles setting for the - // entity reference field is null. - $assert_session->elementContains('css', '.field--name-field-null-types-media', $field_ui_uninstalled_message); - $assert_session->elementNotExists('css', '.media-library-open-button[name^="field_null_types_media"]'); - } - - /** - * Tests that the widget access works as expected. - */ - public function testWidgetAccess() { - $assert_session = $this->assertSession(); - - $this->drupalLogout(); - - $role = Role::load(RoleInterface::ANONYMOUS_ID); - $role->revokePermission('view media'); - $role->save(); - - // Create a working state. - $allowed_types = ['type_one', 'type_two', 'type_three', 'type_four']; - $state = MediaLibraryState::create('test', $allowed_types, 'type_three', 2); - $url_options = ['query' => $state->all()]; - - // Verify that unprivileged users can't access the widget view. - $this->drupalGet('admin/content/media-widget', $url_options); - $assert_session->responseContains('Access denied'); - $this->drupalGet('media-library', $url_options); - $assert_session->responseContains('Access denied'); - - // Allow users with 'view media' permission to access the media library view - // and controller. - $this->grantPermissions($role, [ - 'view media', - ]); - $this->drupalGet('admin/content/media-widget', $url_options); - $assert_session->elementExists('css', '.view-media-library'); - $this->drupalGet('media-library', $url_options); - $assert_session->elementExists('css', '.view-media-library'); - // Assert the user does not have access to the media add form if the user - // does not have the 'create media' permission. - $assert_session->fieldNotExists('files[upload][]'); - - // Assert users with the 'create media' permission can access the media add - // form. - $this->grantPermissions($role, [ - 'create media', - ]); - $this->drupalGet('media-library', $url_options); - $assert_session->elementExists('css', '.view-media-library'); - $assert_session->fieldExists('Add files'); - - // Assert the media library can not be accessed if the required state - // parameters are changed without changing the hash. - $this->drupalGet('media-library', [ - 'query' => array_merge($url_options['query'], ['media_library_opener_id' => 'fail']), - ]); - $assert_session->responseContains('Access denied'); - $this->drupalGet('media-library', [ - 'query' => array_merge($url_options['query'], ['media_library_allowed_types' => ['type_one', 'type_two']]), - ]); - $assert_session->responseContains('Access denied'); - $this->drupalGet('media-library', [ - 'query' => array_merge($url_options['query'], ['media_library_selected_type' => 'type_one']), - ]); - $assert_session->responseContains('Access denied'); - $this->drupalGet('media-library', [ - 'query' => array_merge($url_options['query'], ['media_library_remaining' => 3]), - ]); - $assert_session->responseContains('Access denied'); - $this->drupalGet('media-library', [ - 'query' => array_merge($url_options['query'], ['hash' => 'fail']), - ]); - $assert_session->responseContains('Access denied'); - } - - /** - * Tests that the Media library's widget works as expected. - */ - public function testWidget() { - $assert_session = $this->assertSession(); - $page = $this->getSession()->getPage(); - - // Visit a node create page. - $this->drupalGet('node/add/basic_page'); - - // Assert that media widget instances are present. - $assert_session->pageTextContains('Unlimited media'); - $assert_session->pageTextContains('Twin media'); - $assert_session->pageTextContains('Single media type'); - $assert_session->pageTextContains('Empty types media'); - - // Assert generic media library elements. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $this->assertFalse($assert_session->elementExists('css', '.media-library-select-all')->isVisible()); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Assert that the media type menu is available when more than 1 type is - // configured for the field. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $menu = $assert_session->elementExists('css', '.media-library-menu'); - $this->assertTrue($menu->hasLink('Type One')); - $this->assertFalse($menu->hasLink('Type Two')); - $this->assertTrue($menu->hasLink('Type Three')); - $this->assertFalse($menu->hasLink('Type Four')); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - $assert_session->assertWaitOnAjaxRequest(); - - // Assert that the media type menu is available when the target_bundles - // setting for the entity reference field is null. All types should be - // allowed in this case. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_null_types_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $menu = $assert_session->elementExists('css', '.media-library-menu'); - $this->assertTrue($menu->hasLink('Type One')); - $this->assertTrue($menu->hasLink('Type Two')); - $this->assertTrue($menu->hasLink('Type Three')); - $this->assertTrue($menu->hasLink('Type Four')); - $this->assertTrue($menu->hasLink('Type Five')); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Assert that the media type menu is not available when only 1 type is - // configured for the field. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_single_media_type"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->elementTextContains('css', '.media-library-selected-count', '0 of 1 item selected'); - // Select a media item, assert the hidden selection field contains the ID of - // the selected item. - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $this->assertGreaterThanOrEqual(1, count($checkboxes)); - $checkboxes[0]->click(); - $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4'); - $assert_session->elementTextContains('css', '.media-library-selected-count', '1 of 1 item selected'); - $assert_session->elementNotExists('css', '.media-library-menu'); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Assert the menu links can be sorted through the widget configuration. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $links = $page->findAll('css', '.media-library-menu a'); - $link_titles = []; - foreach ($links as $link) { - $link_titles[] = $link->getText(); - } - $expected_link_titles = ['Type Three (active tab)', 'Type One', 'Type Two', 'Type Four']; - $this->assertSame($link_titles, $expected_link_titles); - $this->drupalGet('admin/structure/types/manage/basic_page/form-display'); - $assert_session->buttonExists('field_twin_media_settings_edit')->press(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->buttonExists('Show row weights')->press(); - $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_one][weight]')->selectOption(0); - $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_three][weight]')->selectOption(1); - $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_four][weight]')->selectOption(2); - $assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_two][weight]')->selectOption(3); - $assert_session->buttonExists('Save')->press(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->buttonExists('Hide row weights')->press(); - $this->drupalGet('node/add/basic_page'); - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $link_titles = array_map(function ($link) { - return $link->getText(); - }, $page->findAll('css', '.media-library-menu a')); - $this->assertSame($link_titles, ['Type One (active tab)', 'Type Three', 'Type Four', 'Type Two']); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Assert media is only visible on the tab for the related media type. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextContains('Bear'); - $assert_session->pageTextNotContains('Turtle'); - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->elementExists('named', ['link', 'Type Three (active tab)']); - $assert_session->pageTextNotContains('Dog'); - $assert_session->pageTextNotContains('Bear'); - $assert_session->pageTextNotContains('Turtle'); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Assert the exposed name filter of the view. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $session = $this->getSession(); - $session->getPage()->fillField('Name', 'Dog'); - $session->getPage()->pressButton('Apply filters'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Bear'); - $session->getPage()->fillField('Name', ''); - $session->getPage()->pressButton('Apply filters'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextContains('Bear'); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Assert the media library contains header links to switch between the grid - // and table display. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->elementExists('css', '.media-library-view .media-library-item--grid'); - $assert_session->elementNotExists('css', '.media-library-view .media-library-item--table'); - // Assert the 'Apply filter' button is not moved to the button pane. - $button_pane = $assert_session->elementExists('css', '.ui-dialog-buttonpane'); - $assert_session->buttonExists('Insert selected', $button_pane); - $assert_session->buttonNotExists('Apply filters', $button_pane); - $assert_session->linkExists('Grid'); - $page->clickLink('Table'); - // Assert the display change is correctly announced for screen readers. - $this->assertNotEmpty($assert_session->waitForText('Loading table view.')); - $this->assertNotEmpty($assert_session->waitForText('Changed to table view.')); - $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.media-library-view .media-library-item--table')); - $assert_session->elementNotExists('css', '.media-library-view .media-library-item--grid'); - // Assert the 'Apply filter' button is not moved to the button pane. - $assert_session->buttonExists('Insert selected', $button_pane); - $assert_session->buttonNotExists('Apply filters', $button_pane); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextContains('Bear'); - $assert_session->pageTextNotContains('Turtle'); - // Assert the exposed filters can be applied. - $page->fillField('Name', 'Dog'); - $page->pressButton('Apply filters'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Bear'); - $assert_session->pageTextNotContains('Turtle'); - $page->checkField('Select Dog'); - $assert_session->linkExists('Table'); - $page->clickLink('Grid'); - // Assert the display change is correctly announced for screen readers. - $this->assertNotEmpty($assert_session->waitForText('Loading grid view.')); - $this->assertNotEmpty($assert_session->waitForText('Changed to grid view.')); - $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.media-library-view .media-library-item--grid')); - $assert_session->elementNotExists('css', '.media-library-view .media-library-item--table'); - // Assert the exposed filters are persisted when changing display. - $this->assertSame('Dog', $page->findField('Name')->getValue()); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Bear'); - $assert_session->pageTextNotContains('Turtle'); - $assert_session->linkExists('Grid'); - $assert_session->linkExists('Table'); - // Select the item. - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - // Ensure that the selection completed successfully. - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Bear'); - $assert_session->pageTextNotContains('Turtle'); - // Clear the selection. - $assert_session->elementAttributeContains('css', '.media-library-item__remove', 'aria-label', 'Remove Dog'); - $assert_session->elementExists('css', '.media-library-item__remove')->click(); - $assert_session->assertWaitOnAjaxRequest(); - - // Assert adding a single media item and removing it. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $this->assertGreaterThanOrEqual(1, count($checkboxes)); - $checkboxes[0]->click(); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the focus is set back on the open button of the media field. - $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":focus")'); - $assert_session->elementAttributeContains('css', '.media-library-item__remove', 'aria-label', 'Remove Dog'); - $assert_session->elementExists('css', '.media-library-item__remove')->click(); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the focus is set back on the open button of the media field. - $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":focus")'); - - // Assert the selection is persistent in the media library modal, and - // the number of selected items is displayed correctly. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the number of selected items is displayed correctly. - $assert_session->elementExists('css', '.media-library-selected-count'); - $assert_session->elementTextContains('css', '.media-library-selected-count', '0 of 2 items selected'); - $assert_session->elementAttributeContains('css', '.media-library-selected-count', 'role', 'status'); - $assert_session->elementAttributeContains('css', '.media-library-selected-count', 'aria-live', 'polite'); - $assert_session->elementAttributeContains('css', '.media-library-selected-count', 'aria-atomic', 'true'); - // Select a media item, assert the hidden selection field contains the ID of - // the selected item. - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $this->assertCount(4, $checkboxes); - $checkboxes[0]->click(); - $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4'); - // Assert the number of selected items is displayed correctly. - $assert_session->elementTextContains('css', '.media-library-selected-count', '1 of 2 items selected'); - // Select another item and assert the number of selected items is updated. - $checkboxes[1]->click(); - $assert_session->elementTextContains('css', '.media-library-selected-count', '2 of 2 items selected'); - $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4,3'); - // Assert unselected items are disabled when the maximum allowed items are - // selected (cardinality for this field is 2). - $this->assertTrue($checkboxes[2]->hasAttribute('disabled')); - $this->assertTrue($checkboxes[3]->hasAttribute('disabled')); - // Assert the selected items are updated when deselecting an item. - $checkboxes[0]->click(); - $assert_session->elementTextContains('css', '.media-library-selected-count', '1 of 2 items selected'); - $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '3'); - // Assert deselected items are available again. - $this->assertFalse($checkboxes[2]->hasAttribute('disabled')); - $this->assertFalse($checkboxes[3]->hasAttribute('disabled')); - // The selection should be persisted when navigating to other media types in - // the modal. - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - $page->clickLink('Type One'); - $assert_session->assertWaitOnAjaxRequest(); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $selected_checkboxes = []; - foreach ($checkboxes as $checkbox) { - if ($checkbox->isChecked()) { - $selected_checkboxes[] = $checkbox->getValue(); - } - } - $this->assertCount(1, $selected_checkboxes); - $assert_session->hiddenFieldValueEquals('media-library-modal-selection', implode(',', $selected_checkboxes)); - $assert_session->elementTextContains('css', '.media-library-selected-count', '1 of 2 items selected'); - // Add to selection from another type. - $page->clickLink('Type Two'); - $assert_session->assertWaitOnAjaxRequest(); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $this->assertCount(4, $checkboxes); - $checkboxes[0]->click(); - // Assert the selection is updated correctly. - $assert_session->elementTextContains('css', '.media-library-selected-count', '2 of 2 items selected'); - $assert_session->hiddenFieldValueEquals('media-library-modal-selection', '3,8'); - // Assert unselected items are disabled when the maximum allowed items are - // selected (cardinality for this field is 2). - $this->assertFalse($checkboxes[0]->hasAttribute('disabled')); - $this->assertTrue($checkboxes[1]->hasAttribute('disabled')); - $this->assertTrue($checkboxes[2]->hasAttribute('disabled')); - $this->assertTrue($checkboxes[3]->hasAttribute('disabled')); - // Assert the checkboxes are also disabled on other pages. - $page->clickLink('Type One'); - $assert_session->assertWaitOnAjaxRequest(); - $this->assertTrue($checkboxes[0]->hasAttribute('disabled')); - $this->assertFalse($checkboxes[1]->hasAttribute('disabled')); - $this->assertTrue($checkboxes[2]->hasAttribute('disabled')); - $this->assertTrue($checkboxes[3]->hasAttribute('disabled')); - // Select the items. - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the open button is disabled. - $open_button = $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]'); - $this->assertTrue($open_button->hasAttribute('data-disabled-focus')); - $this->assertTrue($open_button->hasAttribute('disabled')); - $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .media-library-open-button").is(":disabled")'); - - // Ensure that the selection completed successfully. - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextNotContains('Dog'); - $assert_session->pageTextContains('Cat'); - $assert_session->pageTextContains('Turtle'); - $assert_session->pageTextNotContains('Snake'); - - // Remove "Cat" (happens to be the first remove button on the page). - $assert_session->elementAttributeContains('css', '.media-library-item__remove', 'aria-label', 'Remove Cat'); - $assert_session->elementExists('css', '.media-library-item__remove')->click(); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the focus is set to the remove button of the other selected item. - $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .media-library-item__remove").is(":focus")'); - $assert_session->pageTextNotContains('Cat'); - $assert_session->pageTextContains('Turtle'); - // Assert the open button is no longer disabled. - $open_button = $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]'); - $this->assertFalse($open_button->hasAttribute('data-disabled-focus')); - $this->assertFalse($open_button->hasAttribute('disabled')); - $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .media-library-open-button").is(":not(:disabled)")'); - - // Open the media library again and select another item. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $this->assertGreaterThanOrEqual(1, count($checkboxes)); - $checkboxes[0]->click(); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Cat'); - $assert_session->pageTextContains('Turtle'); - $assert_session->pageTextNotContains('Snake'); - // Assert the open button is disabled. - $this->assertTrue($assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->hasAttribute('data-disabled-focus')); - $this->assertTrue($assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->hasAttribute('disabled')); - $this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .media-library-open-button").is(":disabled")'); - - // Assert the selection is cleared when the modal is closed. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $this->assertGreaterThanOrEqual(4, count($checkboxes)); - // Nothing is selected yet. - $this->assertFalse($checkboxes[0]->isChecked()); - $this->assertFalse($checkboxes[1]->isChecked()); - $this->assertFalse($checkboxes[2]->isChecked()); - $this->assertFalse($checkboxes[3]->isChecked()); - $assert_session->elementTextContains('css', '.media-library-selected-count', '0 items selected'); - // Select the first 2 items. - $checkboxes[0]->click(); - $assert_session->elementTextContains('css', '.media-library-selected-count', '1 item selected'); - $checkboxes[1]->click(); - $assert_session->elementTextContains('css', '.media-library-selected-count', '2 items selected'); - $this->assertTrue($checkboxes[0]->isChecked()); - $this->assertTrue($checkboxes[1]->isChecked()); - $this->assertFalse($checkboxes[2]->isChecked()); - $this->assertFalse($checkboxes[3]->isChecked()); - // Close the dialog, reopen it and assert not is selected again. - $page->find('css', '.ui-dialog-titlebar-close')->click(); - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $this->assertFalse($checkboxes[0]->isChecked()); - $this->assertFalse($checkboxes[1]->isChecked()); - $this->assertFalse($checkboxes[2]->isChecked()); - $this->assertFalse($checkboxes[3]->isChecked()); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Finally, save the form. - $assert_session->elementExists('css', '.js-media-library-widget-toggle-weight')->click(); - $this->submitForm([ - 'title[0][value]' => 'My page', - 'field_twin_media[selection][0][weight]' => '2', - ], 'Save'); - $assert_session->pageTextContains('Basic Page My page has been created'); - // We removed this item earlier. - $assert_session->pageTextNotContains('Cat'); - // This item was never selected. - $assert_session->pageTextNotContains('Snake'); - // "Dog" should come after "Turtle", since we changed the weight. - $assert_session->elementExists('css', '.field--name-field-twin-media > .field__items > .field__item:last-child:contains("Turtle")'); - // Make sure everything that was selected shows up. - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextContains('Turtle'); - - // Re-edit the content and make a new selection. - $this->drupalGet('node/1/edit'); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextNotContains('Cat'); - $assert_session->pageTextNotContains('Bear'); - $assert_session->pageTextNotContains('Horse'); - $assert_session->pageTextContains('Turtle'); - $assert_session->pageTextNotContains('Snake'); - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - // Select all media items of type one (should also contain Dog, again). - $checkbox_selector = '.media-library-view .js-click-to-select-checkbox input'; - $checkboxes = $page->findAll('css', $checkbox_selector); - $this->assertGreaterThanOrEqual(4, count($checkboxes)); - $checkboxes[0]->click(); - $checkboxes[1]->click(); - $checkboxes[2]->click(); - $checkboxes[3]->click(); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextContains('Cat'); - $assert_session->pageTextContains('Bear'); - $assert_session->pageTextContains('Horse'); - $assert_session->pageTextContains('Turtle'); - $assert_session->pageTextNotContains('Snake'); - $this->submitForm([], 'Save'); - $assert_session->pageTextContains('Dog'); - $assert_session->pageTextContains('Cat'); - $assert_session->pageTextContains('Bear'); - $assert_session->pageTextContains('Horse'); - $assert_session->pageTextContains('Turtle'); - $assert_session->pageTextNotContains('Snake'); - } - - /** - * Tests that the widget works as expected for anonymous users. - */ - public function testWidgetAnonymous() { - $assert_session = $this->assertSession(); - $page = $this->getSession()->getPage(); - - $this->drupalLogout(); - - // Allow the anonymous user to create pages and view media. - $role = Role::load(RoleInterface::ANONYMOUS_ID); - $this->grantPermissions($role, [ - 'access content', - 'create basic_page content', - 'view media', - ]); - - // Ensure the widget works as an anonymous user. - $this->drupalGet('node/add/basic_page'); - - // Add to the unlimited cardinality field. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - - // Select the first media item (should be Dog). - $page->find('css', '.media-library-view .js-click-to-select-checkbox input')->click(); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - - // Ensure that the selection completed successfully. - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains('Dog'); - - // Save the form. - $assert_session->elementExists('css', '.js-media-library-widget-toggle-weight')->click(); - $this->submitForm([ - 'title[0][value]' => 'My page', - 'field_unlimited_media[selection][0][weight]' => '0', - ], 'Save'); - $assert_session->pageTextContains('Basic Page My page has been created'); - $assert_session->pageTextContains('Dog'); - } - - /** - * Tests that uploads in the Media library's widget works as expected. - */ - public function testWidgetUpload() { - $assert_session = $this->assertSession(); - $page = $this->getSession()->getPage(); - $driver = $this->getSession()->getDriver(); - - foreach ($this->getTestFiles('image') as $image) { - $extension = pathinfo($image->filename, PATHINFO_EXTENSION); - if ($extension === 'png') { - $png_image = $image; - } - elseif ($extension === 'jpg') { - $jpg_image = $image; - } - } - - if (!isset($png_image) || !isset($jpg_image)) { - $this->fail('Expected test files not present.'); - } - - // Create a user that can only add media of type four. - $user = $this->drupalCreateUser([ - 'access administration pages', - 'access content', - 'create basic_page content', - 'create type_one media', - 'create type_four media', - 'view media', - ]); - $this->drupalLogin($user); - - // Visit a node create page and open the media library. - $this->drupalGet('node/add/basic_page'); - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - - // Assert the upload form is not visible for default tab type_three without - // the proper permissions. - $assert_session->elementNotExists('css', '.media-library-add-form'); - - // Assert the upload form is not visible for the non-file based media type - // type_one. - $page->clickLink('Type One'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->elementNotExists('css', '.media-library-add-form'); - - // Assert the upload form is visible for type_four. - $page->clickLink('Type Four'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->fieldExists('Add files'); - $assert_session->pageTextContains('Maximum 2 files.'); - - // Create a user that can create media for all media types. - $user = $this->drupalCreateUser([ - 'access administration pages', - 'access content', - 'create basic_page content', - 'create media', - 'view media', - ]); - $this->drupalLogin($user); - - // Visit a node create page. - $this->drupalGet('node/add/basic_page'); - - $file_storage = $this->container->get('entity_type.manager')->getStorage('file'); - /** @var \Drupal\Core\File\FileSystemInterface $file_system */ - $file_system = $this->container->get('file_system'); - - // Add to the twin media field. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - - // Assert the upload form is now visible for default tab type_three. - $assert_session->elementExists('css', '.media-library-add-form'); - $assert_session->fieldExists('Add files'); - - // Assert we can upload a file to the default tab type_three. - $assert_session->elementExists('css', '.media-library-add-form--without-input'); - $assert_session->elementNotExists('css', '.media-library-add-form--with-input'); - $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); - $assert_session->assertWaitOnAjaxRequest(); - $this->assertJsCondition('jQuery(".media-library-add-form__added-media").is(":focus")'); - $assert_session->pageTextContains('The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.'); - $assert_session->elementAttributeContains('css', '.media-library-add-form__added-media', 'aria-label', 'Added media items'); - $assert_session->elementExists('css', '.media-library-add-form--with-input'); - $assert_session->elementNotExists('css', '.media-library-add-form--without-input'); - // We do not have a pre-selected items, so the container should not be added - // to the form. - $assert_session->elementNotExists('css', '.media-library-add-form__selected-media'); - // Files are temporary until the form is saved. - $files = $file_storage->loadMultiple(); - $file = array_pop($files); - $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri())); - $this->assertTrue($file->isTemporary()); - // Assert the revision_log_message field is not shown. - $upload_form = $assert_session->elementExists('css', '.media-library-add-form'); - $assert_session->fieldNotExists('Revision log message', $upload_form); - // Assert the name field contains the filename and the alt text is required. - $assert_session->fieldValueEquals('Name', $png_image->filename); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Alternative text field is required'); - $page->fillField('Alternative text', $this->randomString()); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - // The file should be permanent now. - $files = $file_storage->loadMultiple(); - $file = array_pop($files); - $this->assertFalse($file->isTemporary()); - // Load the created media item. - $media_items = Media::loadMultiple(); - $added_media = array_pop($media_items); - // Ensure the media item was saved to the library and automatically - // selected. The added media items should be in the first position of the - // add form. - $assert_session->pageTextContains('Add or select media'); - $assert_session->pageTextContains($png_image->filename); - $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); - $assert_session->checkboxChecked('media_library_select_form[0]'); - $assert_session->pageTextContains('1 of 2 items selected'); - $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); - // Ensure the created item is added in the widget. - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains($png_image->filename); - - // Remove the item. - $assert_session->elementExists('css', '.media-library-item__remove')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains($png_image->filename); - - // Assert we can also directly insert uploaded files in the widget. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - $png_uri_2 = $file_system->copy($png_image->uri, 'public://'); - $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_2)); - $assert_session->assertWaitOnAjaxRequest(); - $page->fillField('Alternative text', $this->randomString()); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and insert'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains($file_system->basename($png_uri_2)); - - // Also make sure that we can upload to the unlimited cardinality field. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - - // Select a media item to check if the selection is persisted when adding - // new items. - $existing_media_name = $file_system->basename($png_uri_2); - $checkbox = $page->findField("Select $existing_media_name"); - $selected_item_id = $checkbox->getAttribute('value'); - $checkbox->click(); - $assert_session->pageTextContains('1 item selected'); - $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); - $png_uri_3 = $file_system->copy($png_image->uri, 'public://'); - $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_3)); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->checkboxChecked("Select $existing_media_name"); - $page->fillField('Name', 'Unlimited Cardinality Image'); - $page->fillField('Alternative text', $this->randomString()); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - // Load the created media item. - $media_items = Media::loadMultiple(); - $added_media = array_pop($media_items); - $added_media_name = $added_media->label(); - // Ensure the media item was saved to the library and automatically - // selected. The added media items should be in the first position of the - // add form. - $assert_session->pageTextContains('Add or select media'); - $assert_session->pageTextContains('Unlimited Cardinality Image'); - $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); - $assert_session->checkboxChecked('media_library_select_form[0]'); - // Assert the item that was selected before uploading the file is still - // selected. - $assert_session->pageTextContains('2 items selected'); - $assert_session->checkboxChecked("Select $added_media_name"); - $assert_session->checkboxChecked("Select $existing_media_name"); - $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $selected_checkboxes = []; - foreach ($checkboxes as $checkbox) { - if ($checkbox->isChecked()) { - $selected_checkboxes[] = $checkbox->getAttribute('value'); - } - } - $this->assertCount(2, $selected_checkboxes); - // Ensure the created item is added in the widget. - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains('Unlimited Cardinality Image'); - - // Assert we can now only upload one more media item. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_twin_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Four'); - $assert_session->assertWaitOnAjaxRequest(); - $this->assertFalse($assert_session->fieldExists('Add file')->hasAttribute('multiple')); - $assert_session->pageTextContains('One file only.'); - - // Assert media type four should only allow jpg files by trying a png file - // first. - $png_uri_4 = $file_system->copy($png_image->uri, 'public://'); - $page->attachFileToField('Add file', $file_system->realpath($png_uri_4)); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Only files with the following extensions are allowed'); - // Assert that jpg files are accepted by type four. - $jpg_uri_2 = $file_system->copy($jpg_image->uri, 'public://'); - $page->attachFileToField('Add file', $file_system->realpath($jpg_uri_2)); - $assert_session->assertWaitOnAjaxRequest(); - $page->fillField('Alternative text', $this->randomString()); - // The type_four media type has another optional image field. - $assert_session->pageTextContains('Extra Image'); - $jpg_uri_3 = $file_system->copy($jpg_image->uri, 'public://'); - $page->attachFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_uri_3)); - $assert_session->assertWaitOnAjaxRequest(); - // Ensure that the extra image was uploaded to the correct directory. - $files = $file_storage->loadMultiple(); - $file = array_pop($files); - $this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri())); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - // Ensure the media item was saved to the library and automatically - // selected. - $assert_session->pageTextContains('Add or select media'); - $assert_session->pageTextContains($file_system->basename($jpg_uri_2)); - // Ensure the created item is added in the widget. - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains($file_system->basename($jpg_uri_2)); - - // Assert we can also remove selected items from the selection area in the - // upload form. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - $checkbox = $page->findField("Select $existing_media_name"); - $selected_item_id = $checkbox->getAttribute('value'); - $checkbox->click(); - $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); - $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); - $png_uri_5 = $file_system->copy($png_image->uri, 'public://'); - $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_5)); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the pre-selected items are shown. - $selection_area = $assert_session->elementExists('css', '.media-library-add-form__selected-media'); - $assert_session->elementExists('css', 'summary', $selection_area)->click(); - $assert_session->checkboxChecked("Select $existing_media_name", $selection_area); - $page->uncheckField("Select $existing_media_name"); - $page->fillField('Alternative text', $this->randomString()); - $assert_session->hiddenFieldValueEquals('current_selection', ''); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - $media_items = Media::loadMultiple(); - $added_media = array_pop($media_items); - $added_media_name = $added_media->label(); - $assert_session->pageTextContains('1 item selected'); - $assert_session->checkboxChecked("Select $added_media_name"); - $assert_session->checkboxNotChecked("Select $existing_media_name"); - $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains($file_system->basename($png_uri_5)); - - // Assert removing an uploaded media item before save works as expected. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the focus is shifted to the added media items. - $this->assertJsCondition('jQuery(".media-library-add-form__added-media").is(":focus")'); - // Assert the media item fields are shown and the vertical tabs are no - // longer shown. - $assert_session->elementExists('css', '.media-library-add-form__fields'); - $assert_session->elementNotExists('css', '.media-library-menu'); - // Press the 'Remove button' and assert the user is sent back to the media - // library. - $assert_session->elementExists('css', '.media-library-add-form__remove-button')->click(); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the remove message is shown. - $assert_session->pageTextContains("The media item $png_image->filename has been removed."); - // Assert the focus is shifted to the first tabbable element of the add - // form, which should be the source field. - $this->assertJsCondition('jQuery("#media-library-add-form-wrapper :tabbable").is(":focus")'); - $assert_session->elementNotExists('css', '.media-library-add-form__fields'); - $assert_session->elementExists('css', '.media-library-menu'); - $page->find('css', '.ui-dialog-titlebar-close')->click(); - - // Assert uploading multiple files. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the existing items are remembered when adding and removing media. - $checkbox = $page->findField("Select $existing_media_name"); - $checkbox->click(); - // Assert we can add multiple files. - $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); - // Create a list of new files to upload. - $filenames = []; - $remote_paths = []; - foreach (range(1, 3) as $i) { - $path = $file_system->copy($png_image->uri, 'public://'); - $filenames[] = $file_system->basename($path); - $remote_paths[] = $driver->uploadFileAndGetRemoteFilePath($file_system->realpath($path)); - } - $page->findField('Add files')->setValue(implode("\n", $remote_paths)); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the media item fields are shown and the vertical tabs are no - // longer shown. - $assert_session->elementExists('css', '.media-library-add-form__fields'); - $assert_session->elementNotExists('css', '.media-library-menu'); - // Assert all files have been added. - $assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]); - $assert_session->fieldValueEquals('media[1][fields][name][0][value]', $filenames[1]); - $assert_session->fieldValueEquals('media[2][fields][name][0][value]', $filenames[2]); - // Assert the pre-selected items are shown. - $selection_area = $assert_session->elementExists('css', '.media-library-add-form__selected-media'); - $assert_session->elementExists('css', 'summary', $selection_area)->click(); - $assert_session->checkboxChecked("Select $existing_media_name", $selection_area); - // Set alt texts for items 1 and 2, leave the alt text empty for item 3 to - // assert the field validation does not stop users from removing items. - $page->fillField('media[0][fields][field_media_test_image][0][alt]', $filenames[0]); - $page->fillField('media[1][fields][field_media_test_image][0][alt]', $filenames[1]); - // Remove the second file and assert the focus is shifted to the container - // of the next media item and field values are still correct. - $page->pressButton('media-1-remove-button'); - $this->assertJsCondition('jQuery(".media-library-add-form__media[data-media-library-added-delta=2]").is(":focus")'); - $assert_session->pageTextContains('The media item ' . $filenames[1] . ' has been removed.'); - // The second media item should be removed (this has the delta 1 since we - // start counting from 0). - $assert_session->elementNotExists('css', '.media-library-add-form__media[data-media-library-added-delta=1]'); - $media_item_one = $assert_session->elementExists('css', '.media-library-add-form__media[data-media-library-added-delta=0]'); - $assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one); - $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); - $media_item_three = $assert_session->elementExists('css', '.media-library-add-form__media[data-media-library-added-delta=2]'); - $assert_session->fieldValueEquals('Name', $filenames[2], $media_item_three); - $assert_session->fieldValueEquals('Alternative text', '', $media_item_three); - // Assert the pre-selected items are still shown. - $selection_area = $assert_session->elementExists('css', '.media-library-add-form__selected-media'); - $assert_session->elementExists('css', 'summary', $selection_area)->click(); - $assert_session->checkboxChecked("Select $existing_media_name", $selection_area); - // Remove the last file and assert the focus is shifted to the container - // of the first media item and field values are still correct. - $page->pressButton('media-2-remove-button'); - $this->assertJsCondition('jQuery(".media-library-add-form__media[data-media-library-added-delta=0]").is(":focus")'); - $assert_session->pageTextContains('The media item ' . $filenames[2] . ' has been removed.'); - $assert_session->elementNotExists('css', '.media-library-add-form__media[data-media-library-added-delta=1]'); - $assert_session->elementNotExists('css', '.media-library-add-form__media[data-media-library-added-delta=2]'); - $media_item_one = $assert_session->elementExists('css', '.media-library-add-form__media[data-media-library-added-delta=0]'); - $assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one); - $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); - } - - /** - * Tests that oEmbed media can be added in the Media library's widget. - */ - public function testWidgetOEmbed() { - $assert_session = $this->assertSession(); - $page = $this->getSession()->getPage(); - - $youtube_title = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)"; - $youtube_url = 'https://www.youtube.com/watch?v=PWjcqE3QKBg'; - $vimeo_title = "Drupal Rap Video - Schipulcon09"; - $vimeo_url = 'https://vimeo.com/7073899'; - ResourceController::setResourceUrl($youtube_url, $this->getFixturesDirectory() . '/video_youtube.json'); - - // Visit a node create page. - $this->drupalGet('node/add/basic_page'); - - // Add to the unlimited media field. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - - // Assert the default tab for media type one does not have an oEmbed form. - $assert_session->fieldNotExists('Add Type Five via URL'); - - // Assert other media types don't have the oEmbed form fields. - $page->clickLink('Type Three'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->fieldNotExists('Add Type Five via URL'); - - // Assert we can add an oEmbed video to media type five. - $page->clickLink('Type Five'); - $assert_session->assertWaitOnAjaxRequest(); - $page->fillField('Add Type Five via URL', $youtube_url); - $assert_session->pageTextContains('Allowed providers: YouTube, Vimeo.'); - $page->pressButton('Add'); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the name field contains the remote video title. - $assert_session->fieldValueEquals('Name', $youtube_title); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - - // Load the created media item. - $media_items = Media::loadMultiple(); - $added_media = array_pop($media_items); - - // Ensure the media item was saved to the library and automatically - // selected. The added media items should be in the first position of the - // add form. - $assert_session->pageTextContains('Add or select media'); - $assert_session->pageTextContains($youtube_title); - $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); - $assert_session->checkboxChecked('media_library_select_form[0]'); - - // Assert the created oEmbed video is correctly added to the widget. - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains($youtube_title); - - // Open the media library again for the unlimited field and go to the tab - // for media type five. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Five'); - $assert_session->assertWaitOnAjaxRequest(); - - // Assert the video is available on the tab. - $assert_session->pageTextContains($youtube_title); - - // Assert we can only add supported URLs. - $page->fillField('Add Type Five via URL', 'https://www.youtube.com/'); - $page->pressButton('Add'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('No matching provider found.'); - // Assert we can not add a video ID that doesn't exist. We need to use a - // video ID that will not be filtered by the regex, because otherwise the - // message 'No matching provider found.' will be returned. - $page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1'); - $page->pressButton('Add'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Could not retrieve the oEmbed resource.'); - - // Select a media item to check if the selection is persisted when adding - // new items. - $checkbox = $page->findField("Select $youtube_title"); - $selected_item_id = $checkbox->getAttribute('value'); - $checkbox->click(); - $assert_session->pageTextContains('1 item selected'); - $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); - - // Assert we can add a oEmbed video with a custom name. - $page->fillField('Add Type Five via URL', $youtube_url); - $page->pressButton('Add'); - $assert_session->assertWaitOnAjaxRequest(); - $page->fillField('Name', 'Custom video title'); - $selection_area = $assert_session->elementExists('css', '.media-library-add-form__selected-media'); - $assert_session->checkboxChecked("Select $youtube_title", $selection_area); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - - // Load the created media item. - $media_items = Media::loadMultiple(); - $added_media = array_pop($media_items); - // Ensure the media item was saved to the library and automatically - // selected. The added media items should be in the first position of the - // add form. - $assert_session->pageTextContains('Add or select media'); - $assert_session->pageTextContains('Custom video title'); - $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); - $assert_session->checkboxChecked('media_library_select_form[0]'); - // Assert the item that was selected before uploading the file is still - // selected. - $assert_session->pageTextContains('2 items selected'); - $assert_session->checkboxChecked("Select Custom video title"); - $assert_session->checkboxChecked("Select $youtube_title"); - $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); - $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); - $selected_checkboxes = []; - foreach ($checkboxes as $checkbox) { - if ($checkbox->isChecked()) { - $selected_checkboxes[] = $checkbox->getAttribute('value'); - } - } - $this->assertCount(2, $selected_checkboxes); - // Ensure the created item is added in the widget. - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains('Custom video title'); - - // Assert we can directly insert added oEmbed media in the widget. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Five'); - $assert_session->assertWaitOnAjaxRequest(); - $page->fillField('Add Type Five via URL', $vimeo_url); - $page->pressButton('Add'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and insert'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains($vimeo_title); - - // Assert we can remove selected items from the selection area in the oEmbed - // form. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Five'); - $assert_session->assertWaitOnAjaxRequest(); - $checkbox = $page->findField("Select $vimeo_title"); - $selected_item_id = $checkbox->getAttribute('value'); - $checkbox->click(); - $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); - $page->fillField('Add Type Five via URL', $youtube_url); - $page->pressButton('Add'); - $assert_session->assertWaitOnAjaxRequest(); - $page->fillField('Name', 'Another video'); - $selection_area = $assert_session->elementExists('css', '.media-library-add-form__selected-media'); - $assert_session->elementExists('css', 'summary', $selection_area)->click(); - $assert_session->checkboxChecked("Select $vimeo_title", $selection_area); - $page->uncheckField("Select $vimeo_title"); - $assert_session->hiddenFieldValueEquals('current_selection', ''); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and select'); - $assert_session->assertWaitOnAjaxRequest(); - $media_items = Media::loadMultiple(); - $added_media = array_pop($media_items); - $assert_session->pageTextContains('1 item selected'); - $assert_session->checkboxChecked('Select Another video'); - $assert_session->checkboxNotChecked("Select $vimeo_title"); - $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); - $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextNotContains('Add or select media'); - $assert_session->pageTextContains('Another video'); - - // Assert removing an added oEmbed media item before save works as expected. - $assert_session->elementExists('css', '.media-library-open-button[name^="field_unlimited_media"]')->click(); - $assert_session->assertWaitOnAjaxRequest(); - $assert_session->pageTextContains('Add or select media'); - $page->clickLink('Type Five'); - $assert_session->assertWaitOnAjaxRequest(); - $page->fillField('Add Type Five via URL', $youtube_url); - $page->pressButton('Add'); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the focus is shifted to the added media items. - $this->assertJsCondition('jQuery(".media-library-add-form__added-media").is(":focus")'); - // Assert the media item fields are shown and the vertical tabs are no - // longer shown. - $assert_session->elementExists('css', '.media-library-add-form__fields'); - $assert_session->elementNotExists('css', '.media-library-menu'); - // Press the 'Remove button' and assert the user is sent back to the media - // library. - $assert_session->elementExists('css', '.media-library-add-form__remove-button')->click(); - $assert_session->assertWaitOnAjaxRequest(); - // Assert the remove message is shown. - $assert_session->pageTextContains("The media item $youtube_title has been removed."); - // Assert the focus is shifted to the first tabbable element of the add - // form, which should be the source field. - $this->assertJsCondition('jQuery("#media-library-add-form-wrapper :tabbable").is(":focus")'); - $assert_session->elementNotExists('css', '.media-library-add-form__fields'); - $assert_session->elementExists('css', '.media-library-menu'); - } - -} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTestBase.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTestBase.php new file mode 100644 index 000000000..c49002c18 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTestBase.php @@ -0,0 +1,452 @@ + $names) { + foreach ($names as $name) { + /** @var \Drupal\media\MediaInterface $media */ + $media = Media::create([ + 'name' => $name, + 'bundle' => $type, + ]); + $source_field = $media->getSource() + ->getSourceFieldDefinition($media->bundle->entity) + ->getName(); + $media->set($source_field, $name)->setCreatedTime(++$time)->save(); + } + } + } + + /** + * Asserts that text appears on page after a wait. + * + * @param string $text + * The text that should appear on the page. + * @param int $timeout + * Timeout in milliseconds, defaults to 10000. + * + * @todo replace with whatever gets added in + * https://www.drupal.org/node/3061852 + */ + protected function waitForText($text, $timeout = 10000) { + $result = $this->assertSession()->waitForText($text, $timeout); + $this->assertNotEmpty($result, "\"$text\" not found"); + } + + /** + * Asserts that text does not appear on page after a wait. + * + * @param string $text + * The text that should not be on the page. + * @param int $timeout + * Timeout in milliseconds, defaults to 10000. + * + * @todo replace with whatever gets added in + * https://www.drupal.org/node/3061852 + */ + protected function waitForNoText($text, $timeout = 10000) { + $page = $this->getSession()->getPage(); + $result = $page->waitFor($timeout / 1000, function ($page) use ($text) { + $actual = preg_replace('/\s+/u', ' ', $page->getText()); + $regex = '/' . preg_quote($text, '/') . '/ui'; + return (bool) !preg_match($regex, $actual); + }); + $this->assertNotEmpty($result, "\"$text\" was found but shouldn't be there."); + } + + /** + * Checks for a specified number of specific elements on page after wait. + * + * @param string $selector_type + * Element selector type (css, xpath) + * @param string|array $selector + * Element selector. + * @param int $count + * Expected count. + * @param int $timeout + * Timeout in milliseconds, defaults to 10000. + * + * @todo replace with whatever gets added in + * https://www.drupal.org/node/3061852 + */ + protected function waitForElementsCount($selector_type, $selector, $count, $timeout = 10000) { + $page = $this->getSession()->getPage(); + + $start = microtime(TRUE); + $end = $start + ($timeout / 1000); + do { + $nodes = $page->findAll($selector_type, $selector); + if (count($nodes) === $count) { + return; + } + usleep(100000); + } while (microtime(TRUE) < $end); + + $this->assertSession()->elementsCount($selector_type, $selector, $count); + } + + /** + * Asserts that text appears in an element after a wait. + * + * @param string $selector + * The CSS selector of the element to check. + * @param string $text + * The text that should appear in the element. + * @param int $timeout + * Timeout in milliseconds, defaults to 10000. + * + * @todo replace with whatever gets added in + * https://www.drupal.org/node/3061852 + */ + protected function waitForElementTextContains($selector, $text, $timeout = 10000) { + $element = $this->assertSession()->waitForElement('css', "$selector:contains('$text')", $timeout); + $this->assertNotEmpty($element); + } + + /** + * Waits for the specified selector and returns it if not empty. + * + * @param string $selector + * The selector engine name. See ElementInterface::findAll() for the + * supported selectors. + * @param string|array $locator + * The selector locator. + * @param int $timeout + * Timeout in milliseconds, defaults to 10000. + * + * @return \Behat\Mink\Element\NodeElement + * The page element node if found. If not found, the test fails. + * + * @todo replace with whatever gets added in + * https://www.drupal.org/node/3061852 + */ + protected function assertElementExistsAfterWait($selector, $locator, $timeout = 10000) { + $element = $this->assertSession()->waitForElement($selector, $locator, $timeout); + $this->assertNotEmpty($element); + return $element; + } + + /** + * Gets the menu of available media types. + * + * @return \Behat\Mink\Element\NodeElement + * The menu of available media types. + */ + protected function getTypesMenu() { + return $this->assertSession() + ->elementExists('css', '.js-media-library-menu'); + } + + /** + * Clicks a media type tab and waits for it to appear. + */ + protected function switchToMediaType($type) { + $link = $this->assertSession() + ->elementExists('named', ['link', "Type $type"], $this->getTypesMenu()); + + if ($link->hasClass('active')) { + // There is nothing to do as the type is already active. + return; + } + + $link->click(); + $result = $link->waitFor(10, function ($link) { + /** @var \Behat\Mink\Element\NodeElement $link */ + return $link->hasClass('active'); + }); + $this->assertNotEmpty($result); + + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $this->assertSession()->assertWaitOnAjaxRequest(); + } + + /** + * Checks for the existence of a field on page after wait. + * + * @param string $field + * The field to find. + * @param int $timeout + * Timeout in milliseconds, defaults to 10000. + * + * @return \Behat\Mink\Element\NodeElement|null + * The element if found, otherwise null. + * + * @todo replace with whatever gets added in + * https://www.drupal.org/node/3061852 + */ + protected function waitForFieldExists($field, $timeout = 10000) { + $assert_session = $this->assertSession(); + $assert_session->waitForField($field, $timeout); + return $assert_session->fieldExists($field); + } + + /** + * Waits for a file field to exist before uploading. + */ + public function addMediaFileToField($locator, $path) { + $page = $this->getSession()->getPage(); + $this->waitForFieldExists($locator); + $page->attachFileToField($locator, $path); + } + + /** + * Clicks "Save and select||insert" button and waits for AJAX completion. + * + * @param string $operation + * The final word of the button to be clicked. + */ + protected function saveAnd($operation) { + $this->assertElementExistsAfterWait('css', '.ui-dialog-buttonpane')->pressButton("Save and $operation"); + + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $this->assertSession()->assertWaitOnAjaxRequest(); + } + + /** + * Clicks "Save" button and waits for AJAX completion. + * + * @param bool $expect_errors + * Whether validation errors are expected after the "Save" button is + * pressed. Defaults to FALSE. + */ + protected function pressSaveButton($expect_errors = FALSE) { + $buttons = $this->assertElementExistsAfterWait('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Save'); + + // If no validation errors are expected, wait for the "Insert selected" + // button to return. + if (!$expect_errors) { + $result = $buttons->waitFor(10, function ($buttons) { + /** @var \Behat\Mink\Element\NodeElement $buttons */ + return $buttons->findButton('Insert selected'); + }); + $this->assertNotEmpty($result); + } + + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $this->assertSession()->assertWaitOnAjaxRequest(); + } + + /** + * Clicks a button that opens a media widget and confirms it is open. + * + * @param string $field_name + * The machine name of the field for which to open the media library. + * @param string $after_open_selector + * The selector to look for after the button is clicked. + * + * @return \Behat\Mink\Element\NodeElement + * The NodeElement found via $after_open_selector. + */ + protected function openMediaLibraryForField($field_name, $after_open_selector = '.js-media-library-menu') { + $this->assertElementExistsAfterWait('css', "#$field_name-media-library-wrapper.js-media-library-widget") + ->pressButton('Add media'); + $this->waitForText('Add or select media'); + + // Assert that the grid display is visible and the links to toggle between + // the grid and table displays are present. + $this->assertMediaLibraryGrid(); + $assert_session = $this->assertSession(); + $assert_session->linkExists('Grid'); + $assert_session->linkExists('Table'); + + // The "select all" checkbox should never be present in the modal. + $assert_session->elementNotExists('css', '.media-library-select-all'); + + return $this->assertElementExistsAfterWait('css', $after_open_selector); + } + + /** + * Gets the "Additional selected media" area after adding new media. + * + * @param bool $open + * Whether or not to open the area before returning it. Defaults to TRUE. + * + * @return \Behat\Mink\Element\NodeElement + * The "additional selected media" area. + */ + protected function getSelectionArea($open = TRUE) { + $summary = $this->assertElementExistsAfterWait('css', 'summary:contains("Additional selected media")'); + if ($open) { + $summary->click(); + } + return $summary->getParent(); + } + + /** + * Asserts a media item was added, but not yet saved. + * + * @param int $index + * (optional) The index of the media item, if multiple items can be added at + * once. Defaults to 0. + */ + protected function assertMediaAdded($index = 0) { + $selector = '.js-media-library-add-form-added-media'; + + // Assert that focus is shifted to the new media items. + $this->assertJsCondition('jQuery("' . $selector . '").is(":focus")'); + + $assert_session = $this->assertSession(); + $assert_session->pageTextMatches('/The media items? ha(s|ve) been created but ha(s|ve) not yet been saved. Fill in any required fields and save to add (it|them) to the media library./'); + $assert_session->elementAttributeContains('css', $selector, 'aria-label', 'Added media items'); + + $fields = $this->assertElementExistsAfterWait('css', '[data-drupal-selector="edit-media-' . $index . '-fields"]'); + $assert_session->elementNotExists('css', '.js-media-library-menu'); + + // Assert extraneous components were removed in + // FileUploadForm::hideExtraSourceFieldComponents(). + $assert_session->elementNotExists('css', '[data-drupal-selector$="preview"]', $fields); + $assert_session->buttonNotExists('Remove', $fields); + $assert_session->elementNotExists('css', '[data-drupal-selector$="filename"]', $fields); + $assert_session->elementNotExists('css', '.file-size', $fields); + } + + /** + * Asserts that media was not added, i.e. due to a validation error. + */ + protected function assertNoMediaAdded() { + // Assert the focus is shifted to the first tabbable element of the add + // form, which should be the source field. + $this->assertJsCondition('jQuery("#media-library-add-form-wrapper :tabbable").is(":focus")'); + + $this->assertSession() + ->elementNotExists('css', '[data-drupal-selector="edit-media-0-fields"]'); + $this->getTypesMenu(); + } + + /** + * Presses the modal's "Insert selected" button. + * + * @param string $expected_announcement + * (optional) The expected screen reader announcement once the modal is + * closed. + * + * @todo Consider requiring screen reader assertion every time "Insert + * selected" is pressed in + * https://www.drupal.org/project/drupal/issues/3087227. + */ + protected function pressInsertSelected($expected_announcement = NULL) { + $this->assertSession() + ->elementExists('css', '.ui-dialog-buttonpane') + ->pressButton('Insert selected'); + $this->waitForNoText('Add or select media'); + + if ($expected_announcement) { + $this->waitForText($expected_announcement); + } + } + + /** + * Gets all available media item checkboxes. + * + * @return \Behat\Mink\Element\NodeElement[] + * The available checkboxes. + */ + protected function getCheckboxes() { + return $this->getSession() + ->getPage() + ->findAll('css', '.js-media-library-view .js-click-to-select-checkbox input'); + } + + /** + * Selects an item in the media library modal. + * + * @param int $index + * The zero-based index of the item to select. + * @param string $expected_selected_count + * (optional) The expected text of the selection counter. + */ + protected function selectMediaItem($index, $expected_selected_count = NULL) { + $checkboxes = $this->getCheckboxes(); + $this->assertGreaterThan($index, count($checkboxes)); + $checkboxes[$index]->check(); + + if ($expected_selected_count) { + $this->assertSelectedMediaCount($expected_selected_count); + } + } + + /** + * Switches to the grid display of the widget view. + */ + protected function switchToMediaLibraryGrid() { + $this->getSession()->getPage()->clickLink('Grid'); + // Assert the display change is correctly announced for screen readers. + $this->waitForText('Loading grid view.'); + $this->waitForText('Changed to grid view.'); + $this->assertMediaLibraryGrid(); + } + + /** + * Switches to the table display of the widget view. + */ + protected function switchToMediaLibraryTable() { + $this->getSession()->getPage()->clickLink('Table'); + // Assert the display change is correctly announced for screen readers. + $this->waitForText('Loading table view.'); + $this->waitForText('Changed to table view.'); + $this->assertMediaLibraryTable(); + } + + /** + * Asserts that the grid display of the widget view is visible. + */ + protected function assertMediaLibraryGrid() { + $this->assertSession() + ->elementExists('css', '.js-media-library-view[data-view-display-id="widget"]'); + } + + /** + * Asserts that the table display of the widget view is visible. + */ + protected function assertMediaLibraryTable() { + $this->assertSession() + ->elementExists('css', '.js-media-library-view[data-view-display-id="widget_table"]'); + } + + /** + * Asserts the current text of the selected item counter. + * + * @param string $text + * The expected text of the counter. + */ + protected function assertSelectedMediaCount($text) { + $selected_count = $this->assertSession() + ->elementExists('css', '.js-media-library-selected-count'); + + $this->assertSame('status', $selected_count->getAttribute('role')); + $this->assertSame('polite', $selected_count->getAttribute('aria-live')); + $this->assertSame('true', $selected_count->getAttribute('aria-atomic')); + $this->assertSame($text, $selected_count->getText()); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php new file mode 100644 index 000000000..bc038b53e --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php @@ -0,0 +1,139 @@ +createMediaItems([ + 'type_one' => [ + 'Horse', + 'Bear', + 'Cat', + 'Dog', + ], + 'type_two' => [ + 'Crocodile', + 'Lizard', + 'Snake', + 'Turtle', + ], + ]); + + $this->drupalPlaceBlock('local_tasks_block'); + $this->drupalPlaceBlock('local_actions_block'); + + $user = $this->drupalCreateUser([ + 'access media overview', + 'create media', + 'delete any media', + 'update any media', + ]); + $this->drupalLogin($user); + } + + /** + * Tests that the Media Library's administration page works as expected. + */ + public function testAdministrationPage() { + $session = $this->getSession(); + $page = $session->getPage(); + $assert_session = $this->assertSession(); + + // Visit the administration page. + $this->drupalGet('admin/content/media'); + + // There should be links to both the grid and table displays. + $assert_session->linkExists('Grid'); + $assert_session->linkExists('Table'); + + // We should see the table view and a link to add media. + $assert_session->elementExists('css', '.view-media .views-table'); + $assert_session->linkExists('Add media'); + + // Go to the grid display for the rest of the test. + $page->clickLink('Grid'); + $assert_session->addressEquals('admin/content/media-grid'); + + // Verify that the "Add media" link is present. + $assert_session->linkExists('Add media'); + + // Verify that media from two separate types is present. + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextContains('Turtle'); + + // Verify that the media name does not contain a link. + $assert_session->elementNotExists('css', '.media-library-item__name a'); + // Verify that there are links to edit and delete media items. + $assert_session->linkExists('Edit Dog'); + $assert_session->linkExists('Delete Turtle'); + + // Test that users can filter by type. + $page->selectFieldOption('Media type', 'Type One'); + $page->pressButton('Apply filters'); + $this->waitForNoText('Turtle'); + $assert_session->pageTextContains('Dog'); + $page->selectFieldOption('Media type', 'Type Two'); + $page->pressButton('Apply filters'); + $this->waitForText('Turtle'); + $assert_session->pageTextNotContains('Dog'); + + // Test that selecting elements as a part of bulk operations works. + $page->selectFieldOption('Media type', '- Any -'); + $assert_session->elementExists('css', '#views-exposed-form-media-library-page')->submit(); + $this->waitForText('Dog'); + + // This tests that anchor tags clicked inside the preview are suppressed. + $this->getSession()->executeScript('jQuery(".js-click-to-select-trigger a")[4].click()'); + $this->submitForm([], 'Apply to selected items'); + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextNotContains('Cat'); + // For reasons that are not clear, deleting media items by pressing the + // "Delete" button can fail (the button is found, but never actually pressed + // by the Mink driver). This workaround allows the delete form to be + // submitted. + $assert_session->elementExists('css', 'form')->submit(); + $assert_session->pageTextNotContains('Dog'); + $assert_session->pageTextContains('Cat'); + + // Test the 'Select all media' checkbox and assert that it makes the + // expected announcements. + $select_all = $this->waitForFieldExists('Select all media'); + $select_all->check(); + $this->waitForText('All 7 items selected'); + $select_all->uncheck(); + $this->waitForText('Zero items selected'); + $select_all->check(); + $page->selectFieldOption('Action', 'media_delete_action'); + $this->submitForm([], 'Apply to selected items'); + // For reasons that are not clear, deleting media items by pressing the + // "Delete" button can fail (the button is found, but never actually pressed + // by the Mink driver). This workaround allows the delete form to be + // submitted. + $assert_session->elementExists('css', 'form')->submit(); + + $assert_session->pageTextNotContains('Cat'); + $assert_session->pageTextNotContains('Turtle'); + $assert_session->pageTextNotContains('Snake'); + + // Test empty text. + $assert_session->pageTextContains('No media available.'); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/TranslationsTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/TranslationsTest.php new file mode 100644 index 000000000..0a659b5d8 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/TranslationsTest.php @@ -0,0 +1,173 @@ +save(); + } + + // Create an image media type and article node type. + $this->createMediaType('image', ['id' => 'image']); + $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + + // Make the media translatable and ensure the change is picked up. + \Drupal::service('content_translation.manager')->setEnabled('media', 'image', TRUE); + + // Create a media reference field on articles. + $this->createEntityReferenceField( + 'node', + 'article', + 'field_media', + 'Media', + 'media', + 'default', + ['target_bundles' => ['image']], + FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED + ); + // Add the media field to the form display. + \Drupal::service('entity_display.repository')->getFormDisplay('node', 'article') + ->setComponent('field_media', ['type' => 'media_library_widget']) + ->save(); + + // Create a file to user for our images. + $image = File::create([ + 'uri' => $this->getTestFiles('image')[0]->uri, + ]); + $image->setPermanent(); + $image->save(); + + // Create a translated and untranslated media item in each language. + $media_items = [ + ['nl' => 'Eekhoorn', 'es' => 'Ardilla'], + ['es' => 'Zorro', 'nl' => 'Vos'], + ['nl' => 'Hert'], + ['es' => 'Tejón'], + ]; + foreach ($media_items as $translations) { + $default_langcode = key($translations); + $default_name = array_shift($translations); + + $media = Media::create([ + 'name' => $default_name, + 'bundle' => 'image', + 'field_media_image' => $image, + 'langcode' => $default_langcode, + ]); + foreach ($translations as $langcode => $name) { + $media->addTranslation($langcode, ['name' => $name]); + } + $media->save(); + } + + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'access media overview', + 'edit own article content', + 'create article content', + 'administer media', + ]); + $this->drupalLogin($user); + } + + /** + * Tests the media library widget shows all media only once. + */ + public function testMediaLibraryTranslations() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + // All translations should be shown in the administration overview, + // regardless of the interface language. + $this->drupalGet('nl/admin/content/media-grid'); + $assert_session->elementsCount('css', '.js-media-library-item', 6); + $media_items = $page->findAll('css', '.media-library-item__name'); + $media_names = []; + foreach ($media_items as $media_item) { + $media_names[] = $media_item->getText(); + } + sort($media_names); + $this->assertSame(['Ardilla', 'Eekhoorn', 'Hert', 'Tejón', 'Vos', 'Zorro'], $media_names); + + $this->drupalGet('es/admin/content/media-grid'); + $assert_session->elementsCount('css', '.js-media-library-item', 6); + $media_items = $page->findAll('css', '.media-library-item__name'); + $media_names = []; + foreach ($media_items as $media_item) { + $media_names[] = $media_item->getText(); + } + sort($media_names); + $this->assertSame(['Ardilla', 'Eekhoorn', 'Hert', 'Tejón', 'Vos', 'Zorro'], $media_names); + + // All media should only be shown once, and should be shown in the interface + // language. + $this->drupalGet('nl/node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->waitForText('Add or select media'); + $assert_session->elementsCount('css', '.js-media-library-item', 4); + $media_items = $page->findAll('css', '.media-library-item__name'); + $media_names = []; + foreach ($media_items as $media_item) { + $media_names[] = $media_item->getText(); + } + sort($media_names); + $this->assertSame(['Eekhoorn', 'Hert', 'Tejón', 'Vos'], $media_names); + + $this->drupalGet('es/node/add/article'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click(); + $assert_session->waitForText('Add or select media'); + $assert_session->elementsCount('css', '.js-media-library-item', 4); + $media_items = $page->findAll('css', '.media-library-item__name'); + $media_names = []; + foreach ($media_items as $media_item) { + $media_names[] = $media_item->getText(); + } + sort($media_names); + $this->assertSame(['Ardilla', 'Hert', 'Tejón', 'Zorro'], $media_names); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/ViewsUiIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/ViewsUiIntegrationTest.php new file mode 100644 index 000000000..7e68f16a5 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/ViewsUiIntegrationTest.php @@ -0,0 +1,75 @@ +createMediaItems([ + 'type_one' => [ + 'Horse', + 'Bear', + 'Cat', + 'Dog', + ], + 'type_two' => [ + 'Crocodile', + 'Lizard', + 'Snake', + 'Turtle', + ], + ]); + + $account = $this->drupalCreateUser(['administer views']); + $this->drupalLogin($account); + } + + /** + * Tests that the integration with Views works correctly. + */ + public function testViewsAdmin() { + $page = $this->getSession()->getPage(); + + // Assert that the widget can be seen and that there are 8 items. + $this->drupalGet('/admin/structure/views/view/media_library/edit/widget'); + $this->waitForElementsCount('css', '.js-media-library-item', 8); + + // Assert that filtering works in live preview. + $page->find('css', '.js-media-library-view .view-filters')->fillField('name', 'snake'); + $page->find('css', '.js-media-library-view .view-filters')->pressButton('Apply filters'); + $this->waitForElementsCount('css', '.js-media-library-item', 1); + + // Test the same routine but in the view for the table wiget. + $this->drupalGet('/admin/structure/views/view/media_library/edit/widget_table'); + $this->waitForElementsCount('css', '.js-media-library-item', 8); + + // Assert that filtering works in live preview. + $page->find('css', '.js-media-library-view .view-filters')->fillField('name', 'snake'); + $page->find('css', '.js-media-library-view .view-filters')->pressButton('Apply filters'); + $this->waitForElementsCount('css', '.js-media-library-item', 1); + + // We cannot test clicking the 'Insert selected' button in either view + // because we expect an AJAX error, which would always throw an exception + // on ::tearDown even if we try to catch it here. If there is an API for + // marking certain elements 'unsuitable for previewing', we could test that + // here. + // @see https://www.drupal.org/project/drupal/issues/3060852 + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetAccessTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetAccessTest.php new file mode 100644 index 000000000..d62b84b8c --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetAccessTest.php @@ -0,0 +1,104 @@ +assertSession(); + + $role = Role::load(RoleInterface::ANONYMOUS_ID); + $role->revokePermission('view media'); + $role->save(); + + // Create a working state. + $allowed_types = ['type_one', 'type_two', 'type_three', 'type_four']; + // The opener parameters are not relevant to the test, but the opener + // expects them to be there or it will deny access. + $state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_types, 'type_three', 2, [ + 'entity_type_id' => 'node', + 'bundle' => 'basic_page', + 'field_name' => 'field_unlimited_media', + ]); + $url_options = ['query' => $state->all()]; + + // Verify that unprivileged users can't access the widget view. + $this->drupalGet('admin/content/media-widget', $url_options); + $assert_session->responseContains('Access denied'); + $this->drupalGet('admin/content/media-widget-table', $url_options); + $assert_session->responseContains('Access denied'); + $this->drupalGet('media-library', $url_options); + $assert_session->responseContains('Access denied'); + + // Allow users with 'view media' permission to access the media library view + // and controller. Since we are using the node entity type in the state + // object, ensure the user also has permission to work with those. + $this->grantPermissions($role, [ + 'create basic_page content', + 'view media', + ]); + $this->drupalGet('admin/content/media-widget', $url_options); + $assert_session->elementExists('css', '.view-media-library'); + $this->drupalGet('admin/content/media-widget-table', $url_options); + $assert_session->elementExists('css', '.view-media-library'); + $this->drupalGet('media-library', $url_options); + $assert_session->elementExists('css', '.view-media-library'); + // Assert the user does not have access to the media add form if the user + // does not have the 'create media' permission. + $assert_session->fieldNotExists('files[upload][]'); + + // Assert users can not access the widget displays of the media library view + // without a valid media library state. + $this->drupalGet('admin/content/media-widget'); + $assert_session->responseContains('Access denied'); + $this->drupalGet('admin/content/media-widget-table'); + $assert_session->responseContains('Access denied'); + $this->drupalGet('media-library'); + $assert_session->responseContains('Access denied'); + + // Assert users with the 'create media' permission can access the media add + // form. + $this->grantPermissions($role, [ + 'create media', + ]); + $this->drupalGet('media-library', $url_options); + $assert_session->elementExists('css', '.view-media-library'); + $assert_session->fieldExists('Add files'); + + // Assert the media library can not be accessed if the required state + // parameters are changed without changing the hash. + $this->drupalGet('media-library', [ + 'query' => array_merge($url_options['query'], ['media_library_opener_id' => 'fail']), + ]); + $assert_session->responseContains('Access denied'); + $this->drupalGet('media-library', [ + 'query' => array_merge($url_options['query'], ['media_library_allowed_types' => ['type_one', 'type_two']]), + ]); + $assert_session->responseContains('Access denied'); + $this->drupalGet('media-library', [ + 'query' => array_merge($url_options['query'], ['media_library_selected_type' => 'type_one']), + ]); + $assert_session->responseContains('Access denied'); + $this->drupalGet('media-library', [ + 'query' => array_merge($url_options['query'], ['media_library_remaining' => 3]), + ]); + $assert_session->responseContains('Access denied'); + $this->drupalGet('media-library', [ + 'query' => array_merge($url_options['query'], ['hash' => 'fail']), + ]); + $assert_session->responseContains('Access denied'); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetAnonymousTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetAnonymousTest.php new file mode 100644 index 000000000..089c87066 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetAnonymousTest.php @@ -0,0 +1,74 @@ +createMediaItems([ + 'type_one' => [ + 'Dog', + ], + ]); + + // Allow the anonymous user to create pages and view media. + $role = Role::load(RoleInterface::ANONYMOUS_ID); + $this->grantPermissions($role, [ + 'access content', + 'create basic_page content', + 'view media', + ]); + } + + /** + * Tests that the widget works as expected for anonymous users. + */ + public function testWidgetAnonymous() { + $assert_session = $this->assertSession(); + + // Allow the anonymous user to create pages and view media. + $role = Role::load(RoleInterface::ANONYMOUS_ID); + $this->grantPermissions($role, [ + 'access content', + 'create basic_page content', + 'view media', + ]); + + // Ensure the widget works as an anonymous user. + $this->drupalGet('node/add/basic_page'); + + // Add to the unlimited cardinality field. + $this->openMediaLibraryForField('field_unlimited_media'); + + // Select the first media item (should be Dog). + $this->selectMediaItem(0); + $this->pressInsertSelected('Added one media item.'); + + // Ensure that the selection completed successfully. + $this->waitForText('Dog'); + + // Save the form. + $assert_session->elementExists('css', '.js-media-library-widget-toggle-weight')->click(); + $this->submitForm([ + 'title[0][value]' => 'My page', + 'field_unlimited_media[selection][0][weight]' => '0', + ], 'Save'); + $assert_session->pageTextContains('Basic Page My page has been created'); + $assert_session->pageTextContains('Dog'); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetOEmbedTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetOEmbedTest.php new file mode 100644 index 000000000..2b1dbb57f --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetOEmbedTest.php @@ -0,0 +1,414 @@ +lockHttpClientToFixtures(); + $this->hijackProviderEndpoints(); + + // Create a user who can use the Media library. + $user = $this->drupalCreateUser([ + 'access content', + 'create basic_page content', + 'view media', + 'create media', + 'administer media', + ]); + $this->drupalLogin($user); + } + + /** + * Tests that oEmbed media can be added in the Media library's widget. + */ + public function testWidgetOEmbed() { + $this->hijackProviderEndpoints(); + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $youtube_title = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)"; + $youtube_url = 'https://www.youtube.com/watch?v=PWjcqE3QKBg'; + $vimeo_title = "Drupal Rap Video - Schipulcon09"; + $vimeo_url = 'https://vimeo.com/7073899'; + ResourceController::setResourceUrl($youtube_url, $this->getFixturesDirectory() . '/video_youtube.json'); + ResourceController::setResourceUrl($vimeo_url, $this->getFixturesDirectory() . '/video_vimeo.json'); + ResourceController::setResource404('https://www.youtube.com/watch?v=PWjcqE3QKBg1'); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + // Add to the unlimited media field. + $this->openMediaLibraryForField('field_unlimited_media'); + + // Assert the default tab for media type one does not have an oEmbed form. + $assert_session->fieldNotExists('Add Type Five via URL'); + + // Assert other media types don't have the oEmbed form fields. + $this->switchToMediaType('Three'); + $assert_session->fieldNotExists('Add Type Five via URL'); + + // Assert we can add an oEmbed video to media type five. + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $youtube_url); + $assert_session->pageTextContains('Allowed providers: YouTube, Vimeo.'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved.'); + // Assert the name field contains the remote video title. + $assert_session->fieldValueEquals('Name', $youtube_title); + $this->pressSaveButton(); + $this->waitForText('Add Type Five via URL'); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains($youtube_title); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + + // Assert the created oEmbed video is correctly added to the widget. + $this->pressInsertSelected('Added one media item.'); + $this->waitForText($youtube_title); + + // Open the media library again for the unlimited field and go to the tab + // for media type five. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + // Assert the video is available on the tab. + $assert_session->pageTextContains($youtube_title); + + // Assert we can only add supported URLs. + $page->fillField('Add Type Five via URL', 'https://www.youtube.com/'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('No matching provider found.'); + // Assert we can not add a video ID that doesn't exist. We need to use a + // video ID that will not be filtered by the regex, because otherwise the + // message 'No matching provider found.' will be returned. + $page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('Could not retrieve the oEmbed resource.'); + + // Select a media item to check if the selection is persisted when adding + // new items. + $checkbox = $page->findField("Select $youtube_title"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + + // Assert we can add a oEmbed video with a custom name. + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved.'); + $page->fillField('Name', 'Custom video title'); + $assert_session->elementNotExists('css', '.media-library-add-form__selected-media'); + $this->pressSaveButton(); + + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains('Custom video title'); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + // Assert the item that was selected before uploading the file is still + // selected. + $assert_session->pageTextContains('2 items selected'); + $assert_session->checkboxChecked("Select Custom video title"); + $assert_session->checkboxChecked("Select $youtube_title"); + $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); + $selected_checkboxes = []; + foreach ($this->getCheckboxes() as $checkbox) { + if ($checkbox->isChecked()) { + $selected_checkboxes[] = $checkbox->getAttribute('value'); + } + } + $this->assertCount(2, $selected_checkboxes); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added 2 media items.'); + $this->waitForText('Custom video title'); + + // Assert we can directly insert added oEmbed media in the widget. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $vimeo_url); + $page->pressButton('Add'); + $this->waitForText('The media item has been created but has not yet been saved.'); + $this->pressSaveButton(); + $this->waitForText('Add or select media'); + $this->pressInsertSelected(); + $this->waitForText($vimeo_title); + + // Assert we can remove selected items from the selection area in the oEmbed + // form. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + $checkbox = $page->findField("Select $vimeo_title"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved'); + $page->fillField('Name', 'Another video'); + $this->pressSaveButton(); + $page->uncheckField('media_library_select_form[1]'); + $this->waitForText('1 item selected'); + $this->pressInsertSelected('Added one media item.'); + $this->waitForText('Another video'); + + // Assert removing an added oEmbed media item before save works as expected. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $this->assertMediaAdded(); + // Press the 'Remove button' and assert the user is sent back to the media + // library. + $page->pressButton('media-0-remove-button'); + // Assert the remove message is shown. + $this->waitForText("The media item $youtube_title has been removed."); + $this->assertNoMediaAdded(); + } + + /** + * Tests that oEmbed media can be added in the widget's advanced UI. + * + * @todo Merge this with testWidgetOEmbed() in + * https://www.drupal.org/project/drupal/issues/3087227 + */ + public function testWidgetOEmbedAdvancedUi() { + $this->config('media_library.settings')->set('advanced_ui', TRUE)->save(); + + $this->hijackProviderEndpoints(); + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $youtube_title = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)"; + $youtube_url = 'https://www.youtube.com/watch?v=PWjcqE3QKBg'; + $vimeo_title = "Drupal Rap Video - Schipulcon09"; + $vimeo_url = 'https://vimeo.com/7073899'; + ResourceController::setResourceUrl($youtube_url, $this->getFixturesDirectory() . '/video_youtube.json'); + ResourceController::setResourceUrl($vimeo_url, $this->getFixturesDirectory() . '/video_vimeo.json'); + ResourceController::setResource404('https://www.youtube.com/watch?v=PWjcqE3QKBg1'); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + // Add to the unlimited media field. + $this->openMediaLibraryForField('field_unlimited_media'); + + // Assert the default tab for media type one does not have an oEmbed form. + $assert_session->fieldNotExists('Add Type Five via URL'); + + // Assert other media types don't have the oEmbed form fields. + $this->switchToMediaType('Three'); + $assert_session->fieldNotExists('Add Type Five via URL'); + + // Assert we can add an oEmbed video to media type five. + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $youtube_url); + $assert_session->pageTextContains('Allowed providers: YouTube, Vimeo.'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved.'); + // Assert the name field contains the remote video title. + $assert_session->fieldValueEquals('Name', $youtube_title); + $this->saveAnd('select'); + $this->waitForText('Add Type Five via URL'); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains($youtube_title); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + + // Assert the created oEmbed video is correctly added to the widget. + $this->pressInsertSelected('Added one media item.'); + $this->waitForText($youtube_title); + + // Open the media library again for the unlimited field and go to the tab + // for media type five. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + // Assert the video is available on the tab. + $assert_session->pageTextContains($youtube_title); + + // Assert we can only add supported URLs. + $page->fillField('Add Type Five via URL', 'https://www.youtube.com/'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('No matching provider found.'); + // Assert we can not add a video ID that doesn't exist. We need to use a + // video ID that will not be filtered by the regex, because otherwise the + // message 'No matching provider found.' will be returned. + $page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('Could not retrieve the oEmbed resource.'); + + // Select a media item to check if the selection is persisted when adding + // new items. + $checkbox = $page->findField("Select $youtube_title"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + + // Assert we can add a oEmbed video with a custom name. + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved.'); + $page->fillField('Name', 'Custom video title'); + $assert_session->checkboxChecked("Select $youtube_title", $this->getSelectionArea()); + $this->saveAnd('select'); + $this->waitForNoText('Save and select'); + + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains('Custom video title'); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + // Assert the item that was selected before uploading the file is still + // selected. + $assert_session->pageTextContains('2 items selected'); + $assert_session->checkboxChecked("Select Custom video title"); + $assert_session->checkboxChecked("Select $youtube_title"); + $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); + $selected_checkboxes = []; + foreach ($this->getCheckboxes() as $checkbox) { + if ($checkbox->isChecked()) { + $selected_checkboxes[] = $checkbox->getAttribute('value'); + } + } + $this->assertCount(2, $selected_checkboxes); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added 2 media items.'); + $this->waitForText('Custom video title'); + + // Assert we can directly insert added oEmbed media in the widget. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $vimeo_url); + $page->pressButton('Add'); + $this->waitForText('The media item has been created but has not yet been saved.'); + + $this->saveAnd('insert'); + $this->waitForText('Added one media item.'); + $this->waitForNoText('Add or select media'); + $this->waitForText($vimeo_title); + + // Assert we can remove selected items from the selection area in the oEmbed + // form. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + $checkbox = $page->findField("Select $vimeo_title"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved'); + $page->fillField('Name', 'Another video'); + $selection_area = $this->getSelectionArea(); + $assert_session->checkboxChecked("Select $vimeo_title", $selection_area); + $page->uncheckField("Select $vimeo_title"); + $assert_session->hiddenFieldValueEquals('current_selection', ''); + // Close the details element so that clicking the Save and select works. + // @todo Fix dialog or test so this is not necessary to prevent random + // fails. https://www.drupal.org/project/drupal/issues/3055648 + $selection_area->find('css', 'summary')->click(); + $this->saveAnd('select'); + + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + $this->waitForText('1 item selected'); + $assert_session->checkboxChecked('Select Another video'); + $assert_session->checkboxNotChecked("Select $vimeo_title"); + $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); + $this->pressInsertSelected('Added one media item.'); + $this->waitForText('Another video'); + + // Assert removing an added oEmbed media item before save works as expected. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $this->assertMediaAdded(); + // Press the 'Remove button' and assert the user is sent back to the media + // library. + $page->pressButton('media-0-remove-button'); + // Assert the remove message is shown. + $this->waitForText("The media item $youtube_title has been removed."); + $this->assertNoMediaAdded(); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php new file mode 100644 index 000000000..b90fecb08 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php @@ -0,0 +1,712 @@ +assertSession(); + $page = $this->getSession()->getPage(); + $driver = $this->getSession()->getDriver(); + + foreach ($this->getTestFiles('image') as $image) { + $extension = pathinfo($image->filename, PATHINFO_EXTENSION); + if ($extension === 'png') { + $png_image = $image; + } + elseif ($extension === 'jpg') { + $jpg_image = $image; + } + } + + if (!isset($png_image) || !isset($jpg_image)) { + $this->fail('Expected test files not present.'); + } + + // Create a user that can only add media of type four. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create type_one media', + 'create type_four media', + 'view media', + ]); + $this->drupalLogin($user); + + // Visit a node create page and open the media library. + $this->drupalGet('node/add/basic_page'); + $this->openMediaLibraryForField('field_twin_media'); + + // Assert the upload form is not visible for default tab type_three without + // the proper permissions. + $assert_session->elementNotExists('css', '.js-media-library-add-form'); + + // Assert the upload form is not visible for the non-file based media type + // type_one. + $this->switchToMediaType('One'); + $assert_session->elementNotExists('css', '.js-media-library-add-form'); + + // Assert the upload form is visible for type_four. + $this->switchToMediaType('Four'); + $assert_session->fieldExists('Add files'); + $assert_session->pageTextContains('Maximum 2 files.'); + + // Create a user that can create media for all media types. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create media', + 'view media', + ]); + $this->drupalLogin($user); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + $file_storage = $this->container->get('entity_type.manager')->getStorage('file'); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = $this->container->get('file_system'); + + // Add to the twin media field. + $this->openMediaLibraryForField('field_twin_media'); + + // Assert the upload form is now visible for default tab type_three. + $assert_session->elementExists('css', '.js-media-library-add-form'); + $assert_session->fieldExists('Add files'); + + // Assert we can upload a file to the default tab type_three. + $assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + $this->assertMediaAdded(); + $assert_session->elementExists('css', '.js-media-library-add-form[data-input]'); + // We do not have pre-selected items, so the container should not be added + // to the form. + $assert_session->pageTextNotContains('Additional selected media'); + // Files are temporary until the form is saved. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri())); + $this->assertTrue($file->isTemporary()); + // Assert the revision_log_message field is not shown. + $upload_form = $assert_session->elementExists('css', '.js-media-library-add-form'); + $assert_session->fieldNotExists('Revision log message', $upload_form); + // Assert the name field contains the filename and the alt text is required. + $assert_session->fieldValueEquals('Name', $png_image->filename); + $this->pressSaveButton(TRUE); + $this->waitForText('Alternative text field is required'); + $page->fillField('Alternative text', $this->randomString()); + $this->pressSaveButton(); + $this->assertJsCondition('jQuery("input[name=\'media_library_select_form[0]\']").is(":focus")'); + // The file should be permanent now. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertFalse($file->isTemporary()); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains($png_image->filename); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + $assert_session->pageTextContains('1 of 2 items selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added one media item.'); + $this->waitForText($png_image->filename); + + // Remove the item. + $assert_session->elementExists('css', '.field--name-field-twin-media')->pressButton('Remove'); + $this->waitForNoText($png_image->filename); + + $this->openMediaLibraryForField('field_twin_media'); + $this->switchToMediaType('Three'); + $png_uri_2 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_2)); + $this->waitForFieldExists('Alternative text')->setValue($this->randomString()); + $this->pressSaveButton(); + $this->pressInsertSelected('Added one media item.'); + $this->waitForText($file_system->basename($png_uri_2)); + + // Also make sure that we can upload to the unlimited cardinality field. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + + // Select a media item to check if the selection is persisted when adding + // new items. + $existing_media_name = $file_system->basename($png_uri_2); + $checkbox = $page->findField("Select $existing_media_name"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $png_uri_3 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_3)); + $this->waitForText('The media item has been created but has not yet been saved.'); + $page->fillField('Name', 'Unlimited Cardinality Image'); + $page->fillField('Alternative text', $this->randomString()); + $this->pressSaveButton(); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + $added_media_name = $added_media->label(); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains('Unlimited Cardinality Image'); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + // Assert the item that was selected before uploading the file is still + // selected. + $assert_session->pageTextContains('2 items selected'); + $assert_session->checkboxChecked("Select $added_media_name"); + $assert_session->checkboxChecked("Select $existing_media_name"); + $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); + $selected_checkboxes = []; + foreach ($this->getCheckboxes() as $checkbox) { + if ($checkbox->isChecked()) { + $selected_checkboxes[] = $checkbox->getAttribute('value'); + } + } + $this->assertCount(2, $selected_checkboxes); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added 2 media items.'); + $this->waitForText('Unlimited Cardinality Image'); + + // Assert we can now only upload one more media item. + $this->openMediaLibraryForField('field_twin_media'); + $this->switchToMediaType('Four'); + $this->assertFalse($assert_session->fieldExists('Add file')->hasAttribute('multiple')); + $assert_session->pageTextContains('One file only.'); + + // Assert media type four should only allow jpg files by trying a png file + // first. + $png_uri_4 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add file', $file_system->realpath($png_uri_4), FALSE); + $this->waitForText('Only files with the following extensions are allowed'); + // Assert that jpg files are accepted by type four. + $jpg_uri_2 = $file_system->copy($jpg_image->uri, 'public://'); + $this->addMediaFileToField('Add file', $file_system->realpath($jpg_uri_2)); + $this->waitForFieldExists('Alternative text')->setValue($this->randomString()); + // The type_four media type has another optional image field. + $assert_session->pageTextContains('Extra Image'); + $jpg_uri_3 = $file_system->copy($jpg_image->uri, 'public://'); + $this->addMediaFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_uri_3)); + $this->waitForText($file_system->basename($jpg_uri_3)); + // Ensure that the extra image was uploaded to the correct directory. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri())); + $this->pressSaveButton(); + // Ensure the media item was saved to the library and automatically + // selected. + $this->waitForText('Add or select media'); + $this->waitForText($file_system->basename($jpg_uri_2)); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added one media item.'); + $assert_session->pageTextContains($file_system->basename($jpg_uri_2)); + + // Assert we can also remove selected items from the selection area in the + // upload form. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + $checkbox = $page->findField("Select $existing_media_name"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); + $png_uri_5 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_5)); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $page->fillField('Alternative text', $this->randomString()); + $this->pressSaveButton(); + $page->uncheckField('media_library_select_form[2]'); + $this->waitForText('1 item selected'); + $this->waitForText("Select $existing_media_name"); + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + $added_media_name = $added_media->label(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->checkboxChecked("Select $added_media_name"); + $assert_session->checkboxNotChecked("Select $existing_media_name"); + $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); + $this->pressInsertSelected('Added one media item.'); + $this->waitForText($file_system->basename($png_uri_5)); + + // Assert removing an uploaded media item before save works as expected. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $this->waitForFieldExists('Alternative text'); + $this->assertMediaAdded(); + // Press the 'Remove button' and assert the user is sent back to the media + // library. + $page->pressButton('media-0-remove-button'); + // Assert the remove message is shown. + $this->waitForText("The media item $png_image->filename has been removed."); + // Assert the focus is shifted to the first tabbable element of the add + // form, which should be the source field. + $this->assertNoMediaAdded(); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert uploading multiple files. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + // Assert the existing items are remembered when adding and removing media. + $checkbox = $page->findField("Select $existing_media_name"); + $checkbox->click(); + // Assert we can add multiple files. + $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); + // Create a list of new files to upload. + $filenames = []; + $remote_paths = []; + foreach (range(1, 4) as $i) { + $path = $file_system->copy($png_image->uri, 'public://'); + $filenames[] = $file_system->basename($path); + $remote_paths[] = $driver->uploadFileAndGetRemoteFilePath($file_system->realpath($path)); + } + $page->findField('Add files')->setValue(implode("\n", $remote_paths)); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $this->assertMediaAdded(); + // Assert all files have been added. + $assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]); + $assert_session->fieldValueEquals('media[1][fields][name][0][value]', $filenames[1]); + $assert_session->fieldValueEquals('media[2][fields][name][0][value]', $filenames[2]); + $assert_session->fieldValueEquals('media[3][fields][name][0][value]', $filenames[3]); + // Set alt texts for items 1 and 2, leave the alt text empty for items 3 + // and 4 to assert the field validation does not stop users from removing + // items. + $page->fillField('media[0][fields][field_media_test_image][0][alt]', $filenames[0]); + $page->fillField('media[1][fields][field_media_test_image][0][alt]', $filenames[1]); + // Assert the file is available in the file storage. + $files = $file_storage->loadByProperties(['filename' => $filenames[1]]); + $this->assertCount(1, $files); + $file_1_uri = reset($files)->getFileUri(); + // Remove the second file and assert the focus is shifted to the container + // of the next media item and field values are still correct. + $page->pressButton('media-1-remove-button'); + $this->assertJsCondition('jQuery("[data-media-library-added-delta=2]").is(":focus")'); + $assert_session->pageTextContains('The media item ' . $filenames[1] . ' has been removed.'); + // Assert the file was deleted. + $this->assertEmpty($file_storage->loadByProperties(['filename' => $filenames[1]])); + $this->assertFileNotExists($file_1_uri); + + // When a file is already in usage, it should not be deleted. To test, + // let's add a usage for $filenames[3] (now in the third position). + $files = $file_storage->loadByProperties(['filename' => $filenames[3]]); + $this->assertCount(1, $files); + $target_file = reset($files); + Media::create([ + 'bundle' => 'type_three', + 'name' => 'Disturbing', + 'field_media_test_image' => [ + ['target_id' => $target_file->id()], + ], + ])->save(); + // Remove $filenames[3] (now in the third position) and assert the focus is + // shifted to the container of the previous media item and field values are + // still correct. + $page->pressButton('media-3-remove-button'); + $this->assertTrue($assert_session->waitForText('The media item ' . $filenames[3] . ' has been removed.')); + // Assert the file was not deleted, due to being in use elsewhere. + $this->assertNotEmpty($file_storage->loadByProperties(['filename' => $filenames[3]])); + $this->assertFileExists($target_file->getFileUri()); + + // The second media item should be removed (this has the delta 1 since we + // start counting from 0). + $assert_session->elementNotExists('css', '[data-media-library-added-delta=1]'); + $media_item_one = $assert_session->elementExists('css', '[data-media-library-added-delta=0]'); + $assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one); + $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); + $media_item_three = $assert_session->elementExists('css', '[data-media-library-added-delta=2]'); + $assert_session->fieldValueEquals('Name', $filenames[2], $media_item_three); + $assert_session->fieldValueEquals('Alternative text', '', $media_item_three); + } + + /** + * Tests that uploads in the widget's advanced UI works as expected. + * + * @todo Merge this with testWidgetUpload() in + * https://www.drupal.org/project/drupal/issues/3087227 + */ + public function testWidgetUploadAdvancedUi() { + $this->config('media_library.settings')->set('advanced_ui', TRUE)->save(); + + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + $driver = $this->getSession()->getDriver(); + + foreach ($this->getTestFiles('image') as $image) { + $extension = pathinfo($image->filename, PATHINFO_EXTENSION); + if ($extension === 'png') { + $png_image = $image; + } + elseif ($extension === 'jpg') { + $jpg_image = $image; + } + } + + if (!isset($png_image) || !isset($jpg_image)) { + $this->fail('Expected test files not present.'); + } + + // Create a user that can only add media of type four. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create type_one media', + 'create type_four media', + 'view media', + ]); + $this->drupalLogin($user); + + // Visit a node create page and open the media library. + $this->drupalGet('node/add/basic_page'); + $this->openMediaLibraryForField('field_twin_media'); + + // Assert the upload form is not visible for default tab type_three without + // the proper permissions. + $assert_session->elementNotExists('css', '.js-media-library-add-form'); + + // Assert the upload form is not visible for the non-file based media type + // type_one. + $this->switchToMediaType('One'); + $assert_session->elementNotExists('css', '.js-media-library-add-form'); + + // Assert the upload form is visible for type_four. + $this->switchToMediaType('Four'); + $assert_session->fieldExists('Add files'); + $assert_session->pageTextContains('Maximum 2 files.'); + + // Create a user that can create media for all media types. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create media', + 'view media', + ]); + $this->drupalLogin($user); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + $file_storage = $this->container->get('entity_type.manager')->getStorage('file'); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = $this->container->get('file_system'); + + // Add to the twin media field. + $this->openMediaLibraryForField('field_twin_media'); + + // Assert the upload form is now visible for default tab type_three. + $assert_session->elementExists('css', '.js-media-library-add-form'); + $assert_session->fieldExists('Add files'); + + // Assert we can upload a file to the default tab type_three. + $assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + $this->assertMediaAdded(); + $assert_session->elementExists('css', '.js-media-library-add-form[data-input]'); + // We do not have a pre-selected items, so the container should not be added + // to the form. + $assert_session->elementNotExists('css', 'details summary:contains(Additional selected media)'); + // Files are temporary until the form is saved. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri())); + $this->assertTrue($file->isTemporary()); + // Assert the revision_log_message field is not shown. + $upload_form = $assert_session->elementExists('css', '.js-media-library-add-form'); + $assert_session->fieldNotExists('Revision log message', $upload_form); + // Assert the name field contains the filename and the alt text is required. + $assert_session->fieldValueEquals('Name', $png_image->filename); + $this->saveAnd('select'); + $this->waitForText('Alternative text field is required'); + $page->fillField('Alternative text', $this->randomString()); + $this->saveAnd('select'); + $this->assertJsCondition('jQuery("input[name=\'media_library_select_form[0]\']").is(":focus")'); + // The file should be permanent now. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertFalse($file->isTemporary()); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains($png_image->filename); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + $assert_session->pageTextContains('1 of 2 items selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added one media item.'); + $this->waitForText($png_image->filename); + + // Remove the item. + $assert_session->elementExists('css', '.field--name-field-twin-media')->pressButton('Remove'); + $this->waitForNoText($png_image->filename); + + $this->openMediaLibraryForField('field_twin_media'); + $this->switchToMediaType('Three'); + $png_uri_2 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_2)); + $this->waitForFieldExists('Alternative text')->setValue($this->randomString()); + // Assert we can also directly insert uploaded files in the widget. + $this->saveAnd('insert'); + $this->waitForText('Added one media item.'); + $this->waitForNoText('Add or select media'); + $this->waitForText($file_system->basename($png_uri_2)); + + // Also make sure that we can upload to the unlimited cardinality field. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + + // Select a media item to check if the selection is persisted when adding + // new items. + $existing_media_name = $file_system->basename($png_uri_2); + $checkbox = $page->findField("Select $existing_media_name"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $png_uri_3 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_3)); + $this->waitForText('The media item has been created but has not yet been saved.'); + $assert_session->checkboxChecked("Select $existing_media_name"); + $page->fillField('Name', 'Unlimited Cardinality Image'); + $page->fillField('Alternative text', $this->randomString()); + $this->saveAnd('select'); + $this->waitForNoText('Save and select'); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + $added_media_name = $added_media->label(); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains('Unlimited Cardinality Image'); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + // Assert the item that was selected before uploading the file is still + // selected. + $assert_session->pageTextContains('2 items selected'); + $assert_session->checkboxChecked("Select $added_media_name"); + $assert_session->checkboxChecked("Select $existing_media_name"); + $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); + $selected_checkboxes = []; + foreach ($this->getCheckboxes() as $checkbox) { + if ($checkbox->isChecked()) { + $selected_checkboxes[] = $checkbox->getAttribute('value'); + } + } + $this->assertCount(2, $selected_checkboxes); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added 2 media items.'); + $this->waitForText('Unlimited Cardinality Image'); + + // Assert we can now only upload one more media item. + $this->openMediaLibraryForField('field_twin_media'); + $this->switchToMediaType('Four'); + $this->assertFalse($assert_session->fieldExists('Add file')->hasAttribute('multiple')); + $assert_session->pageTextContains('One file only.'); + + // Assert media type four should only allow jpg files by trying a png file + // first. + $png_uri_4 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add file', $file_system->realpath($png_uri_4), FALSE); + $this->waitForText('Only files with the following extensions are allowed'); + // Assert that jpg files are accepted by type four. + $jpg_uri_2 = $file_system->copy($jpg_image->uri, 'public://'); + $this->addMediaFileToField('Add file', $file_system->realpath($jpg_uri_2)); + $this->waitForFieldExists('Alternative text')->setValue($this->randomString()); + // The type_four media type has another optional image field. + $assert_session->pageTextContains('Extra Image'); + $jpg_uri_3 = $file_system->copy($jpg_image->uri, 'public://'); + $this->addMediaFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_uri_3)); + $this->waitForText($file_system->basename($jpg_uri_3)); + // Ensure that the extra image was uploaded to the correct directory. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri())); + $this->saveAnd('select'); + // Ensure the media item was saved to the library and automatically + // selected. + $this->waitForText('Add or select media'); + $this->waitForText($file_system->basename($jpg_uri_2)); + // Ensure the created item is added in the widget. + $this->pressInsertSelected('Added one media item.'); + $assert_session->pageTextContains($file_system->basename($jpg_uri_2)); + + // Assert we can also remove selected items from the selection area in the + // upload form. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + $checkbox = $page->findField("Select $existing_media_name"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); + $png_uri_5 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_5)); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $page->fillField('Alternative text', $this->randomString()); + // Assert the pre-selected items are shown. + $selection_area = $this->getSelectionArea(); + $assert_session->checkboxChecked("Select $existing_media_name", $selection_area); + $selection_area->uncheckField("Select $existing_media_name"); + $assert_session->hiddenFieldValueEquals('current_selection', ''); + // Close the details element so that clicking the Save and select works. + // @todo Fix dialog or test so this is not necessary to prevent random + // fails. https://www.drupal.org/project/drupal/issues/3055648 + $selection_area->find('css', 'summary')->click(); + $this->saveAnd('select'); + $this->waitForText("Select $existing_media_name"); + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + $added_media_name = $added_media->label(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->checkboxChecked("Select $added_media_name"); + $assert_session->checkboxNotChecked("Select $existing_media_name"); + $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); + $this->pressInsertSelected('Added one media item.'); + $this->waitForText($file_system->basename($png_uri_5)); + + // Assert removing an uploaded media item before save works as expected. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $this->assertMediaAdded(); + // Press the 'Remove button' and assert the user is sent back to the media + // library. + $page->pressButton('media-0-remove-button'); + // Assert the remove message is shown. + $this->waitForText("The media item $png_image->filename has been removed."); + $this->assertNoMediaAdded(); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert uploading multiple files. + $this->openMediaLibraryForField('field_unlimited_media'); + $this->switchToMediaType('Three'); + // Assert the existing items are remembered when adding and removing media. + $checkbox = $page->findField("Select $existing_media_name"); + $checkbox->click(); + // Assert we can add multiple files. + $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); + // Create a list of new files to upload. + $filenames = []; + $remote_paths = []; + foreach (range(1, 4) as $i) { + $path = $file_system->copy($png_image->uri, 'public://'); + $filenames[] = $file_system->basename($path); + $remote_paths[] = $driver->uploadFileAndGetRemoteFilePath($file_system->realpath($path)); + } + $page->findField('Add files')->setValue(implode("\n", $remote_paths)); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $this->assertMediaAdded(); + // Assert all files have been added. + $assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]); + $assert_session->fieldValueEquals('media[1][fields][name][0][value]', $filenames[1]); + $assert_session->fieldValueEquals('media[2][fields][name][0][value]', $filenames[2]); + $assert_session->fieldValueEquals('media[3][fields][name][0][value]', $filenames[3]); + // Assert the pre-selected items are shown. + $assert_session->checkboxChecked("Select $existing_media_name", $this->getSelectionArea()); + // Set alt texts for items 1 and 2, leave the alt text empty for items 3 + // and 4 to assert the field validation does not stop users from removing + // items. + $page->fillField('media[0][fields][field_media_test_image][0][alt]', $filenames[0]); + $page->fillField('media[1][fields][field_media_test_image][0][alt]', $filenames[1]); + // Assert the file is available in the file storage. + $files = $file_storage->loadByProperties(['filename' => $filenames[1]]); + $this->assertCount(1, $files); + $file_1_uri = reset($files)->getFileUri(); + // Remove the second file and assert the focus is shifted to the container + // of the next media item and field values are still correct. + $page->pressButton('media-1-remove-button'); + $this->assertJsCondition('jQuery("[data-media-library-added-delta=2]").is(":focus")'); + $assert_session->pageTextContains('The media item ' . $filenames[1] . ' has been removed.'); + // Assert the file was deleted. + $this->assertEmpty($file_storage->loadByProperties(['filename' => $filenames[1]])); + $this->assertFileNotExists($file_1_uri); + + // When a file is already in usage, it should not be deleted. To test, + // let's add a usage for $filenames[3] (now in the third position). + $files = $file_storage->loadByProperties(['filename' => $filenames[3]]); + $this->assertCount(1, $files); + $target_file = reset($files); + Media::create([ + 'bundle' => 'type_three', + 'name' => 'Disturbing', + 'field_media_test_image' => [ + ['target_id' => $target_file->id()], + ], + ])->save(); + // Remove $filenames[3] (now in the third position) and assert the focus is + // shifted to the container of the previous media item and field values are + // still correct. + $page->pressButton('media-3-remove-button'); + $this->assertTrue($assert_session->waitForText('The media item ' . $filenames[3] . ' has been removed.')); + // Assert the file was not deleted, due to being in use elsewhere. + $this->assertNotEmpty($file_storage->loadByProperties(['filename' => $filenames[3]])); + $this->assertFileExists($target_file->getFileUri()); + + // The second media item should be removed (this has the delta 1 since we + // start counting from 0). + $assert_session->elementNotExists('css', '[data-media-library-added-delta=1]'); + $media_item_one = $assert_session->elementExists('css', '[data-media-library-added-delta=0]'); + $assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one); + $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); + $media_item_three = $assert_session->elementExists('css', '[data-media-library-added-delta=2]'); + $assert_session->fieldValueEquals('Name', $filenames[2], $media_item_three); + $assert_session->fieldValueEquals('Alternative text', '', $media_item_three); + // Assert the pre-selected items are still shown. + $assert_session->checkboxChecked("Select $existing_media_name", $this->getSelectionArea()); + + // Remove the last file and assert the focus is shifted to the container + // of the first media item and field values are still correct. + $page->pressButton('media-2-remove-button'); + $this->assertJsCondition('jQuery("[data-media-library-added-delta=0]").is(":focus")'); + $assert_session->pageTextContains('The media item ' . $filenames[2] . ' has been removed.'); + $assert_session->elementNotExists('css', '[data-media-library-added-delta=1]'); + $assert_session->elementNotExists('css', '[data-media-library-added-delta=2]'); + $media_item_one = $assert_session->elementExists('css', '[data-media-library-added-delta=0]'); + $assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one); + $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetViewsTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetViewsTest.php new file mode 100644 index 000000000..ade178162 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetViewsTest.php @@ -0,0 +1,126 @@ +createMediaItems([ + 'type_one' => [ + 'Horse', + 'Bear', + 'Cat', + 'Dog', + 'Goat', + 'Sheep', + 'Pig', + 'Cow', + 'Chicken', + 'Duck', + 'Donkey', + 'Llama', + 'Mouse', + 'Goldfish', + 'Rabbit', + 'Turkey', + 'Dove', + 'Giraffe', + 'Tiger', + 'Hamster', + 'Parrot', + 'Monkey', + 'Koala', + 'Panda', + 'Kangaroo', + ], + 'type_two' => [ + 'Crocodile', + 'Lizard', + 'Snake', + 'Turtle', + ], + ]); + + // Create a user who can use the Media library. + $user = $this->drupalCreateUser([ + 'access content', + 'create basic_page content', + 'view media', + 'create media', + ]); + $this->drupalLogin($user); + } + + /** + * Tests that the views in the Media library's widget work as expected. + */ + public function testWidgetViews() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $this->drupalGet('node/add/basic_page'); + + $this->openMediaLibraryForField('field_unlimited_media'); + + // Assert the 'Apply filter' button is not moved to the button pane. + $button_pane = $assert_session->elementExists('css', '.ui-dialog-buttonpane'); + $assert_session->buttonExists('Insert selected', $button_pane); + $assert_session->buttonNotExists('Apply filters', $button_pane); + + // Assert the pager works as expected. + $assert_session->elementTextContains('css', '.js-media-library-view .pager__item.is-active', 'Page 1'); + $this->assertCount(24, $this->getCheckboxes()); + $page->clickLink('Next page'); + $this->waitForElementTextContains('.js-media-library-view .pager__item.is-active', 'Page 2'); + $this->assertCount(1, $this->getCheckboxes()); + $page->clickLink('Previous page'); + $this->waitForElementTextContains('.js-media-library-view .pager__item.is-active', 'Page 1'); + $this->assertCount(24, $this->getCheckboxes()); + + $this->switchToMediaLibraryTable(); + + // Assert the 'Apply filter' button is not moved to the button pane. + $assert_session->buttonExists('Insert selected', $button_pane); + $assert_session->buttonNotExists('Apply filters', $button_pane); + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextContains('Bear'); + $assert_session->pageTextNotContains('Turtle'); + + // Assert the exposed filters can be applied. + $page->fillField('Name', 'Dog'); + $page->pressButton('Apply filters'); + $this->waitForText('Dog'); + $this->waitForNoText('Bear'); + $assert_session->pageTextNotContains('Turtle'); + $page->checkField('Select Dog'); + $assert_session->linkExists('Table'); + $this->switchToMediaLibraryGrid(); + + // Assert the exposed filters are persisted when changing display. + $this->assertSame('Dog', $page->findField('Name')->getValue()); + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextNotContains('Bear'); + $assert_session->pageTextNotContains('Turtle'); + $assert_session->linkExists('Grid'); + $this->switchToMediaLibraryTable(); + + // Select the item. + $this->pressInsertSelected('Added one media item.'); + // Ensure that the selection completed successfully. + $assert_session->pageTextContains('Dog'); + $assert_session->pageTextNotContains('Bear'); + $assert_session->pageTextNotContains('Turtle'); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetWithoutTypesTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetWithoutTypesTest.php new file mode 100644 index 000000000..63beba8ad --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetWithoutTypesTest.php @@ -0,0 +1,149 @@ +assertSession(); + + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create media', + 'view media', + ]); + $this->drupalLogin($user); + + $default_message = 'There are no allowed media types configured for this field. Please contact the site administrator.'; + + $this->drupalGet('node/add/basic_page'); + + // Assert a properly configured field does not show a message. + $assert_session->elementTextNotContains('css', '.field--name-field-twin-media', 'There are no allowed media types configured for this field.'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]'); + // Assert that the message is shown when the target_bundles setting for the + // entity reference field is an empty array. No types are allowed in this + // case. + $assert_session->elementTextContains('css', '.field--name-field-empty-types-media', $default_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]'); + // Assert that the message is not shown when the target_bundles setting for + // the entity reference field is null. All types are allowed in this case. + $assert_session->elementTextNotContains('css', '.field--name-field-null-types-media', 'There are no allowed media types configured for this field.'); + $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_null_types_media"]'); + + // Delete all media and media types. + $entity_type_manager = \Drupal::entityTypeManager(); + $media_storage = $entity_type_manager->getStorage('media'); + $media_type_storage = $entity_type_manager->getStorage('media_type'); + $media_storage->delete($media_storage->loadMultiple()); + $media_type_storage->delete($media_type_storage->loadMultiple()); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + // Assert a properly configured field now shows a message. + $assert_session->elementTextContains('css', '.field--name-field-twin-media', $default_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_twin_media"]'); + // Assert that the message is shown when the target_bundles setting for the + // entity reference field is an empty array. + $assert_session->elementTextContains('css', '.field--name-field-empty-types-media', $default_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]'); + // Assert that the message is shown when the target_bundles setting for + // the entity reference field is null. + $assert_session->elementTextContains('css', '.field--name-field-null-types-media', $default_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]'); + + // Assert a different message is shown when the user is allowed to + // administer the fields. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'view media', + 'administer node fields', + ]); + $this->drupalLogin($user); + + $route_bundle_params = FieldUI::getRouteBundleParameter(\Drupal::entityTypeManager()->getDefinition('node'), 'basic_page'); + + $field_twin_url = new Url('entity.field_config.node_field_edit_form', [ + 'field_config' => 'node.basic_page.field_twin_media', + ] + $route_bundle_params); + $field_twin_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; + + $field_empty_types_url = new Url('entity.field_config.node_field_edit_form', [ + 'field_config' => 'node.basic_page.field_empty_types_media', + ] + $route_bundle_params); + $field_empty_types_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; + + $field_null_types_url = new Url('entity.field_config.node_field_edit_form', [ + 'field_config' => 'node.basic_page.field_null_types_media', + ] + $route_bundle_params); + $field_null_types_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + // Assert a properly configured field still shows a message. + $assert_session->elementContains('css', '.field--name-field-twin-media', $field_twin_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_twin_media"]'); + // Assert that the message is shown when the target_bundles setting for the + // entity reference field is an empty array. + $assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_empty_types_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]'); + // Assert that the message is shown when the target_bundles setting for the + // entity reference field is null. + $assert_session->elementContains('css', '.field--name-field-null-types-media', $field_null_types_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]'); + + // Assert the messages are also shown in the default value section of the + // field edit form. + $this->drupalGet($field_empty_types_url); + $assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_empty_types_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]'); + $this->drupalGet($field_null_types_url); + $assert_session->elementContains('css', '.field--name-field-null-types-media', $field_null_types_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]'); + + // Uninstall the Field UI and check if the link is removed from the message. + \Drupal::service('module_installer')->uninstall(['field_ui']); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + $field_ui_uninstalled_message = 'There are no allowed media types configured for this field. Edit the field settings to select the allowed media types.'; + + // Assert the link is now longer part of the message. + $assert_session->elementNotExists('named', ['link', 'Edit the field settings']); + // Assert a properly configured field still shows a message. + $assert_session->elementContains('css', '.field--name-field-twin-media', $field_ui_uninstalled_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_twin_media"]'); + // Assert that the message is shown when the target_bundles setting for the + // entity reference field is an empty array. + $assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_ui_uninstalled_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]'); + // Assert that the message is shown when the target_bundles setting for the + // entity reference field is null. + $assert_session->elementContains('css', '.field--name-field-null-types-media', $field_ui_uninstalled_message); + $assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]'); + } + +} diff --git a/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php b/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php index 2b83916e5..389561d4e 100644 --- a/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php +++ b/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php @@ -2,6 +2,12 @@ namespace Drupal\Tests\media_library\Kernel; +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Access\AccessResultReasonInterface; +use Drupal\entity_test\Entity\EntityTest; +use Drupal\entity_test\Entity\EntityTestBundle; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\image\Entity\ImageStyle; use Drupal\KernelTests\KernelTestBase; use Drupal\media_library\MediaLibraryState; @@ -21,8 +27,11 @@ class MediaLibraryAccessTest extends KernelTestBase { * {@inheritdoc} */ protected static $modules = [ + 'entity_test', 'media', 'media_library', + 'media_library_test', + 'filter', 'file', 'field', 'image', @@ -41,6 +50,8 @@ protected function setUp() { $this->installEntitySchema('file'); $this->installSchema('file', 'file_usage'); $this->installSchema('system', ['sequences', 'key_value_expire']); + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('filter_format'); $this->installEntitySchema('media'); $this->installConfig([ 'field', @@ -51,6 +62,23 @@ protected function setUp() { 'media_library', ]); + EntityTestBundle::create(['id' => 'test'])->save(); + + $field_storage = FieldStorageConfig::create([ + 'type' => 'entity_reference', + 'field_name' => 'field_test_media', + 'entity_type' => 'entity_test', + 'settings' => [ + 'target_type' => 'media', + ], + ]); + $field_storage->save(); + + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'test', + ])->save(); + // Create an account with special UID 1. $this->createUser([]); } @@ -72,29 +100,250 @@ public function testMediaLibraryImageStyleAccess() { } /** - * Tests the Media Library access. + * Tests that the field widget opener respects entity creation permissions. + */ + public function testFieldWidgetEntityCreateAccess() { + /** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */ + $ui_builder = $this->container->get('media_library.ui_builder'); + + // Create a media library state to test access. + $state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [ + 'entity_type_id' => 'entity_test', + 'bundle' => 'test', + 'field_name' => 'field_test_media', + ]); + + $access_result = $ui_builder->checkAccess($this->createUser(), $state); + $this->assertAccess($access_result, FALSE, "The following permissions are required: 'administer entity_test content' OR 'administer entity_test_with_bundle content' OR 'create test entity_test_with_bundle entities'.", [], ['url.query_args', 'user.permissions']); + + // Create a user with the appropriate permissions and assert that access is + // granted. + $account = $this->createUser([ + 'create test entity_test_with_bundle entities', + 'view media', + ]); + $access_result = $ui_builder->checkAccess($account, $state); + $this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['url.query_args', 'user.permissions']); + } + + /** + * @covers \Drupal\media_library\MediaLibraryEditorOpener::checkAccess + * + * @param bool $media_embed_enabled + * Whether to test with media_embed filter enabled on the text format. + * @param bool $can_use_format + * Whether the logged in user is allowed to use the text format. + * + * @dataProvider editorOpenerAccessProvider + */ + public function testEditorOpenerAccess($media_embed_enabled, $can_use_format) { + $format = $this->container + ->get('entity_type.manager') + ->getStorage('filter_format')->create([ + 'format' => $this->randomMachineName(), + 'name' => $this->randomString(), + 'filters' => [ + 'media_embed' => ['status' => $media_embed_enabled], + ], + ]); + $format->save(); + + $permissions = [ + 'access media overview', + 'view media', + ]; + if ($can_use_format) { + $permissions[] = $format->getPermissionName(); + } + + $state = MediaLibraryState::create( + 'media_library.opener.editor', + ['image'], + 'image', + 1, + ['filter_format_id' => $format->id()] + ); + + $access_result = $this->container + ->get('media_library.ui_builder') + ->checkAccess($this->createUser($permissions), $state); + + if ($media_embed_enabled && $can_use_format) { + $this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['user.permissions']); + } + else { + $this->assertAccess($access_result, FALSE, NULL, [], ['user.permissions']); + } + } + + /** + * Data provider for ::testEditorOpenerAccess. + */ + public function editorOpenerAccessProvider() { + return [ + 'media_embed filter enabled' => [ + TRUE, + TRUE, + ], + 'media_embed filter disabled' => [ + FALSE, + TRUE, + ], + 'media_embed filter enabled, user not allowed to use text format' => [ + TRUE, + FALSE, + ], + ]; + } + + /** + * Tests that the field widget opener respects entity-specific access. + */ + public function testFieldWidgetEntityEditAccess() { + /** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */ + $ui_builder = $this->container->get('media_library.ui_builder'); + + $forbidden_entity = EntityTest::create([ + 'type' => 'test', + // This label will automatically cause an access denial. + // @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess() + 'name' => 'forbid_access', + ]); + $forbidden_entity->save(); + + // Create a media library state to test access. + $state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [ + 'entity_type_id' => $forbidden_entity->getEntityTypeId(), + 'bundle' => $forbidden_entity->bundle(), + 'field_name' => 'field_test_media', + 'entity_id' => $forbidden_entity->id(), + ]); + + $access_result = $ui_builder->checkAccess($this->createUser(), $state); + $this->assertAccess($access_result, FALSE, NULL, [], ['url.query_args']); + + $neutral_entity = EntityTest::create([ + 'type' => 'test', + // This label will result in neutral access. + // @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess() + 'name' => $this->randomString(), + ]); + $neutral_entity->save(); + + $parameters = $state->getOpenerParameters(); + $parameters['entity_id'] = $neutral_entity->id(); + $state = MediaLibraryState::create( + $state->getOpenerId(), + $state->getAllowedTypeIds(), + $state->getSelectedTypeId(), + $state->getAvailableSlots(), + $parameters + ); + + $access_result = $ui_builder->checkAccess($this->createUser(), $state); + $this->assertTrue($access_result->isNeutral()); + $this->assertAccess($access_result, FALSE, NULL, [], ['url.query_args', 'user.permissions']); + + // Give the user permission to edit the entity and assert that access is + // granted. + $account = $this->createUser([ + 'administer entity_test content', + 'view media', + ]); + $access_result = $ui_builder->checkAccess($account, $state); + $this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['url.query_args', 'user.permissions']); + } + + /** + * Tests that the field widget opener respects entity field-level access. */ - public function testMediaLibraryAccess() { + public function testFieldWidgetEntityFieldAccess() { + $field_storage = FieldStorageConfig::create([ + 'type' => 'entity_reference', + 'entity_type' => 'entity_test', + // The media_library_test module will deny access to this field. + // @see media_library_test_entity_field_access() + 'field_name' => 'field_media_no_access', + 'settings' => [ + 'target_type' => 'media', + ], + ]); + $field_storage->save(); + + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'test', + ])->save(); + + /** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */ + $ui_builder = $this->container->get('media_library.ui_builder'); + + // Create an account with administrative access to the test entity type, + // so that we can be certain that field access is checked. + $account = $this->createUser(['administer entity_test content']); + + // Test that access is denied even without an entity to work with. + $state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [ + 'entity_type_id' => 'entity_test', + 'bundle' => 'test', + 'field_name' => $field_storage->getName(), + ]); + $access_result = $ui_builder->checkAccess($account, $state); + $this->assertAccess($access_result, FALSE, 'Field access denied by test module', [], ['url.query_args', 'user.permissions']); + + // Assert that field access is also checked with a real entity. + $entity = EntityTest::create([ + 'type' => 'test', + 'name' => $this->randomString(), + ]); + $entity->save(); + + $parameters = $state->getOpenerParameters(); + $parameters['entity_id'] = $entity->id(); + + $state = MediaLibraryState::create( + $state->getOpenerId(), + $state->getAllowedTypeIds(), + $state->getSelectedTypeId(), + $state->getAvailableSlots(), + $parameters + ); + $access_result = $ui_builder->checkAccess($account, $state); + $this->assertAccess($access_result, FALSE, 'Field access denied by test module', [], ['url.query_args', 'user.permissions']); + } + + /** + * Tests that media library access respects the media_library view. + */ + public function testViewAccess() { /** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */ $ui_builder = $this->container->get('media_library.ui_builder'); // Create a media library state to test access. - $state = MediaLibraryState::create('test', ['file', 'image'], 'file', 2); + $state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [ + 'entity_type_id' => 'entity_test', + 'bundle' => 'test', + 'field_name' => 'field_test_media', + ]); // Create a clone of the view so we can reset the original later. $view_original = clone Views::getView('media_library'); - // Create our test users. - $forbidden_account = $this->createUser([]); - $allowed_account = $this->createUser(['view media']); + // Create our test users. Both have permission to create entity_test content + // so that we can specifically test Views-related access checking. + // @see ::testEntityCreateAccess() + $forbidden_account = $this->createUser([ + 'create test entity_test_with_bundle entities', + ]); + $allowed_account = $this->createUser([ + 'create test entity_test_with_bundle entities', + 'view media', + ]); // Assert the 'view media' permission is needed to access the library and // validate the cache dependencies. $access_result = $ui_builder->checkAccess($forbidden_account, $state); - $this->assertFalse($access_result->isAllowed()); - $this->assertSame("The 'view media' permission is required.", $access_result->getReason()); - $this->assertSame($view_original->storage->getCacheTags(), $access_result->getCacheTags()); - $this->assertSame(['user.permissions'], $access_result->getCacheContexts()); + $this->assertAccess($access_result, FALSE, "The 'view media' permission is required.", $view_original->storage->getCacheTags(), ['url.query_args', 'user.permissions']); // Assert that the media library access is denied when the view widget // display is deleted. @@ -104,27 +353,42 @@ public function testMediaLibraryAccess() { $view_storage->set('display', $displays); $view_storage->save(); $access_result = $ui_builder->checkAccess($allowed_account, $state); - $this->assertFalse($access_result->isAllowed()); - $this->assertSame('The media library widget display does not exist.', $access_result->getReason()); - $this->assertSame($view_original->storage->getCacheTags(), $access_result->getCacheTags()); - $this->assertSame([], $access_result->getCacheContexts()); + $this->assertAccess($access_result, FALSE, 'The media library widget display does not exist.', $view_original->storage->getCacheTags()); // Restore the original view and assert that the media library controller // works again. $view_original->storage->save(); $access_result = $ui_builder->checkAccess($allowed_account, $state); - $this->assertTrue($access_result->isAllowed()); - $this->assertSame($view_original->storage->getCacheTags(), $access_result->getCacheTags()); - $this->assertSame(['user.permissions'], $access_result->getCacheContexts()); + $this->assertAccess($access_result, TRUE, NULL, $view_original->storage->getCacheTags(), ['url.query_args', 'user.permissions']); // Assert that the media library access is denied when the entire media // library view is deleted. Views::getView('media_library')->storage->delete(); $access_result = $ui_builder->checkAccess($allowed_account, $state); - $this->assertFalse($access_result->isAllowed()); - $this->assertSame('The media library view does not exist.', $access_result->getReason()); - $this->assertSame([], $access_result->getCacheTags()); - $this->assertSame([], $access_result->getCacheContexts()); + $this->assertAccess($access_result, FALSE, 'The media library view does not exist.'); + } + + /** + * Asserts various aspects of an access result. + * + * @param \Drupal\Core\Access\AccessResult $access_result + * The access result. + * @param bool $is_allowed + * The expected access status. + * @param string $expected_reason + * (optional) The expected reason attached to the access result. + * @param string[] $expected_cache_tags + * (optional) The expected cache tags attached to the access result. + * @param string[] $expected_cache_contexts + * (optional) The expected cache contexts attached to the access result. + */ + private function assertAccess(AccessResult $access_result, $is_allowed, $expected_reason = NULL, array $expected_cache_tags = [], array $expected_cache_contexts = []) { + $this->assertSame($is_allowed, $access_result->isAllowed()); + if ($access_result instanceof AccessResultReasonInterface && isset($expected_reason)) { + $this->assertSame($expected_reason, $access_result->getReason()); + } + $this->assertSame($expected_cache_tags, $access_result->getCacheTags()); + $this->assertSame($expected_cache_contexts, $access_result->getCacheContexts()); } } diff --git a/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php b/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php index 833ede9a6..51b70c978 100644 --- a/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php +++ b/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php @@ -120,7 +120,8 @@ protected function buildLibraryUi($selected_type_id) { */ public function testFormStateValidation() { $form_state = new FormState(); - $this->setExpectedException(\InvalidArgumentException::class, 'The media library state is not present in the form state.'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The media library state is not present in the form state.'); \Drupal::formBuilder()->buildForm(FileUploadForm::class, $form_state); } @@ -131,7 +132,8 @@ public function testSelectedTypeValidation() { $state = MediaLibraryState::create('test', ['image', 'remote_video', 'header_image'], 'header_image', -1); $form_state = new FormState(); $form_state->set('media_library_state', $state); - $this->setExpectedException(\InvalidArgumentException::class, "The 'header_image' media type does not exist."); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("The 'header_image' media type does not exist."); \Drupal::formBuilder()->buildForm(FileUploadForm::class, $form_state); } diff --git a/core/modules/media_library/tests/src/Kernel/MediaLibraryStateTest.php b/core/modules/media_library/tests/src/Kernel/MediaLibraryStateTest.php index 16c5531b2..ab0bdcce2 100644 --- a/core/modules/media_library/tests/src/Kernel/MediaLibraryStateTest.php +++ b/core/modules/media_library/tests/src/Kernel/MediaLibraryStateTest.php @@ -54,7 +54,7 @@ protected function setUp() { ]); // Create some media types to validate against. - $this->createMediaType('file', ['id' => 'file']); + $this->createMediaType('file', ['id' => 'document']); $this->createMediaType('image', ['id' => 'image']); $this->createMediaType('video_file', ['id' => 'video']); } @@ -64,7 +64,7 @@ protected function setUp() { */ public function testMethods() { $opener_id = 'test'; - $allowed_media_type_ids = ['file', 'image']; + $allowed_media_type_ids = ['document', 'image']; $selected_media_type_id = 'image'; $remaining_slots = 2; @@ -99,7 +99,8 @@ public function testMethods() { */ public function testCreate($opener_id, array $allowed_media_type_ids, $selected_type_id, $remaining_slots, $exception_message = '') { if ($exception_message) { - $this->setExpectedException(\InvalidArgumentException::class, $exception_message); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($exception_message); } $state = MediaLibraryState::create($opener_id, $allowed_media_type_ids, $selected_type_id, $remaining_slots); $this->assertInstanceOf(MediaLibraryState::class, $state); @@ -117,7 +118,7 @@ public function providerCreate() { // Assert no exception is thrown when we add the parameters as expected. $test_data['valid parameters'] = [ 'test', - ['file', 'image'], + ['document', 'image'], 'image', 2, ]; @@ -125,7 +126,7 @@ public function providerCreate() { // Assert an exception is thrown when the opener ID parameter is empty. $test_data['empty opener ID'] = [ '', - ['file', 'image'], + ['document', 'image'], 'image', 2, 'The opener ID parameter is required and must be a string.', @@ -134,21 +135,21 @@ public function providerCreate() { // valid string. $test_data['integer opener ID'] = [ 1, - ['file', 'image'], + ['document', 'image'], 'image', 2, 'The opener ID parameter is required and must be a string.', ]; $test_data['boolean opener ID'] = [ TRUE, - ['file', 'image'], + ['document', 'image'], 'image', 2, 'The opener ID parameter is required and must be a string.', ]; $test_data['spaces opener ID'] = [ ' ', - ['file', 'image'], + ['document', 'image'], 'image', 2, 'The opener ID parameter is required and must be a string.', @@ -191,7 +192,7 @@ public function providerCreate() { // Assert an exception is thrown when the selected type parameter is empty. $test_data['empty selected type'] = [ 'test', - ['file', 'image'], + ['document', 'image'], '', 2, 'The selected type parameter is required and must be a string.', @@ -200,21 +201,21 @@ public function providerCreate() { // valid string. $test_data['numeric selected type'] = [ 'test', - ['file', 'image'], + ['document', 'image'], 1, 2, 'The selected type parameter is required and must be a string.', ]; $test_data['boolean selected type'] = [ 'test', - ['file', 'image'], + ['document', 'image'], TRUE, 2, 'The selected type parameter is required and must be a string.', ]; $test_data['spaces selected type'] = [ 'test', - ['file', 'image'], + ['document', 'image'], ' ', 2, 'The selected type parameter is required and must be a string.', @@ -223,7 +224,7 @@ public function providerCreate() { // the list of allowed types. $test_data['non-present selected type'] = [ 'test', - ['file', 'image'], + ['document', 'image'], 'video', 2, 'The selected type parameter must be present in the list of allowed types.', @@ -233,7 +234,7 @@ public function providerCreate() { // empty. $test_data['empty remaining slots'] = [ 'test', - ['file', 'image'], + ['document', 'image'], 'image', '', 'The remaining slots parameter is required and must be numeric.', @@ -242,14 +243,14 @@ public function providerCreate() { // not numeric. $test_data['string remaining slots'] = [ 'test', - ['file', 'image'], + ['document', 'image'], 'image', 'fail', 'The remaining slots parameter is required and must be numeric.', ]; $test_data['boolean remaining slots'] = [ 'test', - ['file', 'image'], + ['document', 'image'], 'image', TRUE, 'The remaining slots parameter is required and must be numeric.', @@ -275,12 +276,23 @@ public function testFromRequest(array $query_overrides, $exception_expected) { $query = MediaLibraryState::create('test', ['file', 'image'], 'image', 2)->all(); $query = array_merge($query, $query_overrides); if ($exception_expected) { - $this->setExpectedException(BadRequestHttpException::class, "Invalid media library parameters specified."); + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage("Invalid media library parameters specified."); } $state = MediaLibraryState::fromRequest(new Request($query)); $this->assertInstanceOf(MediaLibraryState::class, $state); } + /** + * @covers ::fromRequest + */ + public function testFromRequestQueryLess() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The opener ID parameter is required and must be a string.'); + $state = MediaLibraryState::fromRequest(new Request()); + $this->assertInstanceOf(MediaLibraryState::class, $state); + } + /** * Data provider for testFromRequest(). * @@ -342,4 +354,38 @@ public function providerFromRequest() { return $test_data; } + /** + * @covers ::getOpenerParameters + */ + public function testOpenerParameters() { + $state = MediaLibraryState::create('test', ['file'], 'file', -1, [ + 'foo' => 'baz', + ]); + $this->assertSame(['foo' => 'baz'], $state->getOpenerParameters()); + } + + /** + * Test that hash is unaffected by allowed media type order. + */ + public function testHashUnaffectedByMediaTypeOrder() { + $state1 = MediaLibraryState::create('test', ['file', 'image'], 'image', 2); + $state2 = MediaLibraryState::create('test', ['image', 'file'], 'image', 2); + $this->assertSame($state1->getHash(), $state2->getHash()); + } + + /** + * Test that hash is unaffected by opener parameter order. + */ + public function testHashUnaffectedByOpenerParamOrder() { + $state1 = MediaLibraryState::create('test', ['file'], 'file', -1, [ + 'foo' => 'baz', + 'baz' => 'foo', + ]); + $state2 = MediaLibraryState::create('test', ['file'], 'file', -1, [ + 'baz' => 'foo', + 'foo' => 'baz', + ]); + $this->assertSame($state1->getHash(), $state2->getHash()); + } + } diff --git a/core/modules/menu_link_content/menu_link_content.info.yml b/core/modules/menu_link_content/menu_link_content.info.yml index a8a96fb0a..c001f5eb5 100644 --- a/core/modules/menu_link_content/menu_link_content.info.yml +++ b/core/modules/menu_link_content/menu_link_content.info.yml @@ -2,13 +2,7 @@ name: 'Custom Menu Links' type: module description: 'Allows administrators to create custom menu links.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:link - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/menu_link_content/menu_link_content.install b/core/modules/menu_link_content/menu_link_content.install index 8a04d0285..8a881a566 100644 --- a/core/modules/menu_link_content/menu_link_content.install +++ b/core/modules/menu_link_content/menu_link_content.install @@ -9,8 +9,8 @@ * Implements hook_install(). */ function menu_link_content_install() { - // Add a higher weight so that menu_link_content_path_update() is called after - // system_path_update() clears the path alias cache. + // Add a higher weight so that menu_link_content_path_alias_update() is called + // after system_path_alias_update() clears the path alias cache. // @todo remove this when the cache clearing is moved to path module or if // caching is removed for path aliases due to // https://www.drupal.org/node/1965074 diff --git a/core/modules/menu_link_content/menu_link_content.module b/core/modules/menu_link_content/menu_link_content.module index a0ea8da55..def0ed9db 100644 --- a/core/modules/menu_link_content/menu_link_content.module +++ b/core/modules/menu_link_content/menu_link_content.module @@ -7,6 +7,7 @@ use Drupal\Core\Url; use Drupal\Core\Entity\EntityInterface; +use Drupal\path_alias\PathAliasInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\system\MenuInterface; @@ -44,16 +45,16 @@ function menu_link_content_entity_type_alter(array &$entity_types) { * Implements hook_menu_delete(). */ function menu_link_content_menu_delete(MenuInterface $menu) { - $storage = \Drupal::entityManager()->getStorage('menu_link_content'); + $storage = \Drupal::entityTypeManager()->getStorage('menu_link_content'); $menu_links = $storage->loadByProperties(['menu_name' => $menu->id()]); $storage->delete($menu_links); } /** - * Implements hook_path_insert(). + * Implements hook_ENTITY_TYPE_insert() for 'path_alias'. */ -function menu_link_content_path_insert($path) { - _menu_link_content_update_path_alias($path['alias']); +function menu_link_content_path_alias_insert(PathAliasInterface $path_alias) { + _menu_link_content_update_path_alias($path_alias->getAlias()); } /** @@ -66,7 +67,7 @@ function _menu_link_content_update_path_alias($path) { /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); /** @var \Drupal\menu_link_content\MenuLinkContentInterface[] $entities */ - $entities = \Drupal::entityManager() + $entities = \Drupal::entityTypeManager() ->getStorage('menu_link_content') ->loadByProperties(['link.uri' => 'internal:' . $path]); foreach ($entities as $menu_link) { @@ -75,23 +76,23 @@ function _menu_link_content_update_path_alias($path) { } /** - * Implements hook_path_update(). + * Implements hook_ENTITY_TYPE_update() for 'path_alias'. */ -function menu_link_content_path_update($path) { - if ($path['alias'] != $path['original']['alias']) { - _menu_link_content_update_path_alias($path['alias']); - _menu_link_content_update_path_alias($path['original']['alias']); +function menu_link_content_path_alias_update(PathAliasInterface $path_alias) { + if ($path_alias->getAlias() != $path_alias->original->getAlias()) { + _menu_link_content_update_path_alias($path_alias->getAlias()); + _menu_link_content_update_path_alias($path_alias->original->getAlias()); } - elseif ($path['source'] != $path['original']['source']) { - _menu_link_content_update_path_alias($path['alias']); + elseif ($path_alias->getPath() != $path_alias->original->getPath()) { + _menu_link_content_update_path_alias($path_alias->getAlias()); } } /** - * Implements hook_path_delete(). + * Implements hook_ENTITY_TYPE_delete() for 'path_alias'. */ -function menu_link_content_path_delete($path) { - _menu_link_content_update_path_alias($path['alias']); +function menu_link_content_path_alias_delete(PathAliasInterface $path_alias) { + _menu_link_content_update_path_alias($path_alias->getAlias()); } /** diff --git a/core/modules/menu_link_content/migrations/d6_menu_links.yml b/core/modules/menu_link_content/migrations/d6_menu_links.yml index 404ed186b..86dfb8057 100644 --- a/core/modules/menu_link_content/migrations/d6_menu_links.yml +++ b/core/modules/menu_link_content/migrations/d6_menu_links.yml @@ -26,8 +26,7 @@ process: bypass: true 'link/uri': plugin: link_uri - source: - - link_path + source: link_path 'link/options': options route: plugin: route diff --git a/core/modules/menu_link_content/migrations/d7_menu_links.yml b/core/modules/menu_link_content/migrations/d7_menu_links.yml index 8f90bc3e4..a2e431027 100644 --- a/core/modules/menu_link_content/migrations/d7_menu_links.yml +++ b/core/modules/menu_link_content/migrations/d7_menu_links.yml @@ -23,8 +23,7 @@ process: method: row 'link/uri': plugin: link_uri - source: - - link_path + source: link_path 'link/options': options route: plugin: route diff --git a/core/modules/menu_link_content/migrations/state/menu_link_content.migrate_drupal.yml b/core/modules/menu_link_content/migrations/state/menu_link_content.migrate_drupal.yml new file mode 100644 index 000000000..c7111dc53 --- /dev/null +++ b/core/modules/menu_link_content/migrations/state/menu_link_content.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + menu: menu_link_content + 7: + menu: menu_link_content diff --git a/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php b/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php index c53f1327c..515c251c4 100644 --- a/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php +++ b/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php @@ -3,7 +3,8 @@ namespace Drupal\menu_link_content\Plugin\Deriver; use Drupal\Component\Plugin\Derivative\DeriverBase; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Menu\MenuLinkManagerInterface; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -15,13 +16,19 @@ * compared to entity referenced ones. */ class MenuLinkContentDeriver extends DeriverBase implements ContainerDeriverInterface { + use DeprecatedServicePropertyTrait; /** - * The entity manager. + * {@inheritdoc} + */ + protected $deprecatedProperties = ['entityManager' => 'entity.manager']; + + /** + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * The menu link manager. @@ -33,13 +40,13 @@ class MenuLinkContentDeriver extends DeriverBase implements ContainerDeriverInte /** * Constructs a MenuLinkContentDeriver instance. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager * The menu link manager. */ - public function __construct(EntityManagerInterface $entity_manager, MenuLinkManagerInterface $menu_link_manager) { - $this->entityManager = $entity_manager; + public function __construct(EntityTypeManagerInterface $entity_type_manager, MenuLinkManagerInterface $menu_link_manager) { + $this->entityTypeManager = $entity_type_manager; $this->menuLinkManager = $menu_link_manager; } @@ -48,7 +55,7 @@ public function __construct(EntityManagerInterface $entity_manager, MenuLinkMana */ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( - $container->get('entity.manager'), + $container->get('entity_type.manager'), $container->get('plugin.manager.menu.link') ); } @@ -58,11 +65,11 @@ public static function create(ContainerInterface $container, $base_plugin_id) { */ public function getDerivativeDefinitions($base_plugin_definition) { // Get all custom menu links which should be rediscovered. - $entity_ids = $this->entityManager->getStorage('menu_link_content')->getQuery() + $entity_ids = $this->entityTypeManager->getStorage('menu_link_content')->getQuery() ->condition('rediscover', TRUE) ->execute(); $plugin_definitions = []; - $menu_link_content_entities = $this->entityManager->getStorage('menu_link_content')->loadMultiple($entity_ids); + $menu_link_content_entities = $this->entityTypeManager->getStorage('menu_link_content')->loadMultiple($entity_ids); /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link_content */ foreach ($menu_link_content_entities as $menu_link_content) { $plugin_definitions[$menu_link_content->uuid()] = $menu_link_content->getPluginDefinition(); diff --git a/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php b/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php index bd95a606f..9d9e94729 100644 --- a/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php +++ b/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php @@ -12,9 +12,32 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Processes a link path into an 'internal:' or 'entity:' URI. + * Generates an internal URI from the source value. * - * @todo: Add documentation in https://www.drupal.org/node/2954908 + * Converts the source path value to an 'entity:', 'internal:' or 'base:' URI. + * + * Available configuration keys: + * - source: A source path to be converted into an URI. + * - validate_route: (optional) Whether the plugin should validate that the URI + * derived from the source link path has a valid Drupal route. + * - TRUE: Throw a MigrateException if the resulting URI is not routed. This + * value is the default. + * - FALSE: Return the URI for the unrouted path. + * + * Examples: + * + * @code + * process: + * link/uri: + * plugin: link_uri + * validate_route: false + * source: link_path + * @endcode + * + * This will set the uri property of link to the internal notation of link_path + * without validating if the resulting URI is valid. For example, if the + * 'link_path' property is 'node/12', the uri property value of link will be + * 'entity:node/12'. * * @MigrateProcessPlugin( * id = "link_uri" @@ -65,8 +88,12 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { - list($path) = $value; - $path = ltrim($path, '/'); + if (is_array($value)) { + $value = reset($value); + @trigger_error('Passing an array as source value into the link_uri migrate process plugin is deprecated in drupal:8.8.0. The possibility to pass an array as source value to the plugin will be removed in drupal:9.0.0. Pass a string value instead. See https://www.drupal.org/node/3043694', E_USER_DEPRECATED); + } + + $path = ltrim($value, '/'); if (parse_url($path, PHP_URL_SCHEME) === NULL) { if ($path == '') { diff --git a/core/modules/menu_link_content/src/Plugin/migrate/process/d6/LinkUri.php b/core/modules/menu_link_content/src/Plugin/migrate/process/d6/LinkUri.php index 7467c72f1..0d9d66df8 100644 --- a/core/modules/menu_link_content/src/Plugin/migrate/process/d6/LinkUri.php +++ b/core/modules/menu_link_content/src/Plugin/migrate/process/d6/LinkUri.php @@ -7,7 +7,7 @@ /** * Processes a link path into an 'internal:' or 'entity:' URI. * - * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * \Drupal\menu_link_content\Plugin\migrate\process\LinkUri instead. * * @see https://www.drupal.org/node/2761389 diff --git a/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php b/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php index 2cb8aaf21..d417a9897 100644 --- a/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php +++ b/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php @@ -7,7 +7,7 @@ /** * Processes an internal uri into an 'internal:' or 'entity:' URI. * - * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * \Drupal\menu_link_content\Plugin\migrate\process\LinkUri instead. * * @see https://www.drupal.org/node/2761389 diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php b/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php index 983c31599..045020d38 100644 --- a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php +++ b/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php @@ -41,7 +41,6 @@ public function query() { // Add in the property, which is either title or description. Cast the mlid // to text so PostgreSQL can make the join. $query->leftJoin(static::I18N_STRING_TABLE, 'i18n', 'CAST(ml.mlid as CHAR(255)) = i18n.objectid'); - $query->isNotNull('i18n.lid'); $query->addField('i18n', 'lid'); $query->addField('i18n', 'property'); diff --git a/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml index 4fcbbf310..8de1db409 100644 --- a/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml +++ b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml @@ -1,12 +1,6 @@ name: 'Menu link content dynamic route' type: module -# core: 8.x +core: 8.x hidden: true dependencies: - drupal:menu_link_content - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml b/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml index 7977fdda2..6f4e4a9b3 100644 --- a/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml +++ b/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml @@ -1,10 +1,4 @@ name: 'Outbound route/path processing' type: module -# core: 8.x +core: 8.x hidden: true - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonAnonTest.php b/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonAnonTest.php index d374d2b5a..1438c0cba 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonAnonTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonAnonTest.php @@ -20,6 +20,11 @@ class MenuLinkContentHalJsonAnonTest extends MenuLinkContentResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonBasicAuthTest.php b/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonBasicAuthTest.php index 40e93a0c7..d94f3e98a 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonBasicAuthTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class MenuLinkContentHalJsonBasicAuthTest extends MenuLinkContentHalJsonAnonTest */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonCookieTest.php b/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonCookieTest.php index 32cba4ac8..aeb7d858e 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonCookieTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Hal/MenuLinkContentHalJsonCookieTest.php @@ -16,4 +16,9 @@ class MenuLinkContentHalJsonCookieTest extends MenuLinkContentHalJsonAnonTest { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentDeleteFormTest.php b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentDeleteFormTest.php index ff829659e..942703ae5 100644 --- a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentDeleteFormTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentDeleteFormTest.php @@ -20,6 +20,11 @@ class MenuLinkContentDeleteFormTest extends BrowserTestBase { */ public static $modules = ['menu_link_content']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php index b85766779..128cd4195 100644 --- a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php @@ -21,6 +21,11 @@ class MenuLinkContentFormTest extends BrowserTestBase { 'menu_link_content', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * User with 'administer menu' and 'link to any page' permission. * @@ -76,7 +81,7 @@ public function testMenuLinkContentFormLinkToAnyPage() { public function testMenuLinkContentForm() { $this->drupalGet('admin/structure/menu/manage/admin/add'); $element = $this->xpath('//select[@id = :id]/option[@selected]', [':id' => 'edit-menu-parent']); - $this->assertTrue($element, 'A default menu parent was found.'); + $this->assertNotEmpty($element, 'A default menu parent was found.'); $this->assertEqual('admin:', $element[0]->getValue(), ' menu is the parent.'); // Test that the field description is present. $this->assertRaw('The location this menu link points to.'); diff --git a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php index ec3cdf053..badeb9cc0 100644 --- a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php +++ b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentTranslationUITest.php @@ -29,6 +29,11 @@ class MenuLinkContentTranslationUITest extends ContentTranslationUITestBase { 'menu_ui', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -88,7 +93,7 @@ public function testTranslationLinkTheme() { $entityId = $this->createEntity([], 'en'); // Set up Seven as the admin theme to test. - $this->container->get('theme_handler')->install(['seven']); + $this->container->get('theme_installer')->install(['seven']); $edit = []; $edit['admin_theme'] = 'seven'; $this->drupalPostForm('admin/appearance', $edit, t('Save configuration')); diff --git a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonAnonTest.php b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonAnonTest.php index fefd6b1d5..27ee2909c 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonAnonTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonAnonTest.php @@ -21,4 +21,9 @@ class MenuLinkContentJsonAnonTest extends MenuLinkContentResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonBasicAuthTest.php b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonBasicAuthTest.php index 269e94991..620da15da 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonBasicAuthTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class MenuLinkContentJsonBasicAuthTest extends MenuLinkContentResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonCookieTest.php b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonCookieTest.php index 05679525e..85deeeb6a 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonCookieTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentJsonCookieTest.php @@ -26,4 +26,9 @@ class MenuLinkContentJsonCookieTest extends MenuLinkContentResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlAnonTest.php b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlAnonTest.php index 7c39dae81..a1c1ed25a 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlAnonTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlAnonTest.php @@ -23,4 +23,9 @@ class MenuLinkContentXmlAnonTest extends MenuLinkContentResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlBasicAuthTest.php b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlBasicAuthTest.php index 7bbf96617..1e4999e6d 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlBasicAuthTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class MenuLinkContentXmlBasicAuthTest extends MenuLinkContentResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlCookieTest.php b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlCookieTest.php index 8b56dde81..310e27c18 100644 --- a/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlCookieTest.php +++ b/core/modules/menu_link_content/tests/src/Functional/Rest/MenuLinkContentXmlCookieTest.php @@ -28,4 +28,9 @@ class MenuLinkContentXmlCookieTest extends MenuLinkContentResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php index eebe42761..36904bd5f 100644 --- a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php +++ b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php @@ -131,7 +131,7 @@ public function testMenuLinks() { $tree = $menu_link_tree_service->load(static::MENU_NAME, $parameters); $found = FALSE; foreach ($tree as $menu_link_tree_element) { - $this->assertTrue($menu_link_tree_element->link->getUrlObject()->toString()); + $this->assertNotEmpty($menu_link_tree_element->link->getUrlObject()->toString()); if ($menu_link_tree_element->link->getTitle() == 'custom link test') { $found = TRUE; break; diff --git a/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php b/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php index 104f356cf..7980de976 100644 --- a/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php +++ b/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php @@ -3,22 +3,25 @@ namespace Drupal\Tests\menu_link_content\Kernel; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Menu\MenuTreeParameters; use Drupal\menu_link_content\Entity\MenuLinkContent; use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\Traits\Core\PathAliasTestTrait; /** * Ensures that the menu tree adapts to path alias changes. * * @group menu_link_content + * @group path */ class PathAliasMenuLinkContentTest extends KernelTestBase { + use PathAliasTestTrait; + /** * {@inheritdoc} */ - public static $modules = ['menu_link_content', 'system', 'link', 'test_page_test', 'user']; + public static $modules = ['menu_link_content', 'system', 'link', 'path_alias', 'test_page_test', 'user']; /** * {@inheritdoc} @@ -28,6 +31,7 @@ protected function setUp() { $this->installEntitySchema('user'); $this->installEntitySchema('menu_link_content'); + $this->installEntitySchema('path_alias'); // Ensure that the weight of module_link_content is higher than system. // @see menu_link_content_install() @@ -40,7 +44,7 @@ protected function setUp() { public function register(ContainerBuilder $container) { parent::register($container); - $definition = $container->getDefinition('path_processor_alias'); + $definition = $container->getDefinition('path_alias.path_processor'); $definition ->addTag('path_processor_inbound', ['priority' => 100]); } @@ -51,11 +55,7 @@ public function register(ContainerBuilder $container) { public function testPathAliasChange() { \Drupal::service('router.builder')->rebuild(); - /** @var \Drupal\Core\Path\AliasStorageInterface $path_alias_storage */ - $path_alias_storage = \Drupal::service('path.alias_storage'); - $alias = $path_alias_storage->save('/test-page', '/my-blog'); - $pid = $alias['pid']; - + $path_alias = $this->createPathAlias('/test-page', '/my-blog'); $menu_link_content = MenuLinkContent::create([ 'title' => 'Menu title', 'link' => ['uri' => 'internal:/my-blog'], @@ -67,13 +67,15 @@ public function testPathAliasChange() { $this->assertEqual('test_page_test.test_page', $tree[$menu_link_content->getPluginId()]->link->getPluginDefinition()['route_name']); // Saving an alias should clear the alias manager cache. - $path_alias_storage->save('/test-render-title', '/my-blog', LanguageInterface::LANGCODE_NOT_SPECIFIED, $pid); + $path_alias->setPath('/test-render-title'); + $path_alias->setAlias('/my-blog'); + $path_alias->save(); $tree = \Drupal::menuTree()->load('tools', new MenuTreeParameters()); $this->assertEqual('test_page_test.render_title', $tree[$menu_link_content->getPluginId()]->link->getPluginDefinition()['route_name']); // Delete the alias. - $path_alias_storage->delete(['pid' => $pid]); + $path_alias->delete(); $tree = \Drupal::menuTree()->load('tools', new MenuTreeParameters()); $this->assertTrue(isset($tree[$menu_link_content->getPluginId()])); $this->assertEqual('', $tree[$menu_link_content->getPluginId()]->link->getRouteName()); diff --git a/core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/process/LinkUriTest.php b/core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/process/LinkUriTest.php index 9ee3d6c7b..6b86bc080 100644 --- a/core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/process/LinkUriTest.php +++ b/core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/process/LinkUriTest.php @@ -40,7 +40,7 @@ public function setUp() { /** * Tests LinkUri::transform(). * - * @param array $value + * @param string $value * The value to pass to LinkUri::transform(). * @param string $expected * The expected return value of LinkUri::transform(). @@ -49,11 +49,23 @@ public function setUp() { * * @covers ::transform */ - public function testRouted(array $value, $expected) { + public function testRouted($value, $expected) { $actual = $this->doTransform($value); $this->assertSame($expected, $actual); } + /** + * Tests legacy handling for LinkUri::transform(). + * + * @dataProvider providerTestRouted + * @covers ::transform + * @expectedDeprecation Passing an array as source value into the link_uri migrate process plugin is deprecated in drupal:8.8.0. The possibility to pass an array as source value to the plugin will be removed in drupal:9.0.0. Pass a string value instead. See https://www.drupal.org/node/3043694 + * @group legacy + */ + public function testRoutedLegacy($value, $expected) { + $this->testRouted([$value], $expected); + } + /** * Provides test cases for LinkUriTest::testTransform(). * @@ -65,11 +77,11 @@ public function testRouted(array $value, $expected) { public function providerTestRouted() { $tests = []; - $value = ['http://example.com']; + $value = 'http://example.com'; $expected = 'http://example.com'; $tests['with_scheme'] = [$value, $expected]; - $value = ['']; + $value = ''; $expected = 'internal:/'; $tests['front'] = [$value, $expected]; @@ -79,15 +91,16 @@ public function providerTestRouted() { /** * Tests that Non routed URLs throws an exception. * - * @param array $value + * @param string $value * The value to pass to LinkUri::transform(). * @param string $exception_message * The expected exception message. * * @dataProvider providerTestNotRouted */ - public function testNotRouted(array $value, $exception_message) { - $this->setExpectedException(MigrateException::class, $exception_message); + public function testNotRouted($value, $exception_message) { + $this->expectException(MigrateException::class); + $this->expectExceptionMessage($exception_message); $this->doTransform($value); } @@ -105,12 +118,12 @@ public function providerTestNotRouted() { $message = 'The path "%s" failed validation.'; - $value = ['/test']; + $value = '/test'; $expected = 'internal:/test'; $exception_message = sprintf($message, $expected); $tests['leading_slash'] = [$value, $exception_message]; - $value = ['test']; + $value = 'test'; $expected = 'internal:/test'; $exception_message = sprintf($message, $expected); $tests['without_scheme'] = [$value, $exception_message]; @@ -121,7 +134,7 @@ public function providerTestNotRouted() { /** * Tests disabling route validation in LinkUri::transform(). * - * @param array $value + * @param string $value * The value to pass to LinkUri::transform(). * @param string $expected * The expected return value of LinkUri::transform(). @@ -130,7 +143,7 @@ public function providerTestNotRouted() { * * @covers ::transform */ - public function testDisablingRouteValidation(array $value, $expected) { + public function testDisablingRouteValidation($value, $expected) { // Create a node so we have a valid route. Node::create([ 'nid' => 1, @@ -153,11 +166,11 @@ public function testDisablingRouteValidation(array $value, $expected) { public function providerTestDisablingRouteValidation() { $tests = []; - $value = ['node/1']; + $value = 'node/1'; $expected = 'entity:node/1'; $tests['routed'] = [$value, $expected]; - $value = ['node/2']; + $value = 'node/2'; $expected = 'base:node/2'; $tests['unrouted'] = [$value, $expected]; @@ -167,7 +180,7 @@ public function providerTestDisablingRouteValidation() { /** * Transforms a link path into an 'internal:' or 'entity:' URI. * - * @param array $value + * @param string $value * The value to pass to LinkUri::transform(). * @param array $configuration * The plugin configuration. @@ -175,7 +188,7 @@ public function providerTestDisablingRouteValidation() { * @return string * The transformed link. */ - public function doTransform(array $value, $configuration = []) { + public function doTransform($value, $configuration = []) { $entityTypeManager = $this->container->get('entity_type.manager'); $routeBuilder = $this->container->get('router.builder'); $row = new Row(); diff --git a/core/modules/menu_ui/menu_ui.info.yml b/core/modules/menu_ui/menu_ui.info.yml index 64bbd70a2..062332058 100644 --- a/core/modules/menu_ui/menu_ui.info.yml +++ b/core/modules/menu_ui/menu_ui.info.yml @@ -2,14 +2,8 @@ name: Menu UI type: module description: 'Allows administrators to customize the site navigation menu.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: entity.menu.collection dependencies: - drupal:menu_link_content - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module index d7cebf146..dbcb2cfb0 100644 --- a/core/modules/menu_ui/menu_ui.module +++ b/core/modules/menu_ui/menu_ui.module @@ -24,7 +24,7 @@ use Drupal\node\NodeInterface; /** * Maximum length of menu name as entered by the user. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH because the * menu name is a configuration entity ID. * @@ -125,6 +125,7 @@ function _menu_ui_node_save(NodeInterface $node, array $values) { $entity->menu_name->value = $values['menu_name']; $entity->parent->value = $values['parent']; $entity->weight->value = isset($values['weight']) ? $values['weight'] : 0; + $entity->isDefaultRevision($node->isDefaultRevision()); $entity->save(); } @@ -188,7 +189,7 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) { if (!$defaults) { // Get the default max_length of a menu link title from the base field // definition. - $field_definitions = \Drupal::entityManager()->getBaseFieldDefinitions('menu_link_content'); + $field_definitions = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('menu_link_content'); $max_length = $field_definitions['title']->getSetting('max_length'); $description_max_length = $field_definitions['description']->getSetting('max_length'); $defaults = [ diff --git a/core/modules/menu_ui/menu_ui.routing.yml b/core/modules/menu_ui/menu_ui.routing.yml index 7f94a6d71..39c917209 100644 --- a/core/modules/menu_ui/menu_ui.routing.yml +++ b/core/modules/menu_ui/menu_ui.routing.yml @@ -16,7 +16,7 @@ menu_ui.parent_options_js: menu_ui.link_edit: path: '/admin/structure/menu/link/{menu_link_plugin}/edit' defaults: - _form: 'Drupal\menu_ui\Form\MenuLinkEditForm' + _form: '\Drupal\menu_ui\Form\MenuLinkEditForm' _title: 'Edit menu link' options: parameters: @@ -28,7 +28,7 @@ menu_ui.link_edit: menu_ui.link_reset: path: '/admin/structure/menu/link/{menu_link_plugin}/reset' defaults: - _form: 'Drupal\menu_ui\Form\MenuLinkResetForm' + _form: '\Drupal\menu_ui\Form\MenuLinkResetForm' _title: 'Reset menu link' options: parameters: diff --git a/core/modules/menu_ui/migrations/state/menu_ui.migrate_drupal.yml b/core/modules/menu_ui/migrations/state/menu_ui.migrate_drupal.yml new file mode 100644 index 000000000..55597fecf --- /dev/null +++ b/core/modules/menu_ui/migrations/state/menu_ui.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + menu: menu_ui + 7: + menu: menu_ui diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php index 74ec5fbb4..ccedad313 100644 --- a/core/modules/menu_ui/src/MenuForm.php +++ b/core/modules/menu_ui/src/MenuForm.php @@ -281,9 +281,9 @@ protected function buildOverviewForm(array &$form, FormStateInterface $form_stat ]; $form['links']['#empty'] = $this->t('There are no menu links yet. Add link.', [ - ':url' => $this->url('entity.menu.add_link_form', ['menu' => $this->entity->id()], [ + ':url' => Url::fromRoute('entity.menu.add_link_form', ['menu' => $this->entity->id()], [ 'query' => ['destination' => $this->entity->toUrl('edit-form')->toString()], - ]), + ])->toString(), ]); $links = $this->buildOverviewTreeForm($tree, $delta); diff --git a/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraint.php b/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraint.php index 35b29e92a..b5828af51 100644 --- a/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraint.php +++ b/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraint.php @@ -15,5 +15,8 @@ class MenuSettingsConstraint extends Constraint { public $message = 'You can only change the menu settings for the published version of this content.'; + public $messageWeight = 'You can only change the menu item weight for the published version of this content.'; + public $messageParent = 'You can only change the parent menu item for the published version of this content.'; + public $messageRemove = 'You can only remove the menu item in the published version of this content.'; } diff --git a/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraintValidator.php b/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraintValidator.php index e47ee3e11..33bd6eb85 100644 --- a/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraintValidator.php +++ b/core/modules/menu_ui/src/Plugin/Validation/Constraint/MenuSettingsConstraintValidator.php @@ -31,39 +31,35 @@ public function validate($entity, Constraint $constraint) { $values['parent'] = $parent; } - // Handle the case when a menu link is added to a pending revision. - if (!$defaults['entity_id'] && $values['enabled']) { - $violation_path = 'menu'; - } // Handle the case when the menu link is deleted in a pending revision. - elseif (empty($values['enabled']) && $defaults['entity_id']) { - $violation_path = 'menu'; + if (empty($values['enabled']) && $defaults['entity_id']) { + $this->context->buildViolation($constraint->messageRemove) + ->atPath('menu') + ->setInvalidValue($entity) + ->addViolation(); } - // Handle all the other menu link changes in a pending revision. + // Handle all the other non-revisionable menu link changes in a pending + // revision. elseif ($defaults['entity_id']) { - if (($values['title'] != $defaults['title'])) { - $violation_path = 'menu.title'; - } - elseif (($values['description'] != $defaults['description'])) { - $violation_path = 'menu.description'; - } - elseif ($defaults['entity_id'] && ($values['menu_name'] != $defaults['menu_name'])) { - $violation_path = 'menu.menu_parent'; + if ($defaults['entity_id'] && ($values['menu_name'] != $defaults['menu_name'])) { + $this->context->buildViolation($constraint->messageParent) + ->atPath('menu.menu_parent') + ->setInvalidValue($entity) + ->addViolation(); } elseif (isset($values['parent']) && ($values['parent'] != $defaults['parent'])) { - $violation_path = 'menu.menu_parent'; + $this->context->buildViolation($constraint->messageParent) + ->atPath('menu.menu_parent') + ->setInvalidValue($entity) + ->addViolation(); } elseif (($values['weight'] != $defaults['weight'])) { - $violation_path = 'menu.weight'; + $this->context->buildViolation($constraint->messageWeight) + ->atPath('menu.weight') + ->setInvalidValue($entity) + ->addViolation(); } } - - if ($violation_path) { - $this->context->buildViolation($constraint->message) - ->atPath($violation_path) - ->setInvalidValue($entity) - ->addViolation(); - } } } diff --git a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php index a8b986222..3a2584f32 100644 --- a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php +++ b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php @@ -9,7 +9,7 @@ /** * Base class for menu web tests. * - * @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\menu_ui\Traits\MenuUiTrait methods, instead. * * @see https://www.drupal.org/node/2917910 @@ -37,7 +37,7 @@ public function assertMenuLink($menu_plugin_id, array $expected_item) { $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); $menu_link_manager->resetDefinitions(); // Reset the static load cache. - \Drupal::entityManager()->getStorage('menu_link_content')->resetCache(); + \Drupal::entityTypeManager()->getStorage('menu_link_content')->resetCache(); $definition = $menu_link_manager->getDefinition($menu_plugin_id); $entity = NULL; @@ -46,7 +46,7 @@ public function assertMenuLink($menu_plugin_id, array $expected_item) { if (strpos($menu_plugin_id, 'menu_link_content') === 0) { list(, $uuid) = explode(':', $menu_plugin_id, 2); /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $entity */ - $entity = \Drupal::entityManager()->loadEntityByUuid('menu_link_content', $uuid); + $entity = \Drupal::service('entity.repository')->loadEntityByUuid('menu_link_content', $uuid); } if (isset($expected_item['children'])) { diff --git a/core/modules/menu_ui/tests/src/Functional/MenuCacheTagsTest.php b/core/modules/menu_ui/tests/src/Functional/MenuCacheTagsTest.php index c0d36690b..4adf2a2e3 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuCacheTagsTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuCacheTagsTest.php @@ -19,6 +19,11 @@ class MenuCacheTagsTest extends PageCacheTagsTestBase { */ public static $modules = ['menu_ui', 'block', 'test_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests cache tags presence and invalidation of the Menu entity. * diff --git a/core/modules/menu_ui/tests/src/Functional/MenuLinkReorderTest.php b/core/modules/menu_ui/tests/src/Functional/MenuLinkReorderTest.php index 4d2bba60d..88e515857 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuLinkReorderTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuLinkReorderTest.php @@ -25,6 +25,11 @@ class MenuLinkReorderTest extends BrowserTestBase { */ public static $modules = ['menu_ui', 'test_page_test', 'node', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test creating, editing, deleting menu links via node form widget. */ diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiContentModerationTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiContentModerationTest.php index b5d4b43c8..6aadd2908 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiContentModerationTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiContentModerationTest.php @@ -21,6 +21,11 @@ class MenuUiContentModerationTest extends BrowserTestBase { */ public static $modules = ['block', 'content_moderation', 'node', 'menu_ui', 'test_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -79,72 +84,64 @@ public function testMenuUiWithPendingRevisions() { $this->assertSession()->linkExists('Test menu link'); - // Try to change the menu link title and save a new non-default (draft) + // Try to change the menu link weight and save a new non-default (draft) // revision. $edit = [ - 'menu[title]' => 'Test menu link draft', + 'menu[weight]' => 1, 'moderation_state[0][state]' => 'draft', ]; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); // Check that the menu settings were not applied. - $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.'); - $this->assertSession()->linkExists('Test menu link'); - $this->assertSession()->linkNotExists('Test menu link draft'); + $this->assertSession()->pageTextContains('You can only change the menu item weight for the published version of this content.'); - // Try to change the menu link description and save a new non-default - // (draft) revision. + // Try to change the menu link parent and save a new non-default (draft) + // revision. $edit = [ - 'menu[description]' => 'Test menu link description', + 'menu[menu_parent]' => 'main:test_page_test.front_page', 'moderation_state[0][state]' => 'draft', ]; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); // Check that the menu settings were not applied. - $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.'); + $this->assertSession()->pageTextContains('You can only change the parent menu item for the published version of this content.'); - // Try to change the menu link weight and save a new non-default (draft) - // revision. + // Try to delete the menu link and save a new non-default (draft) revision. $edit = [ - 'menu[weight]' => 1, + 'menu[enabled]' => 0, 'moderation_state[0][state]' => 'draft', ]; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); // Check that the menu settings were not applied. - $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.'); + $this->assertSession()->pageTextContains('You can only remove the menu item in the published version of this content.'); + $this->assertSession()->linkExists('Test menu link'); - // Try to change the menu link parent and save a new non-default (draft) - // revision. + // Try to change the menu link title and description and save a new + // non-default (draft) revision. $edit = [ - 'menu[menu_parent]' => 'main:test_page_test.front_page', + 'menu[title]' => 'Test menu link draft', + 'menu[description]' => 'Test menu link description', 'moderation_state[0][state]' => 'draft', ]; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->assertSession()->responseContains(t('Page %label has been updated.', ['%label' => $node->toLink($node->label())->toString()])); - // Check that the menu settings were not applied. - $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.'); + // Ensure the content was not immediately published. + $this->assertSession()->linkExists('Test menu link'); - // Try to delete the menu link and save a new non-default (draft) revision. + // Publish the node and ensure the new link text was published. $edit = [ - 'menu[enabled]' => 0, - 'moderation_state[0][state]' => 'draft', + 'moderation_state[0][state]' => 'published', ]; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - - // Check that the menu settings were not applied. - $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.'); - $this->assertSession()->linkExists('Test menu link'); + $this->assertSession()->linkExists('Test menu link draft'); // Try to save a new non-default (draft) revision without any changes and // check that the error message is not shown. $edit = ['moderation_state[0][state]' => 'draft']; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - // Check that the menu settings were not applied. - $this->assertSession()->pageTextNotContains('You can only change the menu settings for the published version of this content.'); - $this->assertSession()->linkExists('Test menu link'); - // Create a node. $node = $this->drupalCreateNode(); @@ -153,14 +150,24 @@ public function testMenuUiWithPendingRevisions() { $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); $this->assertSession()->responseContains(t('Page %label has been updated.', ['%label' => $node->toLink($node->label())->toString()])); - // Add a menu link and save and create a new non-default (draft) revision. + // Add a menu link and save and create a new non-default (draft) revision + // and ensure it's not immediately published. $edit = [ 'menu[enabled]' => 1, - 'menu[title]' => 'Test menu link', + 'menu[title]' => 'Second test menu link', 'moderation_state[0][state]' => 'draft', ]; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.'); + $this->assertSession()->responseContains(t('Page %label has been updated.', ['%label' => $node->toLink($node->label())->toString()])); + $this->assertSession()->linkNotExists('Second test menu link'); + + // Publish the content and ensure the new menu link shows up. + $edit = [ + 'moderation_state[0][state]' => 'published', + ]; + $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); + $this->assertSession()->responseContains(t('Page %label has been updated.', ['%label' => $node->toLink($node->label())->toString()])); + $this->assertSession()->linkExists('Second test menu link'); } } diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiLanguageTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiLanguageTest.php index 9e0e844af..dcdb3277b 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiLanguageTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiLanguageTest.php @@ -30,6 +30,11 @@ class MenuUiLanguageTest extends BrowserTestBase { 'menu_ui', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -78,7 +83,7 @@ public function testMenuLanguage() { ]; $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save')); // Check the link was added with the correct menu link default language. - $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $link_title]); + $menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties(['title' => $link_title]); $menu_link = reset($menu_links); $this->assertMenuLink([ 'menu_name' => $menu_name, @@ -100,7 +105,7 @@ public function testMenuLanguage() { ]; $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save')); // Check the link was added with the correct new menu link default language. - $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $link_title]); + $menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties(['title' => $link_title]); $menu_link = reset($menu_links); $this->assertMenuLink([ 'menu_name' => $menu_name, diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php index 24a81d980..c1392b013 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php @@ -28,6 +28,11 @@ class MenuUiNodeTest extends BrowserTestBase { */ public static $modules = ['menu_ui', 'test_page_test', 'node', 'block', 'locale', 'language', 'content_translation']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -63,12 +68,12 @@ public function testMenuNodeFormWidget() { $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.roles:authenticated'); // Verify that the menu link title has the correct maxlength. - $title_max_length = \Drupal::entityManager()->getBaseFieldDefinitions('menu_link_content')['title']->getSetting('max_length'); + $title_max_length = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('menu_link_content')['title']->getSetting('max_length'); $this->drupalGet('node/add/page'); $this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.'); // Verify that the menu link description has the correct maxlength. - $description_max_length = \Drupal::entityManager()->getBaseFieldDefinitions('menu_link_content')['description']->getSetting('max_length'); + $description_max_length = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('menu_link_content')['description']->getSetting('max_length'); $this->drupalGet('node/add/page'); $this->assertPattern('//', 'Menu link description field has correct maxlength in node add form.'); @@ -217,7 +222,7 @@ public function testMenuNodeFormWidget() { // Assert that the link is still in the Administration menu after save. $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); $link = MenuLinkContent::load($item->id()); - $this->assertTrue($link, 'Link in not allowed menu still exists after saving node'); + $this->assertInstanceOf(MenuLinkContent::class, $link, 'Link in not allowed menu still exists after saving node'); // Move the menu link back to the Tools menu. $item->menu_name->value = 'tools'; diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php index e5c9f9646..d04075d00 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php @@ -39,6 +39,11 @@ class MenuUiTest extends BrowserTestBase { 'test_page_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with administration rights. * @@ -254,8 +259,8 @@ public function deleteCustomMenu() { $this->assertNull(Menu::load($menu_name), 'Custom menu was deleted'); // Test if all menu links associated with the menu were removed from // database. - $result = entity_load_multiple_by_properties('menu_link_content', ['menu_name' => $menu_name]); - $this->assertFalse($result, 'All menu links associated with the custom menu were deleted.'); + $result = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties(['menu_name' => $menu_name]); + $this->assertEmpty($result, 'All menu links associated with the custom menu were deleted.'); // Make sure there's no delete button on system menus. $this->drupalGet('admin/structure/menu/manage/main'); @@ -612,10 +617,10 @@ public function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $ex $this->assertResponse(200); $this->assertText('The menu link has been saved.'); - $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $title]); + $menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties(['title' => $title]); $menu_link = reset($menu_links); - $this->assertTrue($menu_link, 'Menu link was found in database.'); + $this->assertInstanceOf(MenuLinkContent::class, $menu_link, 'Menu link was found in database.'); $this->assertMenuLink(['menu_name' => $menu_name, 'children' => [], 'parent' => $parent], $menu_link->getPluginId()); return $menu_link; @@ -660,7 +665,7 @@ public function checkInvalidParentMenuLinks() { 'weight[0][value]' => '0', ]; $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $title]); + $menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties(['title' => $title]); $last_link = reset($menu_links); $created_links[] = 'tools:' . $last_link->getPluginId(); } @@ -1027,8 +1032,7 @@ public function testMenuUiWithPendingRevisions() { // Check that the menu overview form can be saved without errors when there // are pending revisions. $this->drupalPostForm('admin/structure/menu/manage/' . $menu_2->id(), [], 'Save'); - $errors = $this->xpath('//div[contains(@class, "messages--error")]'); - $this->assertFalse($errors, 'Menu overview form saved without errors.'); + $this->assertSession()->elementNotExists('xpath', '//div[contains(@class, "messages--error")]'); } } diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUninstallTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUninstallTest.php index a0320f44b..c176c5187 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUninstallTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUninstallTest.php @@ -19,15 +19,20 @@ class MenuUninstallTest extends BrowserTestBase { */ public static $modules = ['menu_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests Menu uninstall. */ public function testMenuUninstall() { \Drupal::service('module_installer')->uninstall(['menu_ui']); - \Drupal::entityManager()->getStorage('menu')->resetCache(['admin']); + \Drupal::entityTypeManager()->getStorage('menu')->resetCache(['admin']); - $this->assertTrue(Menu::load('admin'), 'The \'admin\' menu still exists after uninstalling Menu UI module.'); + $this->assertNotEmpty(Menu::load('admin'), 'The \'admin\' menu still exists after uninstalling Menu UI module.'); } } diff --git a/core/modules/menu_ui/tests/src/FunctionalJavascript/MenuUiJavascriptTest.php b/core/modules/menu_ui/tests/src/FunctionalJavascript/MenuUiJavascriptTest.php index 22959326d..fea04e60b 100644 --- a/core/modules/menu_ui/tests/src/FunctionalJavascript/MenuUiJavascriptTest.php +++ b/core/modules/menu_ui/tests/src/FunctionalJavascript/MenuUiJavascriptTest.php @@ -28,6 +28,11 @@ class MenuUiJavascriptTest extends WebDriverTestBase { 'test_page_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the contextual links on a menu block. */ diff --git a/core/modules/migrate/migrate.info.yml b/core/modules/migrate/migrate.info.yml index 577eea2a9..3d2f40805 100644 --- a/core/modules/migrate/migrate.info.yml +++ b/core/modules/migrate/migrate.info.yml @@ -2,11 +2,5 @@ name: Migrate type: module description: 'Handles migrations' package: Migration -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate/migrate.services.yml b/core/modules/migrate/migrate.services.yml index d9f7960b8..5ada988d1 100644 --- a/core/modules/migrate/migrate.services.yml +++ b/core/modules/migrate/migrate.services.yml @@ -30,3 +30,9 @@ services: plugin.manager.migration: class: Drupal\migrate\Plugin\MigrationPluginManager arguments: ['@module_handler', '@cache.discovery_migration', '@language_manager'] + migrate.lookup: + class: Drupal\migrate\MigrateLookup + arguments: ['@plugin.manager.migration'] + migrate.stub: + class: Drupal\migrate\MigrateStub + arguments: ['@plugin.manager.migration'] diff --git a/core/modules/migrate/src/Exception/EntityValidationException.php b/core/modules/migrate/src/Exception/EntityValidationException.php new file mode 100644 index 000000000..7d0fb246c --- /dev/null +++ b/core/modules/migrate/src/Exception/EntityValidationException.php @@ -0,0 +1,87 @@ +violations = $violations; + + $entity = $this->violations->getEntity(); + $locator = $entity->getEntityTypeId(); + + if ($entity_id = $entity->id()) { + $locator = sprintf('%s: %s', $locator, $entity_id); + + if ($entity instanceof RevisionableInterface && $revision_id = $entity->getRevisionId()) { + $locator .= sprintf(', revision: %s', $revision_id); + } + } + + // Example: "[user]: field_a=Violation 1., field_b=Violation 2.". + // Example: "[user: 1]: field_a=Violation 1., field_b=Violation 2.". + // Example: "[node: 19, revision: 12129]: field_a=Violation 1.". + parent::__construct(sprintf('[%s]: %s', $locator, implode(static::MESSAGES_SEPARATOR, $this->getViolationMessages()))); + } + + /** + * Returns the list of violation messages. + * + * @return string[] + * The list of violation messages. + */ + public function getViolationMessages() { + $messages = []; + + foreach ($this->violations as $violation) { + assert($violation instanceof ConstraintViolationInterface); + $messages[] = sprintf('%s=%s', $violation->getPropertyPath(), $violation->getMessage()); + } + + return $messages; + } + + /** + * Returns the list of violations generated during the entity validation. + * + * @return \Drupal\Core\Entity\EntityConstraintViolationListInterface + * The list of violations generated during the entity validation. + */ + public function getViolations() { + return $this->violations; + } + +} diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php index b0f2fa8a1..c3a050641 100644 --- a/core/modules/migrate/src/MigrateExecutable.php +++ b/core/modules/migrate/src/MigrateExecutable.php @@ -546,9 +546,9 @@ protected function attemptMemoryReclaim() { drupal_static_reset(); // Entity storage can blow up with caches so clear them out. - $manager = \Drupal::entityManager(); - foreach ($manager->getDefinitions() as $id => $definition) { - $manager->getStorage($id)->resetCache(); + $entity_type_manager = \Drupal::entityTypeManager(); + foreach ($entity_type_manager->getDefinitions() as $id => $definition) { + $entity_type_manager->getStorage($id)->resetCache(); } // @TODO: explore resetting the container. diff --git a/core/modules/migrate/src/MigrateLookup.php b/core/modules/migrate/src/MigrateLookup.php new file mode 100644 index 000000000..f3e116dd6 --- /dev/null +++ b/core/modules/migrate/src/MigrateLookup.php @@ -0,0 +1,74 @@ +migrationPluginManager = $migration_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public function lookup($migration_id, array $source_id_values) { + $results = []; + $migrations = $this->migrationPluginManager->createInstances($migration_id); + if (!$migrations) { + throw new PluginNotFoundException($migration_id); + } + foreach ($migrations as $migration) { + if ($result = $this->doLookup($migration, $source_id_values)) { + $results = array_merge($results, $result); + } + } + return $results; + } + + /** + * Performs a lookup. + * + * @param \Drupal\migrate\Plugin\MigrationInterface $migration + * The migration upon which to perform the lookup. + * @param array $source_id_values + * The source ID values to look up. + * + * @return array + * An array of arrays of destination identifier values. + * + * @throws \Drupal\migrate\MigrateException + * Thrown when $source_id_values contains unknown keys, or the wrong number + * of keys. + */ + protected function doLookup(MigrationInterface $migration, array $source_id_values) { + $destination_keys = array_keys($migration->getDestinationPlugin()->getIds()); + $indexed_ids = $migration->getIdMap() + ->lookupDestinationIds($source_id_values); + $keyed_ids = []; + foreach ($indexed_ids as $id) { + $keyed_ids[] = array_combine($destination_keys, $id); + } + return $keyed_ids; + } + +} diff --git a/core/modules/migrate/src/MigrateLookupInterface.php b/core/modules/migrate/src/MigrateLookupInterface.php new file mode 100644 index 000000000..ed2f2f3f1 --- /dev/null +++ b/core/modules/migrate/src/MigrateLookupInterface.php @@ -0,0 +1,33 @@ +migrationPluginManager = $migration_plugin_manager; + } + + /** + * Creates a stub. + * + * @param string $migration_id + * The migration to stub. + * @param array $source_ids + * An array of source ids. + * @param array $default_values + * (optional) An array of default values to add to the stub. + * @param bool $key_by_destination_ids + * (optional) NULL or TRUE to force indexing of the return array by + * destination id keys (default), or FALSE to return the raw return value of + * the destination plugin's ::import() method. The return value from + * MigrateDestinationInterface::import() is very poorly defined as "The + * entity ID or an indication of success". In practice, the mapping systems + * expect and all destination plugins return an array of destination + * identifiers. Unfortunately these arrays are inconsistently keyed. The + * core destination plugins return a numerically indexed array of + * destination identifiers, but several contrib destinations return an array + * of identifiers indexed by the destination keys. This method will + * generally index all return arrays for consistency and to provide as much + * information as possible, but this parameter is added for backwards + * compatibility to allow accessing the original array. + * + * @return array|false + * An array of destination ids for the new stub, keyed by destination id + * key, or false if the stub failed. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * @throws \Drupal\migrate\MigrateException + */ + public function createStub($migration_id, array $source_ids, array $default_values = [], $key_by_destination_ids = NULL) { + $migrations = $this->migrationPluginManager->createInstances([$migration_id]); + if (!$migrations) { + throw new PluginNotFoundException($migration_id); + } + if (count($migrations) !== 1) { + throw new \LogicException(sprintf('Cannot stub derivable migration "%s". You must specify the id of a specific derivative to stub.', $migration_id)); + } + $migration = reset($migrations); + $source_id_keys = array_keys($migration->getSourcePlugin()->getIds()); + if (count($source_id_keys) !== count($source_ids)) { + throw new \InvalidArgumentException('Expected and provided source id counts do not match.'); + } + if (array_keys($source_ids) === range(0, count($source_ids) - 1)) { + $source_ids = array_combine($source_id_keys, $source_ids); + } + $stub = $this->doCreateStub($migration, $source_ids, $default_values); + + // If the return from ::import is numerically indexed, and we aren't + // requesting the raw return value, index it associatively using the + // destination id keys. + if (($key_by_destination_ids !== FALSE) && array_keys($stub) === range(0, count($stub) - 1)) { + $stub = array_combine(array_keys($migration->getDestinationPlugin()->getIds()), $stub); + } + return $stub; + } + + /** + * Creates a stub. + * + * @param \Drupal\migrate\Plugin\MigrationInterface $migration + * The migration to use to create the stub. + * @param array $source_ids + * The source ids to map to the stub. + * @param array $default_values + * (optional) An array of values to include in the stub. + * + * @return array|bool + * An array of destination ids for the stub. + * + * @throws \Drupal\migrate\MigrateException + */ + protected function doCreateStub(MigrationInterface $migration, array $source_ids, array $default_values = []) { + $destination = $migration->getDestinationPlugin(TRUE); + $process = $migration->getProcess(); + $id_map = $migration->getIdMap(); + $migrate_executable = new MigrateExecutable($migration); + $row = new Row($source_ids + $migration->getSourceConfiguration(), $migration->getSourcePlugin()->getIds(), TRUE); + $migrate_executable->processRow($row, $process); + foreach ($default_values as $key => $value) { + $row->setDestinationProperty($key, $value); + } + $destination_ids = []; + try { + $destination_ids = $destination->import($row); + } + catch (\Exception $e) { + $id_map->saveMessage($row->getSourceIdValues(), $e->getMessage()); + } + if ($destination_ids) { + $id_map->saveIdMapping($row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE); + return $destination_ids; + } + return FALSE; + } + +} diff --git a/core/modules/migrate/src/MigrateStubInterface.php b/core/modules/migrate/src/MigrateStubInterface.php new file mode 100644 index 000000000..9b72f6953 --- /dev/null +++ b/core/modules/migrate/src/MigrateStubInterface.php @@ -0,0 +1,26 @@ +get('entity.manager')->getDefinitions() + $container->get('entity_type.manager')->getDefinitions() ); } diff --git a/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php b/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php index cee3b3985..99bb6530b 100644 --- a/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php +++ b/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php @@ -36,7 +36,7 @@ public function __construct(array $entity_definitions) { */ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( - $container->get('entity.manager')->getDefinitions() + $container->get('entity_type.manager')->getDefinitions() ); } diff --git a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php index 3482e4d3b..e69f56091 100644 --- a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php +++ b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php @@ -10,7 +10,9 @@ * Defines an interface for migrate ID mappings. * * Migrate ID mappings maintain a relation between source ID and destination ID - * for audit and rollback purposes. + * for audit and rollback purposes. The keys used in the migrate_map table are + * of the form sourceidN and destidN for the source and destination values + * respectively. */ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface { @@ -62,6 +64,27 @@ public function saveIdMapping(Row $row, array $destination_id_values, $status = */ public function saveMessage(array $source_id_values, $message, $level = MigrationInterface::MESSAGE_ERROR); + /** + * Retrieves a traversable object of messages related to source records. + * + * @param array $source_id_values + * (optional) The source identifier keyed values of the record, e.g. + * ['nid' => 5]. If empty (the default), all messages are retrieved. + * @param int $level + * (optional) Message severity. If NULL (the default), retrieve messages of + * all severities. + * + * @return \Traversable + * Retrieves a traversable object of message objects of unspecified class. + * Each object has the following public properties: + * - source_row_hash: the hash of the entire serialized source row data. + * - message: the text of the message. + * - level: one of MigrationInterface::MESSAGE_ERROR, + * MigrationInterface::MESSAGE_WARNING, MigrationInterface::MESSAGE_NOTICE, + * MigrationInterface::MESSAGE_INFORMATIONAL. + */ + public function getMessages(array $source_id_values = [], $level = NULL); + /** * Retrieves an iterator over messages relate to source records. * @@ -74,6 +97,11 @@ public function saveMessage(array $source_id_values, $message, $level = Migratio * * @return \Iterator * Retrieves an iterator over the message rows. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. + * Use \Drupal\migrate\Plugin\MigrateIdMapInterface::getMessages() instead. + * + * @see https://www.drupal.org/node/3060969 */ public function getMessageIterator(array $source_id_values = [], $level = NULL); @@ -210,7 +238,7 @@ public function lookupSourceId(array $destination_id_values); * @return array * The destination identifier values of the record, or empty on failure. * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use * lookupDestinationIds() instead. * * @see https://www.drupal.org/node/2725809 diff --git a/core/modules/migrate/src/Plugin/MigrateValidatableEntityInterface.php b/core/modules/migrate/src/Plugin/MigrateValidatableEntityInterface.php new file mode 100644 index 000000000..baf0cdfcf --- /dev/null +++ b/core/modules/migrate/src/Plugin/MigrateValidatableEntityInterface.php @@ -0,0 +1,36 @@ +destinationPluginManager = $destination_plugin_manager; $this->idMapPluginManager = $idmap_plugin_manager; - foreach (NestedArray::mergeDeep($plugin_definition, $configuration) as $key => $value) { + foreach (NestedArray::mergeDeepArray([$plugin_definition, $configuration], TRUE) as $key => $value) { $this->$key = $value; } } @@ -321,7 +322,7 @@ public function label() { * @return mixed * The value for that property, or NULL if the property does not exist. * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use * more specific getters instead. * * @see https://www.drupal.org/node/2873795 @@ -363,6 +364,9 @@ public function getProcessPlugins(array $process = NULL) { $this->processPlugins[$index] = []; foreach ($this->getProcessNormalized($process) as $property => $configurations) { $this->processPlugins[$index][$property] = []; + if (!is_array($configurations) && !$this->processPlugins[$index][$property]) { + throw new MigrateException(sprintf("Process configuration for '$property' must be an array", $property)); + } foreach ($configurations as $configuration) { if (isset($configuration['source'])) { $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance('get', $configuration, $this); @@ -615,6 +619,9 @@ public function setTrackLastImported($track_last_imported) { */ public function getMigrationDependencies() { $this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []]; + if (count($this->migration_dependencies) !== 2 || !is_array($this->migration_dependencies['required']) || !is_array($this->migration_dependencies['optional'])) { + throw new InvalidPluginDefinitionException($this->id(), "Invalid migration dependencies configuration for migration {$this->id()}"); + } $this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process))); return $this->migration_dependencies; } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/ComponentEntityDisplayBase.php b/core/modules/migrate/src/Plugin/migrate/destination/ComponentEntityDisplayBase.php index 1c94214fd..65b1d06b4 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/ComponentEntityDisplayBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/ComponentEntityDisplayBase.php @@ -2,8 +2,11 @@ namespace Drupal\migrate\Plugin\migrate\destination; +use Drupal\Core\Entity\EntityDisplayRepositoryInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Row; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a destination plugin for migrating entity display components. @@ -15,10 +18,54 @@ * @see \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityDisplay * @see \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityFormDisplay */ -abstract class ComponentEntityDisplayBase extends DestinationBase { +abstract class ComponentEntityDisplayBase extends DestinationBase implements ContainerFactoryPluginInterface { const MODE_NAME = ''; + /** + * The entity display repository. + * + * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface + */ + protected $entityDisplayRepository; + + /** + * PerComponentEntityDisplay constructor. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\migrate\Plugin\MigrationInterface $migration + * The migration. + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository + * The entity display repository service. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityDisplayRepositoryInterface $entity_display_repository = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration); + + if (!$entity_display_repository) { + @trigger_error('The entity_display.repository service must be passed to PerComponentEntityDisplay::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2835616.', E_USER_DEPRECATED); + $entity_display_repository = \Drupal::service('entity_display.repository'); + } + $this->entityDisplayRepository = $entity_display_repository; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $migration, + $container->get('entity_display.repository') + ); + } + /** * {@inheritdoc} */ diff --git a/core/modules/migrate/src/Plugin/migrate/destination/Entity.php b/core/modules/migrate/src/Plugin/migrate/destination/Entity.php index a3b173f8d..127920055 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/Entity.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/Entity.php @@ -13,11 +13,6 @@ /** * Provides a generic destination to import entities. * - * Available configuration keys: - * - translations: (optional) Boolean, if TRUE, the destination will be - * associated with the langcode provided by the source plugin. Defaults to - * FALSE. - * * Examples: * * @code @@ -48,12 +43,8 @@ * revision_timestamp: timestamp * destination: * plugin: entity:node - * translations: true * @endcode * - * This will save the processed, migrated row as a node with the relevant - * langcode because the translations configuration is set to "true". - * * @MigrateDestination( * id = "entity", * deriver = "Drupal\migrate\Plugin\Derivative\MigrateEntity" @@ -114,7 +105,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('entity.manager')->getStorage($entity_type_id), + $container->get('entity_type.manager')->getStorage($entity_type_id), array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id)) ); } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php index 868ad1a83..5ae0904cd 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php @@ -112,7 +112,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('entity.manager')->getStorage($entity_type_id), + $container->get('entity_type.manager')->getStorage($entity_type_id), array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id)), $container->get('language_manager'), $container->get('config.factory') diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php index 079eed08e..3f8dae982 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php @@ -2,14 +2,18 @@ namespace Drupal\migrate\Plugin\migrate\destination; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\TypedData\TranslatableInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\migrate\Audit\HighestIdInterface; +use Drupal\migrate\Exception\EntityValidationException; +use Drupal\migrate\Plugin\MigrateValidatableEntityInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\Plugin\MigrateIdMapInterface; @@ -25,6 +29,8 @@ * - overwrite_properties: (optional) A list of properties that will be * overwritten if an entity with the same ID already exists. Any properties * that are not listed will not be overwritten. + * - validate: (optional) Boolean, indicates whether an entity should be + * validated, defaults to FALSE. * * Example: * @@ -74,18 +80,27 @@ * overwrite_properties: * - title * - body + * # Run entity and fields validation before saving an entity. + * # @see \Drupal\Core\Entity\FieldableEntityInterface::validate() + * validate: true * @endcode * * @see \Drupal\migrate\Plugin\migrate\destination\EntityRevision */ -class EntityContentBase extends Entity implements HighestIdInterface { +class EntityContentBase extends Entity implements HighestIdInterface, MigrateValidatableEntityInterface { + use DeprecatedServicePropertyTrait; /** - * Entity manager. + * {@inheritdoc} + */ + protected $deprecatedProperties = ['entityManager' => 'entity.manager']; + + /** + * Entity field manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityFieldManagerInterface */ - protected $entityManager; + protected $entityFieldManager; /** * Field type plugin manager. @@ -109,14 +124,14 @@ class EntityContentBase extends Entity implements HighestIdInterface { * The storage for this entity type. * @param array $bundles * The list of bundles this entity type has. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager service. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager. * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager * The field type plugin manager service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles); - $this->entityManager = $entity_manager; + $this->entityFieldManager = $entity_field_manager; $this->fieldTypeManager = $field_type_manager; } @@ -130,15 +145,20 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('entity.manager')->getStorage($entity_type), + $container->get('entity_type.manager')->getStorage($entity_type), array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type)), - $container->get('entity.manager'), + $container->get('entity_field.manager'), $container->get('plugin.manager.field.field_type') ); } /** * {@inheritdoc} + * + * @throws \Drupal\migrate\MigrateException + * When an entity cannot be looked up. + * @throws \Drupal\migrate\Exception\EntityValidationException + * When an entity validation hasn't been passed. */ public function import(Row $row, array $old_destination_id_values = []) { $this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE; @@ -146,7 +166,10 @@ public function import(Row $row, array $old_destination_id_values = []) { if (!$entity) { throw new MigrateException('Unable to get entity'); } - + assert($entity instanceof ContentEntityInterface); + if ($this->isEntityValidationRequired($entity)) { + $this->validateEntity($entity); + } $ids = $this->save($entity, $old_destination_id_values); if ($this->isTranslationDestination()) { $ids[] = $entity->language()->getId(); @@ -154,6 +177,27 @@ public function import(Row $row, array $old_destination_id_values = []) { return $ids; } + /** + * {@inheritdoc} + */ + public function isEntityValidationRequired(FieldableEntityInterface $entity) { + // Prioritize the entity method over migration config because it won't be + // possible to save that entity unvalidated. + /* @see \Drupal\Core\Entity\ContentEntityBase::preSave() */ + return $entity->isValidationRequired() || !empty($this->configuration['validate']); + } + + /** + * {@inheritdoc} + */ + public function validateEntity(FieldableEntityInterface $entity) { + $violations = $entity->validate(); + + if (count($violations) > 0) { + throw new EntityValidationException($violations); + } + } + /** * Saves the entity. * @@ -272,7 +316,7 @@ protected function processStubRow(Row $row) { } // Populate any required fields not already populated. - $fields = $this->entityManager + $fields = $this->entityFieldManager ->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key); foreach ($fields as $field_name => $field_definition) { if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) { @@ -346,7 +390,7 @@ public function rollback(array $destination_identifier) { protected function getDefinitionFromEntity($key) { $entity_type_id = static::getEntityTypeId($this->getPluginId()); /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */ - $definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id); + $definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id); $field_definition = $definitions[$key]; return [ diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php index ff3f0071c..4b651fe9f 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php @@ -3,7 +3,7 @@ namespace Drupal\migrate\Plugin\migrate\destination; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -114,11 +114,11 @@ class EntityRevision extends EntityContentBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager) { $plugin_definition += [ 'label' => new TranslatableMarkup('@entity_type revisions', ['@entity_type' => $storage->getEntityType()->getSingularLabel()]), ]; - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager); + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_field_manager, $field_type_manager); } /** diff --git a/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityDisplay.php b/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityDisplay.php index 3b9a982f0..499ff0f45 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityDisplay.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityDisplay.php @@ -56,7 +56,7 @@ class PerComponentEntityDisplay extends ComponentEntityDisplayBase { * {@inheritdoc} */ protected function getEntity($entity_type, $bundle, $view_mode) { - return entity_get_display($entity_type, $bundle, $view_mode); + return $this->entityDisplayRepository->getViewDisplay($entity_type, $bundle, $view_mode); } } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityFormDisplay.php b/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityFormDisplay.php index bd837f03f..667e8fbcb 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityFormDisplay.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/PerComponentEntityFormDisplay.php @@ -49,7 +49,7 @@ class PerComponentEntityFormDisplay extends ComponentEntityDisplayBase { * {@inheritdoc} */ protected function getEntity($entity_type, $bundle, $form_mode) { - return entity_get_form_display($entity_type, $bundle, $form_mode); + return $this->entityDisplayRepository->getFormDisplay($entity_type, $bundle, $form_mode); } } diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php b/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php index 0e10ea9df..0699703ec 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php @@ -56,6 +56,7 @@ public function lookupSourceId(array $destination_id_values) { * {@inheritdoc} */ public function lookupDestinationId(array $source_id_values) { + @trigger_error(__NAMESPACE__ . '\NullIdMap::lookupDestinationId() is deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use Sql::lookupDestinationIds() instead. See https://www.drupal.org/node/2725809', E_USER_DEPRECATED); return []; } @@ -83,10 +84,18 @@ public function saveMessage(array $source_id_values, $message, $level = Migratio /** * {@inheritdoc} */ - public function getMessageIterator(array $source_id_values = [], $level = NULL) { + public function getMessages(array $source_id_values = [], $level = NULL) { return new \ArrayIterator([]); } + /** + * {@inheritdoc} + */ + public function getMessageIterator(array $source_id_values = [], $level = NULL) { + @trigger_error('getMessageIterator() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use getMessages() instead. See https://www.drupal.org/node/3060969', E_USER_DEPRECATED); + return $this->getMessages($source_id_values, $level); + } + /** * {@inheritdoc} */ diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 120b68052..03f631fb2 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -545,6 +545,7 @@ public function lookupSourceId(array $destination_id_values) { * {@inheritdoc} */ public function lookupDestinationId(array $source_id_values) { + @trigger_error(__NAMESPACE__ . '\Sql::lookupDestinationId() is deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use Sql::lookupDestinationIds() instead. See https://www.drupal.org/node/2725809', E_USER_DEPRECATED); $results = $this->lookupDestinationIds($source_id_values); return $results ? reset($results) : []; } @@ -675,19 +676,36 @@ public function saveMessage(array $source_id_values, $message, $level = Migratio /** * {@inheritdoc} */ - public function getMessageIterator(array $source_id_values = [], $level = NULL) { - $query = $this->getDatabase()->select($this->messageTableName(), 'msg') - ->fields('msg'); + public function getMessages(array $source_id_values = [], $level = NULL) { + $query = $this->getDatabase()->select($this->messageTableName(), 'msg'); + $condition = sprintf('msg.%s = map.%s', $this::SOURCE_IDS_HASH, $this::SOURCE_IDS_HASH); + $query->addJoin('LEFT', $this->mapTableName(), 'map', $condition); + // Explicitly define the fields we want. The order will be preserved: source + // IDs, destination IDs (if possible), and then the rest. + foreach ($this->sourceIdFields() as $id => $column_name) { + $query->addField('map', $column_name, "src_$id"); + } + foreach ($this->destinationIdFields() as $id => $column_name) { + $query->addField('map', $column_name, "dest_$id"); + } + $query->fields('msg', ['msgid', $this::SOURCE_IDS_HASH, 'level', 'message']); if ($source_id_values) { - $query->condition($this::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values)); + $query->condition('msg.' . $this::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values)); } - if ($level) { - $query->condition('level', $level); + $query->condition('msg.level', $level); } return $query->execute(); } + /** + * {@inheritdoc} + */ + public function getMessageIterator(array $source_id_values = [], $level = NULL) { + @trigger_error('getMessageIterator() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use getMessages() instead. See https://www.drupal.org/node/3060969', E_USER_DEPRECATED); + return $this->getMessages($source_id_values, $level); + } + /** * {@inheritdoc} */ diff --git a/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php b/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php index 90801ebdc..2a0f8891d 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php +++ b/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php @@ -15,7 +15,7 @@ * * @link https://www.drupal.org/node/2345929 Online handbook documentation for dedupebase process plugin @endlink * - * @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate\Plugin\migrate\process\MakeUniqueBase instead. * * @see https://www.drupal.org/node/2873762 diff --git a/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php b/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php index e63591883..67d7cbf8e 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php +++ b/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php @@ -17,7 +17,7 @@ * id = "dedupe_entity" * ) * - * @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate\Plugin\migrate\process\MakeUniqueEntityField instead. * * @see https://www.drupal.org/node/2873762 diff --git a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php index f69ab140f..8f50f4ca7 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php +++ b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php @@ -6,6 +6,7 @@ use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StreamWrapper\LocalStream; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\MigrateExecutableInterface; @@ -166,7 +167,8 @@ public function transform($value, MigrateExecutableInterface $migrate_executable * @param string $destination * The destination path or URI. * @param int $replace - * (optional) FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME. + * (optional) FileSystemInterface::EXISTS_REPLACE (default) or + * FileSystemInterface::EXISTS_RENAME. * * @return string|bool * File destination on success, FALSE on failure. @@ -243,7 +245,7 @@ protected function isLocationUnchanged($source, $destination) { * @return bool */ protected function isLocalUri($uri) { - $scheme = $this->fileSystem->uriScheme($uri); + $scheme = StreamWrapperManager::getScheme($uri); // The vfs scheme is vfsStream, which is used in testing. vfsStream is a // simulated file system that exists only in memory, but should be treated diff --git a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php index 4878ad45f..637a9fe90 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php +++ b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php @@ -7,7 +7,7 @@ /** * Iterates and processes an associative array. * - * @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate\Plugin\migrate\process\SubProcess instead. * * @see https://www.drupal.org/node/2880427 diff --git a/core/modules/migrate/src/Plugin/migrate/process/MenuLinkParent.php b/core/modules/migrate/src/Plugin/migrate/process/MenuLinkParent.php index 2d33e3abe..8e30c8516 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/MenuLinkParent.php +++ b/core/modules/migrate/src/Plugin/migrate/process/MenuLinkParent.php @@ -2,10 +2,12 @@ namespace Drupal\migrate\Plugin\migrate\process; +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Menu\MenuLinkManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Url; +use Drupal\migrate\MigrateLookupInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\MigrateSkipRowException; @@ -29,21 +31,70 @@ class MenuLinkParent extends ProcessPluginBase implements ContainerFactoryPlugin protected $menuLinkManager; /** + * The Migration process plugin. + * * @var \Drupal\migrate\Plugin\MigrateProcessInterface + * + * @deprecated in drupal:8.8.x and is removed from drupal:9.0.0. Use + * the migrate.lookup service instead. + * + * @see https://www.drupal.org/node/3047268 */ protected $migrationPlugin; + /** + * The currently running migration. + * + * @var \Drupal\migrate\Plugin\MigrationInterface + */ + protected $migration; + + /** + * The migrate lookup service. + * + * @var \Drupal\migrate\MigrateLookupInterface + */ + protected $migrateLookup; + /** * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $menuLinkStorage; /** - * {@inheritdoc} + * Constructs a MenuLinkParent object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\migrate\MigrateLookupInterface $migrate_lookup + * The migrate lookup service. + * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager + * The menu link manager. + * @param \Drupal\Core\Entity\EntityStorageInterface $menu_link_storage + * The menu link storage object. + * @param \Drupal\migrate\Plugin\MigrationInterface $migration + * The currently running migration. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateProcessInterface $migration_plugin, MenuLinkManagerInterface $menu_link_manager, EntityStorageInterface $menu_link_storage) { + // @codingStandardsIgnoreLine + public function __construct(array $configuration, $plugin_id, $plugin_definition, $migrate_lookup, MenuLinkManagerInterface $menu_link_manager, EntityStorageInterface $menu_link_storage, MigrationInterface $migration = NULL) { parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->migrationPlugin = $migration_plugin; + if ($migrate_lookup instanceof MigrateProcessInterface) { + @trigger_error('Passing a migration process plugin as the fourth argument to ' . __METHOD__ . ' is deprecated in drupal:8.8.0 and will throw an error in drupal:9.0.0. Pass the migrate.lookup service instead. See https://www.drupal.org/node/3047268', E_USER_DEPRECATED); + $this->migrationPlugin = $migrate_lookup; + $migrate_lookup = \Drupal::service('migrate.lookup'); + } + elseif (!$migrate_lookup instanceof MigrateLookupInterface) { + throw new \InvalidArgumentException("The fourth argument to " . __METHOD__ . " must be an instance of MigrateLookupInterface."); + } + elseif (!$migration instanceof MigrationInterface) { + throw new \InvalidArgumentException("The seventh argument to " . __METHOD__ . " must be an instance of MigrationInterface."); + } + $this->migration = $migration; + $this->migrateLookup = $migrate_lookup; $this->menuLinkManager = $menu_link_manager; $this->menuLinkStorage = $menu_link_storage; } @@ -57,9 +108,10 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('plugin.manager.migrate.process')->createInstance('migration', $migration_configuration, $migration), + $container->get('migrate.lookup'), $container->get('plugin.manager.menu.link'), - $container->get('entity.manager')->getStorage('menu_link_content') + $container->get('entity_type.manager')->getStorage('menu_link_content'), + $migration ); } @@ -74,28 +126,50 @@ public function transform($value, MigrateExecutableInterface $migrate_executable // Top level item. return ''; } - try { - $already_migrated_id = $this - ->migrationPlugin - ->transform($parent_id, $migrate_executable, $row, $destination_property); - if ($already_migrated_id && ($link = $this->menuLinkStorage->load($already_migrated_id))) { - return $link->getPluginId(); + // This BC layer is included because if the plugin constructor was called + // in the legacy way with a migration_lookup process plugin, it may have + // been preconfigured with a different migration to look up against. While + // this is unlikely, for maximum BC we will continue to use the plugin to do + // the lookup if it is provided, and support for this will be removed in + // Drupal 9. + if ($this->migrationPlugin) { + try { + $already_migrated_id = $this + ->migrationPlugin + ->transform($parent_id, $migrate_executable, $row, $destination_property); + } + catch (MigrateSkipRowException $e) { + } + } + else { + $lookup_result = $this->migrateLookup->lookup($this->migration->id(), [$parent_id]); + if ($lookup_result) { + $already_migrated_id = $lookup_result[0]['id']; } } - catch (MigrateSkipRowException $e) { + if (!empty($already_migrated_id) && ($link = $this->menuLinkStorage->load($already_migrated_id))) { + return $link->getPluginId(); } + if (isset($value[1])) { list($menu_name, $parent_link_path) = $value; - $url = Url::fromUserInput("/$parent_link_path"); - if ($url->isRouted()) { - $links = $this->menuLinkManager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters(), $menu_name); - if (count($links) == 1) { - /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ - $link = reset($links); - return $link->getPluginId(); + + $links = []; + if (UrlHelper::isExternal($parent_link_path)) { + $links = $this->menuLinkStorage->loadByProperties(['link__uri' => $parent_link_path]); + } + else { + $url = Url::fromUserInput("/$parent_link_path"); + if ($url->isRouted()) { + $links = $this->menuLinkManager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters(), $menu_name); } } + if (count($links) == 1) { + /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ + $link = reset($links); + return $link->getPluginId(); + } } throw new MigrateSkipRowException(sprintf("No parent link found for plid '%d' in menu '%s'.", $parent_id, $value[0])); } diff --git a/core/modules/migrate/src/Plugin/migrate/process/Migration.php b/core/modules/migrate/src/Plugin/migrate/process/Migration.php index 8cae1dd70..bee3863d3 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/Migration.php +++ b/core/modules/migrate/src/Plugin/migrate/process/Migration.php @@ -13,7 +13,7 @@ * id = "migration" * ) * - * @deprecated in Drupal 8.3.x and will be removed in Drupal 9.0.x. + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. * Use \Drupal\migrate\Plugin\migrate\process\MigrationLookup instead. */ class Migration extends MigrationLookup {} diff --git a/core/modules/migrate/src/Plugin/migrate/process/MigrationLookup.php b/core/modules/migrate/src/Plugin/migrate/process/MigrationLookup.php index 57ba7deee..2f9f296bc 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/MigrationLookup.php +++ b/core/modules/migrate/src/Plugin/migrate/process/MigrationLookup.php @@ -2,10 +2,13 @@ namespace Drupal\migrate\Plugin\migrate\process; +use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\migrate\MigrateException; +use Drupal\migrate\MigrateLookupInterface; use Drupal\migrate\MigrateSkipProcessException; -use Drupal\migrate\Plugin\MigrationPluginManagerInterface; -use Drupal\migrate\Plugin\MigrateIdMapInterface; +use Drupal\migrate\MigrateSkipRowException; +use Drupal\migrate\MigrateStubInterface; use Drupal\migrate\ProcessPluginBase; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateExecutableInterface; @@ -106,18 +109,25 @@ class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPluginInterface { /** - * The migration plugin manager. + * The migration to be executed. * - * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface + * @var \Drupal\migrate\Plugin\MigrationInterface */ - protected $migrationPluginManager; + protected $migration; /** - * The migration to be executed. + * The migrate lookup service. * - * @var \Drupal\migrate\Plugin\MigrationInterface + * @var \Drupal\migrate\MigrateLookupInterface */ - protected $migration; + protected $migrateLookup; + + /** + * The migrate stub service. + * + * @var \Drupal\migrate\MigrateStubInterface + */ + protected $migrateStub; /** * Constructs a MigrationLookup object. @@ -130,13 +140,26 @@ class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPlugi * The plugin implementation definition. * @param \Drupal\migrate\Plugin\MigrationInterface $migration * The Migration the plugin is being used in. - * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager - * The Migration Plugin Manager Interface. + * @param \Drupal\migrate\MigrateLookupInterface $migrate_lookup + * The migrate lookup service. + * @param \Drupal\migrate\MigrateStubInterface $migrate_stub + * The migrate stub service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager) { + // @codingStandardsIgnoreLine + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, $migrate_lookup, $migrate_stub = NULL) { parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->migrationPluginManager = $migration_plugin_manager; + if (!$migrate_lookup instanceof MigrateLookupInterface) { + @trigger_error('Not passing the migrate lookup service as the fifth parameter to ' . __METHOD__ . ' is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \\Drupal\\migrate\\MigrateLookupInterface. See https://www.drupal.org/node/3047268', E_USER_DEPRECATED); + $migrate_lookup = \Drupal::service('migrate.lookup'); + } + if (!$migrate_stub instanceof MigrateStubInterface) { + @trigger_error('Not passing the migrate stub service as the sixth parameter to ' . __METHOD__ . ' is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \\Drupal\\migrate\\MigrateStubInterface. See https://www.drupal.org/node/3047268', E_USER_DEPRECATED); + $migrate_stub = \Drupal::service('migrate.stub'); + } $this->migration = $migration; + + $this->migrateLookup = $migrate_lookup; + $this->migrateStub = $migrate_stub; } /** @@ -148,7 +171,8 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('plugin.manager.migration') + $container->get('migrate.lookup'), + $container->get('migrate.stub') ); } @@ -156,20 +180,14 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} * * @throws \Drupal\migrate\MigrateSkipProcessException + * @throws \Drupal\migrate\MigrateException */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { - $lookup_migrations_ids = $this->configuration['migration']; - if (!is_array($lookup_migrations_ids)) { - $lookup_migrations_ids = [$lookup_migrations_ids]; - } + $lookup_migration_ids = (array) $this->configuration['migration']; $self = FALSE; - /** @var \Drupal\migrate\Plugin\MigrationInterface[] $lookup_migrations */ $destination_ids = NULL; $source_id_values = []; - - $lookup_migrations = $this->migrationPluginManager->createInstances($lookup_migrations_ids); - - foreach ($lookup_migrations as $lookup_migration_id => $lookup_migration) { + foreach ($lookup_migration_ids as $lookup_migration_id) { if ($lookup_migration_id == $this->migration->id()) { $self = TRUE; } @@ -181,8 +199,24 @@ public function transform($value, MigrateExecutableInterface $migrate_executable } $this->skipInvalid($value); $source_id_values[$lookup_migration_id] = $value; - // Break out of the loop as soon as a destination ID is found. - if ($destination_ids = $lookup_migration->getIdMap()->lookupDestinationId($source_id_values[$lookup_migration_id])) { + + // Re-throw any PluginException as a MigrateException so the executable + // can shut down the migration. + try { + $destination_id_array = $this->migrateLookup->lookup($lookup_migration_id, $value); + } + catch (PluginNotFoundException $e) { + $destination_id_array = []; + } + catch (MigrateException $e) { + throw $e; + } + catch (\Exception $e) { + throw new MigrateException(sprintf('A %s was thrown while processing this migration lookup', gettype($e)), $e->getCode(), $e); + } + + if ($destination_id_array) { + $destination_ids = array_values(reset($destination_id_array)); break; } } @@ -191,45 +225,38 @@ public function transform($value, MigrateExecutableInterface $migrate_executable return NULL; } - if (!$destination_ids && ($self || isset($this->configuration['stub_id']) || count($lookup_migrations) == 1)) { + if (!$destination_ids && ($self || isset($this->configuration['stub_id']) || count($lookup_migration_ids) == 1)) { // If the lookup didn't succeed, figure out which migration will do the // stubbing. if ($self) { - $stub_migration = $this->migration; + $stub_migration = $this->migration->id(); } elseif (isset($this->configuration['stub_id'])) { - $stub_migration = $lookup_migrations[$this->configuration['stub_id']]; + $stub_migration = $this->configuration['stub_id']; } else { - $stub_migration = reset($lookup_migrations); - } - $destination_plugin = $stub_migration->getDestinationPlugin(TRUE); - // Only keep the process necessary to produce the destination ID. - $process = $stub_migration->getProcess(); - - // We already have the source ID values but need to key them for the Row - // constructor. - $source_ids = $stub_migration->getSourcePlugin()->getIds(); - $values = []; - foreach (array_keys($source_ids) as $index => $source_id) { - $values[$source_id] = $source_id_values[$stub_migration->id()][$index]; + $stub_migration = reset($lookup_migration_ids); } - - $stub_row = $this->createStubRow($values + $stub_migration->getSourceConfiguration(), $source_ids); - - // Do a normal migration with the stub row. - $migrate_executable->processRow($stub_row, $process); - $destination_ids = []; - $id_map = $stub_migration->getIdMap(); + // Rethrow any exception as a MigrateException so the executable can shut + // down the migration. try { - $destination_ids = $destination_plugin->import($stub_row); + $destination_ids = $this->migrateStub->createStub($stub_migration, $source_id_values[$stub_migration], [], FALSE); } - catch (\Exception $e) { - $id_map->saveMessage($stub_row->getSourceIdValues(), $e->getMessage()); + catch (\LogicException $e) { + // For BC reasons, we must allow attempting to stub a derived migration. } - - if ($destination_ids) { - $id_map->saveIdMapping($stub_row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE); + catch (PluginNotFoundException $e) { + // For BC reasons, we must allow attempting to stub a non-existent + // migration. + } + catch (MigrateException $e) { + throw $e; + } + catch (MigrateSkipRowException $e) { + throw $e; + } + catch (\Exception $e) { + throw new MigrateException(sprintf('A(n) %s was thrown while attempting to stub.', gettype($e)), $e->getCode(), $e); } } if ($destination_ids) { @@ -285,8 +312,14 @@ protected function isValid($value) { * * @return \Drupal\migrate\Row * The stub row. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the + * migrate.stub service to create stubs. + * + * @see https://www.drupal.org/node/3047268 */ protected function createStubRow(array $values, array $source_ids) { + @trigger_error(__METHOD__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the migrate.stub service to create stubs. See https://www.drupal.org/node/3047268', E_USER_DEPRECATED); return new Row($values, $source_ids, TRUE); } diff --git a/core/modules/migrate/src/Plugin/migrate/process/NullCoalesce.php b/core/modules/migrate/src/Plugin/migrate/process/NullCoalesce.php new file mode 100644 index 000000000..eda70f988 --- /dev/null +++ b/core/modules/migrate/src/Plugin/migrate/process/NullCoalesce.php @@ -0,0 +1,57 @@ +configuration['default_value'])) { + return $this->configuration['default_value']; + } + return NULL; + } + +} diff --git a/core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml b/core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml index 3794cbf5f..8f2da388c 100644 --- a/core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml +++ b/core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml @@ -2,11 +2,5 @@ name: 'Migrate entity test' type: module description: 'Support module for entity destination test.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate/tests/modules/migrate_events_test/migrate_events_test.info.yml b/core/modules/migrate/tests/modules/migrate_events_test/migrate_events_test.info.yml index 8c72637de..6ef8e1ffa 100644 --- a/core/modules/migrate/tests/modules/migrate_events_test/migrate_events_test.info.yml +++ b/core/modules/migrate/tests/modules/migrate_events_test/migrate_events_test.info.yml @@ -1,11 +1,5 @@ name: 'Migrate events test' type: module package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate/tests/modules/migrate_external_translated_test/migrate_external_translated_test.info.yml b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrate_external_translated_test.info.yml index a8dbb34d3..8573407fa 100644 --- a/core/modules/migrate/tests/modules/migrate_external_translated_test/migrate_external_translated_test.info.yml +++ b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrate_external_translated_test.info.yml @@ -1,14 +1,8 @@ name: 'Migration external translated test' type: module package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:node - drupal:migrate - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate/tests/modules/migrate_high_water_test/migrate_high_water_test.info.yml b/core/modules/migrate/tests/modules/migrate_high_water_test/migrate_high_water_test.info.yml index 238759248..98340a97e 100644 --- a/core/modules/migrate/tests/modules/migrate_high_water_test/migrate_high_water_test.info.yml +++ b/core/modules/migrate/tests/modules/migrate_high_water_test/migrate_high_water_test.info.yml @@ -2,12 +2,6 @@ type: module name: Migration High Water Test description: 'Provides test fixtures for testing high water marks.' package: Testing -# core: 8.x +core: 8.x dependencies: - drupal:migrate - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate/tests/modules/migrate_lookup_test/migrate_lookup_test.info.yml b/core/modules/migrate/tests/modules/migrate_lookup_test/migrate_lookup_test.info.yml new file mode 100644 index 000000000..e93c4949a --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_lookup_test/migrate_lookup_test.info.yml @@ -0,0 +1,8 @@ +name: 'Migration Lookup Test' +type: module +description: 'Provides test migrations to test migration lookup service.' +package: Testing +version: VERSION +core: 8.x +dependencies: + - drupal:migrate diff --git a/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration.yml b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration.yml new file mode 100644 index 000000000..1c313e9cb --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration.yml @@ -0,0 +1,20 @@ +id: sample_lookup_migration +label: Sample Lookup Migration +source: + plugin: embedded_data + data_rows: + - id: 17 + nid: 1 + title: Node 1 + - id: 25 + nid: 2 + title: Node 2 + ids: + id: + type: integer +process: + nid: nid + title: title +destination: + default_bundle: node_lookup + plugin: entity:node diff --git a/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_2.yml b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_2.yml new file mode 100644 index 000000000..474518909 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_2.yml @@ -0,0 +1,20 @@ +id: sample_lookup_migration_2 +label: Sample Lookup Migration +source: + plugin: embedded_data + data_rows: + - id: 27 + nid: 3 + title: Node 3 + - id: 35 + nid: 4 + title: Node 4 + ids: + id: + type: integer +process: + nid: nid + title: title +destination: + default_bundle: node_lookup + plugin: entity:node diff --git a/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_multiple_source_ids.yml b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_multiple_source_ids.yml new file mode 100644 index 000000000..a8524b17c --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_multiple_source_ids.yml @@ -0,0 +1,28 @@ +id: sample_lookup_migration_multiple_source_ids +label: "Sample Lookup Migration With Multiple Source Ids." +source: + plugin: embedded_data + data_rows: + - id: 17 + version_id: 17 + nid: 1 + title: "Node 1" + - id: 25 + version_id: 25 + nid: 2 + title: "Node 2" + - id: 25 + version_id: 26 + nid: 3 + title: "Node 3" + ids: + id: + type: integer + version_id: + type: integer +process: + nid: nid + title: title +destination: + default_bundle: node_lookup + plugin: entity:node diff --git a/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_string_ids.yml b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_string_ids.yml new file mode 100644 index 000000000..3cb922bc5 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_lookup_test/migrations/sample_lookup_migration_string_ids.yml @@ -0,0 +1,20 @@ +id: sample_lookup_migration_string_ids +label: Sample Lookup Migration with string ids +source: + plugin: embedded_data + data_rows: + - id: node1 + nid: 10 + title: Node 10 + - id: node2 + nid: 11 + title: Node 11 + ids: + id: + type: string +process: + nid: nid + title: title +destination: + default_bundle: node_lookup + plugin: entity:node diff --git a/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.info.yml b/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.info.yml index c482eaf4d..f9ec92f78 100644 --- a/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.info.yml +++ b/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.info.yml @@ -2,11 +2,5 @@ name: 'Migrate module prepareRow tests' type: module description: 'Support module for source plugin prepareRow testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate/tests/modules/migrate_query_batch_test/migrate_query_batch_test.info.yml b/core/modules/migrate/tests/modules/migrate_query_batch_test/migrate_query_batch_test.info.yml index 230c21381..acaeec4d5 100644 --- a/core/modules/migrate/tests/modules/migrate_query_batch_test/migrate_query_batch_test.info.yml +++ b/core/modules/migrate/tests/modules/migrate_query_batch_test/migrate_query_batch_test.info.yml @@ -2,12 +2,6 @@ type: module name: Migrate query batch Source test description: 'Provides a database table and records for SQL import with batch testing.' package: Testing -# core: 8.x +core: 8.x dependencies: - drupal:migrate - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate/tests/modules/migrate_stub_test/migrate_stub_test.info.yml b/core/modules/migrate/tests/modules/migrate_stub_test/migrate_stub_test.info.yml new file mode 100644 index 000000000..2b0440185 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_stub_test/migrate_stub_test.info.yml @@ -0,0 +1,8 @@ +name: 'Migration Stub Test' +type: module +description: 'Provides test migrations to test migration stub service.' +package: Testing +version: VERSION +core: 8.x +dependencies: + - drupal:migrate diff --git a/core/modules/migrate/tests/modules/migrate_stub_test/migrations/sample_stubbing_migration.yml b/core/modules/migrate/tests/modules/migrate_stub_test/migrations/sample_stubbing_migration.yml new file mode 100644 index 000000000..8d86ee435 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_stub_test/migrations/sample_stubbing_migration.yml @@ -0,0 +1,23 @@ +id: sample_stubbing_migration +label: "Sample Stubbing Migration" +source: + plugin: embedded_data + data_rows: + - id: 17 + title: "Sample 1" + bodyvalue: "This is the body for ID 17" + bodyformat: "plain_text" + - id: 25 + title: "Sample 2" + bodyvalue: "This is the body for ID 25" + bodyformat: "plain_text" + ids: + id: + type: integer +process: + title: title + body/0/value: bodyvalue + body/0/format: bodyformat +destination: + default_bundle: node_stub + plugin: entity:node diff --git a/core/modules/migrate/tests/modules/migrate_stub_test/migrations/sample_stubbing_migration_with_multiple_source_ids.yml b/core/modules/migrate/tests/modules/migrate_stub_test/migrations/sample_stubbing_migration_with_multiple_source_ids.yml new file mode 100644 index 000000000..1192e0180 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_stub_test/migrations/sample_stubbing_migration_with_multiple_source_ids.yml @@ -0,0 +1,27 @@ +id: sample_stubbing_migration_with_multiple_source_ids +label: "Sample stubbing migration with multiple source ids." +source: + plugin: embedded_data + data_rows: + - id: 17 + version_id: 17 + title: "Sample 1" + bodyvalue: "This is the body for ID 17" + bodyformat: "plain_text" + - id: 25 + version_id: 25 + title: "Sample 2" + bodyvalue: "This is the body for ID 25" + bodyformat: "plain_text" + ids: + id: + type: integer + version_id: + type: integer +process: + title: title + body/0/value: bodyvalue + body/0/format: bodyformat +destination: + default_bundle: node_stub + plugin: entity:node diff --git a/core/modules/migrate/tests/modules/migrate_track_changes_test/migrate_track_changes_test.info.yml b/core/modules/migrate/tests/modules/migrate_track_changes_test/migrate_track_changes_test.info.yml index b644f188f..6f4141069 100644 --- a/core/modules/migrate/tests/modules/migrate_track_changes_test/migrate_track_changes_test.info.yml +++ b/core/modules/migrate/tests/modules/migrate_track_changes_test/migrate_track_changes_test.info.yml @@ -2,12 +2,6 @@ type: module name: Migration Track Changes Test description: 'Provides test fixtures for testing track changes marks.' package: Testing -# core: 8.x +core: 8.x dependencies: - drupal:migrate - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate/tests/modules/migration_directory_test/migration_directory_test.info.yml b/core/modules/migrate/tests/modules/migration_directory_test/migration_directory_test.info.yml index 7e68ffe9b..c9bce4b82 100644 --- a/core/modules/migrate/tests/modules/migration_directory_test/migration_directory_test.info.yml +++ b/core/modules/migrate/tests/modules/migration_directory_test/migration_directory_test.info.yml @@ -1,13 +1,7 @@ name: 'Migration directory test' type: module package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:migrate - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate/tests/src/Functional/process/DownloadFunctionalTest.php b/core/modules/migrate/tests/src/Functional/process/DownloadFunctionalTest.php index 5e890a0e6..bf6da018c 100644 --- a/core/modules/migrate/tests/src/Functional/process/DownloadFunctionalTest.php +++ b/core/modules/migrate/tests/src/Functional/process/DownloadFunctionalTest.php @@ -19,6 +19,11 @@ class DownloadFunctionalTest extends BrowserTestBase { */ public static $modules = ['migrate', 'file']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that an exception is thrown bu migration continues with the next row. */ @@ -65,7 +70,7 @@ public function testExceptionThrow() { $this->assertNull($map_row['destid1']); // Check that a message with the thrown exception has been logged. - $messages = $id_map_plugin->getMessageIterator(['url' => $invalid_url])->fetchAll(); + $messages = $id_map_plugin->getMessages(['url' => $invalid_url])->fetchAll(); $this->assertCount(1, $messages); $message = reset($messages); $this->assertEquals("Cannot read from non-readable stream ($invalid_url)", $message->message); diff --git a/core/modules/migrate/tests/src/Kernel/MigrateBundleTest.php b/core/modules/migrate/tests/src/Kernel/MigrateBundleTest.php index b6c305133..977b55d34 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateBundleTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateBundleTest.php @@ -18,7 +18,7 @@ class MigrateBundleTest extends MigrateTestBase { * * @var array */ - public static $modules = ['taxonomy', 'text', 'user']; + public static $modules = ['taxonomy', 'text', 'user', 'system']; /** * {@inheritdoc} diff --git a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php index 13803bd21..24eecfcba 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php @@ -55,7 +55,7 @@ protected function setUp() { ConfigurableLanguage::createFromLangcode('en')->save(); ConfigurableLanguage::createFromLangcode('fr')->save(); - $this->storage = $this->container->get('entity.manager')->getStorage('entity_test_mul'); + $this->storage = $this->container->get('entity_type.manager')->getStorage('entity_test_mul'); } /** @@ -70,7 +70,7 @@ protected function setUp() { */ protected function assertTranslations($id, $default, $others = []) { $entity = $this->storage->load($id); - $this->assertTrue($entity, "Entity exists"); + $this->assertNotEmpty($entity, "Entity exists"); $this->assertEquals($default, $entity->language()->getId(), "Entity default translation"); $translations = array_keys($entity->getTranslationLanguages(FALSE)); sort($others); @@ -89,10 +89,10 @@ protected function createDestination(array $configuration) { $configuration, 'fake_plugin_id', [], - $this->getMock(MigrationInterface::class), + $this->createMock(MigrationInterface::class), $this->storage, [], - $this->container->get('entity.manager'), + $this->container->get('entity_field.manager'), $this->container->get('plugin.manager.field.field_type') ); } @@ -115,7 +115,7 @@ public function testTranslated() { $this->assertTranslations(1, 'en'); $this->assertTranslations(2, 'fr'); $this->assertTranslations(3, 'en', ['fr']); - $this->assertFalse($this->storage->load(4)); + $this->assertNull($this->storage->load(4)); $destination_rows = [ // Existing default translation. diff --git a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentValidationTest.php b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentValidationTest.php new file mode 100644 index 000000000..f5632e24d --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentValidationTest.php @@ -0,0 +1,169 @@ +installConfig(['system', 'user']); + $this->installEntitySchema('user'); + $this->installEntitySchema('entity_test'); + + $this->container + ->get('event_dispatcher') + ->addListener(MigrateEvents::IDMAP_MESSAGE, [$this, 'mapMessageRecorder']); + } + + /** + * Tests an import with invalid data and checks error messages. + */ + public function test1() { + // Make sure that a user with uid 2 exists. + $this->container + ->get('entity_type.manager') + ->getStorage('user') + ->create([ + 'uid' => 2, + 'name' => $this->randomMachineName(), + 'status' => 1, + ]) + ->save(); + + $this->runImport([ + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => [ + [ + 'id' => '1', + 'name' => $this->randomString(256), + 'user_id' => '1', + ], + [ + 'id' => '2', + 'name' => $this->randomString(32), + 'user_id' => '1', + ], + [ + 'id' => '3', + 'name' => $this->randomString(32), + 'user_id' => '2', + ], + ], + 'ids' => [ + 'id' => ['type' => 'integer'], + ], + ], + 'process' => [ + 'id' => 'id', + 'name' => 'name', + 'user_id' => 'user_id', + ], + 'destination' => [ + 'plugin' => 'entity:entity_test', + 'validate' => TRUE, + ], + ]); + + $this->assertSame('1: [entity_test: 1]: name.0.value=Name: may not be longer than 32 characters.||user_id.0.target_id=The referenced entity (user: 1) does not exist.', $this->messages[0], 'First message should have 2 validation errors.'); + $this->assertSame('2: [entity_test: 2]: user_id.0.target_id=The referenced entity (user: 1) does not exist.', $this->messages[1], 'Second message should have 1 validation error.'); + $this->assertArrayNotHasKey(2, $this->messages, 'Third message should not exist.'); + } + + /** + * Tests an import with invalid data and checks error messages. + */ + public function test2() { + $long_username = $this->randomString(61); + $username_constraint = new UserNameConstraint(); + + $this->runImport([ + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => [ + [ + 'id' => 1, + 'name' => $long_username, + ], + [ + 'id' => 2, + 'name' => $this->randomString(32), + ], + [ + 'id' => 3, + 'name' => $this->randomString(32), + ], + ], + 'ids' => [ + 'id' => ['type' => 'integer'], + ], + ], + 'process' => [ + 'name' => 'name', + ], + 'destination' => [ + 'plugin' => 'entity:user', + 'validate' => TRUE, + ], + ]); + + $this->assertSame(sprintf('1: [user]: name=%s||name=%s||mail=Email field is required.', $username_constraint->illegalMessage, t($username_constraint->tooLongMessage, ['%name' => $long_username, '%max' => 60])), $this->messages[0], 'First message should have 3 validation errors.'); + $this->assertSame(sprintf('2: [user]: name=%s||mail=Email field is required.', $username_constraint->illegalMessage), $this->messages[1], 'Second message should have 2 validation errors.'); + $this->assertSame(sprintf('3: [user]: name=%s||mail=Email field is required.', $username_constraint->illegalMessage), $this->messages[2], 'Third message should have 2 validation errors.'); + $this->assertArrayNotHasKey(3, $this->messages, 'Fourth message should not exist.'); + } + + /** + * Reacts to map message event. + * + * @param \Drupal\Migrate\Event\MigrateIdMapMessageEvent $event + * The migration event. + */ + public function mapMessageRecorder(MigrateIdMapMessageEvent $event) { + $this->messages[] = implode(',', $event->getSourceIdValues()) . ': ' . $event->getMessage(); + } + + /** + * Runs an import of a migration. + * + * @param array $definition + * The migration definition. + * + * @throws \Exception + * @throws \Drupal\migrate\MigrateException + */ + protected function runImport(array $definition) { + // Reset the list of messages from a previous migration. + $this->messages = []; + + (new MigrateExecutable($this->container->get('plugin.manager.migration')->createStubMigration($definition)))->import(); + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/MigrateExternalTranslatedTest.php b/core/modules/migrate/tests/src/Kernel/MigrateExternalTranslatedTest.php index d9728f587..ea45a78fc 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateExternalTranslatedTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateExternalTranslatedTest.php @@ -48,7 +48,7 @@ public function setUp() { */ public function testMigrations() { /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ - $storage = $this->container->get('entity.manager')->getStorage('node'); + $storage = $this->container->get('entity_type.manager')->getStorage('node'); $this->assertEquals(0, count($storage->loadMultiple())); // Run the migrations. diff --git a/core/modules/migrate/tests/src/Kernel/MigrateLookupTest.php b/core/modules/migrate/tests/src/Kernel/MigrateLookupTest.php new file mode 100644 index 000000000..9dfcd4d0f --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/MigrateLookupTest.php @@ -0,0 +1,181 @@ +setTestLogger(); + $this->migrateLookup = $this->container->get('migrate.lookup'); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installConfig(['node', 'user']); + $this->createContentType(['type' => 'node_lookup']); + } + + /** + * Tests scenarios around single id lookups. + */ + public function testSingleLookup() { + $this->executeMigration('sample_lookup_migration'); + + // Test numerically indexed source id. + $result = $this->migrateLookup->lookup('sample_lookup_migration', [17]); + $this->assertSame('1', $result[0]['nid']); + + // Test associatively indexed source id. + $result = $this->migrateLookup->lookup('sample_lookup_migration', ['id' => 25]); + $this->assertSame('2', $result[0]['nid']); + + // Test lookup not found. + $result = $this->migrateLookup->lookup('sample_lookup_migration', [1337]); + $this->assertSame([], $result); + } + + /** + * Tests an invalid lookup. + */ + public function testInvalidIdLookup() { + $this->executeMigration('sample_lookup_migration'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("Extra unknown items for map migrate_map_sample_lookup_migration in source IDs: array (\n 'invalid_id' => 25,\n)"); + + // Test invalidly indexed source id. + $this->migrateLookup->lookup('sample_lookup_migration', ['invalid_id' => 25]); + } + + /** + * Tests an invalid lookup. + */ + public function testInvalidMigrationLookup() { + $this->expectException(PluginNotFoundException::class); + $this->expectExceptionMessage("Plugin ID 'invalid_migration' was not found."); + // Test invalid migration_id. + $this->migrateLookup->lookup('invalid_migration', ['id' => 1337]); + } + + /** + * Tests lookups with multiple source ids. + */ + public function testMultipleSourceIds() { + $this->executeMigration('sample_lookup_migration_multiple_source_ids'); + + // Test with full set of numerically indexed source ids. + $result = $this->migrateLookup->lookup('sample_lookup_migration_multiple_source_ids', [ + 25, + 26, + ]); + $this->assertCount(1, $result); + $this->assertSame('3', $result[0]['nid']); + + // Test with full set of associatively indexed source ids. + $result = $this->migrateLookup->lookup('sample_lookup_migration_multiple_source_ids', [ + 'id' => 17, + 'version_id' => 17, + ]); + $this->assertCount(1, $result); + $this->assertSame('1', $result[0]['nid']); + + // Test with full set of associatively indexed source ids in the wrong + // order. + $result = $this->migrateLookup->lookup('sample_lookup_migration_multiple_source_ids', [ + 'version_id' => 26, + 'id' => 25, + ]); + $this->assertCount(1, $result); + $this->assertSame('3', $result[0]['nid']); + + // Test with a partial set of numerically indexed ids. + $result = $this->migrateLookup->lookup('sample_lookup_migration_multiple_source_ids', [25]); + $this->assertCount(2, $result); + $this->assertSame('2', $result[0]['nid']); + $this->assertSame('3', $result[1]['nid']); + + // Test with a partial set of associatively indexed ids. + $result = $this->migrateLookup->lookup('sample_lookup_migration_multiple_source_ids', ['version_id' => 25]); + $this->assertCount(1, $result); + $this->assertSame('2', $result[0]['nid']); + } + + /** + * Tests looking up against multiple migrations at once. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\migrate\MigrateException + */ + public function testMultipleMigrationLookup() { + $migrations = [ + 'sample_lookup_migration', + 'sample_lookup_migration_2', + ]; + foreach ($migrations as $migration) { + $this->executeMigration($migration); + } + + // Test numerically indexed source id. + $result = $this->migrateLookup->lookup($migrations, [17]); + $this->assertSame('1', $result[0]['nid']); + + // Test associatively indexed source id. + $result = $this->migrateLookup->lookup($migrations, ['id' => 35]); + $this->assertSame('4', $result[0]['nid']); + + // Test lookup not found. + $result = $this->migrateLookup->lookup($migrations, [1337]); + $this->assertSame([], $result); + } + + /** + * Tests a lookup with string source ids. + */ + public function testLookupWithStringIds() { + $this->executeMigration('sample_lookup_migration_string_ids'); + + // Test numerically indexed source id. + $result = $this->migrateLookup->lookup('sample_lookup_migration_string_ids', ['node1']); + $this->assertSame('10', $result[0]['nid']); + + // Test associatively indexed source id. + $result = $this->migrateLookup->lookup('sample_lookup_migration_string_ids', ['id' => 'node2']); + $this->assertSame('11', $result[0]['nid']); + + // Test lookup not found. + $result = $this->migrateLookup->lookup('sample_lookup_migration_string_ids', ['node1337']); + $this->assertSame([], $result); + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/MigrateMessageTest.php b/core/modules/migrate/tests/src/Kernel/MigrateMessageTest.php index 6a9990f19..a4cebef64 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateMessageTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateMessageTest.php @@ -8,6 +8,7 @@ use Drupal\migrate\Event\MigrateIdMapMessageEvent; use Drupal\migrate\MigrateExecutable; use Drupal\migrate\MigrateMessageInterface; +use Drupal\migrate\Plugin\migrate\id_map\Sql; /** * Tests whether idmap messages are sent to message interface when requested. @@ -96,6 +97,31 @@ public function testMessagesTeed() { $this->assertIdentical(reset($this->messages), "source_message: 'a message' is not an array"); } + /** + * Tests the return value of getMessages(). + * + * This method returns an iterator of StdClass objects. Check that these + * objects have the expected keys. + */ + public function testGetMessages() { + $expected_message = (object) [ + 'src_name' => 'source_message', + 'dest_config_name' => NULL, + 'msgid' => '1', + Sql::SOURCE_IDS_HASH => '170cde81762e22552d1b1578cf3804c89afefe9efbc7cc835185d7141060b032', + 'level' => '1', + 'message' => "'a message' is not an array", + ]; + $executable = new MigrateExecutable($this->migration, $this); + $executable->import(); + $count = 0; + foreach ($this->migration->getIdMap()->getMessages() as $message) { + ++$count; + $this->assertEqual($message, $expected_message); + } + $this->assertEqual($count, 1); + } + /** * Reacts to map message event. * diff --git a/core/modules/migrate/tests/src/Kernel/MigrateRollbackEntityConfigTest.php b/core/modules/migrate/tests/src/Kernel/MigrateRollbackEntityConfigTest.php index d43ad8af5..23ab7481d 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateRollbackEntityConfigTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateRollbackEntityConfigTest.php @@ -17,7 +17,7 @@ class MigrateRollbackEntityConfigTest extends MigrateTestBase { * * @var array */ - public static $modules = ['field', 'taxonomy', 'text', 'language', 'config_translation', 'user']; + public static $modules = ['field', 'taxonomy', 'text', 'language', 'config_translation', 'user', 'system']; /** * {@inheritdoc} @@ -71,7 +71,7 @@ public function testConfigEntityRollback() { foreach ($vocabulary_data_rows as $row) { /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ $vocabulary = Vocabulary::load($row['id']); - $this->assertTrue($vocabulary); + $this->assertNotEmpty($vocabulary); $map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]); $this->assertNotNull($map_row['destid1']); } @@ -159,7 +159,7 @@ public function testConfigEntityRollback() { foreach ($vocabulary_data_rows as $row) { /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ $vocabulary = Vocabulary::load($row['id']); - $this->assertTrue($vocabulary); + $this->assertNotEmpty($vocabulary); $map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]); $this->assertNotNull($map_row['destid1']); } diff --git a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php index de1666be6..c772c8a27 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php @@ -20,7 +20,7 @@ class MigrateRollbackTest extends MigrateTestBase { * * @var array */ - public static $modules = ['field', 'taxonomy', 'text', 'user']; + public static $modules = ['field', 'taxonomy', 'text', 'user', 'system']; /** * {@inheritdoc} @@ -71,7 +71,7 @@ public function testRollback() { foreach ($vocabulary_data_rows as $row) { /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ $vocabulary = Vocabulary::load($row['id']); - $this->assertTrue($vocabulary); + $this->assertNotEmpty($vocabulary); $map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]); $this->assertNotNull($map_row['destid1']); } @@ -124,7 +124,7 @@ public function testRollback() { foreach ($term_data_rows as $row) { /** @var \Drupal\taxonomy\Entity\Term $term */ $term = Term::load($row['id']); - $this->assertTrue($term); + $this->assertNotEmpty($term); $map_row = $term_id_map->getRowBySource(['id' => $row['id']]); $this->assertNotNull($map_row['destid1']); } diff --git a/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php b/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php index 8c5e01e51..dd42f252f 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php @@ -59,14 +59,14 @@ public function testPrepareRowSkip() { $map_row = $id_map_plugin->getRowBySource(['id' => 1]); $this->assertEqual(MigrateIdMapInterface::STATUS_IGNORED, $map_row['source_row_status']); // Check that no message has been logged for the first exception. - $messages = $id_map_plugin->getMessageIterator(['id' => 1])->fetchAll(); + $messages = $id_map_plugin->getMessages(['id' => 1])->fetchAll(); $this->assertEmpty($messages); // The second row is not recorded in the map. $map_row = $id_map_plugin->getRowBySource(['id' => 2]); $this->assertFalse($map_row); // Check that the correct message has been logged for the second exception. - $messages = $id_map_plugin->getMessageIterator(['id' => 2])->fetchAll(); + $messages = $id_map_plugin->getMessages(['id' => 2])->fetchAll(); $this->assertCount(1, $messages); $message = reset($messages); $this->assertEquals('skip_and_dont_record message', $message->message); diff --git a/core/modules/migrate/tests/src/Kernel/MigrateStatusTest.php b/core/modules/migrate/tests/src/Kernel/MigrateStatusTest.php index 7dc0b5b9b..df9e77030 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateStatusTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateStatusTest.php @@ -17,7 +17,7 @@ class MigrateStatusTest extends MigrateTestBase { public function testStatus() { // Create a minimally valid migration. $definition = [ - 'id' => 'migration_status_test', + 'id' => 'migrate_status_test', 'migration_tags' => ['Testing'], 'source' => ['plugin' => 'empty'], 'destination' => [ diff --git a/core/modules/migrate/tests/src/Kernel/MigrateStubTest.php b/core/modules/migrate/tests/src/Kernel/MigrateStubTest.php new file mode 100644 index 000000000..74e87b059 --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/MigrateStubTest.php @@ -0,0 +1,130 @@ +setTestLogger(); + $this->migrateStub = $this->container->get('migrate.stub'); + $this->migrateLookup = $this->container->get('migrate.lookup'); + $this->migrationPluginManager = $this->container->get('plugin.manager.migration'); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installSchema('node', 'node_access'); + $this->installConfig(['node', 'user']); + $this->createContentType(['type' => 'node_lookup']); + } + + /** + * Tests stub creation. + */ + public function testCreateStub() { + $this->assertSame([], $this->migrateLookup->lookup('sample_stubbing_migration', [17])); + $ids = $this->migrateStub->createStub('sample_stubbing_migration', [17]); + $this->assertSame([$ids], $this->migrateLookup->lookup('sample_stubbing_migration', [17])); + $this->assertNotNull(\Drupal::entityTypeManager()->getStorage('node')->load($ids['nid'])); + } + + /** + * Tests raw stub creation. + */ + public function testCreateStubRawReturn() { + $this->assertSame([], $this->migrateLookup->lookup('sample_stubbing_migration', [17])); + $ids = $this->migrateStub->createStub('sample_stubbing_migration', [17], [], FALSE); + $this->assertSame($ids, [$this->migrateLookup->lookup('sample_stubbing_migration', [17])[0]['nid']]); + $this->assertNotNull(\Drupal::entityTypeManager()->getStorage('node')->load($ids[0])); + } + + /** + * Tests stub creation with default values. + */ + public function testStubWithDefaultValues() { + $this->assertSame([], $this->migrateLookup->lookup('sample_stubbing_migration', [17])); + $ids = $this->migrateStub->createStub('sample_stubbing_migration', [17], ['title' => "Placeholder for source id 17"]); + $this->assertSame([$ids], $this->migrateLookup->lookup('sample_stubbing_migration', [17])); + $node = \Drupal::entityTypeManager()->getStorage('node')->load($ids['nid']); + $this->assertNotNull($node); + // Test that our default value was set as the node title. + $this->assertSame("Placeholder for source id 17", $node->label()); + + // Test that running the migration replaces the node title. + $this->executeMigration('sample_stubbing_migration'); + $node = \Drupal::entityTypeManager()->getStorage('node')->loadUnchanged($ids['nid']); + $this->assertSame("Sample 1", $node->label()); + } + + /** + * Test invalid source id count. + */ + public function testInvalidSourceIdCount() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Expected and provided source id counts do not match.'); + $this->migrateStub->createStub('sample_stubbing_migration_with_multiple_source_ids', [17]); + } + + /** + * Tests invalid source ids keys. + */ + public function testInvalidSourceIdKeys() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('version_id is defined as a source ID but has no value.'); + $this->migrateStub->createStub('sample_stubbing_migration_with_multiple_source_ids', ['id' => 17, 'not_a_key' => 17]); + } + + /** + * Tests that an exception is thrown if a migration does not exist. + */ + public function testErrorOnMigrationNotFound() { + $this->expectException(PluginNotFoundException::class); + $this->expectExceptionMessage("Plugin ID 'nonexistent_migration' was not found."); + $this->migrateStub->createStub('nonexistent_migration', [1]); + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php b/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php index 117b3e188..896c7de71 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateTestBase.php @@ -3,11 +3,11 @@ namespace Drupal\Tests\migrate\Kernel; use Drupal\Core\Database\Database; +use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\KernelTests\KernelTestBase; use Drupal\migrate\MigrateExecutable; use Drupal\migrate\MigrateMessageInterface; use Drupal\migrate\Plugin\MigrateIdMapInterface; -use Drupal\migrate\Plugin\Migration; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Row; @@ -47,6 +47,17 @@ abstract class MigrateTestBase extends KernelTestBase implements MigrateMessageI */ protected $sourceDatabase; + /** + * A logger prophecy object. + * + * Using ::setTestLogger(), this prophecy will be configured and injected into + * the container. Using $this->logger->function(args)->shouldHaveBeenCalled() + * you can assert that the logger was called. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ + protected $logger; + public static $modules = ['migrate']; /** @@ -173,7 +184,8 @@ protected function executeMigration($migration) { * Executes a set of migrations in dependency order. * * @param string[] $ids - * Array of migration IDs, in any order. + * Array of migration IDs, in any order. If any of these migrations use a + * deriver, the derivatives will be made before execution. */ protected function executeMigrations(array $ids) { $manager = $this->container->get('plugin.manager.migration'); @@ -251,4 +263,13 @@ protected function getMigration($plugin_id) { return $this->container->get('plugin.manager.migration')->createInstance($plugin_id); } + /** + * Injects the test logger into the container. + */ + protected function setTestLogger() { + $this->logger = $this->prophesize(LoggerChannelInterface::class); + $this->container->set('logger.channel.migrate', $this->logger->reveal()); + \Drupal::setContainer($this->container); + } + } diff --git a/core/modules/migrate/tests/src/Kernel/NodeCommentCombinationTrait.php b/core/modules/migrate/tests/src/Kernel/NodeCommentCombinationTrait.php index e2fcd9e02..7bdac26ea 100644 --- a/core/modules/migrate/tests/src/Kernel/NodeCommentCombinationTrait.php +++ b/core/modules/migrate/tests/src/Kernel/NodeCommentCombinationTrait.php @@ -3,7 +3,7 @@ namespace Drupal\Tests\migrate\Kernel; /** - * @deprecated in Drupal 8.7.x, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase::migrateFields() * instead. */ diff --git a/core/modules/migrate/tests/src/Kernel/Plugin/LegacyMigrationProvidersExistTest.php b/core/modules/migrate/tests/src/Kernel/Plugin/LegacyMigrationProvidersExistTest.php new file mode 100644 index 000000000..3f6340ae7 --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/Plugin/LegacyMigrationProvidersExistTest.php @@ -0,0 +1,20 @@ +container->get('plugin.manager.migration')->createInstance('d7_blocked_ips', $configuration); + $migration = $this->container->get('plugin.manager.migration') + ->createInstance($id, $configuration); $source_configuration = $migration->getSourceConfiguration(); $this->assertEquals($expected, $source_configuration); } @@ -42,6 +44,7 @@ public function mergeProvider() { // Tests adding new configuration to a migration. [ // New configuration. + 'd7_blocked_ips', [ 'source' => [ 'constants' => [ @@ -60,6 +63,7 @@ public function mergeProvider() { // Tests overriding pre-existing configuration in a migration. [ // New configuration. + 'd7_blocked_ips', [ 'source' => [ 'plugin' => 'a_different_plugin', @@ -70,6 +74,29 @@ public function mergeProvider() { 'plugin' => 'a_different_plugin', ], ], + // New configuration. + [ + 'locale_settings', + [ + 'source' => [ + 'plugin' => 'variable', + 'variables' => [ + 'locale_cache_strings', + 'locale_js_directory', + ], + 'source_module' => 'locale', + ], + ], + // Expected final source and process configuration. + [ + 'plugin' => 'variable', + 'variables' => [ + 'locale_cache_strings', + 'locale_js_directory', + ], + 'source_module' => 'locale', + ], + ], ]; } diff --git a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationPluginListTest.php b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationPluginListTest.php index 1a340e4e4..9dc4f88ea 100644 --- a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationPluginListTest.php +++ b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationPluginListTest.php @@ -49,7 +49,6 @@ class MigrationPluginListTest extends KernelTestBase { 'path', 'search', 'shortcut', - 'simpletest', 'statistics', 'syslog', 'system', diff --git a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php index dce4264bc..194f044a9 100644 --- a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php +++ b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php @@ -5,6 +5,7 @@ use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait; use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException; use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager; +use Drupal\Tests\DeprecatedModulesTestTrait; use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase; /** @@ -14,6 +15,7 @@ */ class MigrationProvidersExistTest extends MigrateDrupalTestBase { + use DeprecatedModulesTestTrait; use FileSystemModuleDiscoveryDataProviderTrait; /** @@ -21,7 +23,8 @@ class MigrationProvidersExistTest extends MigrateDrupalTestBase { */ public function testSourceProvider() { $this->enableModules(['migration_provider_test']); - $this->setExpectedException(BadPluginDefinitionException::class, 'The no_source_module plugin must define the source_module property.'); + $this->expectException(BadPluginDefinitionException::class); + $this->expectExceptionMessage('The no_source_module plugin must define the source_module property.'); $this->container->get('plugin.manager.migration')->getDefinition('migration_provider_no_annotation'); } @@ -173,7 +176,8 @@ public function testFieldProviderMissingRequiredProperty(array $definitions, $mi $plugin_manager->method('getDiscovery') ->willReturn($discovery); - $this->setExpectedException(BadPluginDefinitionException::class, "The missing_{$missing_property} plugin must define the $missing_property property."); + $this->expectException(BadPluginDefinitionException::class); + $this->expectExceptionMessage("The missing_{$missing_property} plugin must define the $missing_property property."); $plugin_manager->getDefinitions(); } diff --git a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationTest.php b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationTest.php index a8dddd277..aceb73e80 100644 --- a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationTest.php +++ b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationTest.php @@ -36,10 +36,29 @@ public function testGetProcessPlugins() { */ public function testGetProcessPluginsException() { $migration = \Drupal::service('plugin.manager.migration')->createStubMigration([]); - $this->setExpectedException(MigrateException::class, 'Invalid process configuration for foobar'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Invalid process configuration for foobar'); $migration->getProcessPlugins(['foobar' => ['plugin' => 'get']]); } + /** + * Tests Migration::getDestinationPlugin() + * + * @covers ::getDestinationPlugin + */ + public function testGetProcessPluginsExceptionMessage() { + // Test with an invalid process pipeline. + $plugin_definition = [ + 'process' => [ + 'dest1' => 123, + ], + ]; + $migration = \Drupal::service('plugin.manager.migration')->createStubMigration($plugin_definition); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("Process configuration for 'dest1' must be an array"); + $migration->getProcessPlugins(); + } + /** * Tests Migration::getMigrationDependencies() * @@ -123,7 +142,8 @@ public function testGetTrackLastImported() { */ public function testGetDestinationPlugin() { $migration = \Drupal::service('plugin.manager.migration')->createStubMigration(['destination' => ['no_stub' => TRUE]]); - $this->setExpectedException(MigrateSkipRowException::class, "Stub requested but not made because no_stub configuration is set."); + $this->expectException(MigrateSkipRowException::class); + $this->expectExceptionMessage("Stub requested but not made because no_stub configuration is set."); $migration->getDestinationPlugin(TRUE); } diff --git a/core/modules/migrate/tests/src/Kernel/QueryBatchTest.php b/core/modules/migrate/tests/src/Kernel/QueryBatchTest.php index e92e1441e..4bb8359ff 100644 --- a/core/modules/migrate/tests/src/Kernel/QueryBatchTest.php +++ b/core/modules/migrate/tests/src/Kernel/QueryBatchTest.php @@ -57,7 +57,8 @@ protected function setUp() { * Tests a negative batch size throws an exception. */ public function testBatchSizeNegative() { - $this->setExpectedException(MigrateException::class, 'batch_size must be greater than or equal to zero'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('batch_size must be greater than or equal to zero'); $plugin = $this->getPlugin(['batch_size' => -1]); $plugin->next(); } @@ -66,7 +67,8 @@ public function testBatchSizeNegative() { * Tests a non integer batch size throws an exception. */ public function testBatchSizeNonInteger() { - $this->setExpectedException(MigrateException::class, 'batch_size must be greater than or equal to zero'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('batch_size must be greater than or equal to zero'); $plugin = $this->getPlugin(['batch_size' => '1']); $plugin->next(); } diff --git a/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php b/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php index 8837d5f38..a851ec196 100644 --- a/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php +++ b/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php @@ -35,7 +35,7 @@ class SqlBaseTest extends MigrateTestBase { protected function setUp() { parent::setUp(); - $this->migration = $this->getMock(MigrationInterface::class); + $this->migration = $this->createMock(MigrationInterface::class); $this->migration->method('id')->willReturn('fubar'); } @@ -123,8 +123,8 @@ public function testConnectionTypes() { \Drupal::state()->delete('migrate.fallback_state_key'); $sql_base->setConfiguration([]); Database::renameConnection('migrate', 'fallback_connection'); - $this->setExpectedException(RequirementsException::class, - 'No database connection configured for source plugin'); + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage('No database connection configured for source plugin'); $sql_base->getDatabase(); } @@ -156,7 +156,7 @@ public function testHighWater($high_water = NULL, array $query_result = []) { $query->method('execute')->willReturn($statement); $query->expects($this->atLeastOnce())->method('orderBy')->with('order', 'ASC'); - $condition_group = $this->getMock(ConditionInterface::class); + $condition_group = $this->createMock(ConditionInterface::class); $query->method('orConditionGroup')->willReturn($condition_group); $source->setQuery($query); diff --git a/core/modules/migrate/tests/src/Kernel/process/DownloadTest.php b/core/modules/migrate/tests/src/Kernel/process/DownloadTest.php index 11674c0bc..a183c0e4a 100644 --- a/core/modules/migrate/tests/src/Kernel/process/DownloadTest.php +++ b/core/modules/migrate/tests/src/Kernel/process/DownloadTest.php @@ -100,14 +100,14 @@ public function testWriteProtectedDestination() { */ protected function doTransform($destination_uri, $configuration = []) { // Prepare a mock HTTP client. - $this->container->set('http_client', $this->getMock(Client::class)); + $this->container->set('http_client', $this->createMock(Client::class)); // Instantiate the plugin statically so it can pull dependencies out of // the container. $plugin = Download::create($this->container, $configuration, 'download', []); // Execute the transformation. - $executable = $this->getMock(MigrateExecutableInterface::class); + $executable = $this->createMock(MigrateExecutableInterface::class); $row = new Row([], []); // Return the downloaded file's local URI. diff --git a/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php b/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php index e107f4157..38d92f90b 100644 --- a/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php +++ b/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php @@ -50,7 +50,7 @@ public function testSuccessfulCopies() { $data_sets = [ // Test a local to local copy. [ - $this->root . '/core/modules/simpletest/files/image-test.jpg', + $this->root . '/core/tests/fixtures/files/image-test.jpg', 'public://file1.jpg', ], // Test a temporary file using an absolute path. @@ -61,7 +61,7 @@ public function testSuccessfulCopies() { // Test a temporary file using a relative path. [ $file_absolute, - 'temporary://core/modules/simpletest/files/test.jpg', + 'temporary://core/tests/fixtures/files/test.jpg', ], ]; foreach ($data_sets as $data) { @@ -113,7 +113,7 @@ public function testSuccessfulReuse($source_path, $destination_path) { public function providerSuccessfulReuse() { return [ [ - 'local_source_path' => static::getDrupalRoot() . '/core/modules/simpletest/files/image-test.jpg', + 'local_source_path' => static::getDrupalRoot() . '/core/tests/fixtures/files/image-test.jpg', 'local_destination_path' => 'public://file1.jpg', ], [ @@ -146,7 +146,7 @@ public function testSuccessfulMoves() { // Test a temporary file using a relative path. [ $file_2_absolute, - 'temporary://core/modules/simpletest/files/test.jpg', + 'temporary://core/tests/fixtures/files/test.jpg', ], ]; foreach ($data_sets as $data) { @@ -165,7 +165,8 @@ public function testSuccessfulMoves() { */ public function testNonExistentSourceFile() { $source = '/non/existent/file'; - $this->setExpectedException(MigrateException::class, "File '/non/existent/file' does not exist"); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("File '/non/existent/file' does not exist"); $this->doTransform($source, 'public://wontmatter.jpg'); } @@ -191,7 +192,8 @@ public function testNonWritableDestination() { $this->fileSystem->chmod('public://dir', 0); // Check that the proper exception is raised. - $this->setExpectedException(MigrateException::class, "Could not create or write to directory 'public://dir/subdir2'"); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("Could not create or write to directory 'public://dir/subdir2'"); $this->doTransform($source, 'public://dir/subdir2/file.txt'); } @@ -211,7 +213,7 @@ public function testRenameFile() { * Tests that remote URIs are delegated to the download plugin. */ public function testDownloadRemoteUri() { - $download_plugin = $this->getMock(MigrateProcessInterface::class); + $download_plugin = $this->createMock(MigrateProcessInterface::class); $download_plugin->expects($this->once())->method('transform'); $plugin = new FileCopy( @@ -225,7 +227,7 @@ public function testDownloadRemoteUri() { $plugin->transform( ['http://drupal.org/favicon.ico', '/destination/path'], - $this->getMock(MigrateExecutableInterface::class), + $this->createMock(MigrateExecutableInterface::class), new Row([], []), $this->randomMachineName() ); diff --git a/core/modules/migrate/tests/src/Unit/MigrateExecutableMemoryExceededTest.php b/core/modules/migrate/tests/src/Unit/MigrateExecutableMemoryExceededTest.php index d14e54a42..11ab2e796 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateExecutableMemoryExceededTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateExecutableMemoryExceededTest.php @@ -12,14 +12,14 @@ class MigrateExecutableMemoryExceededTest extends MigrateTestCase { /** * The mocked migration entity. * - * @var \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $migration; /** * The mocked migrate message. * - * @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $message; @@ -52,7 +52,7 @@ class MigrateExecutableMemoryExceededTest extends MigrateTestCase { protected function setUp() { parent::setUp(); $this->migration = $this->getMigration(); - $this->message = $this->getMock('Drupal\migrate\MigrateMessageInterface'); + $this->message = $this->createMock('Drupal\migrate\MigrateMessageInterface'); $this->executable = new TestMigrateExecutable($this->migration, $this->message); $this->executable->setStringTranslation($this->getStringTranslationStub()); diff --git a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php index 957da811c..5acc99566 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php @@ -18,14 +18,14 @@ class MigrateExecutableTest extends MigrateTestCase { /** * The mocked migration entity. * - * @var \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $migration; /** * The mocked migrate message. * - * @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $message; @@ -51,8 +51,8 @@ class MigrateExecutableTest extends MigrateTestCase { protected function setUp() { parent::setUp(); $this->migration = $this->getMigration(); - $this->message = $this->getMock('Drupal\migrate\MigrateMessageInterface'); - $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->message = $this->createMock('Drupal\migrate\MigrateMessageInterface'); + $event_dispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->executable = new TestMigrateExecutable($this->migration, $this->message, $event_dispatcher); $this->executable->setStringTranslation($this->getStringTranslationStub()); } @@ -62,7 +62,7 @@ protected function setUp() { */ public function testImportWithFailingRewind() { $exception_message = $this->getRandomGenerator()->string(); - $source = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface'); + $source = $this->createMock('Drupal\migrate\Plugin\MigrateSourceInterface'); $source->expects($this->once()) ->method('rewind') ->will($this->throwException(new \Exception($exception_message))); @@ -109,7 +109,7 @@ public function testImportWithValidRow() { ->method('getProcessPlugins') ->will($this->returnValue([])); - $destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $destination = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $destination->expects($this->once()) ->method('import') ->with($row, ['test']) @@ -151,7 +151,7 @@ public function testImportWithValidRowWithoutDestinationId() { ->method('getProcessPlugins') ->will($this->returnValue([])); - $destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $destination = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $destination->expects($this->once()) ->method('import') ->with($row, ['test']) @@ -191,7 +191,7 @@ public function testImportWithValidRowNoDestinationValues() { ->method('getProcessPlugins') ->will($this->returnValue([])); - $destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $destination = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $destination->expects($this->once()) ->method('import') ->with($row, ['test']) @@ -251,7 +251,7 @@ public function testImportWithValidRowWithDestinationMigrateException() { ->method('getProcessPlugins') ->will($this->returnValue([])); - $destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $destination = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $destination->expects($this->once()) ->method('import') ->with($row, ['test']) @@ -303,7 +303,7 @@ public function testImportWithValidRowWithProcesMigrateException() { ->method('getProcessPlugins') ->willThrowException(new MigrateException($exception_message)); - $destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $destination = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $destination->expects($this->never()) ->method('import'); @@ -349,7 +349,7 @@ public function testImportWithValidRowWithException() { ->method('getProcessPlugins') ->will($this->returnValue([])); - $destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $destination = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $destination->expects($this->once()) ->method('import') ->with($row, ['test']) @@ -387,7 +387,7 @@ public function testProcessRow() { 'test1' => 'test1 destination', ]; foreach ($expected as $key => $value) { - $plugins[$key][0] = $this->getMock('Drupal\migrate\Plugin\MigrateProcessInterface'); + $plugins[$key][0] = $this->createMock('Drupal\migrate\Plugin\MigrateProcessInterface'); $plugins[$key][0]->expects($this->once()) ->method('getPluginDefinition') ->will($this->returnValue([])); @@ -435,7 +435,8 @@ public function testProcessRowPipelineException() { $plugins['destination_id'] = [$plugin, $plugin]; $this->migration->method('getProcessPlugins')->willReturn($plugins); - $this->setExpectedException(MigrateException::class, 'Pipeline failed at plugin_id plugin for destination destination_id: transform_return_string received instead of an array,'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Pipeline failed at plugin_id plugin for destination destination_id: transform_return_string received instead of an array,'); $this->executable->processRow($row); } @@ -469,11 +470,11 @@ public function testProcessRowEmptyDestination() { /** * Returns a mock migration source instance. * - * @return \Drupal\migrate\Plugin\MigrateSourceInterface|\PHPUnit_Framework_MockObject_MockObject + * @return \Drupal\migrate\Plugin\MigrateSourceInterface|\PHPUnit\Framework\MockObject\MockObject * The mocked migration source. */ protected function getMockSource() { - $iterator = $this->getMock('\Iterator'); + $iterator = $this->createMock('\Iterator'); $class = 'Drupal\migrate\Plugin\migrate\source\SourcePluginBase'; $source = $this->getMockBuilder($class) diff --git a/core/modules/migrate/tests/src/Unit/MigrateLookupTest.php b/core/modules/migrate/tests/src/Unit/MigrateLookupTest.php new file mode 100644 index 000000000..417b08a86 --- /dev/null +++ b/core/modules/migrate/tests/src/Unit/MigrateLookupTest.php @@ -0,0 +1,60 @@ + '1']; + + $destination_ids = [[2]]; + + $id_map = $this->prophesize(MigrateIdMapInterface::class); + $id_map->lookupDestinationIds($source_ids)->willReturn($destination_ids); + + $destination = $this->prophesize(MigrateDestinationInterface::class); + $destination->getIds()->willReturn(['id' => ['type' => 'integer']]); + + $migration = $this->prophesize(MigrationInterface::class); + $migration->getIdMap()->willReturn($id_map->reveal()); + $migration->getDestinationPlugin()->willReturn($destination->reveal()); + + $plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class); + $plugin_manager->createInstances('test_migration')->willReturn([$migration->reveal()]); + + $lookup = new MigrateLookup($plugin_manager->reveal()); + + $this->assertSame([['id' => 2]], $lookup->lookup('test_migration', $source_ids)); + } + + /** + * Tests that an appropriate message is logged if a PluginException is thrown. + */ + public function testExceptionOnMigrationNotFound() { + $migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class); + $migration_plugin_manager->createInstances('bad_plugin')->willReturn([]); + $this->expectException(PluginNotFoundException::class); + $lookup = new MigrateLookup($migration_plugin_manager->reveal()); + $lookup->lookup('bad_plugin', [1]); + } + +} diff --git a/core/modules/migrate/tests/src/Unit/MigrateNullIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateNullIdMapTest.php new file mode 100644 index 000000000..eba4bbb4c --- /dev/null +++ b/core/modules/migrate/tests/src/Unit/MigrateNullIdMapTest.php @@ -0,0 +1,26 @@ +getMessageIterator(); + } + +} diff --git a/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php b/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php index 7f3d2937d..f8dc7541f 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php @@ -82,16 +82,16 @@ protected function getSource($configuration = [], $migrate_config = [], $status $container = new ContainerBuilder(); \Drupal::setContainer($container); - $key_value = $this->getMock(KeyValueStoreInterface::class); + $key_value = $this->createMock(KeyValueStoreInterface::class); - $key_value_factory = $this->getMock(KeyValueFactoryInterface::class); + $key_value_factory = $this->createMock(KeyValueFactoryInterface::class); $key_value_factory ->method('get') ->with('migrate:high_water') ->willReturn($key_value); $container->set('keyvalue', $key_value_factory); - $container->set('cache.migrate', $this->getMock(CacheBackendInterface::class)); + $container->set('cache.migrate', $this->createMock(CacheBackendInterface::class)); $this->migrationConfiguration = $this->defaultMigrationConfiguration + $migrate_config; $this->migration = parent::getMigration(); @@ -108,7 +108,10 @@ protected function getSource($configuration = [], $migrate_config = [], $status $constructor_args = [$configuration, 'd6_action', [], $this->migration]; $methods = ['getModuleHandler', 'fields', 'getIds', '__toString', 'prepareRow', 'initializeIterator']; - $source_plugin = $this->getMock(SourcePluginBase::class, $methods, $constructor_args); + $source_plugin = $this->getMockBuilder(SourcePluginBase::class) + ->setMethods($methods) + ->setConstructorArgs($constructor_args) + ->getMock(); $source_plugin ->method('fields') @@ -136,7 +139,7 @@ protected function getSource($configuration = [], $migrate_config = [], $status ->method('initializeIterator') ->willReturn($iterator); - $module_handler = $this->getMock(ModuleHandlerInterface::class); + $module_handler = $this->createMock(ModuleHandlerInterface::class); $source_plugin ->method('getModuleHandler') ->willReturn($module_handler); @@ -153,7 +156,7 @@ protected function getSource($configuration = [], $migrate_config = [], $status */ public function testHighwaterTrackChangesIncompatible() { $source_config = ['track_changes' => TRUE, 'high_water_property' => ['name' => 'something']]; - $this->setExpectedException(MigrateException::class); + $this->expectException(MigrateException::class); $this->getSource($source_config); } @@ -165,7 +168,7 @@ public function testHighwaterTrackChangesIncompatible() { public function testCount() { // Mock the cache to validate set() receives appropriate arguments. $container = new ContainerBuilder(); - $cache = $this->getMock(CacheBackendInterface::class); + $cache = $this->createMock(CacheBackendInterface::class); $cache->expects($this->any())->method('set') ->with($this->isType('string'), $this->isType('int'), $this->isType('int')); $container->set('cache.migrate', $cache); @@ -203,7 +206,7 @@ public function testCount() { public function testCountCacheKey() { // Mock the cache to validate set() receives appropriate arguments. $container = new ContainerBuilder(); - $cache = $this->getMock(CacheBackendInterface::class); + $cache = $this->createMock(CacheBackendInterface::class); $cache->expects($this->any())->method('set') ->with('test_key', $this->isType('int'), $this->isType('int')); $container->set('cache.migrate', $cache); @@ -440,9 +443,9 @@ public function testDefaultPropertiesValues() { */ protected function getMigrateExecutable($migration) { /** @var \Drupal\migrate\MigrateMessageInterface $message */ - $message = $this->getMock('Drupal\migrate\MigrateMessageInterface'); + $message = $this->createMock('Drupal\migrate\MigrateMessageInterface'); /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ - $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $event_dispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); return new MigrateExecutable($migration, $message, $event_dispatcher); } diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php index 1a3397447..44eeccb7c 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php @@ -205,7 +205,7 @@ protected function runEnsureTablesTest($schema) { ->method('schema') ->willReturn($schema); $migration = $this->getMigration(); - $plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface'); + $plugin = $this->createMock('Drupal\migrate\Plugin\MigrateSourceInterface'); $plugin->expects($this->any()) ->method('getIds') ->willReturn([ @@ -219,7 +219,7 @@ protected function runEnsureTablesTest($schema) { $migration->expects($this->any()) ->method('getSourcePlugin') ->willReturn($plugin); - $plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface'); + $plugin = $this->createMock('Drupal\migrate\Plugin\MigrateSourceInterface'); $plugin->expects($this->any()) ->method('getIds') ->willReturn([ @@ -231,7 +231,7 @@ protected function runEnsureTablesTest($schema) { ->method('getDestinationPlugin') ->willReturn($plugin); /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ - $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $event_dispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $map = new TestSqlIdMap($database, [], 'sql', [], $migration, $event_dispatcher); $map->getDatabase(); } diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php index 417ede6f7..098603ebc 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php @@ -95,7 +95,7 @@ protected function saveMap(array $map) { protected function getIdMap() { $migration = $this->getMigration(); - $plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface'); + $plugin = $this->createMock('Drupal\migrate\Plugin\MigrateSourceInterface'); $plugin ->method('getIds') ->willReturn($this->sourceIds); @@ -103,14 +103,14 @@ protected function getIdMap() { ->method('getSourcePlugin') ->willReturn($plugin); - $plugin = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $plugin = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $plugin ->method('getIds') ->willReturn($this->destinationIds); $migration ->method('getDestinationPlugin') ->willReturn($plugin); - $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $event_dispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $id_map = new TestSqlIdMap($this->database, [], 'sql', [], $migration, $event_dispatcher); $migration @@ -191,7 +191,7 @@ public function testSaveIdMapping() { * Tests the SQL ID map set message method. */ public function testSetMessage() { - $message = $this->getMock('Drupal\migrate\MigrateMessageInterface'); + $message = $this->createMock('Drupal\migrate\MigrateMessageInterface'); $id_map = $this->getIdMap(); $id_map->setMessage($message); $this->assertAttributeEquals($message, 'message', $id_map); @@ -312,7 +312,7 @@ public function testMessageSave() { $id_map->saveMessage(['source_id_property' => $key], $message, $original_value['level']); } - foreach ($id_map->getMessageIterator() as $message_row) { + foreach ($id_map->getMessages() as $message_row) { $key = $message_row->source_ids_hash; $this->assertEquals($expected_results[$key]['message'], $message_row->message); $this->assertEquals($expected_results[$key]['level'], $message_row->level); @@ -321,7 +321,7 @@ public function testMessageSave() { // Insert with default level. $message_default = 'Hello world default.'; $id_map->saveMessage(['source_id_property' => 5], $message_default); - $messages = $id_map->getMessageIterator(['source_id_property' => 5]); + $messages = $id_map->getMessages(['source_id_property' => 5]); $count = 0; foreach ($messages as $key => $message_row) { $count = 1; @@ -331,7 +331,7 @@ public function testMessageSave() { $this->assertEquals($count, 1); // Retrieve messages with a specific level. - $messages = $id_map->getMessageIterator([], MigrationInterface::MESSAGE_WARNING); + $messages = $id_map->getMessages([], MigrationInterface::MESSAGE_WARNING); $count = 0; foreach ($messages as $key => $message_row) { $count = 1; @@ -340,6 +340,17 @@ public function testMessageSave() { $this->assertEquals($count, 1); } + /** + * Tests the SQL ID map get message iterator method. + * + * @group legacy + * + * @expectedDeprecation getMessageIterator() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use getMessages() instead. See https://www.drupal.org/node/3060969 + */ + public function testGetMessageIterator() { + $this->getIdMap()->getMessageIterator(); + } + /** * Tests the getRowBySource method. */ @@ -423,11 +434,11 @@ public function testLookupDestinationIdMapping($num_source_fields, $num_destinat $this->saveMap($row); $id_map = $this->getIdMap(); // Test for a valid hit. - $destination_id = $id_map->lookupDestinationId($source_id_values); - $this->assertSame($expected_result, $destination_id); + $destination_ids = $id_map->lookupDestinationIds($source_id_values); + $this->assertSame([$expected_result], $destination_ids); // Test for a miss. - $destination_id = $id_map->lookupDestinationId($nonexistent_id_values); - $this->assertSame(0, count($destination_id)); + $destination_ids = $id_map->lookupDestinationIds($nonexistent_id_values); + $this->assertSame(0, count($destination_ids)); } /** @@ -549,6 +560,31 @@ public function testLookupDestinationIds() { $this->assertNotEquals([[101, 'en']], $id_map->lookupDestinationIds([1, 'en'])); } + /** + * Tests lookupDestinationId(). + * + * @group legacy + * @expectedDeprecation Drupal\migrate\Plugin\migrate\id_map\Sql::lookupDestinationId() is deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use Sql::lookupDestinationIds() instead. See https://www.drupal.org/node/2725809 + */ + public function testLookupDestinationId() { + // Simple map with one source and one destination ID. + $id_map = $this->setupRows(['nid'], ['nid'], [ + [1, 101], + [2, 102], + [3, 103], + ]); + + // Lookup nothing, gives nothing. + $this->assertEquals([], $id_map->lookupDestinationId([])); + + // Lookup by complete non-associative list. + $this->assertEquals([101], $id_map->lookupDestinationId([1])); + $this->assertEquals([], $id_map->lookupDestinationId([99])); + + // Lookup by complete associative list. + $this->assertEquals([101], $id_map->lookupDestinationId(['nid' => 1])); + } + /** * Tests the getRowByDestination method. */ diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php b/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php index 9fe27ba91..f3aec5092 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php @@ -11,7 +11,7 @@ /** * Base class for Migrate module source unit tests. * - * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase instead. */ abstract class MigrateSqlSourceTestCase extends MigrateTestCase { @@ -80,17 +80,17 @@ abstract class MigrateSqlSourceTestCase extends MigrateTestCase { * {@inheritdoc} */ protected function setUp() { - $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); - $state = $this->getMock('Drupal\Core\State\StateInterface'); - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $module_handler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $state = $this->createMock('Drupal\Core\State\StateInterface'); + $entity_manager = $this->createMock('Drupal\Core\Entity\EntityManagerInterface'); // Mock a key-value store to return high-water values. - $key_value = $this->getMock(KeyValueStoreInterface::class); + $key_value = $this->createMock(KeyValueStoreInterface::class); // SourcePluginBase does not yet support full dependency injection so we // need to make sure that \Drupal::keyValue() works as expected by mocking // the keyvalue service. - $key_value_factory = $this->getMock(KeyValueFactoryInterface::class); + $key_value_factory = $this->createMock(KeyValueFactoryInterface::class); $key_value_factory ->method('get') ->with('migrate:high_water') diff --git a/core/modules/migrate/tests/src/Unit/MigrateStubTest.php b/core/modules/migrate/tests/src/Unit/MigrateStubTest.php new file mode 100644 index 000000000..0a026ffc5 --- /dev/null +++ b/core/modules/migrate/tests/src/Unit/MigrateStubTest.php @@ -0,0 +1,93 @@ +migrationPluginManager = $this->prophesize(MigrationPluginManagerInterface::class); + } + + /** + * Tests stubbing. + * + * @covers ::createStub + */ + public function testCreateStub() { + $destination_plugin = $this->prophesize(MigrateDestinationInterface::class); + $destination_plugin->import(Argument::type(Row::class))->willReturn(['id' => 2]); + + $source_plugin = $this->prophesize(MigrateSourceInterface::class); + $source_plugin->getIds()->willReturn(['id' => ['type' => 'integer']]); + + $id_map = $this->prophesize(MigrateIdMapInterface::class); + + $migration = $this->prophesize(MigrationInterface::class); + $migration->getIdMap()->willReturn($id_map->reveal()); + $migration->getDestinationPlugin(TRUE)->willReturn($destination_plugin->reveal()); + $migration->getProcessPlugins([])->willReturn([]); + $migration->getProcess()->willReturn([]); + $migration->getSourceConfiguration()->willReturn([]); + $migration->getSourcePlugin()->willReturn($source_plugin->reveal()); + + $this->migrationPluginManager->createInstances(['test_migration'])->willReturn([$migration->reveal()]); + + $stub = new MigrateStub($this->migrationPluginManager->reveal()); + + $this->assertSame(['id' => 2], $stub->createStub('test_migration', ['id' => 1], [])); + } + + /** + * Tests that an error is logged if the plugin manager throws an exception. + */ + public function testExceptionOnPluginNotFound() { + $this->migrationPluginManager->createInstances(['test_migration'])->willReturn([]); + $this->expectException(PluginNotFoundException::class); + $stub = new MigrateStub($this->migrationPluginManager->reveal()); + $stub->createStub('test_migration', [1]); + } + + /** + * Tests that an error is logged on derived migrations. + */ + public function testExceptionOnDerivedMigration() { + $this->migrationPluginManager->createInstances(['test_migration'])->willReturn([ + 'test_migration:d1' => $this->prophesize(MigrationInterface::class)->reveal(), + 'test_migration:d2' => $this->prophesize(MigrationInterface::class)->reveal(), + ]); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Cannot stub derivable migration "test_migration". You must specify the id of a specific derivative to stub.'); + $stub = new MigrateStub($this->migrationPluginManager->reveal()); + $stub->createStub('test_migration', [1]); + } + +} diff --git a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php index 5c1dd118e..4cb14415b 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php +++ b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php @@ -22,7 +22,7 @@ abstract class MigrateTestCase extends UnitTestCase { /** * The migration ID map. * - * @var \Drupal\migrate\Plugin\MigrateIdMapInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\Plugin\MigrateIdMapInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $idMap; @@ -36,12 +36,12 @@ abstract class MigrateTestCase extends UnitTestCase { /** * Retrieves a mocked migration. * - * @return \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject + * @return \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit\Framework\MockObject\MockObject * The mocked migration. */ protected function getMigration() { $this->migrationConfiguration += ['migrationClass' => 'Drupal\migrate\Plugin\Migration']; - $this->idMap = $this->getMock('Drupal\migrate\Plugin\MigrateIdMapInterface'); + $this->idMap = $this->createMock('Drupal\migrate\Plugin\MigrateIdMapInterface'); $this->idMap ->method('getQualifiedMapTableName') @@ -114,7 +114,7 @@ protected function getDatabase(array $database_contents, $connection_options = [ // Initialize the DIC with a fake module handler for alterable queries. $container = new ContainerBuilder(); - $container->set('module_handler', $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface')); + $container->set('module_handler', $this->createMock('\Drupal\Core\Extension\ModuleHandlerInterface')); \Drupal::setContainer($container); // Create the tables and load them up with data, skipping empty ones. diff --git a/core/modules/migrate/tests/src/Unit/MigrationPluginManagerTest.php b/core/modules/migrate/tests/src/Unit/MigrationPluginManagerTest.php index 259254739..c1228b281 100644 --- a/core/modules/migrate/tests/src/Unit/MigrationPluginManagerTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrationPluginManagerTest.php @@ -26,9 +26,9 @@ public function setUp() { parent::setUp(); // Get a plugin manager for testing. - $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); - $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); - $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $module_handler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $cache_backend = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); + $language_manager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface'); $this->pluginManager = new MigrationPluginManager($module_handler, $cache_backend, $language_manager); } diff --git a/core/modules/migrate/tests/src/Unit/MigrationTest.php b/core/modules/migrate/tests/src/Unit/MigrationTest.php index 364d8d8c0..27e9a803b 100644 --- a/core/modules/migrate/tests/src/Unit/MigrationTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrationTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\migrate\Unit; +use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\Migration; use Drupal\migrate\Exception\RequirementsException; @@ -31,16 +32,17 @@ class MigrationTest extends UnitTestCase { public function testRequirementsForSourcePlugin() { $migration = new TestMigration(); - $source_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareSourceInterface'); + $source_plugin = $this->createMock('Drupal\Tests\migrate\Unit\RequirementsAwareSourceInterface'); $source_plugin->expects($this->once()) ->method('checkRequirements') ->willThrowException(new RequirementsException('Missing source requirement', ['key' => 'value'])); - $destination_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface'); + $destination_plugin = $this->createMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface'); $migration->setSourcePlugin($source_plugin); $migration->setDestinationPlugin($destination_plugin); - $this->setExpectedException(RequirementsException::class, 'Missing source requirement'); + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage('Missing source requirement'); $migration->checkRequirements(); } @@ -52,8 +54,8 @@ public function testRequirementsForSourcePlugin() { public function testRequirementsForDestinationPlugin() { $migration = new TestMigration(); - $source_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface'); - $destination_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface'); + $source_plugin = $this->createMock('Drupal\migrate\Plugin\MigrateSourceInterface'); + $destination_plugin = $this->createMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface'); $destination_plugin->expects($this->once()) ->method('checkRequirements') ->willThrowException(new RequirementsException('Missing destination requirement', ['key' => 'value'])); @@ -61,7 +63,8 @@ public function testRequirementsForDestinationPlugin() { $migration->setSourcePlugin($source_plugin); $migration->setDestinationPlugin($destination_plugin); - $this->setExpectedException(RequirementsException::class, 'Missing destination requirement'); + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage('Missing destination requirement'); $migration->checkRequirements(); } @@ -74,21 +77,21 @@ public function testRequirementsForMigrations() { $migration = new TestMigration(); // Setup source and destination plugins without any requirements. - $source_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface'); - $destination_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); + $source_plugin = $this->createMock('Drupal\migrate\Plugin\MigrateSourceInterface'); + $destination_plugin = $this->createMock('Drupal\migrate\Plugin\MigrateDestinationInterface'); $migration->setSourcePlugin($source_plugin); $migration->setDestinationPlugin($destination_plugin); - $plugin_manager = $this->getMock('Drupal\migrate\Plugin\MigrationPluginManagerInterface'); + $plugin_manager = $this->createMock('Drupal\migrate\Plugin\MigrationPluginManagerInterface'); $migration->setMigrationPluginManager($plugin_manager); // We setup the requirements that test_a doesn't exist and test_c is not // completed yet. $migration->setRequirements(['test_a', 'test_b', 'test_c', 'test_d']); - $migration_b = $this->getMock(MigrationInterface::class); - $migration_c = $this->getMock(MigrationInterface::class); - $migration_d = $this->getMock(MigrationInterface::class); + $migration_b = $this->createMock(MigrationInterface::class); + $migration_c = $this->createMock(MigrationInterface::class); + $migration_d = $this->createMock(MigrationInterface::class); $migration_b->expects($this->once()) ->method('allRowsProcessed') @@ -105,10 +108,80 @@ public function testRequirementsForMigrations() { ->with(['test_a', 'test_b', 'test_c', 'test_d']) ->willReturn(['test_b' => $migration_b, 'test_c' => $migration_c, 'test_d' => $migration_d]); - $this->setExpectedException(RequirementsException::class, 'Missing migrations test_a, test_c'); + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage('Missing migrations test_a, test_c'); $migration->checkRequirements(); } + /** + * Tests valid migration dependencies configuration returns expected values. + * + * @param array|null $source + * The migration dependencies configuration being tested. + * @param array $expected_value + * The migration dependencies configuration array expected. + * + * @covers ::getMigrationDependencies + * @dataProvider getValidMigrationDependenciesProvider + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + public function testMigrationDependenciesWithValidConfig($source, array $expected_value) { + $migration = new TestMigration(); + if (!is_null($source)) { + $migration->set('migration_dependencies', $source); + } + $this->assertSame($migration->getMigrationDependencies(), $expected_value); + } + + /** + * Tests that getting migration dependencies fails with invalid configuration. + * + * @covers ::getMigrationDependencies + */ + public function testMigrationDependenciesWithInvalidConfig() { + $migration = new TestMigration(); + + // Set the plugin ID to test the returned message. + $plugin_id = 'test_migration'; + $migration->setPluginId($plugin_id); + + // Migration dependencies expects ['optional' => []] or ['required' => []]]. + $migration->set('migration_dependencies', ['test_migration_dependency']); + + $this->expectException(InvalidPluginDefinitionException::class); + $this->expectExceptionMessage("Invalid migration dependencies configuration for migration {$plugin_id}"); + $migration->getMigrationDependencies(); + } + + /** + * Provides data for valid migration configuration test. + */ + public function getValidMigrationDependenciesProvider() { + return [ + [ + 'source' => NULL, + 'expected_value' => ['required' => [], 'optional' => []], + ], + [ + 'source' => [], + 'expected_value' => ['required' => [], 'optional' => []], + ], + [ + 'source' => ['required' => ['test_migration']], + 'expected_value' => ['required' => ['test_migration'], 'optional' => []], + ], + [ + 'source' => ['optional' => ['test_migration']], + 'expected_value' => ['optional' => ['test_migration'], 'required' => []], + ], + [ + 'source' => ['required' => ['req_test_migration'], 'optional' => ['opt_test_migration']], + 'expected_value' => ['required' => ['req_test_migration'], 'optional' => ['opt_test_migration']], + ], + ]; + } + } /** @@ -122,6 +195,16 @@ class TestMigration extends Migration { public function __construct() { } + /** + * Sets the migration ID (machine name). + * + * @param string $plugin_id + * The plugin_id of the plugin instance. + */ + public function setPluginId($plugin_id) { + $this->pluginId = $plugin_id; + } + /** * Sets the requirements values. * diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php index deca2c924..23326ed3d 100644 --- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php +++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php @@ -28,7 +28,8 @@ public function testException() { $this->prophesize(ConfigFactoryInterface::class)->reveal(), $this->prophesize(LanguageManagerInterface::class)->reveal() ); - $this->setExpectedException(RequirementsException::class, "Destination plugin 'test' did not meet the requirements"); + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage("Destination plugin 'test' did not meet the requirements"); $destination->checkRequirements(); } diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php index 12bfd7134..3a4d12ef4 100644 --- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php +++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php @@ -33,9 +33,11 @@ public function testImport() { $this->migration->reveal(), $this->storage->reveal(), $bundles, - $this->entityManager->reveal(), + $this->entityFieldManager->reveal(), $this->prophesize(FieldTypePluginManagerInterface::class)->reveal()); $entity = $this->prophesize(ContentEntityInterface::class); + $entity->isValidationRequired() + ->shouldBeCalledTimes(1); // Assert that save is called. $entity->save() ->shouldBeCalledTimes(1); @@ -60,10 +62,11 @@ public function testImportEntityLoadFailure() { $this->migration->reveal(), $this->storage->reveal(), $bundles, - $this->entityManager->reveal(), + $this->entityFieldManager->reveal(), $this->prophesize(FieldTypePluginManagerInterface::class)->reveal()); $destination->setEntity(FALSE); - $this->setExpectedException(MigrateException::class, 'Unable to get entity'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Unable to get entity'); $destination->import(new Row()); } @@ -74,7 +77,7 @@ public function testUntranslatable() { // An entity type without a language. $this->entityType->getKey('langcode')->willReturn(''); $this->entityType->getKey('id')->willReturn('id'); - $this->entityManager->getBaseFieldDefinitions('foo') + $this->entityFieldManager->getBaseFieldDefinitions('foo') ->willReturn(['id' => BaseFieldDefinitionTest::create('integer')]); $destination = new EntityTestDestination( @@ -84,10 +87,11 @@ public function testUntranslatable() { $this->migration->reveal(), $this->storage->reveal(), [], - $this->entityManager->reveal(), + $this->entityFieldManager->reveal(), $this->prophesize(FieldTypePluginManagerInterface::class)->reveal() ); - $this->setExpectedException(MigrateException::class, 'The "foo" entity type does not support translations.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The "foo" entity type does not support translations.'); $destination->getIds(); } diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityRevisionTest.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityRevisionTest.php index cddb4a707..f0b0379b0 100644 --- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityRevisionTest.php +++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityRevisionTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination; -use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; @@ -33,8 +32,6 @@ protected function setUp() { $this->entityType->getPluralLabel()->willReturn('bar'); $this->storage->getEntityType()->willReturn($this->entityType->reveal()); $this->storage->getEntityTypeId()->willReturn('foo'); - - $this->entityManager = $this->prophesize(EntityManagerInterface::class); } /** @@ -43,7 +40,7 @@ protected function setUp() { public function testUnrevisionable() { $this->entityType->getKey('id')->willReturn('id'); $this->entityType->getKey('revision')->willReturn(''); - $this->entityManager->getBaseFieldDefinitions('foo') + $this->entityFieldManager->getBaseFieldDefinitions('foo') ->willReturn([ 'id' => BaseFieldDefinitionTest::create('integer'), ]); @@ -55,10 +52,11 @@ public function testUnrevisionable() { $this->migration->reveal(), $this->storage->reveal(), [], - $this->entityManager->reveal(), + $this->entityFieldManager->reveal(), $this->prophesize(FieldTypePluginManagerInterface::class)->reveal() ); - $this->setExpectedException(MigrateException::class, 'The "foo" entity type does not support revisions.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The "foo" entity type does not support revisions.'); $destination->getIds(); } @@ -69,7 +67,7 @@ public function testUntranslatable() { $this->entityType->getKey('id')->willReturn('id'); $this->entityType->getKey('revision')->willReturn('vid'); $this->entityType->getKey('langcode')->willReturn(''); - $this->entityManager->getBaseFieldDefinitions('foo') + $this->entityFieldManager->getBaseFieldDefinitions('foo') ->willReturn([ 'id' => BaseFieldDefinitionTest::create('integer'), 'vid' => BaseFieldDefinitionTest::create('integer'), @@ -82,10 +80,11 @@ public function testUntranslatable() { $this->migration->reveal(), $this->storage->reveal(), [], - $this->entityManager->reveal(), + $this->entityFieldManager->reveal(), $this->prophesize(FieldTypePluginManagerInterface::class)->reveal() ); - $this->setExpectedException(MigrateException::class, 'The "foo" entity type does not support translations.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The "foo" entity type does not support translations.'); $destination->getIds(); } diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php index a2dd7c6e8..2b0e22188 100644 --- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php +++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityTestBase.php @@ -7,7 +7,7 @@ namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -33,11 +33,10 @@ class EntityTestBase extends UnitTestCase { * @var \Drupal\Core\Entity\EntityTypeInterface */ protected $entityType; - /** - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityFieldManagerInterface */ - protected $entityManager; + protected $entityFieldManager; /** * {@inheritdoc} @@ -53,7 +52,7 @@ protected function setUp() { $this->storage->getEntityType()->willReturn($this->entityType->reveal()); $this->storage->getEntityTypeId()->willReturn('foo'); - $this->entityManager = $this->prophesize(EntityManagerInterface::class); + $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class); } } diff --git a/core/modules/migrate/tests/src/Unit/RowTest.php b/core/modules/migrate/tests/src/Unit/RowTest.php index edc31b095..416190078 100644 --- a/core/modules/migrate/tests/src/Unit/RowTest.php +++ b/core/modules/migrate/tests/src/Unit/RowTest.php @@ -115,7 +115,7 @@ public function testRowWithInvalidData() { $invalid_values = [ 'title' => 'node X', ]; - $this->setExpectedException(\Exception::class); + $this->expectException(\Exception::class); $row = new Row($invalid_values, $this->testSourceIds); } @@ -130,7 +130,7 @@ public function testSourceFreeze() { $row->rehash(); $this->assertSame($this->testHashMod, $row->getHash(), 'Hash changed correctly.'); $row->freezeSource(); - $this->setExpectedException(\Exception::class); + $this->expectException(\Exception::class); $row->setSourceProperty('title', 'new title'); } @@ -140,7 +140,8 @@ public function testSourceFreeze() { public function testSetFrozenRow() { $row = new Row($this->testValues, $this->testSourceIds); $row->freezeSource(); - $this->setExpectedException(\Exception::class, "The source is frozen and can't be changed any more"); + $this->expectException(\Exception::class); + $this->expectExceptionMessage("The source is frozen and can't be changed any more"); $row->setSourceProperty('title', 'new title'); } diff --git a/core/modules/migrate/tests/src/Unit/SqlBaseTest.php b/core/modules/migrate/tests/src/Unit/SqlBaseTest.php index b8097a3b4..bc2843e1e 100644 --- a/core/modules/migrate/tests/src/Unit/SqlBaseTest.php +++ b/core/modules/migrate/tests/src/Unit/SqlBaseTest.php @@ -62,7 +62,7 @@ public function testMapJoinable($expected_result, $id_map_is_sql, $with_id_map, ->willReturn($idmap_connection); // Setup a migration entity. - $migration = $this->getMock(MigrationInterface::class); + $migration = $this->createMock(MigrationInterface::class); $migration->expects($with_id_map ? $this->once() : $this->never()) ->method('getIdMap') ->willReturn($id_map_is_sql ? $sql : NULL); diff --git a/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php b/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php index b57cffea4..b6fd292d4 100644 --- a/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php +++ b/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php @@ -36,7 +36,7 @@ public function testImport() { $config->expects($this->once()) ->method('getName') ->willReturn('d8_config'); - $config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface'); + $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface'); $config_factory->expects($this->once()) ->method('getEditable') ->with('d8_config') @@ -83,7 +83,7 @@ public function testLanguageImport() { $config->expects($this->any()) ->method('getName') ->willReturn('d8_config'); - $config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface'); + $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface'); $config_factory->expects($this->once()) ->method('getEditable') ->with('d8_config') diff --git a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php index f6ed26666..219a13f17 100644 --- a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php +++ b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php @@ -34,9 +34,9 @@ class EntityRevisionTest extends UnitTestCase { protected $storage; /** - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityFieldManagerInterface */ - protected $entityManager; + protected $entityFieldManager; /** * @var \Drupal\Core\Field\FieldTypePluginManagerInterface @@ -55,7 +55,7 @@ protected function setUp() { $entity_type->getPluralLabel()->willReturn('craziness'); $this->storage->getEntityType()->willReturn($entity_type->reveal()); - $this->entityManager = $this->prophesize('\Drupal\Core\Entity\EntityManagerInterface'); + $this->entityFieldManager = $this->prophesize('\Drupal\Core\Entity\EntityFieldManagerInterface'); $this->fieldTypeManager = $this->prophesize('\Drupal\Core\Field\FieldTypePluginManagerInterface'); } @@ -193,7 +193,7 @@ protected function getEntityRevisionDestination(array $configuration = [], $plug $this->migration->reveal(), $this->storage->reveal(), [], - $this->entityManager->reveal(), + $this->entityFieldManager->reveal(), $this->fieldTypeManager->reveal() ); } diff --git a/core/modules/migrate/tests/src/Unit/process/ArrayBuildTest.php b/core/modules/migrate/tests/src/Unit/process/ArrayBuildTest.php index bbfef8b19..66ffedddc 100644 --- a/core/modules/migrate/tests/src/Unit/process/ArrayBuildTest.php +++ b/core/modules/migrate/tests/src/Unit/process/ArrayBuildTest.php @@ -46,7 +46,8 @@ public function testNonExistentKey() { $source = [ ['bar' => 'foo'], ]; - $this->setExpectedException(MigrateException::class, "The key 'foo' does not exist"); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("The key 'foo' does not exist"); $this->plugin->transform($source, $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -57,7 +58,8 @@ public function testNonExistentValue() { $source = [ ['foo' => 'bar'], ]; - $this->setExpectedException(MigrateException::class, "The key 'bar' does not exist"); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("The key 'bar' does not exist"); $this->plugin->transform($source, $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -66,7 +68,8 @@ public function testNonExistentValue() { */ public function testOneDimensionalArrayInput() { $source = ['foo' => 'bar']; - $this->setExpectedException(MigrateException::class, 'The input should be an array of arrays'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The input should be an array of arrays'); $this->plugin->transform($source, $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -75,7 +78,8 @@ public function testOneDimensionalArrayInput() { */ public function testStringInput() { $source = 'foo'; - $this->setExpectedException(MigrateException::class, 'The input should be an array of arrays'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The input should be an array of arrays'); $this->plugin->transform($source, $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/CallbackTest.php b/core/modules/migrate/tests/src/Unit/process/CallbackTest.php index 6d894c09c..260e9a7a2 100644 --- a/core/modules/migrate/tests/src/Unit/process/CallbackTest.php +++ b/core/modules/migrate/tests/src/Unit/process/CallbackTest.php @@ -39,7 +39,8 @@ public function providerCallback() { * @dataProvider providerCallbackExceptions */ public function testCallbackExceptions($message, $configuration) { - $this->setExpectedException(\InvalidArgumentException::class, $message); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($message); $this->plugin = new Callback($configuration, 'map', []); } diff --git a/core/modules/migrate/tests/src/Unit/process/ConcatTest.php b/core/modules/migrate/tests/src/Unit/process/ConcatTest.php index 8acbc1157..6f6181d84 100644 --- a/core/modules/migrate/tests/src/Unit/process/ConcatTest.php +++ b/core/modules/migrate/tests/src/Unit/process/ConcatTest.php @@ -37,7 +37,7 @@ public function testConcatWithoutDelimiter() { * Test concat fails properly on non-arrays. */ public function testConcatWithNonArray() { - $this->setExpectedException(MigrateException::class); + $this->expectException(MigrateException::class); $this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/DedupeEntityTest.php b/core/modules/migrate/tests/src/Unit/process/DedupeEntityTest.php index 6dd879530..b271d3700 100644 --- a/core/modules/migrate/tests/src/Unit/process/DedupeEntityTest.php +++ b/core/modules/migrate/tests/src/Unit/process/DedupeEntityTest.php @@ -24,7 +24,7 @@ class DedupeEntityTest extends MigrateProcessTestCase { /** * The mocked entity type manager. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeManager; @@ -44,9 +44,9 @@ protected function setUp() { $this->entityQuery = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryInterface') ->disableOriginalConstructor() ->getMock(); - $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class); + $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); - $storage = $this->getMock(EntityStorageInterface::class); + $storage = $this->createMock(EntityStorageInterface::class); $storage->expects($this->any()) ->method('getQuery') ->willReturn($this->entityQuery); @@ -91,7 +91,8 @@ public function testDedupeEntityInvalidStart() { 'start' => 'foobar', ]; $plugin = new DedupeEntity($configuration, 'dedupe_entity', [], $this->getMigration(), $this->entityTypeManager); - $this->setExpectedException('Drupal\migrate\MigrateException', 'The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.'); + $this->expectException('Drupal\migrate\MigrateException'); + $this->expectExceptionMessage('The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.'); $plugin->transform('test_start', $this->migrateExecutable, $this->row, 'testproperty'); } @@ -105,7 +106,8 @@ public function testDedupeEntityInvalidLength() { 'length' => 'foobar', ]; $plugin = new DedupeEntity($configuration, 'dedupe_entity', [], $this->getMigration(), $this->entityTypeManager); - $this->setExpectedException('Drupal\migrate\MigrateException', 'The character length configuration key should be an integer. Omit this key to capture the entire string.'); + $this->expectException('Drupal\migrate\MigrateException'); + $this->expectExceptionMessage('The character length configuration key should be an integer. Omit this key to capture the entire string.'); $plugin->transform('test_length', $this->migrateExecutable, $this->row, 'testproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php b/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php index 466d002c9..2d3f0be32 100644 --- a/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php +++ b/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php @@ -56,7 +56,8 @@ public function testChainedTransform() { * Test explode fails properly on non-strings. */ public function testExplodeWithNonString() { - $this->setExpectedException(MigrateException::class, 'is not a string'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('is not a string'); $this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -93,7 +94,8 @@ public function providerExplodeWithNonStrictAndEmptySource() { */ public function testExplodeWithNonStrictAndNonCastable() { $plugin = new Explode(['delimiter' => '|', 'strict' => FALSE], 'map', []); - $this->setExpectedException(MigrateException::class, 'cannot be casted to a string'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('cannot be casted to a string'); $processed = $plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty'); $this->assertSame(['foo'], $processed); } @@ -112,7 +114,8 @@ public function testExplodeWithStrictAndEmptyString() { * Test explode fails with empty delimiter. */ public function testExplodeWithEmptyDelimiter() { - $this->setExpectedException(MigrateException::class, 'delimiter is empty'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('delimiter is empty'); $plugin = new Explode(['delimiter' => ''], 'map', []); $plugin->transform('foo,bar', $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/ExtractTest.php b/core/modules/migrate/tests/src/Unit/process/ExtractTest.php index ad0a45a25..2af2dc3b7 100644 --- a/core/modules/migrate/tests/src/Unit/process/ExtractTest.php +++ b/core/modules/migrate/tests/src/Unit/process/ExtractTest.php @@ -32,7 +32,8 @@ public function testExtract() { * Tests invalid input. */ public function testExtractFromString() { - $this->setExpectedException(MigrateException::class, 'Input should be an array.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Input should be an array.'); $this->plugin->transform('bar', $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -40,7 +41,8 @@ public function testExtractFromString() { * Tests unsuccessful extraction. */ public function testExtractFail() { - $this->setExpectedException(MigrateException::class, 'Array index missing, extraction failed.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Array index missing, extraction failed.'); $this->plugin->transform(['bar' => 'foo'], $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/FormatDateTest.php b/core/modules/migrate/tests/src/Unit/process/FormatDateTest.php index f931c618a..629243c84 100644 --- a/core/modules/migrate/tests/src/Unit/process/FormatDateTest.php +++ b/core/modules/migrate/tests/src/Unit/process/FormatDateTest.php @@ -23,7 +23,8 @@ public function testMigrateExceptionMissingFromFormat() { 'to_format' => 'Y-m-d', ]; - $this->setExpectedException(MigrateException::class, 'Format date plugin is missing from_format configuration.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Format date plugin is missing from_format configuration.'); $this->plugin = new FormatDate($configuration, 'test_format_date', []); $this->plugin->transform('01/05/1955', $this->migrateExecutable, $this->row, 'field_date'); } @@ -37,7 +38,8 @@ public function testMigrateExceptionMissingToFormat() { 'to_format' => '', ]; - $this->setExpectedException(MigrateException::class, 'Format date plugin is missing to_format configuration.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Format date plugin is missing to_format configuration.'); $this->plugin = new FormatDate($configuration, 'test_format_date', []); $this->plugin->transform('01/05/1955', $this->migrateExecutable, $this->row, 'field_date'); } @@ -51,7 +53,8 @@ public function testMigrateExceptionBadFormat() { 'to_format' => 'Y-m-d', ]; - $this->setExpectedException(MigrateException::class, "Format date plugin could not transform 'January 5, 1955' using the format 'm/d/Y' for destination 'field_date'. Error: The date cannot be created from a format."); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("Format date plugin could not transform 'January 5, 1955' using the format 'm/d/Y' for destination 'field_date'. Error: The date cannot be created from a format."); $this->plugin = new FormatDate($configuration, 'test_format_date', []); $this->plugin->transform('January 5, 1955', $this->migrateExecutable, $this->row, 'field_date'); } @@ -65,7 +68,8 @@ public function testMigrateExceptionUnexpectedValue() { 'to_format' => 'Y-m-d', ]; - $this->setExpectedException(MigrateException::class, "Format date plugin could not transform '01/05/55' using the format 'm/d/Y' for destination 'field_date'. Error: The created date does not match the input value."); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage("Format date plugin could not transform '01/05/55' using the format 'm/d/Y' for destination 'field_date'. Error: The created date does not match the input value."); $this->plugin = new FormatDate($configuration, 'test_format_date', []); $this->plugin->transform('01/05/55', $this->migrateExecutable, $this->row, 'field_date'); } diff --git a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php b/core/modules/migrate/tests/src/Unit/process/IteratorTest.php index f2124a6db..189a8ee24 100644 --- a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php +++ b/core/modules/migrate/tests/src/Unit/process/IteratorTest.php @@ -59,8 +59,8 @@ public function testIterator() { $migration->expects($this->at(2)) ->method('getProcessPlugins') ->will($this->returnValue($key_plugin)); - $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - $migrate_executable = new MigrateExecutable($migration, $this->getMock('Drupal\migrate\MigrateMessageInterface'), $event_dispatcher); + $event_dispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $migrate_executable = new MigrateExecutable($migration, $this->createMock('Drupal\migrate\MigrateMessageInterface'), $event_dispatcher); // The current value of the pipeline. $current_value = [ diff --git a/core/modules/migrate/tests/src/Unit/process/LegacyMigrationLookupTest.php b/core/modules/migrate/tests/src/Unit/process/LegacyMigrationLookupTest.php new file mode 100644 index 000000000..96a068535 --- /dev/null +++ b/core/modules/migrate/tests/src/Unit/process/LegacyMigrationLookupTest.php @@ -0,0 +1,40 @@ +prepareContainer(); + $lookup = MigrationLookup::create($this->prepareContainer(), [], '', [], $this->prophesize(MigrationInterface::class) + ->reveal()); + $method = new \ReflectionMethod($lookup, 'createStubRow'); + $method->setAccessible(TRUE); + /** @var \Drupal\migrate\Row $row */ + $row = $method->invoke($lookup, [ + 'id' => 1, + 'value' => 'test', + ], ['id' => ['type' => 'integer']]); + $this->assertTrue($row->isStub()); + $this->assertSame('test', $row->get('value')); + + } + +} diff --git a/core/modules/migrate/tests/src/Unit/process/MakeUniqueEntityFieldTest.php b/core/modules/migrate/tests/src/Unit/process/MakeUniqueEntityFieldTest.php index 9d3ab06da..1a5a63185 100644 --- a/core/modules/migrate/tests/src/Unit/process/MakeUniqueEntityFieldTest.php +++ b/core/modules/migrate/tests/src/Unit/process/MakeUniqueEntityFieldTest.php @@ -23,7 +23,7 @@ class MakeUniqueEntityFieldTest extends MigrateProcessTestCase { /** * The mocked entity type manager. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeManager; @@ -43,9 +43,9 @@ protected function setUp() { $this->entityQuery = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryInterface') ->disableOriginalConstructor() ->getMock(); - $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class); + $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); - $storage = $this->getMock(EntityStorageInterface::class); + $storage = $this->createMock(EntityStorageInterface::class); $storage->expects($this->any()) ->method('getQuery') ->willReturn($this->entityQuery); @@ -90,7 +90,8 @@ public function testMakeUniqueEntityFieldEntityInvalidStart() { 'start' => 'foobar', ]; $plugin = new MakeUniqueEntityField($configuration, 'make_unique', [], $this->getMigration(), $this->entityTypeManager); - $this->setExpectedException('Drupal\migrate\MigrateException', 'The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.'); + $this->expectException('Drupal\migrate\MigrateException'); + $this->expectExceptionMessage('The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.'); $plugin->transform('test_start', $this->migrateExecutable, $this->row, 'testproperty'); } @@ -104,7 +105,8 @@ public function testMakeUniqueEntityFieldEntityInvalidLength() { 'length' => 'foobar', ]; $plugin = new MakeUniqueEntityField($configuration, 'make_unique', [], $this->getMigration(), $this->entityTypeManager); - $this->setExpectedException('Drupal\migrate\MigrateException', 'The character length configuration key should be an integer. Omit this key to capture the entire string.'); + $this->expectException('Drupal\migrate\MigrateException'); + $this->expectExceptionMessage('The character length configuration key should be an integer. Omit this key to capture the entire string.'); $plugin->transform('test_length', $this->migrateExecutable, $this->row, 'testproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/MenuLinkParentTest.php b/core/modules/migrate/tests/src/Unit/process/MenuLinkParentTest.php index e8354368b..d207be0c0 100644 --- a/core/modules/migrate/tests/src/Unit/process/MenuLinkParentTest.php +++ b/core/modules/migrate/tests/src/Unit/process/MenuLinkParentTest.php @@ -2,11 +2,16 @@ namespace Drupal\Tests\migrate\Unit\process; +use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Menu\MenuLinkManagerInterface; +use Drupal\menu_link_content\MenuLinkContentInterface; +use Drupal\migrate\MigrateLookupInterface; use Drupal\migrate\MigrateSkipRowException; use Drupal\migrate\Plugin\migrate\process\MenuLinkParent; use Drupal\migrate\Plugin\MigrateProcessInterface; +use Drupal\migrate\Plugin\MigrationInterface; /** * Tests the menu link parent process plugin. @@ -16,23 +21,107 @@ */ class MenuLinkParentTest extends MigrateProcessTestCase { + /** + * A MigrationInterface prophecy. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ + protected $migration; + + /** + * A MigrateLookupInterface prophecy. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ + protected $migrateLookup; + + /** + * A MigrationInterface prophecy. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ + protected $menuLinkManager; + + /** + * A MigrationInterface prophecy. + * + * @var \Prophecy\Prophecy\ObjectProphecy + */ + protected $menuLinkStorage; + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - $migration_plugin = $this->prophesize(MigrateProcessInterface::class); - $menu_link_manager = $this->prophesize(MenuLinkManagerInterface::class); - $menu_link_storage = $this->prophesize(EntityStorageInterface::class); - $this->plugin = new MenuLinkParent([], 'map', [], $migration_plugin->reveal(), $menu_link_manager->reveal(), $menu_link_storage->reveal()); + $this->migration = $this->prophesize(MigrationInterface::class); + $this->migrateLookup = $this->prophesize(MigrateLookupInterface::class); + $this->migrateLookup->lookup(NULL, [1])->willReturn([]); + $this->menuLinkManager = $this->prophesize(MenuLinkManagerInterface::class); + $this->menuLinkStorage = $this->prophesize(EntityStorageInterface::class); + $container = new ContainerBuilder(); + $container->set('migrate.lookup', $this->migrateLookup->reveal()); + \Drupal::setContainer($container); + } /** * @covers ::transform */ public function testTransformException() { - $this->setExpectedException(MigrateSkipRowException::class, "No parent link found for plid '1' in menu 'admin'."); - $this->plugin->transform([1, 'admin', NULL], $this->migrateExecutable, $this->row, 'destinationproperty'); + $plugin = new MenuLinkParent([], 'map', [], $this->migrateLookup->reveal(), $this->menuLinkManager->reveal(), $this->menuLinkStorage->reveal(), $this->migration->reveal()); + $this->expectException(MigrateSkipRowException::class); + $this->expectExceptionMessage("No parent link found for plid '1' in menu 'admin'."); + $plugin->transform([1, 'admin', NULL], $this->migrateExecutable, $this->row, 'destinationproperty'); + } + + /** + * Tests the plugin when the parent is an external link. + * + * @covers ::transform + */ + public function testTransformExternal() { + $menu_link_content = $this->prophesize(MenuLinkContentInterface::class); + $menu_link_content->getPluginId()->willReturn('menu_link_content:fe151460-dfa2-4133-8864-c1746f28ab27'); + $this->menuLinkStorage->loadByProperties([ + 'link__uri' => 'http://example.com', + ])->willReturn([ + 9054 => $menu_link_content, + ]); + $plugin = $this->prophesize(PluginInspectionInterface::class); + $this->menuLinkManager->createInstance('menu_link_content:fe151460-dfa2-4133-8864-c1746f28ab27')->willReturn($plugin->reveal()); + $plugin = new MenuLinkParent([], 'map', [], $this->migrateLookup->reveal(), $this->menuLinkManager->reveal(), $this->menuLinkStorage->reveal(), $this->migration->reveal()); + + $result = $plugin->transform([1, 'admin', 'http://example.com'], $this->migrateExecutable, $this->row, 'destinationproperty'); + $this->assertEquals('menu_link_content:fe151460-dfa2-4133-8864-c1746f28ab27', $result); + } + + /** + * Tests the plugin when the parent is an external link. + * + * @covers ::transform + * + * @group legacy + * + * @expectedDeprecation Passing a migration process plugin as the fourth argument to Drupal\migrate\Plugin\migrate\process\MenuLinkParent::__construct is deprecated in drupal:8.8.0 and will throw an error in drupal:9.0.0. Pass the migrate.lookup service instead. See https://www.drupal.org/node/3047268 + */ + public function testLegacyTransformExternal() { + $migration_plugin = $this->prophesize(MigrateProcessInterface::class); + $menu_link_manager = $this->prophesize(MenuLinkManagerInterface::class); + $menu_link_storage = $this->prophesize(EntityStorageInterface::class); + $menu_link_content = $this->prophesize(MenuLinkContentInterface::class); + $menu_link_content->getPluginId()->willReturn('menu_link_content:fe151460-dfa2-4133-8864-c1746f28ab27'); + $menu_link_storage->loadByProperties([ + 'link__uri' => 'http://example.com', + ])->willReturn([ + 9054 => $menu_link_content, + ]); + $plugin = $this->prophesize(PluginInspectionInterface::class); + $menu_link_manager->createInstance('menu_link_content:fe151460-dfa2-4133-8864-c1746f28ab27')->willReturn($plugin->reveal()); + $plugin = new MenuLinkParent([], 'map', [], $migration_plugin->reveal(), $menu_link_manager->reveal(), $menu_link_storage->reveal()); + + $result = $plugin->transform([1, 'admin', 'http://example.com'], $this->migrateExecutable, $this->row, 'destinationproperty'); + $this->assertEquals('menu_link_content:fe151460-dfa2-4133-8864-c1746f28ab27', $result); } } diff --git a/core/modules/migrate/tests/src/Unit/process/MigrateProcessTestCase.php b/core/modules/migrate/tests/src/Unit/process/MigrateProcessTestCase.php index a0fac3dc1..193243333 100644 --- a/core/modules/migrate/tests/src/Unit/process/MigrateProcessTestCase.php +++ b/core/modules/migrate/tests/src/Unit/process/MigrateProcessTestCase.php @@ -17,7 +17,7 @@ abstract class MigrateProcessTestCase extends MigrateTestCase { protected $row; /** - * @var \Drupal\migrate\MigrateExecutable|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\MigrateExecutable|\PHPUnit\Framework\MockObject\MockObject */ protected $migrateExecutable; diff --git a/core/modules/migrate/tests/src/Unit/process/MigrationLookupTest.php b/core/modules/migrate/tests/src/Unit/process/MigrationLookupTest.php index 65073b72b..6ecda3008 100644 --- a/core/modules/migrate/tests/src/Unit/process/MigrationLookupTest.php +++ b/core/modules/migrate/tests/src/Unit/process/MigrationLookupTest.php @@ -2,22 +2,18 @@ namespace Drupal\Tests\migrate\Unit\process; -use Drupal\Core\Entity\EntityStorageInterface; use Drupal\migrate\MigrateSkipProcessException; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\migrate\process\MigrationLookup; -use Drupal\migrate\Plugin\MigrateDestinationInterface; use Drupal\migrate\Plugin\MigrateIdMapInterface; -use Drupal\migrate\Plugin\MigrateSourceInterface; use Drupal\migrate\Plugin\MigrationPluginManagerInterface; -use Drupal\migrate\Row; use Prophecy\Argument; /** * @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\MigrationLookup * @group migrate */ -class MigrationLookupTest extends MigrateProcessTestCase { +class MigrationLookupTest extends MigrationLookupTestCase { /** * @covers ::transform @@ -29,7 +25,7 @@ public function testTransformWithStubSkipping() { $destination_id_map = $this->prophesize(MigrateIdMapInterface::class); $destination_migration = $this->prophesize(MigrationInterface::class); $destination_migration->getIdMap()->willReturn($destination_id_map->reveal()); - $destination_id_map->lookupDestinationId([1])->willReturn(NULL); + $destination_id_map->lookupDestinationIds([1])->willReturn(NULL); // Ensure the migration plugin manager returns our migration. $migration_plugin_manager->createInstances(Argument::exact(['destination_migration'])) @@ -43,7 +39,7 @@ public function testTransformWithStubSkipping() { $migration_plugin->id()->willReturn('actual_migration'); $destination_migration->getDestinationPlugin(TRUE)->shouldNotBeCalled(); - $migration = new MigrationLookup($configuration, '', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal()); + $migration = MigrationLookup::create($this->prepareContainer(), $configuration, '', [], $migration_plugin->reveal()); $result = $migration->transform(1, $this->migrateExecutable, $this->row, ''); $this->assertNull($result); } @@ -53,35 +49,15 @@ public function testTransformWithStubSkipping() { */ public function testTransformWithStubbing() { $migration_plugin = $this->prophesize(MigrationInterface::class); - $migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class); - - $destination_id_map = $this->prophesize(MigrateIdMapInterface::class); - $destination_migration = $this->prophesize('Drupal\migrate\Plugin\Migration'); - $destination_migration->getIdMap()->willReturn($destination_id_map->reveal()); - $migration_plugin_manager->createInstances(['destination_migration']) - ->willReturn(['destination_migration' => $destination_migration->reveal()]); - $destination_id_map->lookupDestinationId([1])->willReturn(NULL); - $destination_id_map->saveIdMapping(Argument::any(), Argument::any(), MigrateIdMapInterface::STATUS_NEEDS_UPDATE)->willReturn(NULL); + $this->migrateLookup->lookup('destination_migration', [1])->willReturn(NULL); + $this->migrateStub->createStub('destination_migration', [1], [], FALSE)->willReturn([2]); $configuration = [ 'no_stub' => FALSE, 'migration' => 'destination_migration', ]; - $migration_plugin->id()->willReturn('actual_migration'); - $destination_migration->id()->willReturn('destination_migration'); - $destination_migration->getDestinationPlugin(TRUE)->shouldBeCalled(); - $destination_migration->getProcess()->willReturn([]); - $destination_migration->getSourceConfiguration()->willReturn([]); - - $source_plugin = $this->prophesize(MigrateSourceInterface::class); - $source_plugin->getIds()->willReturn(['nid']); - $destination_migration->getSourcePlugin()->willReturn($source_plugin->reveal()); - $destination_plugin = $this->prophesize(MigrateDestinationInterface::class); - $destination_plugin->import(Argument::any())->willReturn([2]); - $destination_migration->getDestinationPlugin(TRUE)->willReturn($destination_plugin->reveal()); - - $migration = new MigrationLookup($configuration, '', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal()); + $migration = MigrationLookup::create($this->prepareContainer(), $configuration, '', [], $migration_plugin->reveal()); $result = $migration->transform(1, $this->migrateExecutable, $this->row, ''); $this->assertEquals(2, $result); } @@ -104,8 +80,8 @@ public function testSkipInvalid($value) { $migration_plugin->id()->willReturn(uniqid()); $migration_plugin_manager->createInstances(['foobaz']) ->willReturn(['foobaz' => $migration_plugin->reveal()]); - $migration = new MigrationLookup($configuration, 'migration_lookup', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal()); - $this->setExpectedException(MigrateSkipProcessException::class); + $migration = MigrationLookup::create($this->prepareContainer(), $configuration, '', [], $migration_plugin->reveal()); + $this->expectException(MigrateSkipProcessException::class); $migration->transform($value, $this->migrateExecutable, $this->row, 'foo'); } @@ -137,7 +113,7 @@ public function testNoSkipValid($value) { $migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class); $process_plugin_manager = $this->prophesize(MigratePluginManager::class); $id_map = $this->prophesize(MigrateIdMapInterface::class); - $id_map->lookupDestinationId([$value])->willReturn([]); + $id_map->lookupDestinationIds([$value])->willReturn([]); $migration_plugin->getIdMap()->willReturn($id_map->reveal()); $configuration = [ @@ -147,7 +123,7 @@ public function testNoSkipValid($value) { $migration_plugin->id()->willReturn(uniqid()); $migration_plugin_manager->createInstances(['foobaz']) ->willReturn(['foobaz' => $migration_plugin->reveal()]); - $migration = new MigrationLookup($configuration, 'migration_lookup', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal()); + $migration = MigrationLookup::create($this->prepareContainer(), $configuration, '', [], $migration_plugin->reveal()); $lookup = $migration->transform($value, $this->migrateExecutable, $this->row, 'foo'); /* We provided no values and asked for no stub, so we should get NULL. */ @@ -171,8 +147,6 @@ public function noSkipValidDataProvider() { /** * Tests a successful lookup. * - * @dataProvider successfulLookupDataProvider - * * @param array $source_id_values * The source id(s) of the migration map. * @param array $destination_id_values @@ -181,29 +155,20 @@ public function noSkipValidDataProvider() { * The source value(s) for the migration process plugin. * @param string|array $expected_value * The expected value(s) of the migration process plugin. + * + * @dataProvider successfulLookupDataProvider + * + * @throws \Drupal\migrate\MigrateSkipProcessException */ - public function testSuccessfulLookup($source_id_values, $destination_id_values, $source_value, $expected_value) { + public function testSuccessfulLookup(array $source_id_values, array $destination_id_values, $source_value, $expected_value) { $migration_plugin = $this->prophesize(MigrationInterface::class); - $migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class); + $this->migrateLookup->lookup('foobaz', $source_id_values)->willReturn([$destination_id_values]); $configuration = [ 'migration' => 'foobaz', ]; - $migration_plugin->id()->willReturn(uniqid()); - - $id_map = $this->prophesize(MigrateIdMapInterface::class); - $id_map->lookupDestinationId($source_id_values)->willReturn($destination_id_values); - $migration_plugin->getIdMap()->willReturn($id_map->reveal()); - - $migration_plugin_manager->createInstances(['foobaz']) - ->willReturn(['foobaz' => $migration_plugin->reveal()]); - $migrationStorage = $this->prophesize(EntityStorageInterface::class); - $migrationStorage - ->loadMultiple(['foobaz']) - ->willReturn([$migration_plugin->reveal()]); - - $migration = new MigrationLookup($configuration, 'migration_lookup', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal()); + $migration = MigrationLookup::create($this->prepareContainer(), $configuration, '', [], $migration_plugin->reveal()); $this->assertSame($expected_value, $migration->transform($source_value, $this->migrateExecutable, $this->row, 'foo')); } @@ -211,6 +176,7 @@ public function testSuccessfulLookup($source_id_values, $destination_id_values, * Provides data for the successful lookup test. * * @return array + * The data. */ public function successfulLookupDataProvider() { return [ @@ -272,91 +238,17 @@ public function successfulLookupDataProvider() { ]; } - /** - * Tests that a message is successfully created if import fails. - */ - public function testImportException() { - $migration_plugin = $this->prophesize(MigrationInterface::class); - $migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class); - - $destination_id_map = $this->prophesize(MigrateIdMapInterface::class); - $destination_migration = $this->prophesize('Drupal\migrate\Plugin\Migration'); - $destination_migration->getIdMap()->willReturn($destination_id_map->reveal()); - $migration_plugin_manager->createInstances(['destination_migration']) - ->willReturn(['destination_migration' => $destination_migration->reveal()]); - $destination_id_map->lookupDestinationId([1])->willReturn(NULL); - $destination_id_map->saveMessage(Argument::any(), Argument::any())->willReturn(NULL); - $destination_id_map->saveIdMapping(Argument::any(), Argument::any(), Argument::any())->shouldNotBeCalled(); - - $configuration = [ - 'no_stub' => FALSE, - 'migration' => 'destination_migration', - ]; - - $destination_migration->id()->willReturn('destination_migration'); - $destination_migration->getDestinationPlugin(TRUE)->shouldBeCalled(); - $destination_migration->getProcess()->willReturn([]); - $destination_migration->getSourceConfiguration()->willReturn([]); - - $source_plugin = $this->prophesize(MigrateSourceInterface::class); - $source_plugin->getIds()->willReturn(['nid']); - $destination_migration->getSourcePlugin()->willReturn($source_plugin->reveal()); - $destination_plugin = $this->prophesize(MigrateDestinationInterface::class); - $e = new \Exception(); - $destination_plugin->import(Argument::any())->willThrow($e); - $destination_migration->getDestinationPlugin(TRUE)->willReturn($destination_plugin->reveal()); - - $migration = new MigrationLookup($configuration, '', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal()); - $migration->transform(1, $this->migrateExecutable, $this->row, ''); - } - /** * Tests processing multiple source IDs. */ public function testMultipleSourceIds() { $migration_plugin = $this->prophesize(MigrationInterface::class); - $migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class); - $foobaz_migration = $this->prophesize(MigrationInterface::class); - - $id_map = $this->prophesize(MigrateIdMapInterface::class); - $destination_plugin = $this->prophesize(MigrateDestinationInterface::class); - $source_plugin = $this->prophesize(MigrateSourceInterface::class); - - $migration_plugin_manager->createInstances(['foobaz']) - ->willReturn(['foobaz' => $foobaz_migration->reveal()]); - - $foobaz_migration->getIdMap()->willReturn($id_map->reveal()); - $foobaz_migration->getDestinationPlugin(TRUE)->willReturn($destination_plugin->reveal()); - $foobaz_migration->getProcess()->willReturn([]); - $foobaz_migration->getSourcePlugin()->willReturn($source_plugin->reveal()); - $foobaz_migration->id()->willReturn('foobaz'); - $foobaz_migration->getSourceConfiguration()->willReturn([]); - - $source_plugin_ids = [ - 'string_id' => [ - 'type' => 'string', - 'max_length' => 128, - 'is_ascii' => TRUE, - 'alias' => 'wpt', - ], - 'integer_id' => [ - 'type' => 'integer', - 'unsigned' => FALSE, - 'alias' => 'wpt', - ], - ]; - - $stub_row = new Row(['string_id' => 'example_string', 'integer_id' => 99], $source_plugin_ids, TRUE); - $destination_plugin->import($stub_row)->willReturn([2]); - - $source_plugin->getIds()->willReturn($source_plugin_ids); - + $this->migrateLookup->lookup('foobaz', ['id', 6])->willReturn([[2]]); $configuration = [ 'migration' => 'foobaz', - 'source_ids' => ['foobaz' => ['string_id', 'integer_id']], ]; - $migration = new MigrationLookup($configuration, 'migration', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal()); - $result = $migration->transform(NULL, $this->migrateExecutable, $stub_row, 'foo'); + $migration = MigrationLookup::create($this->prepareContainer(), $configuration, '', [], $migration_plugin->reveal()); + $result = $migration->transform(['id', 6], $this->migrateExecutable, $this->row, ''); $this->assertEquals(2, $result); } diff --git a/core/modules/migrate/tests/src/Unit/process/MigrationLookupTestCase.php b/core/modules/migrate/tests/src/Unit/process/MigrationLookupTestCase.php new file mode 100644 index 000000000..c22127dd9 --- /dev/null +++ b/core/modules/migrate/tests/src/Unit/process/MigrationLookupTestCase.php @@ -0,0 +1,51 @@ +migrateStub = $this->prophesize(MigrateStub::class); + $this->migrateLookup = $this->prophesize(MigrateLookupInterface::class); + } + + /** + * Prepares and sets the container. + * + * @return \Symfony\Component\DependencyInjection\ContainerInterface + * The prepared container. + */ + protected function prepareContainer() { + $container = new ContainerBuilder(); + $container->set('migrate.stub', $this->migrateStub->reveal()); + $container->set('migrate.lookup', $this->migrateLookup->reveal()); + \Drupal::setContainer($container); + return $container; + } + +} diff --git a/core/modules/migrate/tests/src/Unit/process/MigrationTest.php b/core/modules/migrate/tests/src/Unit/process/MigrationTest.php index cdf34fdd5..e337f10cc 100644 --- a/core/modules/migrate/tests/src/Unit/process/MigrationTest.php +++ b/core/modules/migrate/tests/src/Unit/process/MigrationTest.php @@ -2,14 +2,11 @@ namespace Drupal\Tests\migrate\Unit\process; -use Drupal\Core\Entity\EntityStorageInterface; use Drupal\migrate\MigrateSkipProcessException; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\migrate\process\Migration; -use Drupal\migrate\Plugin\MigrateDestinationInterface; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\migrate\Plugin\MigratePluginManager; -use Drupal\migrate\Plugin\MigrateSourceInterface; use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Prophecy\Argument; @@ -18,7 +15,7 @@ * @group migrate * @group legacy */ -class MigrationTest extends MigrateProcessTestCase { +class MigrationTest extends MigrationLookupTestCase { /** * @var \Drupal\migrate\Plugin\MigrationInterface @@ -47,54 +44,42 @@ protected function setUp() { } /** + * Tests a lookup with no stubbing. + * * @covers ::transform + * + * @expectedDeprecation Not passing the migrate lookup service as the fifth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateLookupInterface. See https://www.drupal.org/node/3047268 + * @expectedDeprecation Not passing the migrate stub service as the sixth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateStubInterface. See https://www.drupal.org/node/3047268 */ public function testTransformWithStubSkipping() { - $destination_migration = $this->getMigration(); - $destination_migration->getDestinationPlugin(TRUE)->shouldNotBeCalled(); - - // Ensure the migration plugin manager returns our migration. - $this->migration_plugin_manager->createInstances(Argument::exact(['destination_migration'])) - ->willReturn(['destination_migration' => $destination_migration->reveal()]); - $configuration = [ 'no_stub' => TRUE, 'migration' => 'destination_migration', ]; - $this->migration_plugin->id()->willReturn('actual_migration'); - + $this->migrateLookup->lookup('destination_migration', [1])->willReturn([]); + $this->prepareContainer(); $migration = new Migration($configuration, '', [], $this->migration_plugin->reveal(), $this->migration_plugin_manager->reveal(), $this->process_plugin_manager->reveal()); $result = $migration->transform(1, $this->migrateExecutable, $this->row, ''); $this->assertNull($result); } /** + * Tests a lookup with stubbing. + * * @covers ::transform + * + * @expectedDeprecation Not passing the migrate lookup service as the fifth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateLookupInterface. See https://www.drupal.org/node/3047268 + * @expectedDeprecation Not passing the migrate stub service as the sixth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateStubInterface. See https://www.drupal.org/node/3047268 */ public function testTransformWithStubbing() { - $destination_migration = $this->getMigration(); - $this->migration_plugin_manager->createInstances(['destination_migration']) - ->willReturn(['destination_migration' => $destination_migration->reveal()]); - $configuration = [ 'no_stub' => FALSE, 'migration' => 'destination_migration', ]; - - $this->migration_plugin->id()->willReturn('actual_migration'); - $destination_migration->id()->willReturn('destination_migration'); - $destination_migration->getDestinationPlugin(TRUE)->shouldBeCalled(); - $destination_migration->getProcess()->willReturn([]); - $destination_migration->getSourceConfiguration()->willReturn([]); - - $source_plugin = $this->prophesize(MigrateSourceInterface::class); - $source_plugin->getIds()->willReturn(['nid']); - $destination_migration->getSourcePlugin()->willReturn($source_plugin->reveal()); - $destination_plugin = $this->prophesize(MigrateDestinationInterface::class); - $destination_plugin->import(Argument::any())->willReturn([2]); - $destination_migration->getDestinationPlugin(TRUE)->willReturn($destination_plugin->reveal()); - + $this->migrateLookup->lookup('destination_migration', [1])->willReturn([]); + $this->migrateStub->createStub('destination_migration', [1], [], FALSE)->willReturn([2]); + $this->prepareContainer(); $migration = new Migration($configuration, '', [], $this->migration_plugin->reveal(), $this->migration_plugin_manager->reveal(), $this->process_plugin_manager->reveal()); $result = $migration->transform(1, $this->migrateExecutable, $this->row, ''); $this->assertEquals(2, $result); @@ -108,7 +93,7 @@ public function testTransformWithStubbing() { */ protected function getMigration() { $id_map = $this->prophesize(MigrateIdMapInterface::class); - $id_map->lookupDestinationId([1])->willReturn(NULL); + $id_map->lookupDestinationIds([1])->willReturn(NULL); $id_map->saveIdMapping(Argument::any(), Argument::any(), MigrateIdMapInterface::STATUS_NEEDS_UPDATE)->willReturn(NULL); $migration = $this->prophesize(MigrationInterface::class); @@ -118,6 +103,9 @@ protected function getMigration() { /** * Tests that processing is skipped when the input value is empty. + * + * @expectedDeprecation Not passing the migrate lookup service as the fifth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateLookupInterface. See https://www.drupal.org/node/3047268 + * @expectedDeprecation Not passing the migrate stub service as the sixth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateStubInterface. See https://www.drupal.org/node/3047268 */ public function testSkipOnEmpty() { $configuration = [ @@ -126,16 +114,15 @@ public function testSkipOnEmpty() { $this->migration_plugin->id()->willReturn(uniqid()); $this->migration_plugin_manager->createInstances(['foobaz']) ->willReturn(['foobaz' => $this->migration_plugin->reveal()]); + $this->prepareContainer(); $migration = new Migration($configuration, 'migration', [], $this->migration_plugin->reveal(), $this->migration_plugin_manager->reveal(), $this->process_plugin_manager->reveal()); - $this->setExpectedException(MigrateSkipProcessException::class); + $this->expectException(MigrateSkipProcessException::class); $migration->transform(FALSE, $this->migrateExecutable, $this->row, 'foo'); } /** * Tests a successful lookup. * - * @dataProvider successfulLookupDataProvider - * * @param array $source_id_values * The source id(s) of the migration map. * @param array $destination_id_values @@ -144,25 +131,20 @@ public function testSkipOnEmpty() { * The source value(s) for the migration process plugin. * @param string|array $expected_value * The expected value(s) of the migration process plugin. + * + * @dataProvider successfulLookupDataProvider + * + * @expectedDeprecation Not passing the migrate lookup service as the fifth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateLookupInterface. See https://www.drupal.org/node/3047268 + * @expectedDeprecation Not passing the migrate stub service as the sixth parameter to Drupal\migrate\Plugin\migrate\process\MigrationLookup::__construct is deprecated in drupal:8.8.0 and will throw a type error in drupal:9.0.0. Pass an instance of \Drupal\migrate\MigrateStubInterface. See https://www.drupal.org/node/3047268 + * + * @throws \Drupal\migrate\MigrateSkipProcessException */ - public function testSuccessfulLookup($source_id_values, $destination_id_values, $source_value, $expected_value) { + public function testSuccessfulLookup(array $source_id_values, array $destination_id_values, $source_value, $expected_value) { $configuration = [ 'migration' => 'foobaz', ]; - $this->migration_plugin->id()->willReturn(uniqid()); - - $id_map = $this->prophesize(MigrateIdMapInterface::class); - $id_map->lookupDestinationId($source_id_values)->willReturn($destination_id_values); - $this->migration_plugin->getIdMap()->willReturn($id_map->reveal()); - - $this->migration_plugin_manager->createInstances(['foobaz']) - ->willReturn(['foobaz' => $this->migration_plugin->reveal()]); - - $migrationStorage = $this->prophesize(EntityStorageInterface::class); - $migrationStorage - ->loadMultiple(['foobaz']) - ->willReturn([$this->migration_plugin->reveal()]); - + $this->migrateLookup->lookup('foobaz', $source_id_values)->willReturn([$destination_id_values]); + $this->prepareContainer(); $migration = new Migration($configuration, 'migration', [], $this->migration_plugin->reveal(), $this->migration_plugin_manager->reveal(), $this->process_plugin_manager->reveal()); $this->assertSame($expected_value, $migration->transform($source_value, $this->migrateExecutable, $this->row, 'foo')); } @@ -171,6 +153,7 @@ public function testSuccessfulLookup($source_id_values, $destination_id_values, * Provides data for the successful lookup test. * * @return array + * The data. */ public function successfulLookupDataProvider() { return [ diff --git a/core/modules/migrate/tests/src/Unit/process/NullCoalesceTest.php b/core/modules/migrate/tests/src/Unit/process/NullCoalesceTest.php new file mode 100644 index 000000000..8779be98f --- /dev/null +++ b/core/modules/migrate/tests/src/Unit/process/NullCoalesceTest.php @@ -0,0 +1,92 @@ +expectException(MigrateException::class); + (new NullCoalesce([], 'null_coalesce', []))->transform('invalid', $this->migrateExecutable, $this->row, 'destinationproperty'); + } + + /** + * Tests null_coalesce. + * + * @param array $source + * The source value. + * @param mixed $expected_result + * The expected result. + * + * @covers ::transform + * + * @dataProvider transformDataProvider + * + * @throws \Drupal\migrate\MigrateException + */ + public function testTransform(array $source, $expected_result) { + $plugin = new NullCoalesce([], 'null_coalesce', []); + $result = $plugin->transform($source, $this->migrateExecutable, $this->row, 'destinationproperty'); + $this->assertSame($expected_result, $result); + } + + /** + * Provides Data for ::testTransform. + */ + public function transformDataProvider() { + return [ + 'all null' => [ + 'source' => [NULL, NULL, NULL], + 'expected_result' => NULL, + ], + 'false first' => [ + 'source' => [FALSE, NULL, NULL], + 'expected_result' => FALSE, + ], + 'no null' => [ + 'source' => ['test', 'test2'], + 'expected_result' => 'test', + ], + 'string first' => [ + 'source' => ['test', NULL, 'test2'], + 'expected_result' => 'test', + ], + 'empty string' => [ + 'source' => [NULL, '', NULL], + 'expected_result' => '', + ], + 'array' => [ + 'source' => [NULL, NULL, [1, 2, 3]], + 'expected_result' => [1, 2, 3], + ], + ]; + } + + /** + * Tests null_coalesce with default value. + * + * @covers ::transform + */ + public function testTransformWithDefault() { + $plugin = new NullCoalesce(['default_value' => 'default'], 'null_coalesce', []); + $result = $plugin->transform([NULL, NULL, 'Test', 'Test 2'], $this->migrateExecutable, $this->row, 'destinationproperty'); + $this->assertSame('Test', $result); + + $this->assertSame('default', $plugin->transform([NULL, NULL], $this->migrateExecutable, $this->row, 'destinationproperty')); + } + +} diff --git a/core/modules/migrate/tests/src/Unit/process/SkipOnEmptyTest.php b/core/modules/migrate/tests/src/Unit/process/SkipOnEmptyTest.php index d409a88e5..56e85cd4a 100644 --- a/core/modules/migrate/tests/src/Unit/process/SkipOnEmptyTest.php +++ b/core/modules/migrate/tests/src/Unit/process/SkipOnEmptyTest.php @@ -19,7 +19,7 @@ class SkipOnEmptyTest extends MigrateProcessTestCase { */ public function testProcessSkipsOnEmpty() { $configuration['method'] = 'process'; - $this->setExpectedException(MigrateSkipProcessException::class); + $this->expectException(MigrateSkipProcessException::class); (new SkipOnEmpty($configuration, 'skip_on_empty', [])) ->transform('', $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -39,7 +39,7 @@ public function testProcessBypassesOnNonEmpty() { */ public function testRowSkipsOnEmpty() { $configuration['method'] = 'row'; - $this->setExpectedException(MigrateSkipRowException::class); + $this->expectException(MigrateSkipRowException::class); (new SkipOnEmpty($configuration, 'skip_on_empty', [])) ->transform('', $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -64,7 +64,7 @@ public function testRowSkipWithoutMessage() { 'method' => 'row', ]; $process = new SkipOnEmpty($configuration, 'skip_on_empty', []); - $this->setExpectedException(MigrateSkipRowException::class); + $this->expectException(MigrateSkipRowException::class); $process->transform('', $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -79,7 +79,8 @@ public function testRowSkipWithMessage() { 'message' => 'The value is empty', ]; $process = new SkipOnEmpty($configuration, 'skip_on_empty', []); - $this->setExpectedException(MigrateSkipRowException::class, 'The value is empty'); + $this->expectException(MigrateSkipRowException::class); + $this->expectExceptionMessage('The value is empty'); $process->transform('', $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/SkipRowIfNotSetTest.php b/core/modules/migrate/tests/src/Unit/process/SkipRowIfNotSetTest.php index 26e5b3fb4..21c8aa071 100644 --- a/core/modules/migrate/tests/src/Unit/process/SkipRowIfNotSetTest.php +++ b/core/modules/migrate/tests/src/Unit/process/SkipRowIfNotSetTest.php @@ -23,7 +23,7 @@ public function testRowSkipWithoutMessage() { 'index' => 'some_key', ]; $process = new SkipRowIfNotSet($configuration, 'skip_row_if_not_set', []); - $this->setExpectedException(MigrateSkipRowException::class); + $this->expectException(MigrateSkipRowException::class); $process->transform('', $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -38,7 +38,8 @@ public function testRowSkipWithMessage() { 'message' => "The 'some_key' key is not set", ]; $process = new SkipRowIfNotSet($configuration, 'skip_row_if_not_set', []); - $this->setExpectedException(MigrateSkipRowException::class, "The 'some_key' key is not set"); + $this->expectException(MigrateSkipRowException::class); + $this->expectExceptionMessage("The 'some_key' key is not set"); $process->transform('', $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/StaticMapTest.php b/core/modules/migrate/tests/src/Unit/process/StaticMapTest.php index 1a9a61acd..cc77c393c 100644 --- a/core/modules/migrate/tests/src/Unit/process/StaticMapTest.php +++ b/core/modules/migrate/tests/src/Unit/process/StaticMapTest.php @@ -43,7 +43,7 @@ public function testMapWithSourceList() { * Tests when the source is empty. */ public function testMapwithEmptySource() { - $this->setExpectedException(MigrateException::class); + $this->expectException(MigrateException::class); $this->plugin->transform([], $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -51,7 +51,8 @@ public function testMapwithEmptySource() { * Tests when the source is invalid. */ public function testMapwithInvalidSource() { - $this->setExpectedException(MigrateSkipRowException::class, sprintf("No static mapping found for '%s' and no default value provided for destination '%s'.", Variable::export(['bar']), 'destinationproperty')); + $this->expectException(MigrateSkipRowException::class); + $this->expectExceptionMessage(sprintf("No static mapping found for '%s' and no default value provided for destination '%s'.", Variable::export(['bar']), 'destinationproperty')); $this->plugin->transform(['bar'], $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -85,7 +86,8 @@ public function testMapWithInvalidSourceAndBypass() { $configuration['default_value'] = 'test'; $configuration['bypass'] = TRUE; $this->plugin = new StaticMap($configuration, 'map', []); - $this->setExpectedException(MigrateException::class, 'Setting both default_value and bypass is invalid.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('Setting both default_value and bypass is invalid.'); $this->plugin->transform(['bar'], $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate/tests/src/Unit/process/SubProcessTest.php b/core/modules/migrate/tests/src/Unit/process/SubProcessTest.php index 1766772ef..0d2ea5823 100644 --- a/core/modules/migrate/tests/src/Unit/process/SubProcessTest.php +++ b/core/modules/migrate/tests/src/Unit/process/SubProcessTest.php @@ -53,8 +53,8 @@ public function testSubProcess($process_configuration, $source_values = []) { $migration->expects($this->at(2)) ->method('getProcessPlugins') ->will($this->returnValue($key_plugin)); - $event_dispatcher = $this->getMock(EventDispatcherInterface::class); - $migrate_executable = new MigrateExecutable($migration, $this->getMock(MigrateMessageInterface::class), $event_dispatcher); + $event_dispatcher = $this->createMock(EventDispatcherInterface::class); + $migrate_executable = new MigrateExecutable($migration, $this->createMock(MigrateMessageInterface::class), $event_dispatcher); // The current value of the pipeline. $current_value = [ diff --git a/core/modules/migrate/tests/src/Unit/process/SubstrTest.php b/core/modules/migrate/tests/src/Unit/process/SubstrTest.php index 6a73905d1..54ef1c2b2 100644 --- a/core/modules/migrate/tests/src/Unit/process/SubstrTest.php +++ b/core/modules/migrate/tests/src/Unit/process/SubstrTest.php @@ -60,7 +60,8 @@ public function providerTestSubstr() { public function testSubstrFail() { $configuration = []; $this->plugin = new Substr($configuration, 'map', []); - $this->setExpectedException(MigrateException::class, 'The input value must be a string.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The input value must be a string.'); $this->plugin->transform(['Captain Janeway'], $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -70,7 +71,8 @@ public function testSubstrFail() { public function testStartIsString() { $configuration['start'] = '2'; $this->plugin = new Substr($configuration, 'map', []); - $this->setExpectedException(MigrateException::class, 'The start position configuration value should be an integer. Omit this key to capture from the beginning of the string.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The start position configuration value should be an integer. Omit this key to capture from the beginning of the string.'); $this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty'); } @@ -80,7 +82,8 @@ public function testStartIsString() { public function testLengthIsString() { $configuration['length'] = '1'; $this->plugin = new Substr($configuration, 'map', []); - $this->setExpectedException(MigrateException::class, 'The character length configuration value should be an integer. Omit this key to capture from the start position to the end of the string.'); + $this->expectException(MigrateException::class); + $this->expectExceptionMessage('The character length configuration value should be an integer. Omit this key to capture from the start position to the end of the string.'); $this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty'); } diff --git a/core/modules/migrate_drupal/migrate_drupal.info.yml b/core/modules/migrate_drupal/migrate_drupal.info.yml index 440ffc18f..638bc382f 100644 --- a/core/modules/migrate_drupal/migrate_drupal.info.yml +++ b/core/modules/migrate_drupal/migrate_drupal.info.yml @@ -2,13 +2,7 @@ name: Migrate Drupal type: module description: 'Contains migrations from older Drupal versions.' package: Migration -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:migrate - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate_drupal/migrate_drupal.module b/core/modules/migrate_drupal/migrate_drupal.module index 6f1c6b622..3f9439dd3 100644 --- a/core/modules/migrate_drupal/migrate_drupal.module +++ b/core/modules/migrate_drupal/migrate_drupal.module @@ -46,7 +46,7 @@ function migrate_drupal_migration_plugins_alter(&$definitions) { ], ]; $vocabulary_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($vocabulary_migration_definition); - $translation_active = \Drupal::service('module_handler')->moduleExists('config_translation'); + $translation_active = \Drupal::service('module_handler')->moduleExists('content_translation'); try { $source_plugin = $vocabulary_migration->getSourcePlugin(); @@ -65,7 +65,7 @@ function migrate_drupal_migration_plugins_alter(&$definitions) { if ($translation_active) { $plugin_ids[] = 'd6_term_node_translation:' . $source_vid; } - foreach ($plugin_ids as $plugin_id) { + foreach (array_intersect($plugin_ids, array_keys($definitions)) as $plugin_id) { // Match the field name derivation in d6_vocabulary_field.yml. $field_name = substr('field_' . $row->getDestinationProperty('vid'), 0, 32); diff --git a/core/modules/migrate_drupal/migrate_drupal.services.yml b/core/modules/migrate_drupal/migrate_drupal.services.yml index 7594b465f..77a8b59df 100644 --- a/core/modules/migrate_drupal/migrate_drupal.services.yml +++ b/core/modules/migrate_drupal/migrate_drupal.services.yml @@ -25,3 +25,6 @@ services: - '@plugin.manager.migrate.field' - '@plugin.manager.migration' - '@logger.channel.migrate_drupal' + migrate_drupal.migration_state: + class: Drupal\migrate_drupal\MigrationState + arguments: ['@plugin.manager.migrate.field', '@module_handler', '@messenger', '@string_translation'] diff --git a/core/modules/migrate_drupal/migrations/state/migrate_drupal.migrate_drupal.yml b/core/modules/migrate_drupal/migrations/state/migrate_drupal.migrate_drupal.yml new file mode 100644 index 000000000..770c92075 --- /dev/null +++ b/core/modules/migrate_drupal/migrations/state/migrate_drupal.migrate_drupal.yml @@ -0,0 +1,95 @@ +# The modules listed here do not have an migration. A status of finished is +# assigned so that they appear in the will not be upgraded list on the Review +# form. +finished: + 6: + nodereference: core + userreference: core + # Blog requires node. + blog: node + # The following do not have an upgrade path. + blogapi: core + calendarsignup: core + color: core + content_copy: core + content_multigroup: core + content_permissions: core + date_api: core + date_locale: core + date_php4: core + date_popup: core + date_repeat: core + date_timezone: core + date_tools: core + datepicker: core + ddblock: core + event: core + fieldgroup: core + filefield_meta: core + help: core + # i18n modules require content_translation. + i18ncontent: content_translation + i18npoll: content_translation + i18nstrings: content_translation + i18nsync: content_translation + imageapi: core + imageapi_gd: core + imageapi_imagemagick: core + imagecache_ui: core + nodeaccess: core + number: core + openid: core + php: core + ping: core + poll: core + throttle: core + tracker: core + translation: core + trigger: core + variable: core + variable_admin: core + views_export: core + views_ui: core + 7: + # Blog requires node. + blog: node + # The following do not need have an upgrade path. + bulk_export: core + contextual: core + ctools: core + ctools_access_ruleset: core + ctools_ajax_sample: core + ctools_custom_content: core + dashboard: core + date_all_day: core + date_api: core + date_context: core + date_migrate: core + date_popup: core + date_repeat: core + date_repeat_field: core + date_tools: core + date_views: core + entity: core + entity_feature: core + entity_token: core + entityreference: core + field_ui: core + help: core + openid: core + overlay: core + page_manager: core + php: core + poll: core + search_embedded_form: core + search_extra_type: core + search_node_tags: core + simpletest: core + stylizer: core + term_depth: core + title: core + toolbar: core + translation: core + trigger: core + views_content: core + views_ui: core diff --git a/core/modules/migrate_drupal/src/Annotation/MigrateCckField.php b/core/modules/migrate_drupal/src/Annotation/MigrateCckField.php index 20558200e..5f6033aea 100644 --- a/core/modules/migrate_drupal/src/Annotation/MigrateCckField.php +++ b/core/modules/migrate_drupal/src/Annotation/MigrateCckField.php @@ -7,7 +7,7 @@ /** * Deprecated: Defines a cckfield plugin annotation object. * - * @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\Annotation\MigrateField instead. * * Plugin Namespace: Plugin\migrate\cckfield diff --git a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php index f17768ef3..8cb60347b 100644 --- a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php @@ -13,6 +13,27 @@ */ trait MigrationConfigurationTrait { + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The migration plugin manager service. + * + * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface + */ + protected $migrationPluginManager; + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + /** * The follow-up migration tags. * @@ -82,8 +103,9 @@ protected function createDatabaseStateSettings(array $database, $drupal_version) $database_state['key'] = 'upgrade'; $database_state['database'] = $database; $database_state_key = 'migrate_drupal_' . $drupal_version; - \Drupal::state()->set($database_state_key, $database_state); - \Drupal::state()->set('migrate.fallback_state_key', $database_state_key); + $state = $this->getState(); + $state->set($database_state_key, $database_state); + $state->set('migrate.fallback_state_key', $database_state_key); } /** @@ -99,9 +121,8 @@ protected function createDatabaseStateSettings(array $database, $drupal_version) */ protected function getMigrations($database_state_key, $drupal_version) { $version_tag = 'Drupal ' . $drupal_version; - $plugin_manager = \Drupal::service('plugin.manager.migration'); /** @var \Drupal\migrate\Plugin\Migration[] $all_migrations */ - $all_migrations = $plugin_manager->createInstancesByTag($version_tag); + $all_migrations = $this->getMigrationPluginManager()->createInstancesByTag($version_tag); $migrations = []; foreach ($all_migrations as $migration) { // Skip migrations tagged with any of the follow-up migration tags. They @@ -147,7 +168,7 @@ protected function getMigrations($database_state_key, $drupal_version) { */ protected function getFollowUpMigrationTags() { if ($this->followUpMigrationTags === NULL) { - $this->followUpMigrationTags = \Drupal::configFactory() + $this->followUpMigrationTags = $this->getConfigFactory() ->get('migrate_drupal.settings') ->get('follow_up_migration_tags') ?: []; } @@ -210,4 +231,46 @@ protected function getLegacyDrupalVersion(Connection $connection) { return $version_string ? substr($version_string, 0, 1) : FALSE; } + /** + * Gets the config factory service. + * + * @return \Drupal\Core\Config\ConfigFactoryInterface + * The config factory service. + */ + protected function getConfigFactory() { + if (!$this->configFactory) { + $this->configFactory = \Drupal::service('config.factory'); + } + + return $this->configFactory; + } + + /** + * Gets the migration plugin manager service. + * + * @return \Drupal\migrate\Plugin\MigrationPluginManagerInterface + * The migration plugin manager service. + */ + protected function getMigrationPluginManager() { + if (!$this->migrationPluginManager) { + $this->migrationPluginManager = \Drupal::service('plugin.manager.migration'); + } + + return $this->migrationPluginManager; + } + + /** + * Gets the state service. + * + * @return \Drupal\Core\State\StateInterface + * The state service. + */ + protected function getState() { + if (!$this->state) { + $this->state = \Drupal::service('state'); + } + + return $this->state; + } + } diff --git a/core/modules/migrate_drupal/src/MigrationCreationTrait.php b/core/modules/migrate_drupal/src/MigrationCreationTrait.php index 7894e91cb..8296df57d 100644 --- a/core/modules/migrate_drupal/src/MigrationCreationTrait.php +++ b/core/modules/migrate_drupal/src/MigrationCreationTrait.php @@ -3,7 +3,7 @@ namespace Drupal\migrate_drupal; /** - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\MigrationConfigurationTrait instead. * * @see https://www.drupal.org/node/2873794 diff --git a/core/modules/migrate_drupal/src/MigrationState.php b/core/modules/migrate_drupal/src/MigrationState.php new file mode 100644 index 000000000..b44108a80 --- /dev/null +++ b/core/modules/migrate_drupal/src/MigrationState.php @@ -0,0 +1,494 @@ +.migrate_drupal.yml file. + * + * The .migrate_drupal.yml file uses the following structure: + * + * # (optional) List of the source_module/destination_module(s) for the + * # migration sets that this module provides and are complete. + * finished: + * # One or more Drupal legacy version number mappings (i.e. 6 and/or 7). + * 6: + * # A mapping of legacy module machine names to either an array of modules + * # or a single destination module machine name to define this migration + * # set. + * : + * : + * - + * - + * 7: + * : + * : + * - + * - + * # (optional) List of the migration sets that this module provides, or will be + * # providing, that are incomplete or do not yet exist. + * not_finished: + * 6: + * : + * : + * - + * - + * + * Examples: + * + * @code + * finished: + * 6: + * node: node + * 7: + * node: node + * entity_translation: node + * not_finished: + * 7: + * commerce_product: commerce_product + * other_module: + * - other_module + * - further_module + * @endcode + * + * In this example the module has completed the upgrade path for data from the + * Drupal 6 and Drupal 7 Node modules to the Drupal 8 Node module and for data + * from the Drupal 7 Entity Translation module to the Drupal 8 Node module. + * + * @code + * finished: + * 6: + * pirate: pirate + * 7: + * pirate: pirate + * @endcode + * + * The Pirate module does not require an upgrade path. By declaring the upgrade + * finished the Pirate module will be included in the finished list. That is, + * as long as no other module has an entry "pirate: ' in its + * not_finished section. + */ +class MigrationState { + + use MessengerTrait; + use StringTranslationTrait; + + /** + * Source module upgrade state when all its migrations are complete. + * + * @var string + */ + const FINISHED = 'finished'; + + /** + * Source module upgrade state when all its migrations are not complete. + * + * @var string + */ + const NOT_FINISHED = 'not_finished'; + + /** + * The field plugin manager service. + * + * @var \Drupal\Core\Extension\ModuleHandler + */ + protected $moduleHandler; + + /** + * The field plugin manager service. + * + * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface + */ + protected $fieldPluginManager; + + /** + * Source modules that will not be migrated determined using legacy method. + * + * @var array + */ + protected $unmigratedSourceModules = []; + + /** + * Source modules that will be migrated determined using legacy method, keyed + * by version. + * + * @var array + */ + protected $migratedSourceModules = []; + + /** + * An array of migration states declared for each source migration. + * + * States are keyed by version. Each value is an array keyed by name of the + * source module and the value is an array of all the states declared for this + * source module. + * + * @var array + */ + protected $stateBySource; + + /** + * An array of destinations declared for each source migration. + * + * Destinations are keyed by version. Each value is an array keyed by the name + * of the source module and the value is an array of the destination modules. + * + * @var array + */ + protected $declaredBySource; + + /** + * An array of migration source and destinations derived from migrations. + * + * The key is the source version and the value is an array where the key is + * the source module and the value is an array of destinations derived from + * migration plugins. + * + * @var array + */ + protected $discoveredBySource; + + /** + * An array of migration source and destinations. + * + * Values are derived from migration plugins and declared states. The key is + * the source version and the value is an array where the key is the source + * module and the value is an array of declared or derived destinations. + * + * @var array + */ + protected $destinations = []; + + /** + * Array of enabled modules. + * + * @var array + */ + protected $enabledModules = []; + + /** + * Construct a new MigrationState object. + * + * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $fieldPluginManager + * Field plugin manager. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler + * Module handler. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * Messenger sevice. + * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation + * String translation service. + */ + public function __construct(MigrateFieldPluginManagerInterface $fieldPluginManager, ModuleHandlerInterface $moduleHandler, MessengerInterface $messenger, TranslationInterface $stringTranslation) { + $this->fieldPluginManager = $fieldPluginManager; + $this->moduleHandler = $moduleHandler; + $this->enabledModules = array_keys($this->moduleHandler->getModuleList()); + $this->enabledModules[] = 'core'; + $this->messenger = $messenger; + $this->stringTranslation = $stringTranslation; + } + + /** + * Gets the upgrade states for all enabled source modules. + * + * @param string $version + * The legacy drupal version. + * @param array $source_system_data + * The data from the source site system table. + * @param array $migrations + * An array of migrations. + * + * @return array + * An associative array of data with keys of state, source modules and a + * value which is a comma separated list of destination modules. + */ + public function getUpgradeStates($version, array $source_system_data, array $migrations) { + return $this->buildUpgradeState($version, $source_system_data, $migrations); + } + + /** + * Gets migration state information from *.migrate_drupal.yml. + * + * @return array + * An association array keyed by module of the finished and not_finished + * migrations for each module. + * */ + protected function getMigrationStates() { + // Always instantiate a new YamlDiscovery object so that we always search on + // the up-to-date list of modules. + $discovery = new YamlDiscovery('migrate_drupal', array_map(function (&$value) { + return $value . '/migrations/state'; + }, $this->moduleHandler->getModuleDirectories())); + return $discovery->findAll(); + } + + /** + * Determines migration state for each source module enabled on the source. + * + * If there are no migrations for a module and no declared state the state is + * set to NOT_FINISHED. When a module does not need any migrations, such as + * Overlay, a state of finished is declared in system.migrate_drupal.yml. + * + * If there are migrations for a module the following happens. If the + * destination module is 'core' the state is set to FINISHED. If there are + * any occurrences of 'not_finished' in the *.migrate_drupal.yml information + * for this source module then the state is set to NOT_FINISHED. And finally, + * if there is an occurrence of 'finished' the state is set to FINISHED. + * + * @param string $version + * The legacy drupal version. + * @param array $source_system_data + * The data from the source site system table. + * @param array $migrations + * An array of migrations. + * + * @return array + * An associative array of data with keys of state, source modules and a + * value which is a comma separated list of destination modules. + * Example. + * + * @code + * [ + * 'finished' => [ + * 'menu' => [ + * 'menu_link_content','menu_ui','system' + * ] + * ], + * ] + * @endcode + */ + protected function buildUpgradeState($version, array $source_system_data, array $migrations) { + // Remove core profiles from the system data. + unset($source_system_data['module']['standard'], $source_system_data['module']['minimal']); + $this->buildDiscoveredDestinationsBySource($version, $migrations, $source_system_data); + $this->buildDeclaredStateBySource($version); + + $upgrade_state = []; + // Loop through every source module that is enabled on the source site. + foreach ($source_system_data['module'] as $module) { + // The source plugins check requirements requires that all + // source_modules are enabled so do the same here. + if ($module['status']) { + $source_module = $module['name']; + // If there is not a declared state for this source module then use the + // legacy method for determining the migration state. + if (!isset($this->stateBySource[$version][$source_module])) { + // No migrations found for this source module. + if (!empty($this->unmigratedSourceModules[$version]) && array_key_exists($source_module, $this->unmigratedSourceModules[$version])) { + $upgrade_state[static::NOT_FINISHED][$source_module] = ''; + continue; + } + if (!empty($this->migratedSourceModules[$version]) && array_key_exists($source_module, $this->migratedSourceModules[$version])) { + @trigger_error(sprintf("Using migration plugin definitions to determine the migration state of the module '%s' is deprecated in Drupal 8.7. Add the module to a migrate_drupal.yml file. See https://www.drupal.org/node/2929443", $source_module), E_USER_DEPRECATED); + if (array_diff(array_keys($this->migratedSourceModules[$version][$source_module]), $this->enabledModules)) { + $upgrade_state[static::NOT_FINISHED][$source_module] = implode(', ', array_keys($this->migratedSourceModules[$version][$source_module])); + continue; + } + $upgrade_state[static::FINISHED][$source_module] = implode(', ', array_keys($this->migratedSourceModules[$version][$source_module])); + } + continue; + } + + $upgrade_state[$this->getSourceState($version, $source_module)][$source_module] = implode(', ', $this->getDestinationsForSource($version, $source_module)); + } + + } + foreach ($upgrade_state as $key => $value) { + ksort($upgrade_state[$key]); + } + return $upgrade_state; + } + + /** + * Builds migration source and destination module information. + * + * @param string $version + * The legacy Drupal version. + * @param array $migrations + * The discovered migrations. + * @param array $source_system_data + * The data from the source site system table. + */ + protected function buildDiscoveredDestinationsBySource($version, array $migrations, array $source_system_data) { + $discovered_upgrade_paths = []; + $table_data = []; + foreach ($migrations as $migration) { + $migration_id = $migration->getPluginId(); + $source_module = $migration->getSourcePlugin()->getSourceModule(); + if (!$source_module) { + $this->messenger() + ->addError($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id])); + } + $destination_module = $migration->getDestinationPlugin() + ->getDestinationModule(); + if (!$destination_module) { + $this->messenger() + ->addError($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id])); + } + + if ($source_module && $destination_module) { + $discovered_upgrade_paths[$source_module][] = $destination_module; + $table_data[$source_module][$destination_module][$migration_id] = $migration->label(); + } + } + + // Add entries for the field plugins to discovered_upgrade_paths. + $definitions = $this->fieldPluginManager->getDefinitions(); + foreach ($definitions as $definition) { + // This is not strict so that we find field plugins with an annotation + // where the Drupal core version is an integer and when it is a string. + if (in_array($version, $definition['core'])) { + $source_module = $definition['source_module']; + $destination_module = $definition['destination_module']; + $discovered_upgrade_paths[$source_module][] = $destination_module; + $table_data[$source_module][$destination_module][$definition['id']] = $definition['id']; + } + } + ksort($table_data); + foreach ($table_data as $source_module => $destination_module_info) { + ksort($table_data[$source_module]); + } + $tmp = array_diff_key($source_system_data['module'], $table_data); + foreach ($tmp as $source_module => $module_data) { + if ($module_data['status']) { + $this->unmigratedSourceModules[$version][$source_module] = $module_data; + } + } + $this->migratedSourceModules[$version] = $table_data; + $this->discoveredBySource[$version] = array_map('array_unique', $discovered_upgrade_paths); + } + + /** + * Gets migration data from *.migrate_drupal.yml sorted by source module. + * + * @param string $version + * The legacy Drupal version. + */ + protected function buildDeclaredStateBySource($version) { + $migration_states = $this->getMigrationStates(); + + $state_by_source = []; + $dest_by_source = []; + $states = [static::FINISHED, static::NOT_FINISHED]; + foreach ($migration_states as $module => $info) { + foreach ($states as $state) { + if (isset($info[$state][$version])) { + foreach ($info[$state][$version] as $source => $destination) { + // Add the state. + $state_by_source[$source][] = $state; + // Add the destination modules. + $dest_by_source += [$source => []]; + $dest_by_source[$source] = array_merge($dest_by_source[$source], (array) $destination); + } + } + } + } + $this->stateBySource[$version] = array_map('array_unique', $state_by_source); + $this->declaredBySource[$version] = array_map('array_unique', $dest_by_source); + } + + /** + * Tests if a destination exists for the given source module. + * + * @param string $version + * Source version of Drupal. + * @param string $source_module + * Source module. + * + * @return string + * Migration state, either 'finished' or 'not_finished'. + */ + protected function getSourceState($version, $source_module) { + // The state is finished only when no declarations of 'not_finished' + // were found and each destination module is enabled. + if (!$destinations = $this->getDestinationsForSource($version, $source_module)) { + // No discovered or declared state. + return MigrationState::NOT_FINISHED; + } + if (in_array(MigrationState::NOT_FINISHED, $this->stateBySource[$version][$source_module], TRUE) || !in_array(MigrationState::FINISHED, $this->stateBySource[$version][$source_module], TRUE)) { + return MigrationState::NOT_FINISHED; + } + if (array_diff($destinations, $this->enabledModules)) { + return MigrationState::NOT_FINISHED; + } + return MigrationState::FINISHED; + } + + /** + * Get net destinations for source module. + * + * @param string $version + * Source version. + * @param string $source_module + * Source module. + * + * @return array + * Destination modules either declared by {modulename}.migrate_drupal.yml + * files or discovered from migration plugins. + */ + protected function getDestinationsForSource($version, $source_module) { + if (!isset($this->destinations[$version][$source_module])) { + $this->discoveredBySource[$version] += [$source_module => []]; + $this->declaredBySource[$version] += [$source_module => []]; + $destination = array_unique(array_merge($this->discoveredBySource[$version][$source_module], $this->declaredBySource[$version][$source_module])); + sort($destination); + $this->destinations[$version][$source_module] = $destination; + } + return $this->destinations[$version][$source_module]; + + } + +} diff --git a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldInterface.php b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldInterface.php index 6dceeab70..2bebe9274 100644 --- a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldInterface.php +++ b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldInterface.php @@ -9,7 +9,7 @@ /** * Provides an interface for all CCK field type plugins. * - * @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\Annotation\MigrateField instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php index 515e71e05..222f8823a 100644 --- a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php +++ b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php @@ -7,7 +7,7 @@ /** * Deprecated: Plugin manager for migrate field plugins. * - * @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php index 8eba728f7..9a2abf040 100644 --- a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php +++ b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php @@ -7,7 +7,7 @@ /** * Provides an interface for cck field plugin manager. * - * @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php b/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php index d608fb019..778f7841a 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/CckMigration.php @@ -7,7 +7,7 @@ /** * Migration plugin class for migrations dealing with CCK field values. * - * @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\Plugin\migrate\FieldMigration instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/FieldMigration.php b/core/modules/migrate_drupal/src/Plugin/migrate/FieldMigration.php index f84bdb4a0..0bde6f890 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/FieldMigration.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/FieldMigration.php @@ -23,7 +23,7 @@ class FieldMigration extends Migration implements ContainerFactoryPluginInterfac * fallback to the old 'cck_plugin_method'. * * @const string - * @deprecated This constant is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use the migrate_drupal.field_discovery service instead. See https://www.drupal.org/node/3006076. + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Use the migrate_drupal.field_discovery service instead. See https://www.drupal.org/node/3006076. */ const PLUGIN_METHOD = 'field_plugin_method'; diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/cckfield/CckFieldPluginBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/cckfield/CckFieldPluginBase.php index 80ad6fa50..34520c75e 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/cckfield/CckFieldPluginBase.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/cckfield/CckFieldPluginBase.php @@ -11,7 +11,7 @@ /** * The base class for all field plugins. * - * @deprecated in Drupal 8.4.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase instead. * * @see https://www.drupal.org/node/2751897 diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/destination/EntityFieldStorageConfig.php b/core/modules/migrate_drupal/src/Plugin/migrate/destination/EntityFieldStorageConfig.php index 3e51f4599..9fe4b1ad6 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/destination/EntityFieldStorageConfig.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/destination/EntityFieldStorageConfig.php @@ -17,7 +17,7 @@ * id = "md_entity:field_storage_config" * ) * - * @deprecated in Drupal 8.2.x and will be removed in Drupal 9.0.x. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate\Plugin\migrate\destination\EntityFieldStorageConfig * instead. * @@ -71,7 +71,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('entity.manager')->getStorage($entity_type_id), + $container->get('entity_type.manager')->getStorage($entity_type_id), array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id)), $container->get('language_manager'), $container->get('config.factory'), diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php index 78274704d..a32e08ce2 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php @@ -22,7 +22,7 @@ abstract class FieldPluginBase extends PluginBase implements MigrateFieldInterfa /** * Alters the migration for field definitions. * - * @deprecated in Drupal 8.6.0, to be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * alterFieldMigration() instead. * * @see https://www.drupal.org/node/2944598 @@ -44,7 +44,7 @@ public function alterFieldMigration(MigrationInterface $migration) { /** * Alert field instance migration. * - * @deprecated in Drupal 8.6.0, to be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * alterFieldInstanceMigration() instead. * * @see https://www.drupal.org/node/2944598 @@ -65,7 +65,7 @@ public function alterFieldInstanceMigration(MigrationInterface $migration) { /** * Alter field widget migration. * - * @deprecated in Drupal 8.6.0, to be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * alterFieldWidgetMigration() instead. * * @see https://www.drupal.org/node/2944598 @@ -121,7 +121,7 @@ public function getFieldWidgetMap() { /** * Alter field formatter migration. * - * @deprecated in Drupal 8.6.0, to be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * alterFieldFormatterMigration() instead. * * @see https://www.drupal.org/node/2944598 @@ -150,7 +150,7 @@ public function alterFieldFormatterMigration(MigrationInterface $migration) { /** * Defines the process pipeline for field values. * - * @deprecated in Drupal 8.6.0, to be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * defineValueProcessPipeline() instead. * * @see https://www.drupal.org/node/2944598 diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php index 15acb0213..38b4cbe3a 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/DrupalSqlBase.php @@ -3,8 +3,9 @@ namespace Drupal\migrate_drupal\Plugin\migrate\source; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; use Drupal\Core\Entity\DependencyTrait; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\State\StateInterface; use Drupal\migrate\Plugin\MigrationInterface; @@ -30,6 +31,12 @@ abstract class DrupalSqlBase extends SqlBase implements ContainerFactoryPluginInterface, DependentPluginInterface { use DependencyTrait; + use DeprecatedServicePropertyTrait; + + /** + * {@inheritdoc} + */ + protected $deprecatedProperties = ['entityManager' => 'entity.manager']; /** * The contents of the system table. @@ -46,18 +53,18 @@ abstract class DrupalSqlBase extends SqlBase implements ContainerFactoryPluginIn protected $requirements = TRUE; /** - * The entity manager. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state); - $this->entityManager = $entity_manager; + $this->entityTypeManager = $entity_type_manager; } /** @@ -94,7 +101,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_definition, $migration, $container->get('state'), - $container->get('entity.manager') + $container->get('entity_type.manager') ); } @@ -176,7 +183,7 @@ protected function variableGet($name, $default) { public function calculateDependencies() { // Generic handling for Drupal source plugin constants. if (isset($this->configuration['constants']['entity_type'])) { - $this->addDependency('module', $this->entityManager->getDefinition($this->configuration['constants']['entity_type'])->getProvider()); + $this->addDependency('module', $this->entityTypeManager->getDefinition($this->configuration['constants']['entity_type'])->getProvider()); } if (isset($this->configuration['constants']['module'])) { $this->addDependency('module', $this->configuration['constants']['module']); diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php index 0c49d048c..1eb93af69 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php @@ -3,11 +3,12 @@ namespace Drupal\migrate_drupal\Plugin\migrate\source; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; use Drupal\Core\Entity\DependencyTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\migrate\source\EmptySource as BaseEmptySource; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; /** @@ -21,20 +22,26 @@ class EmptySource extends BaseEmptySource implements ContainerFactoryPluginInterface, DependentPluginInterface { use DependencyTrait; + use DeprecatedServicePropertyTrait; /** - * The entity manager. + * {@inheritdoc} + */ + protected $deprecatedProperties = ['entityManager' => 'entity.manager']; + + /** + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityManagerInterface $entity_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityTypeManagerInterface $entity_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition, $migration); - $this->entityManager = $entity_manager; + $this->entityTypeManager = $entity_manager; } /** @@ -46,7 +53,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('entity.manager') + $container->get('entity_type.manager') ); } @@ -56,7 +63,7 @@ public static function create(ContainerInterface $container, array $configuratio public function calculateDependencies() { // The empty source plugin supports the entity_type constant. if (isset($this->configuration['constants']['entity_type'])) { - $this->addDependency('module', $this->entityManager->getDefinition($this->configuration['constants']['entity_type'])->getProvider()); + $this->addDependency('module', $this->entityTypeManager->getDefinition($this->configuration['constants']['entity_type'])->getProvider()); } return $this->dependencies; } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php index a585510b0..c901b85c1 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php @@ -2,7 +2,7 @@ namespace Drupal\migrate_drupal\Plugin\migrate\source; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\migrate\Plugin\MigrationInterface; @@ -29,8 +29,8 @@ class Variable extends DrupalSqlBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager); + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager); $this->variables = $this->configuration['variables']; } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/D6VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/D6VariableTranslation.php index a4a3a9f08..423d09467 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/D6VariableTranslation.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/D6VariableTranslation.php @@ -2,14 +2,14 @@ namespace Drupal\migrate_drupal\Plugin\migrate\source\d6; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\migrate\Plugin\MigrationInterface; /** * Gets Drupal i18n_variable source from database. * - * @deprecated in Drupal 8.7.x and will be removed in Drupal 9.0.x. + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. * Use \Drupal\migrate_drupal\Plugin\migrate\source\d6\VariableTranslation. * * @see https://www.drupal.org/node/3006487 @@ -24,9 +24,9 @@ class D6VariableTranslation extends VariableTranslation { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) { @trigger_error('The ' . __NAMESPACE__ . '\D6VariableTranslation is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\VariableTranslation. See https://www.drupal.org/node/3006487.', E_USER_DEPRECATED); - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager); + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager); } } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php index e11a305c8..633e7fe0b 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php @@ -2,8 +2,9 @@ namespace Drupal\migrate_drupal\Plugin\migrate\source\d6; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\State\StateInterface; +use Drupal\migrate\Exception\RequirementsException; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; @@ -12,7 +13,7 @@ * * @MigrateSource( * id = "d6_variable_translation", - * source_module = "system", + * source_module = "i18n", * ) */ class VariableTranslation extends DrupalSqlBase { @@ -27,8 +28,8 @@ class VariableTranslation extends DrupalSqlBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager); + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager); $this->variables = $this->configuration['variables']; } @@ -98,4 +99,14 @@ public function getIds() { return $ids; } + /** + * {@inheritdoc} + */ + public function checkRequirements() { + if (!$this->getDatabase()->schema()->tableExists('i18n_variable')) { + throw new RequirementsException("Source database table 'i18n_variable' does not exist"); + } + parent::checkRequirements(); + } + } diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php index f1c338a32..140399cc3 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php @@ -12,7 +12,7 @@ * source_module = "system", * ) * - * @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\migrate_drupal\Plugin\migrate\source\d6\VariableTranslation instead. * * @see https://www.drupal.org/node/2898649 diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php index 4df6a8702..dd68dbeaa 100644 --- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/VariableTranslation.php @@ -2,7 +2,7 @@ namespace Drupal\migrate_drupal\Plugin\migrate\source\d7; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; @@ -26,8 +26,8 @@ class VariableTranslation extends DrupalSqlBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager); + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager); $this->variables = $this->configuration['variables']; } diff --git a/core/modules/migrate_drupal/src/Tests/StubTestTrait.php b/core/modules/migrate_drupal/src/Tests/StubTestTrait.php index 3bf00281a..79a2a0fef 100644 --- a/core/modules/migrate_drupal/src/Tests/StubTestTrait.php +++ b/core/modules/migrate_drupal/src/Tests/StubTestTrait.php @@ -18,7 +18,7 @@ trait StubTestTrait { */ protected function performStubTest($entity_type_id) { $entity_id = $this->createStub($entity_type_id); - $this->assertTrue($entity_id, 'Stub successfully created'); + $this->assertNotEmpty($entity_id, 'Stub successfully created'); if ($entity_id) { $violations = $this->validateStub($entity_type_id, $entity_id); if (!$this->assertIdentical(count($violations), 0, 'Stub is a valid entity')) { @@ -65,7 +65,7 @@ protected function createStub($entity_type_id) { * List of constraint violations identified. */ protected function validateStub($entity_type_id, $entity_id) { - $controller = \Drupal::entityManager()->getStorage($entity_type_id); + $controller = \Drupal::entityTypeManager()->getStorage($entity_type_id); /** @var \Drupal\Core\Entity\ContentEntityInterface $stub_entity */ $stub_entity = $controller->load($entity_id); return $stub_entity->validate(); diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal6.php b/core/modules/migrate_drupal/tests/fixtures/drupal6.php index caa55b0e4..ca641233d 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal6.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal6.php @@ -8353,7 +8353,7 @@ 'fid' => '1', 'uid' => '1', 'filename' => 'Image1.png', - 'filepath' => 'core/modules/simpletest/files/image-1.png', + 'filepath' => 'core/tests/fixtures/files/image-1.png', 'filemime' => 'image/png', 'filesize' => '39325', 'status' => '1', @@ -8363,7 +8363,7 @@ 'fid' => '2', 'uid' => '1', 'filename' => 'Image2.jpg', - 'filepath' => 'core/modules/simpletest/files/image-2.jpg', + 'filepath' => 'core/tests/fixtures/files/image-2.jpg', 'filemime' => 'image/jpeg', 'filesize' => '1831', 'status' => '1', @@ -8373,7 +8373,7 @@ 'fid' => '3', 'uid' => '1', 'filename' => 'Image-test.gif', - 'filepath' => 'core/modules/simpletest/files/image-test.gif', + 'filepath' => 'core/tests/fixtures/files/image-test.gif', 'filemime' => 'image/jpeg', 'filesize' => '183', 'status' => '1', @@ -8383,7 +8383,7 @@ 'fid' => '5', 'uid' => '1', 'filename' => 'html-1.txt', - 'filepath' => 'core/modules/simpletest/files/html-1.txt', + 'filepath' => 'core/tests/fixtures/files/html-1.txt', 'filemime' => 'text/plain', 'filesize' => '24', 'status' => '1', @@ -10645,6 +10645,14 @@ 'action' => 'imagecache_rotate', 'data' => 'a:3:{s:7:"degrees";s:2:"55";s:6:"random";i:0;s:7:"bgcolor";s:0:"";}', )) + ->values(array( + 'actionid' => '7', + 'presetid' => '2', + 'weight' => '0', + 'module' => 'imagecache', + 'action' => '', + 'data' => 'a:3:{s:7:"degrees";s:2:"55";s:6:"random";i:0;s:7:"bgcolor";s:0:"";}', + )) ->execute(); $connection->schema()->createTable('imagecache_preset', array( 'fields' => array( @@ -33171,8 +33179,8 @@ 'menu_name' => 'navigation', 'mlid' => '411', 'plid' => '0', - 'link_path' => 'core/modules/simpletest/files/imagecache', - 'router_path' => 'core/modules/simpletest/files/imagecache', + 'link_path' => 'core/tests/fixtures/files/imagecache', + 'router_path' => 'core/tests/fixtures/files/imagecache', 'link_title' => '', 'options' => 'a:0:{}', 'module' => 'system', @@ -42845,7 +42853,7 @@ 'file' => 'sites/all/modules/cck/includes/content.node_form.inc', )) ->values(array( - 'path' => 'core/modules/simpletest/files/imagecache', + 'path' => 'core/tests/fixtures/files/imagecache', 'load_functions' => '', 'to_arg_functions' => '', 'access_callback' => '_imagecache_menu_access_public_files', @@ -42855,7 +42863,7 @@ 'fit' => '31', 'number_parts' => '5', 'tab_parent' => '', - 'tab_root' => 'core/modules/simpletest/files/imagecache', + 'tab_root' => 'core/tests/fixtures/files/imagecache', 'title' => '', 'title_callback' => 't', 'title_arguments' => '', @@ -48585,7 +48593,7 @@ 'status' => '1', 'timezone' => '3600', 'language' => 'fr', - 'picture' => 'core/modules/simpletest/files/image-test.jpg', + 'picture' => 'core/tests/fixtures/files/image-test.jpg', 'init' => 'doe@example.com', 'data' => 'a:2:{s:7:"contact";i:1;s:13:"form_build_id";s:48:"form-qu_DMjE-Vfg01arT5J4VbuBCkOgx_LeySJx4qrPOSuA";}', 'timezone_name' => 'Europe/Berlin', @@ -48610,7 +48618,7 @@ 'status' => '1', 'timezone' => '7200', 'language' => 'ro', - 'picture' => 'core/modules/simpletest/files/image-test.png', + 'picture' => 'core/tests/fixtures/files/image-test.png', 'init' => 'roe@example.com', 'data' => 'a:2:{s:7:"contact";i:0;s:13:"form_build_id";s:48:"form-1TxjbL2_1dEHIxEu2Db6OvEsSN1x9ILH1VCgnvsO6LE";}', 'timezone_name' => 'Europe/Helsinki', @@ -49567,7 +49575,7 @@ )) ->values(array( 'name' => 'file_directory_path', - 'value' => 's:29:"core/modules/simpletest/files";', + 'value' => 's:25:"core/tests/fixtures/files";', )) ->values(array( 'name' => 'file_directory_temp', diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php index 62d1574e8..43489f27a 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php @@ -3631,7 +3631,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:6:"forums";s:6:"parent";i:0;}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:12:"entity_types";a:0:{}s:12:"translatable";s:1:"0";s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:26:"field_data_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:30:"field_revision_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:1:"5";}', + 'data' => 'a:7:{s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:19:"sujet_de_discussion";s:6:"parent";i:0;}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:12:"entity_types";a:0:{}s:12:"translatable";s:1:"0";s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:26:"field_data_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:30:"field_revision_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:1:"5";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -3841,7 +3841,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:15:"test_vocabulary";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:31:"field_data_field_term_reference";a:1:{s:3:"tid";s:24:"field_term_reference_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:35:"field_revision_field_term_reference";a:1:{s:3:"tid";s:24:"field_term_reference_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"20";}', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:2:{i:0;a:2:{s:10:"vocabulary";s:15:"test_vocabulary";s:6:"parent";s:1:"0";}i:1;a:2:{s:10:"vocabulary";s:4:"tags";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:31:"field_data_field_term_reference";a:1:{s:3:"tid";s:24:"field_term_reference_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:35:"field_revision_field_term_reference";a:1:{s:3:"tid";s:24:"field_term_reference_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"20";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -4201,7 +4201,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:12:"translatable";i:0;s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocablocalized";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:29:"title_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:31:"field_data_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:35:"field_revision_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"44";}', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocablocalized";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:31:"field_data_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:35:"field_revision_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"44";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -4216,7 +4216,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:12:"translatable";i:0;s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocabtranslate";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:29:"title_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:32:"field_data_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:36:"field_revision_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"45";}', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocabtranslate";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:32:"field_data_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:36:"field_revision_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"45";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -4231,7 +4231,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:12:"translatable";i:0;s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:10:"vocabfixed";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:29:"title_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:28:"field_data_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:32:"field_revision_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"46";}', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:10:"vocabfixed";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:28:"field_data_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:32:"field_revision_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"46";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -4889,7 +4889,7 @@ 'field_name' => 'field_reference_2', 'entity_type' => 'node', 'bundle' => 'article', - 'data' => 'a:6:{s:5:"label";s:11:"Reference 2";s:6:"widget";a:4:{s:4:"type";s:14:"options_select";s:6:"weight";s:2:"21";s:8:"settings";a:0:{}s:6:"module";s:7:"options";}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:21:"entityreference_label";s:8:"settings";a:2:{s:4:"link";b:0;s:13:"bypass_access";b:0;}s:6:"module";s:15:"entityreference";s:6:"weight";i:21;}}s:8:"required";b:0;s:11:"description";s:0:"";}', + 'data' => 'a:6:{s:5:"label";s:11:"Reference 2";s:6:"widget";a:4:{s:4:"type";s:33:"entityreference_autocomplete_tags";s:6:"weight";s:2:"21";s:8:"settings";a:3:{s:14:"match_operator";s:8:"CONTAINS";s:4:"size";i:60;s:4:"path";s:0:"";}s:6:"module";s:15:"entityreference";}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:27:"entityreference_entity_view";s:6:"weight";s:2:"21";s:8:"settings";a:3:{s:9:"view_mode";s:7:"default";s:5:"links";b:1;s:20:"use_content_language";b:1;}s:6:"module";s:15:"entityreference";}}s:8:"required";b:0;s:11:"description";s:0:"";}', 'deleted' => '0', )) ->values(array( @@ -4991,6 +4991,15 @@ 'data' => 'a:7:{s:5:"label";s:6:"Rating";s:6:"widget";a:5:{s:6:"weight";s:2:"12";s:4:"type";s:15:"options_buttons";s:6:"module";s:7:"options";s:6:"active";i:1;s:8:"settings";a:0:{}}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"list_default";s:8:"settings";a:0:{}s:6:"module";s:4:"list";s:6:"weight";i:11;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', 'deleted' => '0', )) +->values(array( + 'id' => '78', + 'field_id' => '6', + 'field_name' => 'field_boolean', + 'entity_type' => 'node', + 'bundle' => 'blog', + 'data' => 'a:6:{s:5:"label";s:7:"Boolean";s:6:"widget";a:4:{s:4:"type";s:13:"options_onoff";s:6:"weight";s:2:"14";s:8:"settings";a:1:{s:13:"display_label";i:0;}s:6:"module";s:7:"options";}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"list_default";s:8:"settings";a:0:{}s:6:"module";s:4:"list";s:6:"weight";i:12;}}s:8:"required";b:0;s:11:"description";s:0:"";}', + 'deleted' => '0', +)) ->execute(); $connection->schema()->createTable('field_data_body', array( 'fields' => array( @@ -5494,7 +5503,17 @@ 'deleted' => '0', 'entity_id' => '1', 'revision_id' => '1', - 'language' => 'und', + 'language' => 'fr', + 'delta' => '0', + 'field_boolean_value' => '1', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '1', + 'language' => 'is', 'delta' => '0', 'field_boolean_value' => '1', )) @@ -8694,6 +8713,41 @@ 'mysql_character_set' => 'utf8', )); +$connection->insert('field_data_field_text_plain') + ->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_text_plain_value', + 'field_text_plain_format', + )) + ->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '2', + 'revision_id' => '2', + 'language' => 'und', + 'delta' => '0', + 'field_text_plain_value' => 'Kai Opaka', + 'field_text_plain_format' => NULL, + )) + ->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'field_text_plain_value' => 'Kai Opaka', + 'field_text_plain_format' => NULL, + )) + ->execute(); $connection->schema()->createTable('field_data_field_text_plain_filtered', array( 'fields' => array( 'entity_type' => array( @@ -10595,7 +10649,27 @@ 'deleted' => '0', 'entity_id' => '1', 'revision_id' => '1', - 'language' => 'und', + 'language' => 'en', + 'delta' => '0', + 'field_boolean_value' => '1', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '1', + 'language' => 'fr', + 'delta' => '0', + 'field_boolean_value' => '1', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '1', + 'language' => 'is', 'delta' => '0', 'field_boolean_value' => '1', )) @@ -13815,7 +13889,41 @@ ), 'mysql_character_set' => 'utf8', )); - +$connection->insert('field_revision_field_text_plain') + ->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_text_plain_value', + 'field_text_plain_format', + )) + ->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '2', + 'revision_id' => '2', + 'language' => 'und', + 'delta' => '0', + 'field_text_plain_value' => 'Kai Opaka', + 'field_text_plain_format' => NULL, + )) + ->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '3', + 'revision_id' => '3', + 'language' => 'und', + 'delta' => '0', + 'field_text_plain_value' => 'Kai Opaka', + 'field_text_plain_format' => NULL, + )) + ->execute(); $connection->schema()->createTable('field_revision_field_text_plain_filtered', array( 'fields' => array( 'entity_type' => array( @@ -15704,7 +15812,7 @@ 'format' => 'php_code', 'name' => 'PHP code', 'cache' => '0', - 'status' => '1', + 'status' => '0', 'weight' => '11', )) ->values(array( @@ -16199,183 +16307,183 @@ )) ->values(array( 'lid' => '77', - 'textgroup' => 'field', - 'context' => 'comment_body:comment_node_article:label', - 'objectid' => 'comment_node_article', - 'type' => 'comment_body', - 'property' => 'label', - 'objectindex' => '0', + 'textgroup' => 'taxonomy', + 'context' => 'term:20:name', + 'objectid' => '20', + 'type' => 'term', + 'property' => 'name', + 'objectindex' => '20', 'format' => '', )) ->values(array( 'lid' => '78', - 'textgroup' => 'field', - 'context' => 'comment_body:comment_node_blog:label', - 'objectid' => 'comment_node_blog', - 'type' => 'comment_body', - 'property' => 'label', - 'objectindex' => '0', - 'format' => '', + 'textgroup' => 'taxonomy', + 'context' => 'term:20:description', + 'objectid' => '20', + 'type' => 'term', + 'property' => 'description', + 'objectindex' => '20', + 'format' => 'filtered_html', )) ->values(array( 'lid' => '79', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:1:name', - 'objectid' => '1', - 'type' => 'vocabulary', + 'context' => 'term:19:name', + 'objectid' => '19', + 'type' => 'term', 'property' => 'name', - 'objectindex' => '1', + 'objectindex' => '19', 'format' => '', )) ->values(array( 'lid' => '80', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:1:description', - 'objectid' => '1', - 'type' => 'vocabulary', + 'context' => 'term:19:description', + 'objectid' => '19', + 'type' => 'term', 'property' => 'description', - 'objectindex' => '1', - 'format' => '', + 'objectindex' => '19', + 'format' => 'filtered_html', )) ->values(array( 'lid' => '81', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:3:name', - 'objectid' => '3', + 'context' => 'vocabulary:2:name', + 'objectid' => '2', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '3', + 'objectindex' => '2', 'format' => '', )) ->values(array( 'lid' => '82', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:3:description', - 'objectid' => '3', + 'context' => 'vocabulary:2:description', + 'objectid' => '2', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '3', + 'objectindex' => '2', 'format' => '', )) ->values(array( 'lid' => '83', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:4:name', - 'objectid' => '4', + 'context' => 'vocabulary:1:name', + 'objectid' => '1', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '4', + 'objectindex' => '1', 'format' => '', )) ->values(array( 'lid' => '84', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:4:description', - 'objectid' => '4', + 'context' => 'vocabulary:1:description', + 'objectid' => '1', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '4', + 'objectindex' => '1', 'format' => '', )) ->values(array( 'lid' => '85', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:5:name', - 'objectid' => '5', + 'context' => 'vocabulary:3:name', + 'objectid' => '3', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '5', + 'objectindex' => '3', 'format' => '', )) ->values(array( 'lid' => '86', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:5:description', - 'objectid' => '5', + 'context' => 'vocabulary:3:description', + 'objectid' => '3', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '5', + 'objectindex' => '3', 'format' => '', )) ->values(array( 'lid' => '87', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:6:name', - 'objectid' => '6', + 'context' => 'vocabulary:7:name', + 'objectid' => '7', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '6', + 'objectindex' => '7', 'format' => '', )) ->values(array( 'lid' => '88', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:6:description', - 'objectid' => '6', + 'context' => 'vocabulary:7:description', + 'objectid' => '7', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '6', + 'objectindex' => '7', 'format' => '', )) ->values(array( 'lid' => '89', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:7:name', - 'objectid' => '7', + 'context' => 'vocabulary:5:name', + 'objectid' => '5', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '7', + 'objectindex' => '5', 'format' => '', )) ->values(array( 'lid' => '90', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:7:description', - 'objectid' => '7', + 'context' => 'vocabulary:5:description', + 'objectid' => '5', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '7', + 'objectindex' => '5', 'format' => '', )) ->values(array( 'lid' => '91', 'textgroup' => 'taxonomy', - 'context' => 'term:19:name', - 'objectid' => '19', - 'type' => 'term', + 'context' => 'vocabulary:6:name', + 'objectid' => '6', + 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '19', + 'objectindex' => '6', 'format' => '', )) ->values(array( 'lid' => '92', 'textgroup' => 'taxonomy', - 'context' => 'term:19:description', - 'objectid' => '19', - 'type' => 'term', + 'context' => 'vocabulary:6:description', + 'objectid' => '6', + 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '19', - 'format' => 'filtered_html', + 'objectindex' => '6', + 'format' => '', )) ->values(array( 'lid' => '93', 'textgroup' => 'taxonomy', - 'context' => 'term:20:name', - 'objectid' => '20', - 'type' => 'term', + 'context' => 'vocabulary:4:name', + 'objectid' => '4', + 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '20', + 'objectindex' => '4', 'format' => '', )) ->values(array( 'lid' => '94', 'textgroup' => 'taxonomy', - 'context' => 'term:20:description', - 'objectid' => '20', - 'type' => 'term', + 'context' => 'vocabulary:4:description', + 'objectid' => '4', + 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '20', - 'format' => 'filtered_html', + 'objectindex' => '4', + 'format' => '', )) ->values(array( 'lid' => '95', @@ -17397,6 +17505,26 @@ 'objectindex' => '0', 'format' => '', )) +->values(array( + 'lid' => '795', + 'textgroup' => 'field', + 'context' => 'comment_body:comment_node_article:label', + 'objectid' => 'comment_node_article', + 'type' => 'comment_body', + 'property' => 'label', + 'objectindex' => '0', + 'format' => '', +)) +->values(array( + 'lid' => '796', + 'textgroup' => 'field', + 'context' => 'comment_body:comment_node_blog:label', + 'objectid' => 'comment_node_blog', + 'type' => 'comment_body', + 'property' => 'label', + 'objectindex' => '0', + 'format' => '', +)) ->execute(); $connection->schema()->createTable('i18n_translation_set', array( 'fields' => array( @@ -18421,6 +18549,150 @@ 'context' => 'user:login:title', 'version' => '1', )) +->values(array( + 'lid' => '77', + 'location' => 'taxonomy:term:20:name', + 'textgroup' => 'taxonomy', + 'source' => 'DS9', + 'context' => 'term:20:name', + 'version' => '1', +)) +->values(array( + 'lid' => '78', + 'location' => 'taxonomy:term:20:description', + 'textgroup' => 'taxonomy', + 'source' => 'Terok Nor', + 'context' => 'term:20:description', + 'version' => '1', +)) +->values(array( + 'lid' => '79', + 'location' => 'taxonomy:term:19:name', + 'textgroup' => 'taxonomy', + 'source' => 'Jupiter Station', + 'context' => 'term:19:name', + 'version' => '1', +)) +->values(array( + 'lid' => '80', + 'location' => 'taxonomy:term:19:description', + 'textgroup' => 'taxonomy', + 'source' => 'Holographic research.', + 'context' => 'term:19:description', + 'version' => '1', +)) +->values(array( + 'lid' => '81', + 'location' => 'taxonomy:vocabulary:2:name', + 'textgroup' => 'taxonomy', + 'source' => 'Sujet de discussion', + 'context' => 'vocabulary:2:name', + 'version' => '1', +)) +->values(array( + 'lid' => '82', + 'location' => 'taxonomy:vocabulary:2:description', + 'textgroup' => 'taxonomy', + 'source' => 'Forum navigation vocabulary', + 'context' => 'vocabulary:2:description', + 'version' => '1', +)) +->values(array( + 'lid' => '83', + 'location' => 'taxonomy:vocabulary:1:name', + 'textgroup' => 'taxonomy', + 'source' => 'Tags', + 'context' => 'vocabulary:1:name', + 'version' => '1', +)) +->values(array( + 'lid' => '84', + 'location' => 'taxonomy:vocabulary:1:description', + 'textgroup' => 'taxonomy', + 'source' => 'Use tags to group articles on similar topics into categories.', + 'context' => 'vocabulary:1:description', + 'version' => '1', +)) +->values(array( + 'lid' => '85', + 'location' => 'taxonomy:vocabulary:3:name', + 'textgroup' => 'taxonomy', + 'source' => 'Test Vocabulary', + 'context' => 'vocabulary:3:name', + 'version' => '1', +)) +->values(array( + 'lid' => '86', + 'location' => 'taxonomy:vocabulary:3:description', + 'textgroup' => 'taxonomy', + 'source' => 'This is the vocabulary description', + 'context' => 'vocabulary:3:description', + 'version' => '1', +)) +->values(array( + 'lid' => '87', + 'location' => 'taxonomy:vocabulary:7:name', + 'textgroup' => 'taxonomy', + 'source' => 'VocabFixed', + 'context' => 'vocabulary:7:name', + 'version' => '1', +)) +->values(array( + 'lid' => '88', + 'location' => 'taxonomy:vocabulary:7:description', + 'textgroup' => 'taxonomy', + 'source' => 'Vocabulary fixed option', + 'context' => 'vocabulary:7:description', + 'version' => '1', +)) +->values(array( + 'lid' => '89', + 'location' => 'taxonomy:vocabulary:5:name', + 'textgroup' => 'taxonomy', + 'source' => 'VocabLocalized', + 'context' => 'vocabulary:5:name', + 'version' => '1', +)) +->values(array( + 'lid' => '90', + 'location' => 'taxonomy:vocabulary:5:description', + 'textgroup' => 'taxonomy', + 'source' => 'Vocabulary localize option', + 'context' => 'vocabulary:5:description', + 'version' => '1', +)) +->values(array( + 'lid' => '91', + 'location' => 'taxonomy:vocabulary:6:name', + 'textgroup' => 'taxonomy', + 'source' => 'VocabTranslate', + 'context' => 'vocabulary:6:name', + 'version' => '1', +)) +->values(array( + 'lid' => '92', + 'location' => 'taxonomy:vocabulary:6:description', + 'textgroup' => 'taxonomy', + 'source' => 'Vocabulary translate option', + 'context' => 'vocabulary:6:description', + 'version' => '1', +)) +->values(array( + 'lid' => '93', + 'location' => 'taxonomy:vocabulary:4:name', + 'textgroup' => 'taxonomy', + 'source' => 'vocabulary name clearly different than machine name and much longer than thirty two characters', + 'context' => 'vocabulary:4:name', + 'version' => '1', +)) +->values(array( + 'lid' => '94', + 'location' => 'taxonomy:vocabulary:4:description', + 'textgroup' => 'taxonomy', + 'source' => 'description of vocabulary name much longer than thirty two characters', + 'context' => 'vocabulary:4:description', + 'version' => '1', +)) ->execute(); $connection->schema()->createTable('locales_target', array( 'fields' => array( @@ -18502,23 +18774,23 @@ 'i18n_status' => '0', )) ->values(array( - 'lid' => '85', - 'translation' => 'fr - VocabLocalized', + 'lid' => '77', + 'translation' => 'fr - DS9 (localized)', 'language' => 'fr', 'plid' => '0', 'plural' => '0', 'i18n_status' => '0', )) ->values(array( - 'lid' => '86', - 'translation' => 'fr - Vocabulary localize option', + 'lid' => '78', + 'translation' => 'fr - Terok Nor (localized)', 'language' => 'fr', 'plid' => '0', 'plural' => '0', 'i18n_status' => '0', )) ->values(array( - 'lid' => '89', + 'lid' => '87', 'translation' => 'fr - VocabFixed', 'language' => 'fr', 'plid' => '0', @@ -18526,40 +18798,16 @@ 'i18n_status' => '0', )) ->values(array( - 'lid' => '90', - 'translation' => 'fr - Vocabulary fixed option', - 'language' => 'fr', - 'plid' => '0', - 'plural' => '0', - 'i18n_status' => '0', -)) -->values(array( - 'lid' => '91', - 'translation' => 'fr - Jupiter Station', - 'language' => 'fr', - 'plid' => '0', - 'plural' => '0', - 'i18n_status' => '0', -)) -->values(array( - 'lid' => '92', - 'translation' => 'fr - Holographic research.', - 'language' => 'fr', - 'plid' => '0', - 'plural' => '0', - 'i18n_status' => '0', -)) -->values(array( - 'lid' => '93', - 'translation' => 'fr - DS9', + 'lid' => '89', + 'translation' => 'fr - VocabLocalized', 'language' => 'fr', 'plid' => '0', 'plural' => '0', 'i18n_status' => '0', )) ->values(array( - 'lid' => '94', - 'translation' => 'Terok Nor', + 'lid' => '90', + 'translation' => 'fr - Vocabulary localize option', 'language' => 'fr', 'plid' => '0', 'plural' => '0', @@ -18579,7 +18827,7 @@ 'language' => 'fr', 'plid' => '0', 'plural' => '0', - 'i18n_status' => '0', + 'i18n_status' => '1', )) ->values(array( 'lid' => '163', @@ -18595,7 +18843,7 @@ 'language' => 'fr', 'plid' => '0', 'plural' => '0', - 'i18n_status' => '0', + 'i18n_status' => '1', )) ->values(array( 'lid' => '684', @@ -18603,7 +18851,7 @@ 'language' => 'fr', 'plid' => '0', 'plural' => '0', - 'i18n_status' => '0', + 'i18n_status' => '1', )) ->values(array( 'lid' => '761', @@ -18678,32 +18926,48 @@ 'i18n_status' => '0', )) ->values(array( - 'lid' => '87', - 'translation' => 'is - VocabTranslate', + 'lid' => '79', + 'translation' => 'Jupiter Station', 'language' => 'is', 'plid' => '0', 'plural' => '0', 'i18n_status' => '0', )) ->values(array( - 'lid' => '88', - 'translation' => 'is - Vocabulary translate option', + 'lid' => '80', + 'translation' => 'is - Holographic research. (localized)', 'language' => 'is', 'plid' => '0', 'plural' => '0', 'i18n_status' => '0', )) ->values(array( - 'lid' => '93', - 'translation' => 'is - DS9', + 'lid' => '89', + 'translation' => 'is - VocabLocalized', 'language' => 'is', 'plid' => '0', 'plural' => '0', 'i18n_status' => '0', )) ->values(array( - 'lid' => '94', - 'translation' => 'is - Terok Nor', + 'lid' => '90', + 'translation' => 'is - Vocabulary localize option', + 'language' => 'is', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) +->values(array( + 'lid' => '91', + 'translation' => 'is - VocabTranslate', + 'language' => 'is', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) +->values(array( + 'lid' => '92', + 'translation' => 'is - Vocabulary translate option', 'language' => 'is', 'plid' => '0', 'plural' => '0', @@ -18741,6 +19005,30 @@ 'plural' => '0', 'i18n_status' => '0', )) +->values(array( + 'lid' => '687', + 'translation' => 'is - Off', + 'language' => 'is', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) +->values(array( + 'lid' => '688', + 'translation' => 'is - 1', + 'language' => 'is', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) +->values(array( + 'lid' => '758', + 'translation' => 'is field - vocab_localize', + 'language' => 'is', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) ->values(array( 'lid' => '761', 'translation' => 'Grænn', @@ -52528,33 +52816,33 @@ 'name' => 'i18n_sync', 'type' => 'module', 'owner' => '', - 'status' => '0', + 'status' => '1', 'bootstrap' => '0', 'schema_version' => '-1', 'weight' => '0', - 'info' => 'a:12:{s:4:"name";s:24:"Synchronize translations";s:11:"description";s:73:"Synchronizes taxonomy and fields across translations of the same content.";s:12:"dependencies";a:2:{i:0;s:4:"i18n";i:1;s:11:"translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:5:{i:0;s:16:"i18n_sync.module";i:1;s:17:"i18n_sync.install";i:2;s:20:"i18n_sync.module.inc";i:3;s:18:"i18n_sync.node.inc";i:4;s:14:"i18n_sync.test";}s:7:"version";s:8:"7.x-1.26";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1534531985";s:5:"mtime";i:1534531985;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', + 'info' => 'a:12:{s:4:"name";s:24:"Synchronize translations";s:11:"description";s:73:"Synchronizes taxonomy and fields across translations of the same content.";s:12:"dependencies";a:2:{i:0;s:4:"i18n";i:1;s:11:"translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:5:{i:0;s:16:"i18n_sync.module";i:1;s:17:"i18n_sync.install";i:2;s:20:"i18n_sync.module.inc";i:3;s:18:"i18n_sync.node.inc";i:4;s:14:"i18n_sync.test";}s:7:"version";s:8:"7.x-1.25";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1531342125";s:5:"mtime";i:1537747251;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', )) ->values(array( 'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.module', 'name' => 'i18n_taxonomy', 'type' => 'module', 'owner' => '', - 'status' => '0', + 'status' => '1', 'bootstrap' => '0', - 'schema_version' => '-1', - 'weight' => '0', - 'info' => 'a:12:{s:4:"name";s:20:"Taxonomy translation";s:11:"description";s:30:"Enables multilingual taxonomy.";s:12:"dependencies";a:3:{i:0;s:8:"taxonomy";i:1;s:11:"i18n_string";i:2;s:16:"i18n_translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:4:{i:0;s:17:"i18n_taxonomy.inc";i:1;s:23:"i18n_taxonomy.pages.inc";i:2;s:23:"i18n_taxonomy.admin.inc";i:3;s:18:"i18n_taxonomy.test";}s:7:"version";s:8:"7.x-1.26";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1534531985";s:5:"mtime";i:1534531985;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', + 'schema_version' => '7004', + 'weight' => '5', + 'info' => 'a:12:{s:4:"name";s:20:"Taxonomy translation";s:11:"description";s:30:"Enables multilingual taxonomy.";s:12:"dependencies";a:3:{i:0;s:8:"taxonomy";i:1;s:11:"i18n_string";i:2;s:16:"i18n_translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:4:{i:0;s:17:"i18n_taxonomy.inc";i:1;s:23:"i18n_taxonomy.pages.inc";i:2;s:23:"i18n_taxonomy.admin.inc";i:3;s:18:"i18n_taxonomy.test";}s:7:"version";s:8:"7.x-1.25";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1531342125";s:5:"mtime";i:1537747251;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', )) ->values(array( 'filename' => 'sites/all/modules/i18n/i18n_translation/i18n_translation.module', 'name' => 'i18n_translation', 'type' => 'module', 'owner' => '', - 'status' => '0', + 'status' => '1', 'bootstrap' => '0', 'schema_version' => '-1', 'weight' => '0', - 'info' => 'a:12:{s:4:"name";s:16:"Translation sets";s:11:"description";s:47:"Simple translation sets API for generic objects";s:12:"dependencies";a:1:{i:0;s:4:"i18n";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:1:{i:0;s:20:"i18n_translation.inc";}s:7:"version";s:8:"7.x-1.26";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1534531985";s:5:"mtime";i:1534531985;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', + 'info' => 'a:12:{s:4:"name";s:16:"Translation sets";s:11:"description";s:47:"Simple translation sets API for generic objects";s:12:"dependencies";a:1:{i:0;s:4:"i18n";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:1:{i:0;s:20:"i18n_translation.inc";}s:7:"version";s:8:"7.x-1.25";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1531342125";s:5:"mtime";i:1537747251;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', )) ->values(array( 'filename' => 'sites/all/modules/i18n/i18n_user/i18n_user.module', @@ -54566,6 +54854,14 @@ 'name' => 'i18n_string_textgroup_class_taxonomy', 'value' => 's:29:"i18n_string_textgroup_default";', )) +->values(array( + 'name' => 'i18n_sync_node_type_article', + 'value' => 'a:1:{i:0;s:16:"field_text_plain";}', +)) +->values(array( + 'name' => 'i18n_sync_source_article', + 'value' => 'i:0;', +)) ->values(array( 'name' => 'image_jpeg_quality', 'value' => 'i:80;', @@ -55072,7 +55368,7 @@ )) ->values(array( 'name' => 'user_mail_register_admin_created_body', - 'value' => 's:30:"...and she could take it away.";', + 'value' => 's:30:"...and it could be taken away.";', )) ->values(array( 'name' => 'user_mail_register_admin_created_subject', @@ -55359,7 +55655,7 @@ 'realm' => 'language', 'realm_key' => 'is', 'name' => 'user_mail_register_admin_created_body', - 'value' => "is - ...and she could take it away.\r\n[site:name], [site:url]", + 'value' => "is - ...and it could be taken away.\r\n[site:name], [site:url]", 'serialized' => '0', )) ->values(array( diff --git a/core/modules/migrate_drupal/tests/modules/field_discovery_test/field_discovery_test.info.yml b/core/modules/migrate_drupal/tests/modules/field_discovery_test/field_discovery_test.info.yml index 33aa0e681..effa82da7 100644 --- a/core/modules/migrate_drupal/tests/modules/field_discovery_test/field_discovery_test.info.yml +++ b/core/modules/migrate_drupal/tests/modules/field_discovery_test/field_discovery_test.info.yml @@ -2,11 +2,5 @@ name: 'Migrate drupal field discovery tet' type: module description: 'Module containing a test class exposing protected field discovery methods' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate_drupal/tests/modules/migrate_cckfield_plugin_manager_test/migrate_cckfield_plugin_manager_test.info.yml b/core/modules/migrate_drupal/tests/modules/migrate_cckfield_plugin_manager_test/migrate_cckfield_plugin_manager_test.info.yml index 09b7e82a2..8449e1ade 100644 --- a/core/modules/migrate_drupal/tests/modules/migrate_cckfield_plugin_manager_test/migrate_cckfield_plugin_manager_test.info.yml +++ b/core/modules/migrate_drupal/tests/modules/migrate_cckfield_plugin_manager_test/migrate_cckfield_plugin_manager_test.info.yml @@ -2,11 +2,5 @@ name: 'Migrate cck field plugin manager test' type: module description: 'Example module demonstrating the cck field plugin manager in the Migrate API.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate_drupal/tests/modules/migrate_field_plugin_manager_test/migrate_field_plugin_manager_test.info.yml b/core/modules/migrate_drupal/tests/modules/migrate_field_plugin_manager_test/migrate_field_plugin_manager_test.info.yml index f96ea38de..db67a4f02 100644 --- a/core/modules/migrate_drupal/tests/modules/migrate_field_plugin_manager_test/migrate_field_plugin_manager_test.info.yml +++ b/core/modules/migrate_drupal/tests/modules/migrate_field_plugin_manager_test/migrate_field_plugin_manager_test.info.yml @@ -2,11 +2,5 @@ name: 'Migrate field plugin manager test' type: module description: 'Example module demonstrating the field plugin manager in the Migrate API.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml index 40ce5ac36..503c6cec7 100644 --- a/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml +++ b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml @@ -2,11 +2,5 @@ name: 'Migrate property overwrite test' type: module description: 'Example module demonstrating property overwrite support in the Migrate API.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrate_state_finished_test.info.yml b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrate_state_finished_test.info.yml new file mode 100644 index 000000000..a040bc537 --- /dev/null +++ b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrate_state_finished_test.info.yml @@ -0,0 +1,6 @@ +name: Migrate state active test +type: module +description: Tests the 'active' migrate state +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/migrate_state_finished_test.yml b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/migrate_state_finished_test.yml new file mode 100644 index 000000000..9d1785e58 --- /dev/null +++ b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/migrate_state_finished_test.yml @@ -0,0 +1,19 @@ +id: migrate_state_finished_test +label: Block content body field configuration +migration_tags: + - Drupal 6 + - Drupal 7 + - Configuration +source: + plugin: embedded_data + data_rows: + - + id: 1 + ids: + id: + type: string + source_module: action +process: [] +destination: + plugin: entity:field_config + destination_module: migrate_state_finished_test diff --git a/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/migrate_state_finished_test1.yml b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/migrate_state_finished_test1.yml new file mode 100644 index 000000000..7c19c98f5 --- /dev/null +++ b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/migrate_state_finished_test1.yml @@ -0,0 +1,19 @@ +id: migrate_state_finished_test1 +label: Block content body field configuration +migration_tags: + - Drupal 6 + - Drupal 7 + - Configuration +source: + plugin: embedded_data + data_rows: + - + id: 1 + ids: + id: + type: string + source_module: action +process: [] +destination: + plugin: entity:field_config + destination_module: migrate_state_not_finished_test diff --git a/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/state/migrate_state_finished_test.migrate_drupal.yml b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/state/migrate_state_finished_test.migrate_drupal.yml new file mode 100644 index 000000000..28169879d --- /dev/null +++ b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/migrations/state/migrate_state_finished_test.migrate_drupal.yml @@ -0,0 +1,20 @@ +finished: + 6: + # A field migration + # migrate_state_finished_test: migrate_state_finished_test + aggregator: migrate_state_finished_test + action: + - migrate_state_finished_test + - migrate_state_not_finished_test + 7: + # A field migration + # migrate_state_finished_test: migrate_state_finished_test + aggregator: migrate_state_finished_test + # Migrations + action: + - migrate_state_finished_test + - migrate_state_not_finished_test +not_finished: + 7: + # Migrations + action: system diff --git a/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/src/Plugin/migrate/field/FieldLeft.php b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/src/Plugin/migrate/field/FieldLeft.php new file mode 100644 index 000000000..a411d0176 --- /dev/null +++ b/core/modules/migrate_drupal/tests/modules/migrate_state_finished_test/src/Plugin/migrate/field/FieldLeft.php @@ -0,0 +1,18 @@ +setExpectedException(PluginNotFoundException::class, sprintf("Plugin ID '%s' was not found.", $field_type)); + $this->expectException(PluginNotFoundException::class); + $this->expectExceptionMessage(sprintf("Plugin ID '%s' was not found.", $field_type)); $this->pluginManager->getPluginIdFromFieldType($field_type, ['core' => $core]); } diff --git a/core/modules/migrate_drupal/tests/src/Kernel/MigrationPluginAlterTest.php b/core/modules/migrate_drupal/tests/src/Kernel/MigrationPluginAlterTest.php new file mode 100644 index 000000000..335c44cab --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/MigrationPluginAlterTest.php @@ -0,0 +1,377 @@ +setupDb(); + } + + /** + * Tests migrate_drupal_migrations_plugin_alter without content_translation. + * + * @dataProvider providerMigrationPluginAlter + */ + public function testMigrationPluginAlterNoTranslation($source, $expected) { + $definitions = $source; + migrate_drupal_migration_plugins_alter($definitions); + // Ensure the results have an 'id' key. + foreach ($definitions as $definition) { + $this->assertArrayHasKey('id', $definition); + } + $this->assertSame($expected, $definitions); + } + + /** + * Data provider for testMigrationPluginAlter(). + */ + public function providerMigrationPluginAlter() { + $tests = []; + + // Test without a d6_taxonomy_vocabulary definition. + $tests[0]['source_data'] = [ + 'test' => [ + 'id' => 'test', + 'process' => [ + 'nid' => 'nid', + ], + ], + ]; + $tests[0]['expected_data'] = $tests[0]['source_data']; + + // Test with a d6_taxonomy_vocabulary definition. + $tests[1]['source_data'] = [ + 'd6_taxonomy_vocabulary' => [ + 'id' => 'd6_taxonomy_vocabulary', + 'process' => [ + 'vid' => [ + 'plugin' => 'machine_name', + ], + ], + ], + 'test' => [ + 'id' => 'test', + 'process' => [ + 'nid' => 'nid', + ], + ], + ]; + $tests[1]['expected_data'] = $tests[1]['source_data']; + + // Test with a d6_taxonomy_vocabulary and term_node definitions. + $tests[2] = $tests[1]; + $tests[2]['source_data']['d6_term_node:2'] = [ + 'id' => 'd6_term_node:2', + 'process' => [ + 'vid' => [ + 'plugin' => 'machine_name', + ], + ], + ]; + $tests[2]['source_data']['d6_term_node_revision:4'] = [ + 'id' => 'd6_term_node_revision:4', + 'process' => [ + 'vid' => [ + 'plugin' => 'machine_name', + ], + ], + ]; + + $tests[2]['expected_data'] = $tests[2]['source_data']; + $tests[2]['expected_data']['d6_term_node:2']['process']['taxonomy_forums'] = 'tid'; + $tests[2]['expected_data']['d6_term_node_revision:4']['process']['field_'] = 'tid'; + + // Test with a d6_taxonomy_vocabulary and term_node_translation definition. + $tests[3] = $tests[1]; + $tests[3]['source_data']['d6_term_node_translation:2'] = [ + 'id' => 'd6_term_node_translation:2', + 'process' => [ + 'vid' => [ + 'plugin' => 'machine_name', + ], + ], + ]; + + $tests[3]['expected_data'] = $tests[3]['source_data']; + return $tests; + } + + /** + * Tests migrate_drupal_migrations_plugin_alter. + * + * @dataProvider providerMigrationPluginAlterTranslation + */ + public function testMigrationPluginAlterTranslation($source, $expected) { + /** @var \Drupal\Core\Extension\ModuleInstaller $module_installer */ + $module_installer = \Drupal::service('module_installer'); + $module_installer->install(['content_translation']); + $definitions = $source; + migrate_drupal_migration_plugins_alter($definitions); + // Ensure the results have an 'id' key. + foreach ($definitions as $definition) { + $this->assertArrayHasKey('id', $definition); + } + $this->assertSame($expected, $definitions); + + } + + /** + * Data provider for providerMigrationPluginAlterTranslation(). + */ + public function providerMigrationPluginAlterTranslation() { + $tests = []; + + // Test with a d6_taxonomy_vocabulary definition and + // d6_term_node_translation definitions. + $tests[0]['source_data'] = [ + 'd6_taxonomy_vocabulary' => [ + 'id' => 'd6_taxonomy_vocabulary', + 'process' => [ + 'vid' => [ + 'plugin' => 'machine_name', + ], + ], + ], + 'test' => [ + 'id' => 'test', + 'process' => [ + 'nid' => 'nid', + ], + ], + 'd6_term_node_translation:2' => [ + 'id' => 'd6_term_node_translation:2', + 'process' => [ + 'vid' => [ + 'plugin' => 'machine_name', + ], + ], + ], + 'd6_term_node_translation:4' => [ + 'id' => 'd6_term_node_translation:4', + 'process' => [ + 'vid' => [ + 'plugin' => 'machine_name', + ], + ], + ], + ]; + + $tests[0]['expected_data'] = $tests[0]['source_data']; + $tests[0]['expected_data']['d6_term_node_translation:2']['process']['taxonomy_forums'] = 'tid'; + $tests[0]['expected_data']['d6_term_node_translation:4']['process']['field_'] = 'tid'; + return $tests; + } + + /** + * Creates data in the source database. + */ + protected function setupDb() { + $this->sourceDatabase->schema()->createTable('system', [ + 'fields' => [ + 'name' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '255', + 'default' => '', + ], + 'type' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '255', + 'default' => '', + ], + 'status' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + ], + ], + ]); + $this->sourceDatabase->insert('system') + ->fields([ + 'name', + 'type', + 'status', + ]) + ->values([ + 'name' => 'taxonomy', + 'type' => 'module', + 'status' => '1', + ]) + ->execute(); + + $this->sourceDatabase->schema()->createTable('variable', [ + 'fields' => [ + 'name' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ], + 'value' => [ + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'normal', + ], + ], + ]); + $this->sourceDatabase->insert('variable') + ->fields([ + 'name', + 'value', + ]) + ->values([ + 'name' => 'forum_nav_vocabulary', + 'value' => 's:1:"2";', + ]) + ->execute(); + + $this->sourceDatabase->schema()->createTable('vocabulary', [ + 'fields' => [ + 'vid' => [ + 'type' => 'serial', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ], + 'name' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '255', + 'default' => '', + ], + 'description' => [ + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'normal', + ], + 'help' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '255', + 'default' => '', + ], + 'relations' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'hierarchy' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'multiple' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'required' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'tags' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'module' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '255', + 'default' => '', + ], + 'weight' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + ], + ], + 'primary key' => ['vid'], + ]); + + $this->sourceDatabase->insert('vocabulary') + ->fields([ + 'vid', + 'name', + ]) + ->values([ + 'vid' => '4', + 'name' => 'Tags', + ]) + ->values([ + 'vid' => '2', + 'name' => 'Forums', + ]) + ->execute(); + + $this->sourceDatabase->schema()->createTable('vocabulary_node_types', [ + 'fields' => [ + 'vid' => [ + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'default' => '0', + 'unsigned' => TRUE, + ], + 'type' => [ + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ], + ], + 'primary key' => [ + 'vid', + 'type', + ], + 'mysql_character_set' => 'utf8', + ]); + + $this->sourceDatabase->insert('vocabulary_node_types') + ->fields([ + 'vid', + 'type', + ]) + ->values([ + 'vid' => '4', + 'type' => 'article', + ]) + ->values([ + 'vid' => '2', + 'type' => 'forum', + ]) + ->execute(); + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Kernel/MigrationStateDeprecationTest.php b/core/modules/migrate_drupal/tests/src/Kernel/MigrationStateDeprecationTest.php new file mode 100644 index 000000000..41810f6fd --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/MigrationStateDeprecationTest.php @@ -0,0 +1,48 @@ +createInstancesByTag('Drupal 7'); + + \Drupal::service('migrate_drupal.migration_state') + ->getUpgradeStates(7, [ + 'module' => [ + 'migrate_state_no_file_test' => [ + 'name' => 'migrate_state_no_file_test', + 'status' => TRUE, + ], + ], + ], ['import' => $all_migrations['migrate_state_no_file_test']]); + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/DestinationCategoryTest.php b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/DestinationCategoryTest.php index dde4d7d92..2bec0cc1b 100644 --- a/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/DestinationCategoryTest.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/DestinationCategoryTest.php @@ -13,6 +13,7 @@ use Drupal\shortcut\Plugin\migrate\destination\ShortcutSetUsers; use Drupal\statistics\Plugin\migrate\destination\NodeCounter; use Drupal\system\Plugin\migrate\destination\d7\ThemeSettings; +use Drupal\Tests\DeprecatedModulesTestTrait; use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase; use Drupal\Tests\migrate_drupal\Traits\CreateMigrationsTrait; use Drupal\user\Plugin\migrate\destination\UserData; @@ -26,6 +27,7 @@ class DestinationCategoryTest extends MigrateDrupalTestBase { use FileSystemModuleDiscoveryDataProviderTrait; use CreateMigrationsTrait; + use DeprecatedModulesTestTrait; /** * The migration plugin manager. @@ -40,6 +42,7 @@ class DestinationCategoryTest extends MigrateDrupalTestBase { protected function setUp() { // Enable all modules. self::$modules = array_keys($this->coreModuleListDataProvider()); + self::$modules = $this->removeDeprecatedModules(self::$modules); parent::setUp(); $this->migrationManager = \Drupal::service('plugin.manager.migration'); } diff --git a/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/LegacyDestinationCategoryTest.php b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/LegacyDestinationCategoryTest.php new file mode 100644 index 000000000..4587d0a3e --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/LegacyDestinationCategoryTest.php @@ -0,0 +1,20 @@ + '', ]; - $this->setExpectedException(InvalidPluginDefinitionException::class, 'Missing required "entity_type" definition.'); + $this->expectException(InvalidPluginDefinitionException::class); + $this->expectExceptionMessage('Missing required "entity_type" definition.'); ContentEntity::create($this->container, $configuration, 'content_entity', $plugin_definition, $migration); } @@ -201,7 +202,8 @@ public function testConstructorNonContentEntity() { $plugin_definition = [ 'entity_type' => 'node_type', ]; - $this->setExpectedException(InvalidPluginDefinitionException::class, 'The entity type (node_type) is not supported. The "content_entity" source plugin only supports content entities.'); + $this->expectException(InvalidPluginDefinitionException::class); + $this->expectExceptionMessage('The entity type (node_type) is not supported. The "content_entity" source plugin only supports content entities.'); ContentEntity::create($this->container, $configuration, 'content_entity:node_type', $plugin_definition, $migration); } @@ -216,7 +218,8 @@ public function testConstructorNotBundable() { $plugin_definition = [ 'entity_type' => 'user', ]; - $this->setExpectedException(\InvalidArgumentException::class, 'A bundle was provided but the entity type (user) is not bundleable'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('A bundle was provided but the entity type (user) is not bundleable'); ContentEntity::create($this->container, $configuration, 'content_entity:user', $plugin_definition, $migration); } @@ -231,7 +234,8 @@ public function testConstructorInvalidBundle() { $plugin_definition = [ 'entity_type' => 'node', ]; - $this->setExpectedException(\InvalidArgumentException::class, 'The provided bundle (foo) is not valid for the (node) entity type.'); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The provided bundle (foo) is not valid for the (node) entity type.'); ContentEntity::create($this->container, $configuration, 'content_entity:node', $plugin_definition, $migration); } diff --git a/core/modules/migrate_drupal/tests/src/Kernel/StateFileExists.php b/core/modules/migrate_drupal/tests/src/Kernel/StateFileExists.php new file mode 100644 index 000000000..fd2e20a0a --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/StateFileExists.php @@ -0,0 +1,109 @@ +container->get('module_handler'); + $all_modules = $this->coreModuleListDataProvider(); + $modules_enabled = $module_handler->getModuleList(); + $modules_to_enable = array_keys(array_diff_key($all_modules, $modules_enabled)); + $this->enableModules($modules_to_enable); + + // Modules with a migrate_drupal.yml file. + $has_state_file = (new YamlDiscovery('migrate_drupal', array_map(function (&$value) { + return $value . '/migrations/state'; + }, $module_handler->getModuleDirectories())))->findAll(); + + foreach ($this->stateFileRequired as $module) { + $this->assertArrayHasKey($module, $has_state_file, sprintf("Module '%s' should have a migrate_drupal.yml file", $module)); + } + $this->assertEquals(count($this->stateFileRequired), count($has_state_file)); + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/FieldDiscoveryTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/FieldDiscoveryTest.php index cc2fb8c37..83ae30e90 100644 --- a/core/modules/migrate_drupal/tests/src/Kernel/d6/FieldDiscoveryTest.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/FieldDiscoveryTest.php @@ -246,7 +246,7 @@ public function testAddFields() { 'method' => 'process', ], 2 => [ - 'plugin' => 'migration', + 'plugin' => 'migration_lookup', 'migration' => [ 0 => 'd6_filter_format', 1 => 'd7_filter_format', diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/LegacyMigrateDrupal6AuditIdsTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/LegacyMigrateDrupal6AuditIdsTest.php new file mode 100644 index 000000000..32d983387 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/LegacyMigrateDrupal6AuditIdsTest.php @@ -0,0 +1,20 @@ +coreModuleListDataProvider()); + self::$modules = $this->removeDeprecatedModules(self::$modules); parent::setUp(); // Install required entity schemas. diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrationProcessTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrationProcessTest.php index 429276982..45ffb4334 100644 --- a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrationProcessTest.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrationProcessTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\migrate_drupal\Kernel\d6; use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait; +use Drupal\Tests\DeprecatedModulesTestTrait; /** * Tests the getProcess() method of all Drupal 6 migrations. @@ -10,6 +11,8 @@ * @group migrate_drupal */ class MigrationProcessTest extends MigrateDrupal6TestBase { + + use DeprecatedModulesTestTrait; use FileSystemModuleDiscoveryDataProviderTrait; /** @@ -17,6 +20,7 @@ class MigrationProcessTest extends MigrateDrupal6TestBase { */ public function setUp() { self::$modules = array_keys($this->coreModuleListDataProvider()); + self::$modules = $this->removeDeprecatedModules(self::$modules); parent::setUp(); } diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/ValidateMigrationStateTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/ValidateMigrationStateTest.php new file mode 100644 index 000000000..8ede5ecc0 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/ValidateMigrationStateTest.php @@ -0,0 +1,27 @@ +sourceDatabase->schema()->dropTable('i18n_variable'); + } + + /** + * Tests exception in thrown when the i18n_variable table does not exist. + */ + public function testCheckRequirements() { + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage("Source database table 'i18n_variable' does not exist"); + $this->getMigration('d6_system_maintenance_translation') + ->getSourcePlugin() + ->checkRequirements(); + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d7/FieldDiscoveryTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d7/FieldDiscoveryTest.php index 8f173e6f4..d3ac665d6 100644 --- a/core/modules/migrate_drupal/tests/src/Kernel/d7/FieldDiscoveryTest.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/FieldDiscoveryTest.php @@ -196,6 +196,7 @@ public function addAllFieldProcessesAltersData() { 'taxonomy_term_reference' => [ 'taxonomy_term_reference_link' => 'entity_reference_label', 'i18n_taxonomy_term_reference_link' => 'entity_reference_label', + 'entityreference_entity_view' => 'entity_reference_entity_view', ], 'link_field' => [ 'link_default' => 'link', diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d7/LegacyMigrateDrupal7AuditIdsTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d7/LegacyMigrateDrupal7AuditIdsTest.php new file mode 100644 index 000000000..fc67fb5f9 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/LegacyMigrateDrupal7AuditIdsTest.php @@ -0,0 +1,20 @@ +coreModuleListDataProvider()); + self::$modules = $this->removeDeprecatedModules(self::$modules); parent::setUp(); // Install required entity schemas. diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrationProcessTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrationProcessTest.php index 4f3826f67..833ea3e82 100644 --- a/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrationProcessTest.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrationProcessTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\migrate_drupal\Kernel\d7; use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait; +use Drupal\Tests\DeprecatedModulesTestTrait; /** * Tests the getProcess() method of all Drupal 7 migrations. @@ -10,6 +11,8 @@ * @group migrate_drupal */ class MigrationProcessTest extends MigrateDrupal7TestBase { + + use DeprecatedModulesTestTrait; use FileSystemModuleDiscoveryDataProviderTrait; /** @@ -17,6 +20,7 @@ class MigrationProcessTest extends MigrateDrupal7TestBase { */ public function setUp() { self::$modules = array_keys($this->coreModuleListDataProvider()); + self::$modules = $this->removeDeprecatedModules(self::$modules); parent::setUp(); } diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d7/ValidateMigrationStateTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d7/ValidateMigrationStateTest.php new file mode 100644 index 000000000..e810c9d48 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/ValidateMigrationStateTest.php @@ -0,0 +1,27 @@ +installEntitySchema('file'); $this->installEntitySchema('menu_link_content'); $this->installEntitySchema('node'); + $this->installEntitySchema('path_alias'); $this->installEntitySchema('taxonomy_term'); $this->installEntitySchema('user'); } diff --git a/core/modules/migrate_drupal/tests/src/Traits/ValidateMigrationStateTestTrait.php b/core/modules/migrate_drupal/tests/src/Traits/ValidateMigrationStateTestTrait.php new file mode 100644 index 000000000..bb4f5fb28 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Traits/ValidateMigrationStateTestTrait.php @@ -0,0 +1,160 @@ +enableAllModules(); + + // Build an array for each migration keyed by provider. The value is a + // string consisting of the version number, the provider, the source_module + // and the destination module. + $discovered = []; + $versions = ['6', '7']; + /** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */ + $plugin_manager = $this->container->get('plugin.manager.migration'); + foreach ($versions as $version) { + $migrations = $plugin_manager->createInstancesByTag('Drupal ' . $version); + /** @var \Drupal\migrate\Plugin\Migration $migration */ + foreach ($migrations as $migration) { + $definition = $migration->getPluginDefinition(); + if (is_array($definition['provider'])) { + $provider = reset($definition['provider']); + } + else { + $provider = $definition['provider']; + } + + $source_module = $migration->getSourcePlugin()->getSourceModule(); + $destination_module = $migration->getDestinationPlugin() + ->getDestinationModule(); + + $discovered[$version][] = implode($separator, [ + $version, + $provider, + $source_module, + $destination_module, + ]); + } + } + + // Add the field migrations. + /** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */ + $definitions = $this->container->get('plugin.manager.migrate.field') + ->getDefinitions(); + foreach ($definitions as $key => $definition) { + foreach ($definition['core'] as $version) { + $discovered[$version][] = implode($separator, [ + $version, + $definition['provider'], + $definition['source_module'], + $definition['destination_module'], + ]); + } + } + + // Get the declared migration state information from .migrate_drupal.yml + // and build an array of source modules and there migration state. The + // destination is not used yet but can be later for validating the + // source/destination pairs with the actual source/destination pairs in the + // migrate plugins. + $system_info = (new YamlDiscovery('migrate_drupal', array_map(function (&$value) { + return $value . '/migrations/state/'; + }, \Drupal::moduleHandler()->getModuleDirectories())))->findAll(); + + $declared = []; + $states = [MigrationState::FINISHED, MigrationState::NOT_FINISHED]; + foreach ($system_info as $module => $info) { + foreach ($states as $state) { + if (isset($info[$state])) { + foreach ($info[$state] as $info_version => $migrate_info) { + foreach ($migrate_info as $source => $destination) { + // Do not add the source module i18nstrings or i18_string. The + // 18n migrations can have up to three source modules but only one + // can be handled in the migration. + if (($source !== 'i18nstrings') && ($source !== 'i18n_string')) { + foreach ((array) $destination as $dest) { + $key = [$info_version, $module, $source, trim($dest)]; + $declared[$info_version][$state][] = implode($separator, $key); + } + } + } + } + } + } + } + + $versions = ['6', '7']; + foreach ($versions as $version) { + // Sort and make the array values unique. + sort($declared[$version][MigrationState::FINISHED]); + sort($declared[$version][MigrationState::NOT_FINISHED]); + $declared_unique[$version][MigrationState::FINISHED] = array_unique($declared[$version][MigrationState::FINISHED]); + $declared_unique[$version][MigrationState::NOT_FINISHED] = array_unique($declared[$version][MigrationState::NOT_FINISHED]); + sort($discovered[$version]); + $discovered_unique[$version] = array_unique($discovered[$version]); + + // Assert that each discovered migration has a corresponding declaration + // in a migrate_drupal.yml. + foreach ($discovered_unique[$version] as $datum) { + $data = str_getcsv($datum); + $in_finished = in_array($datum, $declared_unique[$version][MigrationState::FINISHED]); + $in_not_finished = in_array($datum, $declared_unique[$version][MigrationState::NOT_FINISHED]); + $found = $in_finished || $in_not_finished; + $this->assertTrue($found, sprintf("No migration state found for version '%s' with source_module '%s' and destination_module '%s' declared in module '%s'", $data[0], $data[2], $data[3], $data[1])); + } + + // Remove the declared finished from the discovered, leaving just the not + // finished, if there are any. These should have an entry in the declared + // not finished. + $discovered_not_finished = array_diff($discovered_unique[$version], $declared_unique[$version][MigrationState::FINISHED]); + foreach ($discovered_not_finished as $datum) { + $data = str_getcsv($datum); + $this->assertContains($datum, $declared_unique[$version][MigrationState::NOT_FINISHED], sprintf("No migration found for version '%s' with source_module '%s' and destination_module '%s' declared in module '%s'", $data[0], $data[2], $data[3], $data[1])); + } + } + } + + /** + * Enable all available modules. + */ + protected function enableAllModules() { + // Install all available modules. + $module_handler = $this->container->get('module_handler'); + $modules = $this->coreModuleListDataProvider(); + $modules_enabled = $module_handler->getModuleList(); + $modules_to_enable = array_keys(array_diff_key($modules, $modules_enabled)); + $this->enableModules($modules_to_enable); + return $modules; + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Unit/FieldDiscoveryTest.php b/core/modules/migrate_drupal/tests/src/Unit/FieldDiscoveryTest.php index 852d4fa31..813c8b78f 100644 --- a/core/modules/migrate_drupal/tests/src/Unit/FieldDiscoveryTest.php +++ b/core/modules/migrate_drupal/tests/src/Unit/FieldDiscoveryTest.php @@ -228,7 +228,7 @@ public function testGetCoreVersion(array $tags, $expected_result) { $migration->getMigrationTags()->willReturn($tags); $field_discovery = new FieldDiscoveryTestClass($this->fieldPluginManager->reveal(), $this->migrationPluginManager->reveal(), $this->logger->reveal()); if (!$expected_result) { - $this->setExpectedException(\InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); } $actual_result = $field_discovery->getCoreVersion($migration->reveal()); $this->assertEquals($expected_result, $actual_result); @@ -316,7 +316,8 @@ protected function getAllFieldData() { public function testGetFieldInstanceStubMigrationDefinition($core, $expected_definition) { $field_discovery = new FieldDiscoveryTestClass($this->fieldPluginManager->reveal(), $this->migrationPluginManager->reveal(), $this->logger->reveal()); if (!$expected_definition) { - $this->setExpectedException(\InvalidArgumentException::class, sprintf("Drupal version %s is not supported. Valid values for Drupal core version are '6' and '7'.", $core)); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf("Drupal version %s is not supported. Valid values for Drupal core version are '6' and '7'.", $core)); } $actual_definition = $field_discovery->getFieldInstanceStubMigrationDefinition($core); $this->assertSame($expected_definition, $actual_definition); diff --git a/core/modules/migrate_drupal/tests/src/Unit/MigrateFieldPluginManagerTest.php b/core/modules/migrate_drupal/tests/src/Unit/MigrateFieldPluginManagerTest.php index e8a5224b0..dd8d6aca9 100644 --- a/core/modules/migrate_drupal/tests/src/Unit/MigrateFieldPluginManagerTest.php +++ b/core/modules/migrate_drupal/tests/src/Unit/MigrateFieldPluginManagerTest.php @@ -35,7 +35,8 @@ public function testWeights($field_type, $core, $expected_plugin_id) { $discovery->getDefinitions()->willReturn($this->pluginFixtureData()); $manager = new MigrateFieldPluginManagerTestClass('field', new \ArrayObject(), $cache, $module_handler, MigrateField::class, $discovery->reveal()); if (!$expected_plugin_id) { - $this->setExpectedException(PluginNotFoundException::class, sprintf("Plugin ID '%s' was not found.", $field_type)); + $this->expectException(PluginNotFoundException::class); + $this->expectExceptionMessage(sprintf("Plugin ID '%s' was not found.", $field_type)); } $actual_plugin_id = $manager->getPluginIdFromFieldType($field_type, ['core' => $core]); $this->assertSame($expected_plugin_id, $actual_plugin_id); diff --git a/core/modules/migrate_drupal/tests/src/Unit/MigrationStateUnitTest.php b/core/modules/migrate_drupal/tests/src/Unit/MigrationStateUnitTest.php new file mode 100644 index 000000000..b4fd91130 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Unit/MigrationStateUnitTest.php @@ -0,0 +1,618 @@ +prophesize(MigrateFieldPluginManagerInterface::class); + $fieldPluginManager->getDefinitions()->willReturn($field_plugins); + $moduleHandler = $this->prophesize(ModuleHandlerInterface::class); + $moduleHandler->getModuleList()->willReturn($modules_to_enable); + vfsStreamWrapper::register(); + $root = new vfsStreamDirectory('modules'); + vfsStreamWrapper::setRoot($root); + $url = vfsStream::url('modules'); + + foreach ($files as $module => $contents) { + $path = $url . '/' . $module . '/migrations/state'; + mkdir($path, '0755', TRUE); + file_put_contents($path . '/' . $module . '.migrate_drupal.yml', $contents); + } + $moduleHandler->getModuleDirectories() + ->willReturn(array_combine(array_keys($files), array_map(function ($module) use ($url) { + return $url . '/' . $module; + }, array_keys($files)))); + $migrationState = new MigrationState($fieldPluginManager->reveal(), $moduleHandler->reveal(), $this->createMock(MessengerInterface::class), $this->getStringTranslationStub()); + + $all_migrations = []; + foreach ($migrations as $name => $values) { + $migration = $this->prophesize(MigrationInterface::class); + $source = $this->prophesize(MigrateSourceInterface::class); + $destination = $this->prophesize(MigrateDestinationInterface::class); + $source->getSourceModule()->willReturn($values['source_module']); + $destination->getDestinationModule() + ->willReturn($values['destination_module']); + $migration->getSourcePlugin()->willReturn($source->reveal()); + $migration->getDestinationPlugin()->willReturn($destination->reveal()); + $migration->getPluginId()->willReturn($name); + $migration->label()->willReturn($name); + $all_migrations[] = $migration->reveal(); + } + + // Tests Drupal 7 states. + $states = $migrationState->getUpgradeStates(7, $source_system_data, $all_migrations); + $this->assertEquals($expected_7, $states); + $source_system_data['module']['content'] = [ + 'name' => 'content', + 'status' => TRUE, + ]; + + // Tests Drupal 6 states. + unset($source_system_data['module']['rdf'], $source_system_data['module']['filter']); + $states = $migrationState->getUpgradeStates(6, $source_system_data, []); + $this->assertEquals($expected_6, $states); + } + + /** + * Data provider for testGetUpgradeStates. + */ + public function providerGetUpgradeStates() { + + // Tests multiple scenarios: + // Not enabled and not declared. + // Destination module is not enabled. + // Destination module not enabled. + // Declared not finished. + // Not finished. + // No discovered or declared state. + // Declared finished by one module but not finished by another. + // Not declared and non compatible field plugin. + // Update path not needed. + $tests[0] = [ + 'modules_to_enable' => [ + 'entity_test' => [], + 'node' => [], + 'link' => [], + 'rdf' => [], + ], + 'files' => [ + 'node' => << << << << << [ + 'datetime' => [ + 'id' => 'datetime', + 'core' => [7], + 'source_module' => 'date', + 'destination_module' => 'datetime', + ], + 'link' => [ + 'id' => 'link', + 'core' => [6, 7], + 'source_module' => 'link', + 'destination_module' => 'link', + ], + ], + 'migrations' => [ + 'rdf' => [ + 'source_module' => 'rdf', + 'destination_module' => 'rdf', + ], + 'filter' => [ + 'source_module' => 'filter', + 'destination_module' => 'filter', + ], + ], + 'source_system_data' => [ + 'module' => [ + 'entity_test' => [ + 'name' => 'entity_test', + 'status' => TRUE, + ], + 'rdf' => [ + 'name' => 'rdf', + 'status' => TRUE, + ], + 'node' => [ + 'name' => 'node', + 'status' => TRUE, + ], + 'date' => [ + 'name' => 'date', + 'status' => TRUE, + ], + 'link' => [ + 'name' => 'link', + 'status' => TRUE, + ], + 'search' => [ + 'name' => 'search', + 'status' => TRUE, + ], + 'filter' => [ + 'name' => 'filter', + 'status' => TRUE, + ], + 'comment' => [ + 'name' => 'comment', + 'status' => TRUE, + ], + 'standard' => [ + 'name' => 'standard', + 'status' => TRUE, + ], + 'color' => [ + 'name' => 'color', + 'status' => TRUE, + ], + 'user' => [ + 'name' => 'user', + 'status' => TRUE, + ], + 'profile' => [ + 'name' => 'profile', + 'status' => TRUE, + ], + // Disabled, hence ignored. + 'dblog' => [ + 'name' => 'dblog', + 'status' => FALSE, + ], + ], + ], + + 'expected_7' => [ + MigrationState::NOT_FINISHED => [ + // Not enabled and not declared. + 'color' => '', + // Destination module comment is not enabled. + 'comment' => 'comment, node', + // Destination module not enabled. + 'date' => 'datetime', + // Declared not finished. + 'entity_test' => 'entity_test, entity_test_rev', + // Destination module not enabled. + 'filter' => 'filter', + // Not finished. + 'profile' => 'user', + // No discovered or declared state. + 'search' => '', + // Declared finished by one module but not finished by another. + 'user' => 'user', + ], + MigrationState::FINISHED => [ + 'link' => 'link', + 'node' => 'node', + 'rdf' => 'rdf', + ], + ], + 'expected_6' => [ + MigrationState::NOT_FINISHED => [ + // Declared not finished. + 'entity_test' => 'entity_test', + // Destination module comment is not enabled. + 'comment' => 'comment, node', + 'user' => 'user', + // Not finished. + 'profile' => 'user', + // Not declared and non compatible field plugin. + 'date' => '', + // No discovered or declared state. + 'search' => '', + 'color' => '', + ], + MigrationState::FINISHED => [ + 'node' => 'node', + 'content' => 'node', + // Update path not needed. + 'link' => 'link', + ], + ], + ]; + + // Test menu migration with all three required destination modules enabled. + $tests[1] = [ + 'modules_to_enable' => [ + 'menu_link_content' => [], + 'menu_ui' => [], + 'system' => [], + ], + 'files' => [ + 'system' => << << << [], + 'migrations' => [ + 'system' => [ + 'source_module' => 'menu', + 'destination_module' => 'system', + ], + 'menu_ui' => [ + 'source_module' => 'menu', + 'destination_module' => 'menu_ui', + ], + 'menu_link_content' => [ + 'source_module' => 'menu', + 'destination_module' => 'menu_link_content', + ], + ], + 'source_system_data' => [ + 'module' => [ + 'menu' => [ + 'name' => 'menu', + 'status' => TRUE, + ], + 'system' => [ + 'name' => 'system', + 'status' => TRUE, + ], + ], + ], + + 'expected_7' => [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + ], + MigrationState::FINISHED => [ + 'menu' => 'menu_link_content, menu_ui, system', + ], + ], + 'expected_6' => [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + 'content' => '', + ], + MigrationState::FINISHED => [ + 'menu' => 'menu_link_content, menu_ui, system', + ], + ], + ]; + + // Test menu migration with menu_link_content uninstalled. + $tests[2] = $tests[1]; + unset($tests[2]['modules_to_enable']['menu_link_content']); + unset($tests[2]['files']['menu_link_content']); + unset($tests[2]['migrations']['menu_link_content']); + $tests[2]['expected_7'] = [ + MigrationState::NOT_FINISHED => [ + 'menu' => 'menu_link_content, menu_ui, system', + 'system' => '', + ], + ]; + $tests[2]['expected_6'] = [ + MigrationState::NOT_FINISHED => [ + 'menu' => 'menu_link_content, menu_ui, system', + 'system' => '', + 'content' => '', + ], + ]; + + // Test menu migration with menu_ui uninstalled. + $tests[3] = $tests[1]; + unset($tests[3]['modules_to_enable']['menu_ui']); + unset($tests[3]['files']['menu_ui']); + unset($tests[3]['migrations']['menu_ui']); + $tests[3]['expected_7'] = [ + MigrationState::NOT_FINISHED => [ + 'menu' => 'menu_link_content, menu_ui, system', + 'system' => '', + ], + ]; + $tests[3]['expected_6'] = [ + MigrationState::NOT_FINISHED => [ + 'menu' => 'menu_link_content, menu_ui, system', + 'system' => '', + 'content' => '', + ], + ]; + + // Test an i18n migration with all three required destination modules + // enabled. + $tests[4] = [ + 'modules_to_enable' => [ + 'block' => [], + 'block_content' => [], + 'content_translation' => [], + 'system' => [], + ], + 'files' => [ + 'system' => << << << [], + 'migrations' => [ + 'block' => [ + 'source_module' => 'block', + 'destination_module' => 'block', + ], + 'block_content' => [ + 'source_module' => 'block', + 'destination_module' => 'block_content', + ], + 'i18nblocks' => [ + 'source_module' => 'i18nblocks', + 'destination_module' => 'content_translation', + ], + ], + 'source_system_data' => [ + 'module' => [ + 'block' => [ + 'name' => 'block', + 'status' => TRUE, + ], + 'i18nblocks' => [ + 'name' => 'i18nblocks', + 'status' => TRUE, + ], + 'system' => [ + 'name' => 'system', + 'status' => TRUE, + ], + ], + ], + + 'expected_7' => [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + ], + MigrationState::FINISHED => [ + 'block' => 'block, block_content', + 'i18nblocks' => 'block, block_content, content_translation', + ], + ], + 'expected_6' => [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + 'content' => '', + ], + MigrationState::FINISHED => [ + 'block' => 'block, block_content', + 'i18nblocks' => 'block, block_content, content_translation', + ], + ], + ]; + + // Test i18n_block migration with block uninstalled. + $tests[5] = $tests[4]; + unset($tests[5]['modules_to_enable']['block']); + unset($tests[5]['files']['block']); + unset($tests[5]['migrations']['block']); + $tests[5]['expected_7'] = [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + 'i18nblocks' => 'block, block_content, content_translation', + ], + MigrationState::FINISHED => [ + 'block' => 'block_content', + ], + ]; + $tests[5]['expected_6'] = [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + 'content' => '', + 'i18nblocks' => 'block, block_content, content_translation', + ], + MigrationState::FINISHED => [ + 'block' => 'block_content', + ], + ]; + + // Tests modules that don't require an upgrade path. + $tests[6] = [ + 'modules_to_enable' => [ + 'system' => [], + 'content_translation' => [], + ], + 'files' => [ + 'system' => << [], + 'migrations' => [], + 'source_system_data' => [ + 'module' => [ + 'system' => [ + 'name' => 'system', + 'status' => TRUE, + ], + 'help' => [ + 'name' => 'help', + 'status' => TRUE, + ], + 'i18ncontent' => [ + 'name' => 'i18ncontent', + 'status' => TRUE, + ], + ], + ], + + 'expected_7' => [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + ], + MigrationState::FINISHED => [ + 'help' => 'core', + 'i18ncontent' => 'content_translation', + ], + ], + 'expected_6' => [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + 'content' => '', + ], + MigrationState::FINISHED => [ + 'help' => 'core', + 'i18ncontent' => 'content_translation', + ], + ], + ]; + + $tests[7] = $tests[6]; + unset($tests[7]['modules_to_enable']['content_translation']); + $tests[7]['expected_7'] = [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + 'i18ncontent' => 'content_translation', + ], + MigrationState::FINISHED => [ + 'help' => 'core', + ], + ]; + $tests[7]['expected_6'] = [ + MigrationState::NOT_FINISHED => [ + 'system' => '', + 'content' => '', + 'i18ncontent' => 'content_translation', + ], + MigrationState::FINISHED => [ + 'help' => 'core', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Unit/source/DrupalSqlBaseTest.php b/core/modules/migrate_drupal/tests/src/Unit/source/DrupalSqlBaseTest.php index fdaf4b10a..9f7662c7f 100644 --- a/core/modules/migrate_drupal/tests/src/Unit/source/DrupalSqlBaseTest.php +++ b/core/modules/migrate_drupal/tests/src/Unit/source/DrupalSqlBaseTest.php @@ -45,13 +45,14 @@ public function testSourceProviderNotActive() { $plugin_definition['requirements_met'] = TRUE; $plugin_definition['source_module'] = 'module1'; /** @var \Drupal\Core\State\StateInterface $state */ - $state = $this->getMock('Drupal\Core\State\StateInterface'); - /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); - $plugin = new TestDrupalSqlBase([], 'placeholder_id', $plugin_definition, $this->getMigration(), $state, $entity_manager); + $state = $this->createMock('Drupal\Core\State\StateInterface'); + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ + $entity_type_manager = $this->createMock('Drupal\Core\Entity\EntityTypeManagerInterface'); + $plugin = new TestDrupalSqlBase([], 'placeholder_id', $plugin_definition, $this->getMigration(), $state, $entity_type_manager); $plugin->setDatabase($this->getDatabase($this->databaseContents)); $system_data = $plugin->getSystemData(); - $this->setExpectedException(RequirementsException::class, 'The module module1 is not enabled in the source site.'); + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage('The module module1 is not enabled in the source site.'); try { $plugin->checkRequirements(); } @@ -70,12 +71,13 @@ public function testSourceDatabaseError() { $plugin_definition['requirements_met'] = TRUE; $plugin_definition['source_module'] = 'module1'; /** @var \Drupal\Core\State\StateInterface $state */ - $state = $this->getMock('Drupal\Core\State\StateInterface'); - /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $state = $this->createMock('Drupal\Core\State\StateInterface'); + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager */ + $entity_manager = $this->createMock('Drupal\Core\Entity\EntityTypeManagerInterface'); $plugin = new TestDrupalSqlBase([], 'test', $plugin_definition, $this->getMigration(), $state, $entity_manager); $system_data = $plugin->getSystemData(); - $this->setExpectedException(RequirementsException::class, 'No database connection configured for source plugin test'); + $this->expectException(RequirementsException::class); + $this->expectExceptionMessage('No database connection configured for source plugin test'); $plugin->checkRequirements(); } diff --git a/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php b/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php index e8866d6f1..9e97fe24c 100644 --- a/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php +++ b/core/modules/migrate_drupal/tests/src/Unit/source/d6/Drupal6SqlBaseTest.php @@ -69,10 +69,10 @@ class Drupal6SqlBaseTest extends MigrateTestCase { protected function setUp() { $plugin = 'placeholder_id'; /** @var \Drupal\Core\State\StateInterface $state */ - $state = $this->getMock('Drupal\Core\State\StateInterface'); - /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); - $this->base = new TestDrupal6SqlBase($this->migrationConfiguration, $plugin, [], $this->getMigration(), $state, $entity_manager); + $state = $this->createMock('Drupal\Core\State\StateInterface'); + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ + $entity_type_manager = $this->createMock('Drupal\Core\Entity\EntityTypeManagerInterface'); + $this->base = new TestDrupal6SqlBase($this->migrationConfiguration, $plugin, [], $this->getMigration(), $state, $entity_type_manager); $this->base->setDatabase($this->getDatabase($this->databaseContents)); } diff --git a/core/modules/migrate_drupal_multilingual/migrate_drupal_multilingual.info.yml b/core/modules/migrate_drupal_multilingual/migrate_drupal_multilingual.info.yml index e7668a062..75a79dc28 100644 --- a/core/modules/migrate_drupal_multilingual/migrate_drupal_multilingual.info.yml +++ b/core/modules/migrate_drupal_multilingual/migrate_drupal_multilingual.info.yml @@ -2,12 +2,6 @@ name: 'Migrate Drupal Multilingual' type: module description: 'Provides a requirement for multilingual migrations.' package: 'Core (Experimental)' -# core: 8.x +core: 8.x dependencies: - migrate_drupal - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.info.yml b/core/modules/migrate_drupal_ui/migrate_drupal_ui.info.yml index b802c5463..8d39c65e3 100644 --- a/core/modules/migrate_drupal_ui/migrate_drupal_ui.info.yml +++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.info.yml @@ -2,16 +2,10 @@ name: 'Migrate Drupal UI' type: module description: 'Provides a user interface for migrating from older Drupal versions.' package: Migration -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: migrate_drupal_ui.upgrade dependencies: - drupal:migrate - drupal:migrate_drupal - drupal:dblog - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php b/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php index 74b542525..3443db867 100644 --- a/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php @@ -3,10 +3,14 @@ namespace Drupal\migrate_drupal_ui\Form; use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Database\Connection; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\State\StateInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\migrate\Exception\RequirementsException; use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\TransferException; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -36,12 +40,18 @@ class CredentialForm extends MigrateUpgradeFormBase { * CredentialForm constructor. * * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private - * The private tempstore factory. + * The private tempstore factory service. * @param \GuzzleHttp\ClientInterface $http_client * A Guzzle client object. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory service. + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager service. + * @param \Drupal\Core\State\StateInterface $state + * The state service. */ - public function __construct(PrivateTempStoreFactory $tempstore_private, ClientInterface $http_client) { - parent::__construct($tempstore_private); + public function __construct(PrivateTempStoreFactory $tempstore_private, ClientInterface $http_client, ConfigFactoryInterface $config_factory, MigrationPluginManagerInterface $migration_plugin_manager, StateInterface $state) { + parent::__construct($config_factory, $migration_plugin_manager, $state, $tempstore_private); $this->httpClient = $http_client; } @@ -51,7 +61,10 @@ public function __construct(PrivateTempStoreFactory $tempstore_private, ClientIn public static function create(ContainerInterface $container) { return new static( $container->get('tempstore.private'), - $container->get('http_client') + $container->get('http_client'), + $container->get('config.factory'), + $container->get('plugin.manager.migration'), + $container->get('state') ); } @@ -217,25 +230,11 @@ public function validateForm(array &$form, FormStateInterface $form_state) { $this->errors[$name] = $message; } } - else { - $error_key = $database['driver'] . '[database'; + + // Get the Drupal version of the source database so it can be validated. + if (!$this->errors) { try { $connection = $this->getConnection($database); - $version = (string) $this->getLegacyDrupalVersion($connection); - if (!$version) { - $this->errors[$error_key] = $this->t('Source database does not contain a recognizable Drupal version.'); - } - elseif ($version !== (string) $form_state->getValue('version')) { - $this->errors['version'] = $this->t('Source database is Drupal version @version but version @selected was selected.', - [ - '@version' => $version, - '@selected' => $form_state->getValue('version'), - ]); - } - else { - // Setup migrations and save form data to private store. - $this->setupMigrations($database, $form_state); - } } catch (\Exception $e) { $msg = $this->t('Failed to connect to your database server. The server reports the following message: %error.
  • Is the database server running?
  • Does the database exist, and have you entered the correct database name?
  • Have you entered the correct username and password?
  • Have you entered the correct database hostname?
', ['%error' => $e->getMessage()]); @@ -261,7 +260,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { // Setup migrations and save form data to private store. if (!$this->errors) { try { - $this->setupMigrations($database, $form_state); + $this->setupMigrations($connection, $version, $database, $form_state); } catch (BadPluginDefinitionException $e) { // BadPluginDefinitionException occurs if the source_module is not @@ -332,4 +331,51 @@ protected function getDatabaseTypes() { return drupal_get_database_types(); } + /** + * Gets and stores information for this migration in temporary store. + * + * Gets all the migrations, converts each to an array and stores it in the + * form state. The source base path for public and private files is also + * put into form state. + * + * @param \Drupal\Core\Database\Connection $connection + * The database connection used. + * @param string $version + * The Drupal version. + * @param array $database + * Database array representing the source Drupal database. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @throws \Drupal\Core\TempStore\TempStoreException + * Thrown when a lock for the backend storage could not be acquired. + */ + protected function setupMigrations(Connection $connection, $version, array $database, FormStateInterface $form_state) { + $this->createDatabaseStateSettings($database, $version); + $migrations = $this->getMigrations('migrate_drupal_' . $version, $version); + + // Get the system data from source database. + $system_data = $this->getSystemData($connection); + + // Convert the migration object into array + // so that it can be stored in form storage. + $migration_array = []; + foreach ($migrations as $migration) { + $migration_array[$migration->id()] = $migration->label(); + } + + // Store information in the private store. + $this->store->set('version', $version); + $this->store->set('migrations', $migration_array); + if ($version == 6) { + $this->store->set('source_base_path', $form_state->getValue('d6_source_base_path')); + } + else { + $this->store->set('source_base_path', $form_state->getValue('source_base_path')); + } + $this->store->set('source_private_file_path', $form_state->getValue('source_private_file_path')); + // Store the retrieved system data in the private store. + $this->store->set('system_data', $system_data); + } + } diff --git a/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php index 2492143ab..ec58b9408 100644 --- a/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php @@ -3,11 +3,8 @@ namespace Drupal\migrate_drupal_ui\Form; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\migrate\Audit\IdAuditor; use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; -use Drupal\migrate\Plugin\MigrationPluginManagerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Migrate Upgrade Id Conflict form. @@ -16,36 +13,6 @@ */ class IdConflictForm extends MigrateUpgradeFormBase { - /** - * The migration plugin manager service. - * - * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface - */ - protected $pluginManager; - - /** - * IdConflictForm constructor. - * - * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager - * The migration plugin manager service. - * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private - * The private tempstore factory. - */ - public function __construct(MigrationPluginManagerInterface $migration_plugin_manager, PrivateTempStoreFactory $tempstore_private) { - parent::__construct($tempstore_private); - $this->pluginManager = $migration_plugin_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('plugin.manager.migration'), - $container->get('tempstore.private') - ); - } - /** * {@inheritdoc} */ @@ -67,7 +34,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $migration_ids = array_keys($migrations); // Check if there are conflicts. If none, just skip this form! - $migrations = $this->pluginManager->createInstances($migration_ids); + $migrations = $this->migrationPluginManager->createInstances($migration_ids); $translated_content_conflicts = $content_conflicts = []; diff --git a/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php b/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php index 617514f1e..3807a280d 100644 --- a/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php @@ -2,10 +2,12 @@ namespace Drupal\migrate_drupal_ui\Form; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -15,13 +17,6 @@ */ class IncrementalForm extends MigrateUpgradeFormBase { - /** - * The state service. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $state; - /** * The date formatter service. * @@ -37,11 +32,14 @@ class IncrementalForm extends MigrateUpgradeFormBase { * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter * The date formatter service. * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private - * The private temp store factory. + * The private tempstore factory service. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory service. + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager service. */ - public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, PrivateTempStoreFactory $tempstore_private) { - parent::__construct($tempstore_private); - $this->state = $state; + public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, PrivateTempStoreFactory $tempstore_private, ConfigFactoryInterface $config_factory, MigrationPluginManagerInterface $migration_plugin_manager) { + parent::__construct($config_factory, $migration_plugin_manager, $state, $tempstore_private); $this->dateFormatter = $date_formatter; } @@ -52,7 +50,9 @@ public static function create(ContainerInterface $container) { return new static( $container->get('state'), $container->get('date.formatter'), - $container->get('tempstore.private') + $container->get('tempstore.private'), + $container->get('config.factory'), + $container->get('plugin.manager.migration') ); } diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php index ffe899254..193620dfc 100644 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php @@ -2,10 +2,13 @@ namespace Drupal\migrate_drupal_ui\Form; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\migrate_drupal\MigrationConfigurationTrait; +use Drupal\Core\State\StateInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; +use Drupal\migrate_drupal\MigrationConfigurationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -25,10 +28,19 @@ abstract class MigrateUpgradeFormBase extends FormBase { /** * Constructs the Migrate Upgrade Form Base. * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory service. + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager service. + * @param \Drupal\Core\State\StateInterface $state + * The state service. * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private - * Private store. + * The private tempstore factory service. */ - public function __construct(PrivateTempStoreFactory $tempstore_private) { + public function __construct(ConfigFactoryInterface $config_factory, MigrationPluginManagerInterface $migration_plugin_manager, StateInterface $state, PrivateTempStoreFactory $tempstore_private) { + $this->configFactory = $config_factory; + $this->migrationPluginManager = $migration_plugin_manager; + $this->state = $state; $this->store = $tempstore_private->get('migrate_drupal_ui'); } @@ -37,6 +49,9 @@ public function __construct(PrivateTempStoreFactory $tempstore_private) { */ public static function create(ContainerInterface $container) { return new static( + $container->get('config.factory'), + $container->get('plugin.manager.migration'), + $container->get('state'), $container->get('tempstore.private') ); } @@ -56,53 +71,14 @@ public function buildForm(array $form, FormStateInterface $form_state) { return $form; } - /** - * Gets and stores information for this migration in temporary store. - * - * Gets all the migrations, converts each to an array and stores it in the - * form state. The source base path for public and private files is also - * put into form state. - * - * @param array $database - * Database array representing the source Drupal database. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function setupMigrations(array $database, FormStateInterface $form_state) { - $connection = $this->getConnection($database); - $version = $this->getLegacyDrupalVersion($connection); - $this->createDatabaseStateSettings($database, $version); - $migrations = $this->getMigrations('migrate_drupal_' . $version, $version); - - // Get the system data from source database. - $system_data = $this->getSystemData($connection); - - // Convert the migration object into array - // so that it can be stored in form storage. - $migration_array = []; - foreach ($migrations as $migration) { - $migration_array[$migration->id()] = $migration->label(); - } - - // Store information in the private store. - $this->store->set('version', $version); - $this->store->set('migrations', $migration_array); - if ($version == 6) { - $this->store->set('source_base_path', $form_state->getValue('d6_source_base_path')); - } - else { - $this->store->set('source_base_path', $form_state->getValue('source_base_path')); - } - $this->store->set('source_private_file_path', $form_state->getValue('source_private_file_path')); - // Store the retrieved system data in the private store. - $this->store->set('system_data', $system_data); - } - /** * Helper to redirect to the Overview form. * * @return \Symfony\Component\HttpFoundation\RedirectResponse * A redirect response object that may be returned by the controller. + * + * @throws \Drupal\Core\TempStore\TempStoreException + * Thrown when a lock for the backend storage could not be acquired. */ protected function restartUpgradeForm() { $this->store->set('step', 'overview'); diff --git a/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php b/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php index 22b45a910..91c9fb2ab 100644 --- a/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php @@ -3,10 +3,7 @@ namespace Drupal\migrate_drupal_ui\Form; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\State\StateInterface; -use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\Core\Url; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Migrate Upgrade Overview form. @@ -15,36 +12,6 @@ */ class OverviewForm extends MigrateUpgradeFormBase { - /** - * The state service. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $state; - - /** - * Overview form constructor. - * - * @param \Drupal\Core\State\StateInterface $state - * The state service. - * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private - * The private tempstore factory. - */ - public function __construct(StateInterface $state, PrivateTempStoreFactory $tempstore_private) { - parent::__construct($tempstore_private); - $this->state = $state; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('state'), - $container->get('tempstore.private') - ); - } - /** * {@inheritdoc} */ diff --git a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php index 6ef9dd564..5be85c42f 100644 --- a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php @@ -2,47 +2,34 @@ namespace Drupal\migrate_drupal_ui\Form; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; -use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface; -use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Drupal\migrate_drupal_ui\Batch\MigrateUpgradeImportBatch; +use Drupal\migrate_drupal\MigrationState; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Migrate Upgrade review form. * - * This confirmation form uses the source_module and destination_module - * properties on the source, destination and field plugins as well as the - * system data from the source to determine if there is a migration path for - * each module in the source. + * This confirmation form provides the user with a summary of all the modules + * enabled on the source site and whether they will be upgraded or not. Data + * from a module's .migrate_drupal.yml file and all the migration plugins + * (source, destination and field) for each enabled Drupal 8 module are used to + * decide the migration status for each enabled module on the source site. + * + * The migration status displayed on the Review page is a list of all the + * enabled modules on the source site divided into two categories, those that + * will not be upgraded and those that will be upgraded. The intention is to + * provide the admin with enough information to decide if it is OK to proceed + * with the upgrade. * * @internal */ class ReviewForm extends MigrateUpgradeFormBase { - /** - * The state service. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $state; - - /** - * The migration plugin manager service. - * - * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface - */ - protected $pluginManager; - - /** - * The field plugin manager service. - * - * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface - */ - protected $fieldPluginManager; - /** * The migrations. * @@ -51,101 +38,11 @@ class ReviewForm extends MigrateUpgradeFormBase { protected $migrations; /** - * List of extensions that do not need an upgrade path. + * Migration state service. * - * This property is an array where the keys are the major Drupal core version - * from which we are upgrading, and the values are arrays of extension names - * that do not need an upgrade path. - * - * @var array[] + * @var \Drupal\migrate_drupal\MigrationState */ - protected $noUpgradePaths = [ - '6' => [ - 'blog', - 'blogapi', - 'calendarsignup', - 'color', - 'content_copy', - 'content_multigroup', - 'content_permissions', - 'date_api', - 'date_locale', - 'date_php4', - 'date_popup', - 'date_repeat', - 'date_timezone', - 'date_tools', - 'datepicker', - 'ddblock', - 'event', - 'fieldgroup', - 'filefield_meta', - 'help', - 'i18nstrings', - 'i18nsync', - 'imageapi', - 'imageapi_gd', - 'imageapi_imagemagick', - 'imagecache_ui', - 'jquery_ui', - 'nodeaccess', - 'number', - 'openid', - 'php', - 'ping', - 'poll', - 'throttle', - 'tracker', - 'translation', - 'trigger', - 'variable', - 'variable_admin', - 'views_export', - 'views_ui', - ], - '7' => [ - 'blog', - 'bulk_export', - 'contextual', - 'ctools', - 'ctools_access_ruleset', - 'ctools_ajax_sample', - 'ctools_custom_content', - 'dashboard', - 'date_all_day', - 'date_api', - 'date_context', - 'date_migrate', - 'date_popup', - 'date_repeat', - 'date_repeat_field', - 'date_tools', - 'date_views', - 'entity', - 'entity_feature', - 'entity_token', - 'entityreference', - 'field_ui', - 'help', - 'openid', - 'overlay', - 'page_manager', - 'php', - 'poll', - 'search_embedded_form', - 'search_extra_type', - 'search_node_tags', - 'simpletest', - 'stylizer', - 'term_depth', - 'title', - 'toolbar', - 'translation', - 'trigger', - 'views_content', - 'views_ui', - ], - ]; + protected $migrationState; /** * ReviewForm constructor. @@ -154,16 +51,16 @@ class ReviewForm extends MigrateUpgradeFormBase { * The state service. * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager * The migration plugin manager service. - * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager - * The field plugin manager service. * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private - * The private tempstore factory. + * The private tempstore factory service. + * @param \Drupal\migrate_drupal\MigrationState $migrationState + * Migration state service. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory service. */ - public function __construct(StateInterface $state, MigrationPluginManagerInterface $migration_plugin_manager, MigrateFieldPluginManagerInterface $field_plugin_manager, PrivateTempStoreFactory $tempstore_private) { - parent::__construct($tempstore_private); - $this->state = $state; - $this->pluginManager = $migration_plugin_manager; - $this->fieldPluginManager = $field_plugin_manager; + public function __construct(StateInterface $state, MigrationPluginManagerInterface $migration_plugin_manager, PrivateTempStoreFactory $tempstore_private, MigrationState $migrationState, ConfigFactoryInterface $config_factory) { + parent::__construct($config_factory, $migration_plugin_manager, $state, $tempstore_private); + $this->migrationState = $migrationState; } /** @@ -173,8 +70,9 @@ public static function create(ContainerInterface $container) { return new static( $container->get('state'), $container->get('plugin.manager.migration'), - $container->get('plugin.manager.migrate.field'), - $container->get('tempstore.private') + $container->get('tempstore.private'), + $container->get('migrate_drupal.migration_state'), + $container->get('config.factory') ); } @@ -192,7 +90,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Get all the data needed for this form. $version = $this->store->get('version'); $this->migrations = $this->store->get('migrations'); - // Fetch the system data at the first opportunity. + // Fetch the source system data at the first opportunity. $system_data = $this->store->get('system_data'); // If data is missing or this is the wrong step, start over. @@ -204,58 +102,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form = parent::buildForm($form, $form_state); $form['#title'] = $this->t('What will be upgraded?'); - // Get the source_module and destination_module for each migration. - $migrations = $this->pluginManager->createInstances(array_keys($this->store->get('migrations'))); - $table_data = []; - foreach ($migrations as $migration) { - $migration_id = $migration->getPluginId(); - $source_module = $migration->getSourcePlugin()->getSourceModule(); - if (!$source_module) { - $this->messenger()->addError($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id])); - } - $destination_module = $migration->getDestinationPlugin()->getDestinationModule(); - if (!$destination_module) { - $this->messenger()->addError($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id])); - } - - if ($source_module && $destination_module) { - $table_data[$source_module][$destination_module][$migration_id] = $migration->label(); - } - } - - // Get the source_module and destination_module from the field plugins. - $definitions = $this->fieldPluginManager->getDefinitions(); - foreach ($definitions as $definition) { - // This is not strict so that we find field plugins with an annotation - // where the Drupal core version is an integer and when it is a string. - if (in_array($version, $definition['core'])) { - $source_module = $definition['source_module']; - $destination_module = $definition['destination_module']; - $table_data[$source_module][$destination_module][$definition['id']] = $definition['id']; - } - } - - // Add source_module and destination_module for modules that do not need an - // upgrade path and are enabled on the source site. - foreach ($this->noUpgradePaths[$version] as $extension) { - if (isset($system_data['module'][$extension]) && $system_data['module'][$extension]['status']) { - $table_data[$extension]['core'][$extension] = $extension; - } - } - - // Sort the table by source module names and within that destination - // module names. - ksort($table_data); - foreach ($table_data as $source_module => $destination_module_info) { - ksort($table_data[$source_module]); - } - - // Remove core profiles from the system data. - foreach (['standard', 'minimal'] as $profile) { - unset($system_data['module'][$profile]); - } + $migrations = $this->migrationPluginManager->createInstances(array_keys($this->store->get('migrations'))); - $unmigrated_source_modules = array_diff_key($system_data['module'], $table_data); + // Get the upgrade states for the source modules. + $display = $this->migrationState->getUpgradeStates($version, $system_data, $migrations); // Missing migrations. $missing_module_list = [ @@ -263,7 +113,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#open' => TRUE, '#title' => $this->t('Modules that will not be upgraded'), '#summary_attributes' => ['id' => ['error']], - '#description' => $this->t('There are no modules installed on your new site to replace these modules. If you proceed with the upgrade now, configuration and/or content needed by these modules will not be available on your new site. For more information, see Review the pre-upgrade analysis in the Upgrading to Drupal 8 handbook.', [':review' => 'https://www.drupal.org/docs/8/upgrade/upgrade-using-web-browser#pre-upgrade-analysis', ':migrate' => 'https://www.drupal.org/docs/8/upgrade']), + '#description' => $this->t("The new site is missing modules corresponding to the old site's modules. Unless they are installed prior to the upgrade, configuration and/or content needed by them will not be available on your new site. Read the checklist to help decide what to do.", [':review' => 'https://www.drupal.org/docs/8/upgrade/upgrade-using-web-browser#pre-upgrade-analysis']), '#weight' => 2, ]; $missing_module_list['module_list'] = [ @@ -273,12 +123,14 @@ public function buildForm(array $form, FormStateInterface $form_state) { $this->t('Drupal 8'), ], ]; + $missing_count = 0; - ksort($unmigrated_source_modules); - foreach ($unmigrated_source_modules as $source_module => $module_data) { - if ($module_data['status']) { + if (isset($display[MigrationState::NOT_FINISHED])) { + foreach ($display[MigrationState::NOT_FINISHED] as $source_module => $destination_modules) { $missing_count++; - $missing_module_list['module_list'][$source_module] = [ + // Get the migration status for this $source_module, if a module of the + // same name exists on the destination site. + $missing_module_list['module_list'][] = [ 'source_module' => [ '#type' => 'html_tag', '#tag' => 'span', @@ -290,7 +142,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { ], ], ], - 'destination_module' => ['#plain_text' => 'Not upgraded'], + 'destination_module' => [ + '#plain_text' => $destination_modules, + ], ]; } } @@ -300,7 +154,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#type' => 'details', '#title' => $this->t('Modules that will be upgraded'), '#summary_attributes' => ['id' => ['checked']], - '#weight' => 3, + '#weight' => 4, ]; $available_module_list['module_list'] = [ @@ -312,29 +166,26 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $available_count = 0; - foreach ($table_data as $source_module => $destination_module_info) { - $available_count++; - $destination_details = []; - foreach ($destination_module_info as $destination_module => $migration_ids) { - $destination_details[$destination_module] = [ - '#type' => 'item', - '#plain_text' => $destination_module, - ]; - } - $available_module_list['module_list'][$source_module] = [ - 'source_module' => [ - '#type' => 'html_tag', - '#tag' => 'span', - '#value' => $source_module, - '#attributes' => [ - 'class' => [ - 'upgrade-analysis-report__status-icon', - 'upgrade-analysis-report__status-icon--checked', + if (isset($display[MigrationState::FINISHED])) { + foreach ($display[MigrationState::FINISHED] as $source_module => $destination_modules) { + $available_count++; + $available_module_list['module_list'][] = [ + 'source_module' => [ + '#type' => 'html_tag', + '#tag' => 'span', + '#value' => $source_module, + '#attributes' => [ + 'class' => [ + 'upgrade-analysis-report__status-icon', + 'upgrade-analysis-report__status-icon--checked', + ], ], ], - ], - 'destination_module' => $destination_details, - ]; + 'destination_module' => [ + '#plain_text' => $destination_modules, + ], + ]; + } } $counters = []; diff --git a/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php deleted file mode 100644 index 35ea71d30..000000000 --- a/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php +++ /dev/null @@ -1,239 +0,0 @@ -createMigrationConnection(); - $this->sourceDatabase = Database::getConnection('default', 'migrate_drupal_ui'); - - // Log in as user 1. Migrations in the UI can only be performed as user 1. - $this->drupalLogin($this->rootUser); - } - - /** - * Loads a database fixture into the source database connection. - * - * @param string $path - * Path to the dump file. - */ - protected function loadFixture($path) { - $default_db = Database::getConnection()->getKey(); - Database::setActiveConnection($this->sourceDatabase->getKey()); - - if (substr($path, -3) == '.gz') { - $path = 'compress.zlib://' . $path; - } - require $path; - - Database::setActiveConnection($default_db); - } - - /** - * Changes the database connection to the prefixed one. - * - * @todo Remove when we don't use global. https://www.drupal.org/node/2552791 - */ - protected function createMigrationConnection() { - $connection_info = Database::getConnectionInfo('default')['default']; - if ($connection_info['driver'] === 'sqlite') { - // Create database file in the test site's public file directory so that - // \Drupal\simpletest\TestBase::restoreEnvironment() will delete this once - // the test is complete. - $file = $this->publicFilesDirectory . '/' . $this->testId . '-migrate.db.sqlite'; - touch($file); - $connection_info['database'] = $file; - $connection_info['prefix'] = ''; - } - else { - $prefix = is_array($connection_info['prefix']) ? $connection_info['prefix']['default'] : $connection_info['prefix']; - // Simpletest uses fixed length prefixes. Create a new prefix for the - // source database. Adding to the end of the prefix ensures that - // \Drupal\simpletest\TestBase::restoreEnvironment() will remove the - // additional tables. - $connection_info['prefix'] = $prefix . '0'; - } - - Database::addConnectionInfo('migrate_drupal_ui', 'default', $connection_info); - } - - /** - * {@inheritdoc} - */ - protected function tearDown() { - Database::removeConnection('migrate_drupal_ui'); - parent::tearDown(); - } - - /** - * Executes all steps of migrations upgrade. - */ - public function testMigrateUpgrade() { - $connection_options = $this->sourceDatabase->getConnectionOptions(); - $this->drupalGet('/upgrade'); - $this->assertText('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.'); - - $this->drupalPostForm(NULL, [], t('Continue')); - $this->assertText('Provide credentials for the database of the Drupal site you want to upgrade.'); - $this->assertFieldByName('mysql[host]'); - - $driver = $connection_options['driver']; - $connection_options['prefix'] = $connection_options['prefix']['default']; - - // Use the driver connection form to get the correct options out of the - // database settings. This supports all of the databases we test against. - $drivers = drupal_get_database_types(); - $form = $drivers[$driver]->getFormOptions($connection_options); - $connection_options = array_intersect_key($connection_options, $form + $form['advanced_options']); - $edit = [ - $driver => $connection_options, - 'source_base_path' => $this->getSourceBasePath(), - ]; - if (count($drivers) !== 1) { - $edit['driver'] = $driver; - } - $edits = $this->translatePostValues($edit); - - // Ensure submitting the form with invalid database credentials gives us a - // nice warning. - $this->drupalPostForm(NULL, [$driver . '[database]' => 'wrong'] + $edits, t('Review upgrade')); - $this->assertText('Resolve the issue below to continue the upgrade.'); - - $this->drupalPostForm(NULL, $edits, t('Review upgrade')); - $this->assertResponse(200); - $this->assertText('Upgrade analysis report'); - // Ensure we get errors about missing modules. - $this->assertText(t('Source module not found for module_no_annotation.')); - $this->assertText(t('Source module not found for modules_available_test.')); - $this->assertText(t('Destination module not found for modules_available_test')); - - // Uninstall the module causing the missing module error messages. - $this->container->get('module_installer')->uninstall(['modules_available_test'], TRUE); - - // Restart the upgrade process. - $this->drupalGet('/upgrade'); - $this->assertText('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.'); - - $this->drupalPostForm(NULL, [], t('Continue')); - $this->assertText('Provide credentials for the database of the Drupal site you want to upgrade.'); - $this->assertFieldByName('mysql[host]'); - - $this->drupalPostForm(NULL, $edits, t('Review upgrade')); - $this->assertResponse(200); - $this->assertText('Upgrade analysis report'); - // Ensure there are no errors about the missing modules. - $this->assertNoText(t('Source module not found for module_no_annotation.')); - $this->assertNoText(t('Source module not found for modules_available_test.')); - $this->assertNoText(t('Destination module not found for modules_available_test')); - // Check for any missing module errors. - $this->drupalPostForm(NULL, [], t('Perform upgrade')); - $this->assertText(t('Congratulations, you upgraded Drupal!')); - - // Have to reset all the static caches after migration to ensure entities - // are loadable. - $this->resetAll(); - - $expected_counts = $this->getEntityCounts(); - foreach (array_keys(\Drupal::entityTypeManager() - ->getDefinitions()) as $entity_type) { - $real_count = \Drupal::entityQuery($entity_type)->count()->execute(); - $expected_count = isset($expected_counts[$entity_type]) ? $expected_counts[$entity_type] : 0; - $this->assertEqual($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count."); - } - - $version_tag = 'Drupal ' . $this->getLegacyDrupalVersion($this->sourceDatabase); - $plugin_manager = \Drupal::service('plugin.manager.migration'); - /** @var \Drupal\migrate\Plugin\Migration[] $all_migrations */ - $all_migrations = $plugin_manager->createInstancesByTag($version_tag); - foreach ($all_migrations as $migration) { - $id_map = $migration->getIdMap(); - foreach ($id_map as $source_id => $map) { - // Convert $source_id into a keyless array so that - // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as - // expected. - $source_id_values = array_values(unserialize($source_id)); - $row = $id_map->getRowBySource($source_id_values); - $destination = serialize($id_map->currentDestination()); - $message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status']; - // A completed migration should have maps with - // MigrateIdMapInterface::STATUS_IGNORED or - // MigrateIdMapInterface::STATUS_IMPORTED. - if ($row['source_row_status'] == MigrateIdMapInterface::STATUS_FAILED || $row['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE) { - $this->fail($message); - } - else { - $this->pass($message); - } - } - } - \Drupal::service('module_installer')->install(['forum']); - \Drupal::service('module_installer')->install(['book']); - } - - /** - * Gets the source base path for the concrete test. - * - * @return string - * The source base path. - */ - abstract protected function getSourceBasePath(); - - /** - * Gets the expected number of entities per entity type after migration. - * - * @return int[] - * An array of expected counts keyed by entity type ID. - */ - abstract protected function getEntityCounts(); - -} diff --git a/core/modules/migrate_drupal_ui/tests/modules/migration_provider_test/migration_provider_test.info.yml b/core/modules/migrate_drupal_ui/tests/modules/migration_provider_test/migration_provider_test.info.yml index 50fd2e6c3..49b7dce9b 100644 --- a/core/modules/migrate_drupal_ui/tests/modules/migration_provider_test/migration_provider_test.info.yml +++ b/core/modules/migrate_drupal_ui/tests/modules/migration_provider_test/migration_provider_test.info.yml @@ -2,11 +2,5 @@ name: 'Migration provider missing' type: module description: 'Add a migration missing a source and destination migration provider and a source plugin missing a migration provider.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateAccessTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateAccessTest.php index 3c475e206..5a568aec0 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateAccessTest.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateAccessTest.php @@ -18,6 +18,11 @@ class MigrateAccessTest extends BrowserTestBase { */ public static $modules = ['migrate_drupal_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that only user 1 can access the migrate UI. */ diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php index f165bb7c8..7038e7b37 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php @@ -106,7 +106,16 @@ public function testMigrateUpgradeExecute() { $session->fieldExists('mysql[host]'); $this->drupalPostForm(NULL, $edits, t('Review upgrade')); - $this->assertIdConflict($session); + $entity_types = [ + 'block_content', + 'menu_link_content', + 'file', + 'taxonomy_term', + 'user', + 'comment', + 'node', + ]; + $this->assertIdConflict($session, $entity_types); $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.')); $session->statusCodeEquals(200); @@ -117,7 +126,7 @@ public function testMigrateUpgradeExecute() { // Ensure there are no errors about any other missing migration providers. $session->pageTextNotContains(t('module not found')); - // Test the upgrade paths. + // Test the review page. $available_paths = $this->getAvailablePaths(); $missing_paths = $this->getMissingPaths(); $this->assertReviewPage($session, $available_paths, $missing_paths); @@ -146,12 +155,7 @@ public function testMigrateUpgradeExecute() { $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.')); $session->statusCodeEquals(200); - // Need to update available and missing path lists. - $all_available = $this->getAvailablePaths(); - $all_available[] = 'aggregator'; - $all_missing = $this->getMissingPaths(); - $all_missing = array_diff($all_missing, ['aggregator']); - $this->assertReviewPage($session, $all_available, $all_missing); + // Run the incremental migration and check the results. $this->drupalPostForm(NULL, [], t('Perform upgrade')); $session->pageTextContains(t('Congratulations, you upgraded Drupal!')); $this->assertMigrationResults($this->getEntityCountsIncremental(), $version); diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeFormStepsTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeFormStepsTest.php index caa60c343..4ef273835 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeFormStepsTest.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeFormStepsTest.php @@ -22,6 +22,11 @@ class MigrateUpgradeFormStepsTest extends BrowserTestBase { */ public static $modules = ['migrate_drupal_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php index bc73aba4c..db75e036a 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php @@ -169,13 +169,13 @@ abstract protected function getEntityCountsIncremental(); * Helper method to assert the text on the 'Upgrade analysis report' page. * * @param \Drupal\Tests\WebAssert $session - * The current session. - * @param array $all_available - * Array of modules that will be upgraded. - * @param array $all_missing - * Array of modules that will not be upgraded. + * The web-assert session. + * @param array $available_paths + * An array of modules that will be upgraded. + * @param array $missing_paths + * An array of modules that will not be upgraded. */ - protected function assertReviewPage(WebAssert $session, array $all_available, array $all_missing) { + protected function assertReviewPage(WebAssert $session, array $available_paths, array $missing_paths) { $this->assertText('What will be upgraded?'); // Ensure there are no errors about the missing modules from the test module. @@ -185,17 +185,7 @@ protected function assertReviewPage(WebAssert $session, array $all_available, ar // Ensure there are no errors about any other missing migration providers. $session->pageTextNotContains(t('module not found')); - // Test the available migration paths. - foreach ($all_available as $available) { - $session->elementExists('xpath', "//span[contains(@class, 'checked') and text() = '$available']"); - $session->elementNotExists('xpath', "//span[contains(@class, 'error') and text() = '$available']"); - } - - // Test the missing migration paths. - foreach ($all_missing as $missing) { - $session->elementExists('xpath', "//span[contains(@class, 'error') and text() = '$missing']"); - $session->elementNotExists('xpath', "//span[contains(@class, 'checked') and text() = '$missing']"); - } + $this->assertUpgradePaths($session, $available_paths, $missing_paths); } /** @@ -203,20 +193,20 @@ protected function assertReviewPage(WebAssert $session, array $all_available, ar * * @param \Drupal\Tests\WebAssert $session * The current session. - * @param $session - * The current session. + * @param array $entity_types + * An array of entity types */ - protected function assertIdConflict(WebAssert $session) { + protected function assertIdConflict(WebAssert $session, $entity_types) { + /** @var \Drupal\ $entity_type_manager */ + $entity_type_manager = \Drupal::service('entity_type.manager'); + $session->pageTextContains('WARNING: Content may be overwritten on your new site.'); $session->pageTextContains('There is conflicting content of these types:'); - $session->pageTextContains('custom blocks'); - $session->pageTextContains('custom menu links'); - $session->pageTextContains('files'); - $session->pageTextContains('taxonomy terms'); - $session->pageTextContains('users'); - $session->pageTextContains('comments'); + foreach ($entity_types as $entity_type) { + $label = $entity_type_manager->getDefinition($entity_type)->getPluralLabel(); + $session->pageTextContains($label); + } $session->pageTextContains('content item revisions'); - $session->pageTextContains('content items'); $session->pageTextContains('There is translated content of these types:'); } diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MultilingualReviewPageTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MultilingualReviewPageTestBase.php index a200de6b8..b9f11b20e 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MultilingualReviewPageTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MultilingualReviewPageTestBase.php @@ -36,11 +36,11 @@ abstract class MultilingualReviewPageTestBase extends MigrateUpgradeTestBase { * Overlay. * * To do this all modules in the source fixtures are enabled, except test and - * example modules. This means that we can test that the modules listed in the - * the $noUpgradePath property of the update form class are correct, since - * there will be no available migrations which declare those modules as their - * source_module. It is assumed that the test fixtures include all modules - * that have moved to or dropped from core. + * example modules. This means that we can test that the modules that do not + * need any migrations, such as Overlay, since there will be no available + * migrations which declare those modules as their source_module. It is + * assumed that the test fixtures include all modules that have moved to or + * dropped from core. * * The upgrade review form will also display errors for each migration that * does not have a source_module definition. That function is not tested here. @@ -68,10 +68,9 @@ public function testMigrateUpgradeReviewPage() { $missing_paths = $this->getMissingPaths(); $this->assertUpgradePaths($session, $available_paths, $missing_paths); - // Check there are no errors when a module in noUpgradePaths is not in the - // source system tables. Test with a module that is listed in noUpgradePaths - // for both Drupal 6 and Drupal 7. - // @see \Drupal\migrate_drupal_ui\Form\ReviewForm::$noUpgradePaths + // Check there are no errors when a module does not have any migrations and + // does not need any. Test with a module that is was in both Drupal 6 and + // Drupal 7 core. $module = 'help'; $query = $this->sourceDatabase->delete('system'); $query->condition('type', 'module'); @@ -84,7 +83,8 @@ public function testMigrateUpgradeReviewPage() { $this->drupalPostForm(NULL, $this->edits, t('Review upgrade')); $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.')); - // Test the upgrade paths. + // Test the upgrade paths. First remove the module from the available paths + // list. $available_paths = $this->getAvailablePaths(); $available_paths = array_diff($available_paths, [$module]); $missing_paths = $this->getMissingPaths(); @@ -138,6 +138,16 @@ public function prepare() { $conditions->condition('name', 'simpletest'); $update->condition($conditions); $update->execute(); + + // Create entries for D8 test modules. + $insert = $this->sourceDatabase->insert('system') + ->fields([ + 'filename' => 'migrate_status_active_test', + 'name' => 'migrate_status_active_test', + 'type' => 'module', + 'status' => 1, + ]); + $insert->execute(); } /** diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MultilingualReviewPageTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MultilingualReviewPageTest.php index 32ca9a150..dc3a6d68e 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MultilingualReviewPageTest.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MultilingualReviewPageTest.php @@ -11,6 +11,8 @@ * * @group migrate_drupal_6 * @group migrate_drupal_ui + * + * @group legacy */ class MultilingualReviewPageTest extends MultilingualReviewPageTestBase { @@ -31,6 +33,13 @@ class MultilingualReviewPageTest extends MultilingualReviewPageTestBase { 'update', // Required for translation migrations. 'migrate_drupal_multilingual', + // Test migrations states. + 'migrate_state_finished_test', + 'migrate_state_not_finished_test', + // Test missing migrate_drupal.yml. + 'migrate_state_no_file_test', + // Test missing migrate_drupal.yml. + 'migrate_state_no_upgrade_path', ]; /** @@ -53,55 +62,20 @@ protected function getSourceBasePath() { */ protected function getAvailablePaths() { return [ + // Aggregator is set not_finished in migrate_sate_not_finished_test. 'aggregator', - 'block', - 'book', - 'comment', - 'contact', - 'content', - 'date', - 'dblog', - 'email', - 'filefield', - 'filter', - 'forum', - 'i18n', - 'i18nblocks', - 'i18ncck', - 'i18nmenu', - 'i18nprofile', - 'i18nstrings', - 'i18ntaxonomy', - 'imagecache', - 'imagefield', - 'language', - 'link', - 'locale', - 'menu', - 'node', - 'nodereference', - 'optionwidgets', - 'path', - 'profile', - 'search', - 'statistics', - 'syslog', - 'system', - 'taxonomy', - 'text', - 'update', - 'upload', - 'user', - 'userreference', - // Include modules that do not have an upgrade path, defined in the - // $noUpgradePath property in MigrateUpgradeForm. 'blog', 'blogapi', + 'book', 'calendarsignup', 'color', + 'comment', + 'contact', + 'content', 'content_copy', 'content_multigroup', 'content_permissions', + 'date', 'date_api', 'date_locale', 'date_php4', @@ -110,27 +84,56 @@ protected function getAvailablePaths() { 'date_timezone', 'date_tools', 'datepicker', + 'dblog', 'ddblock', + 'email', 'event', 'fieldgroup', + 'filefield', 'filefield_meta', + 'filter', + 'forum', 'help', + 'i18nblocks', + 'i18ncontent', + 'i18nmenu', + 'i18npoll', + 'i18nprofile', 'i18nsync', 'imageapi', 'imageapi_gd', 'imageapi_imagemagick', + 'imagecache', 'imagecache_ui', + 'imagefield', 'jquery_ui', + 'link', + 'menu', 'nodeaccess', + 'nodereference', 'number', 'openid', + 'optionwidgets', + 'path', + 'phone', 'php', 'ping', 'poll', + 'profile', + 'search', + 'statistics', + 'syslog', + 'system', + 'taxonomy', + 'text', 'throttle', 'tracker', 'translation', 'trigger', + 'update', + 'upload', + 'user', + 'userreference', 'variable', 'variable_admin', 'views_export', @@ -143,13 +146,19 @@ protected function getAvailablePaths() { */ protected function getMissingPaths() { return [ + // Block is set not_finished in migrate_sate_not_finished_test. + 'block', 'devel', 'devel_generate', 'devel_node_access', - 'i18ncontent', - 'i18npoll', + 'i18n', + 'i18ncck', + 'i18nstrings', + 'i18ntaxonomy', 'i18nviews', - 'phone', + 'locale', + 'migrate_status_active_test', + 'node', 'views', ]; } diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualReviewPageTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualReviewPageTest.php index cab85e79a..469a73ca8 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualReviewPageTest.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualReviewPageTest.php @@ -12,6 +12,8 @@ * * @group migrate_drupal_6 * @group migrate_drupal_ui + * + * @group legacy */ class NoMultilingualReviewPageTest extends NoMultilingualReviewPageTestBase { @@ -28,6 +30,11 @@ class NoMultilingualReviewPageTest extends NoMultilingualReviewPageTestBase { 'syslog', 'tracker', 'update', + // Test migrations states. + 'migrate_state_finished_test', + 'migrate_state_not_finished_test', + // Test missing migrate_drupal.yml. + 'migrate_state_no_file_test', ]; /** @@ -51,47 +58,18 @@ protected function getSourceBasePath() { protected function getAvailablePaths() { return [ 'aggregator', - 'block', - 'book', - 'comment', - 'contact', - 'content', - 'date', - 'dblog', - 'email', - 'filefield', - 'filter', - 'forum', - 'imagecache', - 'imagefield', - 'language', - 'link', - 'locale', - 'menu', - 'node', - 'nodereference', - 'optionwidgets', - 'path', - 'profile', - 'search', - 'statistics', - 'syslog', - 'system', - 'taxonomy', - 'text', - 'update', - 'upload', - 'user', - 'userreference', - // Include modules that do not have an upgrade path, defined in the - // $noUpgradePath property in MigrateUpgradeForm. 'blog', 'blogapi', + 'book', 'calendarsignup', 'color', + 'comment', + 'contact', + 'content', 'content_copy', 'content_multigroup', 'content_permissions', + 'date', 'date_api', 'date_locale', 'date_php4', @@ -100,28 +78,52 @@ protected function getAvailablePaths() { 'date_timezone', 'date_tools', 'datepicker', + 'dblog', 'ddblock', + 'email', 'event', 'fieldgroup', + 'filefield', 'filefield_meta', + 'filter', + 'forum', 'help', - 'i18nstrings', - 'i18nsync', 'imageapi', 'imageapi_gd', 'imageapi_imagemagick', + 'imagecache', 'imagecache_ui', + 'imagefield', 'jquery_ui', + 'link', + 'locale', + 'menu', + 'node', 'nodeaccess', + 'nodereference', 'number', 'openid', + 'optionwidgets', + 'path', + 'phone', 'php', 'ping', 'poll', + 'profile', + 'search', + 'statistics', + 'syslog', + 'system', + 'taxonomy', + 'text', 'throttle', 'tracker', 'translation', 'trigger', + 'update', + 'upload', + 'user', + 'userreference', 'variable', 'variable_admin', 'views_export', @@ -129,11 +131,20 @@ protected function getAvailablePaths() { ]; } + /** + * {@inheritdoc} + */ + protected function getIncompletePaths() { + return []; + } + /** * {@inheritdoc} */ protected function getMissingPaths() { return [ + // Block is set not_finished in migrate_state_not_finished_test. + 'block', 'devel', 'devel_generate', 'devel_node_access', @@ -144,9 +155,11 @@ protected function getMissingPaths() { 'i18nmenu', 'i18npoll', 'i18nprofile', + 'i18nstrings', + 'i18nsync', 'i18ntaxonomy', 'i18nviews', - 'phone', + 'migrate_status_active_test', 'views', ]; } diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualTest.php index a3f4f10dc..20f7098bd 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualTest.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/NoMultilingualTest.php @@ -128,7 +128,6 @@ protected function getAvailablePaths() { 'forum', 'imagecache', 'imagefield', - 'language', 'link', 'locale', 'menu', @@ -146,8 +145,7 @@ protected function getAvailablePaths() { 'user', 'userreference', // Include modules that do not have an upgrade path and are enabled in the - // source database, defined in the $noUpgradePath property - // in MigrateUpgradeForm. + // source database. 'date_api', 'date_timezone', 'event', diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php index b6a7e80fd..0731f12af 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php @@ -12,6 +12,8 @@ * The test method is provided by the MigrateUpgradeTestBase class. * * @group migrate_drupal_ui + * + * @group legacy */ class Upgrade6Test extends MigrateUpgradeExecuteTestBase { @@ -73,7 +75,7 @@ protected function getEntityCounts() { 'file' => 7, 'filter_format' => 7, 'image_style' => 5, - 'language_content_settings' => 14, + 'language_content_settings' => 15, 'node' => 18, // The 'book' module provides the 'book' node type, and the migration // creates 12 node types. @@ -82,8 +84,9 @@ protected function getEntityCounts() { 'search_page' => 2, 'shortcut' => 2, 'shortcut_set' => 1, - 'action' => 23, + 'action' => 25, 'menu' => 8, + 'path_alias' => 8, 'taxonomy_term' => 15, 'taxonomy_vocabulary' => 7, 'tour' => 5, @@ -130,40 +133,32 @@ protected function getAvailablePaths() { 'contact', 'content', 'date', - 'dblog', 'email', 'filefield', 'filter', 'forum', - 'i18n', 'i18nblocks', - 'i18ncck', + 'i18ncontent', 'i18nmenu', 'i18nprofile', - 'i18nstrings', - 'i18ntaxonomy', + 'i18nsync', 'imagecache', 'imagefield', - 'language', - 'link', - 'locale', 'menu', - 'node', 'nodereference', 'optionwidgets', 'path', - 'profile', 'search', 'statistics', 'system', 'taxonomy', 'text', + 'translation', 'upload', 'user', 'userreference', // Include modules that do not have an upgrade path and are enabled in the - // source database, defined in the $noUpgradePath property - // in MigrateUpgradeForm. + // source database'. 'date_api', 'date_timezone', 'event', @@ -180,7 +175,12 @@ protected function getAvailablePaths() { */ protected function getMissingPaths() { return [ - 'i18ncontent', + 'i18n', + 'i18ncck', + 'i18nstrings', + 'i18ntaxonomy', + 'locale', + 'node', ]; } diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/html-1.txt b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/html-1.txt similarity index 100% rename from core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/html-1.txt rename to core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/html-1.txt diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-1.png b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-1.png similarity index 100% rename from core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-1.png rename to core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-1.png diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-2.jpg b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-2.jpg similarity index 100% rename from core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-2.jpg rename to core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-2.jpg diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-test.gif b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-test.gif similarity index 100% rename from core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-test.gif rename to core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-test.gif diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-test.jpg b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-test.jpg similarity index 100% rename from core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-test.jpg rename to core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-test.jpg diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-test.png b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-test.png similarity index 100% rename from core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/modules/simpletest/files/image-test.png rename to core/modules/migrate_drupal_ui/tests/src/Functional/d6/files/core/tests/fixtures/files/image-test.png diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MultilingualReviewPageTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MultilingualReviewPageTest.php index 18801c0ef..b5fdc0239 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MultilingualReviewPageTest.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MultilingualReviewPageTest.php @@ -11,6 +11,8 @@ * * @group migrate_drupal_7 * @group migrate_drupal_ui + * + * @group legacy */ class MultilingualReviewPageTest extends MultilingualReviewPageTestBase { @@ -30,6 +32,11 @@ class MultilingualReviewPageTest extends MultilingualReviewPageTestBase { 'update', // Required for translation migrations. 'migrate_drupal_multilingual', + // Test migrations states. + 'migrate_state_finished_test', + 'migrate_state_not_finished_test', + // Test missing migrate_drupal.yml. + 'migrate_state_no_file_test', ]; /** @@ -52,55 +59,21 @@ protected function getSourceBasePath() { */ protected function getAvailablePaths() { return [ - 'aggregator', - 'block', + 'blog', 'book', + 'bulk_export', 'color', 'comment', 'contact', - 'date', - 'dblog', - 'email', - 'field', - 'field_sql_storage', - 'file', - 'filter', - 'forum', - 'image', - 'i18n_block', - 'language', - 'link', - 'list', - 'locale', - 'menu', - 'node', - 'number', - 'options', - 'path', - 'phone', - 'rdf', - 'search', - 'shortcut', - 'statistics', - 'syslog', - 'system', - 'taxonomy', - 'text', - 'tracker', - 'update', - 'user', - // Include modules that do not have an upgrade path, defined in the - // $noUpgradePath property in MigrateUpgradeForm. - 'blog', - 'bulk_export', 'contextual', 'ctools', 'ctools_access_ruleset', 'ctools_ajax_sample', 'ctools_custom_content', 'dashboard', - 'date_all_day', + 'date', 'date_api', + 'date_all_day', 'date_context', 'date_migrate', 'date_popup', @@ -108,28 +81,58 @@ protected function getAvailablePaths() { 'date_repeat_field', 'date_tools', 'date_views', + 'dblog', + 'email', 'entity', 'entity_feature', 'entity_token', - 'entityreference', 'entity_translation', + 'entityreference', + 'field', + 'field_sql_storage', 'field_ui', + 'file', + 'filter', + 'forum', 'help', + 'i18n_block', + 'i18n_sync', + 'image', + 'link', + 'list', + 'locale', + 'menu', + 'number', 'openid', + 'options', 'overlay', 'page_manager', + 'path', + 'phone', 'php', 'poll', + 'profile', + 'rdf', + 'search', 'search_embedded_form', 'search_extra_type', 'search_node_tags', + 'shortcut', 'simpletest', + 'statistics', 'stylizer', + 'syslog', + 'system', + 'taxonomy', 'term_depth', + 'text', 'title', 'toolbar', + 'tracker', 'translation', 'trigger', + 'update', + 'user', 'views_content', 'views_ui', ]; @@ -140,6 +143,11 @@ protected function getAvailablePaths() { */ protected function getMissingPaths() { return [ + // Action is set not_finished in migrate_sate_not_finished_test. + // Aggregator is set not_finished in migrate_sate_not_finished_test. + 'aggregator', + // Block is set not_finished in migrate_sate_not_finished_test. + 'block', 'breakpoints', 'entity_translation_i18n_menu', 'entity_translation_upgrade', @@ -156,13 +164,13 @@ protected function getMissingPaths() { 'i18n_redirect', 'i18n_select', 'i18n_string', - 'i18n_sync', 'i18n_taxonomy', 'i18n_translation', 'i18n_user', 'i18n_variable', + 'node', 'picture', - 'profile', + 'migrate_status_active_test', 'variable', 'variable_admin', 'variable_realm', diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/NoMultilingualTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/NoMultilingualTest.php index a3958a7bf..46421a69f 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/NoMultilingualTest.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/NoMultilingualTest.php @@ -148,8 +148,7 @@ protected function getAvailablePaths() { 'text', 'user', // Include modules that do not have an upgrade path and are enabled in the - // source database, defined in the $noUpgradePath property - // in MigrateUpgradeForm. + // source database. 'blog', 'contextual', 'date_api', @@ -164,6 +163,13 @@ protected function getAvailablePaths() { ]; } + /** + * {@inheritdoc} + */ + protected function getIncompletePaths() { + return []; + } + /** * {@inheritdoc} */ diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php index b1ec8a675..4cf61aea5 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php @@ -12,6 +12,8 @@ * The test method is provided by the MigrateUpgradeTestBase class. * * @group migrate_drupal_ui + * + * @group legacy */ class Upgrade7Test extends MigrateUpgradeExecuteTestBase { @@ -72,22 +74,23 @@ protected function getEntityCounts() { 'contact_form' => 3, 'contact_message' => 0, 'editor' => 2, - 'field_config' => 72, + 'field_config' => 73, 'field_storage_config' => 55, 'file' => 3, 'filter_format' => 7, 'image_style' => 6, - 'language_content_settings' => 11, + 'language_content_settings' => 18, 'node' => 6, 'node_type' => 6, 'rdf_mapping' => 8, 'search_page' => 2, 'shortcut' => 6, 'shortcut_set' => 2, - 'action' => 17, + 'action' => 19, 'menu' => 6, 'taxonomy_term' => 24, 'taxonomy_vocabulary' => 7, + 'path_alias' => 8, 'tour' => 5, 'user' => 4, 'user_role' => 3, @@ -128,23 +131,24 @@ protected function getAvailablePaths() { 'color', 'comment', 'contact', + 'ctools', 'date', 'dblog', 'email', + 'entity_translation', 'entityreference', 'field', 'field_sql_storage', 'file', 'filter', 'forum', + 'i18n_block', + 'i18n_sync', 'i18n_variable', 'image', - 'language', 'link', 'list', - 'locale', 'menu', - 'node', 'number', 'options', 'path', @@ -156,10 +160,10 @@ protected function getAvailablePaths() { 'system', 'taxonomy', 'text', + 'title', 'user', // Include modules that do not have an upgrade path and are enabled in the - // source database, defined in the $noUpgradePath property - // in MigrateUpgradeForm. + // source database. 'blog', 'contextual', 'date_api', @@ -180,6 +184,12 @@ protected function getAvailablePaths() { protected function getMissingPaths() { return [ 'i18n', + 'i18n_field', + 'i18n_string', + 'i18n_taxonomy', + 'i18n_translation', + 'locale', + 'node', 'variable', 'variable_realm', 'variable_store', diff --git a/core/modules/migrate_drupal_ui/tests/src/Kernel/LegacyMigrationLabelExistTest.php b/core/modules/migrate_drupal_ui/tests/src/Kernel/LegacyMigrationLabelExistTest.php new file mode 100644 index 000000000..f8c70fbbf --- /dev/null +++ b/core/modules/migrate_drupal_ui/tests/src/Kernel/LegacyMigrationLabelExistTest.php @@ -0,0 +1,20 @@ +label(), $node->toUrl()); + $context['results'][] = Link::fromTextAndUrl($node->label(), $node->toUrl())->toString(); // Update our progress information. $context['sandbox']['progress']++; diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index 6dae238c3..54013ffdc 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -174,8 +174,8 @@ function hook_node_access_records(\Drupal\node\NodeInterface $node) { } // For the example_author array, the GID is equivalent to a UID, which // means there are many groups of just 1 user. - // Note that an author can always view his or her nodes, even if they - // have status unpublished. + // Note that an author can always view nodes they own, even if they have + // status unpublished. if ($node->getOwnerId()) { $grants[] = [ 'realm' => 'example_author', @@ -380,7 +380,7 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, \Drupal\Core\Se * @ingroup entity_crud */ function hook_node_search_result(\Drupal\node\NodeInterface $node) { - $rating = db_query('SELECT SUM(points) FROM {my_rating} WHERE nid = :nid', ['nid' => $node->id()])->fetchField(); + $rating = \Drupal::database()->query('SELECT SUM(points) FROM {my_rating} WHERE nid = :nid', ['nid' => $node->id()])->fetchField(); return ['rating' => \Drupal::translation()->formatPlural($rating, '1 point', '@count points')]; } @@ -400,7 +400,7 @@ function hook_node_search_result(\Drupal\node\NodeInterface $node) { */ function hook_node_update_index(\Drupal\node\NodeInterface $node) { $text = ''; - $ratings = db_query('SELECT title, description FROM {my_ratings} WHERE nid = :nid', [':nid' => $node->id()]); + $ratings = \Drupal::database()->query('SELECT title, description FROM {my_ratings} WHERE nid = :nid', [':nid' => $node->id()]); foreach ($ratings as $rating) { $text .= '

' . Html::escape($rating->title) . '

' . Xss::filter($rating->description); } diff --git a/core/modules/node/node.info.yml b/core/modules/node/node.info.yml index dfcf4a0a5..88cba8572 100644 --- a/core/modules/node/node.info.yml +++ b/core/modules/node/node.info.yml @@ -2,14 +2,8 @@ name: Node type: module description: 'Allows content to be submitted to the site and displayed on pages.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: entity.node_type.collection dependencies: - drupal:text - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 1380d63ae..e05c84112 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -19,7 +19,7 @@ function node_requirements($phase) { // Only show rebuild button if there are either 0, or 2 or more, rows // in the {node_access} table, or if there are modules that // implement hook_node_grants(). - $grant_count = \Drupal::entityManager()->getAccessControlHandler('node')->countGrants(); + $grant_count = \Drupal::entityTypeManager()->getAccessControlHandler('node')->countGrants(); if ($grant_count != 1 || count(\Drupal::moduleHandler()->getImplementations('node_grants')) > 0) { $value = \Drupal::translation()->formatPlural($grant_count, 'One permission in use', '@count permissions in use', ['@count' => $grant_count]); } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index efd893362..bb4580a58 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -17,6 +17,7 @@ use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Link; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; @@ -34,7 +35,7 @@ use Drupal\user\UserInterface; /** * Denotes that the node is not published. * - * @deprecated Scheduled for removal in Drupal 9.0.x. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\node\NodeInterface::NOT_PUBLISHED instead. * * @see https://www.drupal.org/node/2316145 @@ -44,7 +45,7 @@ const NODE_NOT_PUBLISHED = 0; /** * Denotes that the node is published. * - * @deprecated Scheduled for removal in Drupal 9.0.x. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\node\NodeInterface::PUBLISHED instead. * * @see https://www.drupal.org/node/2316145 @@ -54,7 +55,7 @@ const NODE_PUBLISHED = 1; /** * Denotes that the node is not promoted to the front page. * - * @deprecated Scheduled for removal in Drupal 9.0.x. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\node\NodeInterface::NOT_PROMOTED instead. * * @see https://www.drupal.org/node/2316145 @@ -64,7 +65,7 @@ const NODE_NOT_PROMOTED = 0; /** * Denotes that the node is promoted to the front page. * - * @deprecated Scheduled for removal in Drupal 9.0.x. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\node\NodeInterface::PROMOTED instead. * * @see https://www.drupal.org/node/2316145 @@ -74,7 +75,7 @@ const NODE_PROMOTED = 1; /** * Denotes that the node is not sticky at the top of the page. * - * @deprecated Scheduled for removal in Drupal 9.0.x. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\node\NodeInterface::NOT_STICKY instead. * * @see https://www.drupal.org/node/2316145 @@ -84,7 +85,7 @@ const NODE_NOT_STICKY = 0; /** * Denotes that the node is sticky at the top of the page. * - * @deprecated Scheduled for removal in Drupal 9.0.x. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\node\NodeInterface::STICKY instead. * * @see https://www.drupal.org/node/2316145 @@ -226,7 +227,7 @@ function node_title_list(StatementInterface $result, $title = NULL) { // database rows, not actual nodes. $nids[] = $row->nid; $options = !empty($row->comment_count) ? ['attributes' => ['title' => \Drupal::translation()->formatPlural($row->comment_count, '1 comment', '@count comments')]] : []; - $items[] = \Drupal::l($row->title, new Url('entity.node.canonical', ['node' => $row->nid], $options)); + $items[] = Link::fromTextAndUrl($row->title, Url::fromRoute('entity.node.canonical', ['node' => $row->nid], $options))->toString(); $num_rows = TRUE; } @@ -271,7 +272,7 @@ function node_mark($nid, $timestamp) { * @return \Drupal\node\NodeTypeInterface[] * An array of node type entities, keyed by ID. * - * @deprecated in Drupal 8.x, will be removed before Drupal 9.0. + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. * Use \Drupal\node\Entity\NodeType::loadMultiple(). * * @see \Drupal\node\Entity\NodeType::load() @@ -333,7 +334,7 @@ function node_type_get_description(NodeTypeInterface $node_type) { * @return \Drupal\node\NodeTypeInterface * A node type object or NULL if $name does not exist. * - * @deprecated iin Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal\node\Entity\NodeType::load(). * * @see https://www.drupal.org/node/2266845 @@ -367,15 +368,18 @@ function node_add_body_field(NodeTypeInterface $type, $label = 'Body') { ]); $field->save(); - // Assign widget settings for the 'default' form mode. - entity_get_form_display('node', $type->id(), 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + + // Assign widget settings for the default form mode. + $display_repository->getFormDisplay('node', $type->id()) ->setComponent('body', [ 'type' => 'text_textarea_with_summary', ]) ->save(); // Assign display settings for the 'default' and 'teaser' view modes. - entity_get_display('node', $type->id(), 'default') + $display_repository->getViewDisplay('node', $type->id()) ->setComponent('body', [ 'label' => 'hidden', 'type' => 'text_default', @@ -386,7 +390,7 @@ function node_add_body_field(NodeTypeInterface $type, $label = 'Body') { // might not exist. $view_modes = \Drupal::service('entity_display.repository')->getViewModes('node'); if (isset($view_modes['teaser'])) { - entity_get_display('node', $type->id(), 'teaser') + $display_repository->getViewDisplay('node', $type->id(), 'teaser') ->setComponent('body', [ 'label' => 'hidden', 'type' => 'text_summary_or_trimmed', @@ -428,7 +432,7 @@ function node_entity_extra_field_info() { * The number of nodes whose node type field was modified. */ function node_type_update_nodes($old_id, $new_id) { - return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id); + return \Drupal::entityTypeManager()->getStorage('node')->updateType($old_id, $new_id); } /** @@ -447,7 +451,7 @@ function node_type_update_nodes($old_id, $new_id) { * @return \Drupal\node\NodeInterface[] * An array of node entities indexed by nid. * - * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal\node\Entity\Node::loadMultiple(). * * @see https://www.drupal.org/node/2266845 @@ -455,7 +459,7 @@ function node_type_update_nodes($old_id, $new_id) { function node_load_multiple(array $nids = NULL, $reset = FALSE) { @trigger_error('node_load_multiple() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\node\Entity\Node::loadMultiple(). See https://www.drupal.org/node/2266845', E_USER_DEPRECATED); if ($reset) { - \Drupal::entityManager()->getStorage('node')->resetCache($nids); + \Drupal::entityTypeManager()->getStorage('node')->resetCache($nids); } return Node::loadMultiple($nids); } @@ -472,7 +476,7 @@ function node_load_multiple(array $nids = NULL, $reset = FALSE) { * @return \Drupal\node\NodeInterface|null * A fully-populated node entity, or NULL if the node is not found. * - * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal\node\Entity\Node::load(). * * @see https://www.drupal.org/node/2266845 @@ -480,7 +484,7 @@ function node_load_multiple(array $nids = NULL, $reset = FALSE) { function node_load($nid = NULL, $reset = FALSE) { @trigger_error('node_load() is deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use \Drupal\node\Entity\Node::load(). See https://www.drupal.org/node/2266845', E_USER_DEPRECATED); if ($reset) { - \Drupal::entityManager()->getStorage('node')->resetCache([$nid]); + \Drupal::entityTypeManager()->getStorage('node')->resetCache([$nid]); } return Node::load($nid); } @@ -542,7 +546,7 @@ function template_preprocess_node_add_list(&$variables) { foreach ($variables['content'] as $type) { $variables['types'][$type->id()] = [ 'type' => $type->id(), - 'add_link' => \Drupal::l($type->label(), new Url('node.add', ['node_type' => $type->id()])), + 'add_link' => Link::fromTextAndUrl($type->label(), Url::fromRoute('node.add', ['node_type' => $type->id()]))->toString(), 'description' => [ '#markup' => $type->getDescription(), ], @@ -634,7 +638,7 @@ function template_preprocess_node(&$variables) { // preprocessing if the field display is configurable and skipping has been // enabled. // @todo https://www.drupal.org/project/drupal/issues/3015623 - // In D9 delete this code and matching template lines. Using + // Eventually delete this code and matching template lines. Using // $variables['content'] is more flexible and consistent. $submitted_configurable = $node->getFieldDefinition('created')->isDisplayConfigurable('view') || $node->getFieldDefinition('uid')->isDisplayConfigurable('view'); if (!$skip_custom_preprocessing || !$submitted_configurable) { @@ -666,7 +670,7 @@ function template_preprocess_node(&$variables) { // Display post information on certain node types. This only occurs if // custom preprocessing occurred for both of the created and uid fields. // @todo https://www.drupal.org/project/drupal/issues/3015623 - // In D9 delete this code and matching template lines. Using a field + // Eventually delete this code and matching template lines. Using a field // formatter is more flexible and consistent. $node_type = $node->type->entity; // Used by RDF to add attributes around the author and date submitted. @@ -678,7 +682,9 @@ function template_preprocess_node(&$variables) { // 'compact' view mode on the User entity. Note that the 'compact' // view mode might not be configured, so remember to always check the // theme setting first. - $variables['author_picture'] = user_view($node->getOwner(), 'compact'); + $variables['author_picture'] = \Drupal::entityTypeManager() + ->getViewBuilder('user') + ->view($node->getOwner(), 'compact'); } } } @@ -767,7 +773,7 @@ function node_user_cancel($edit, UserInterface $account, $method) { case 'user_cancel_reassign': // Anonymize all of the nodes for this old account. module_load_include('inc', 'node', 'node.admin'); - $vids = \Drupal::entityManager()->getStorage('node')->userRevisionIds($account); + $vids = \Drupal::entityTypeManager()->getStorage('node')->userRevisionIds($account); node_mass_update($vids, [ 'uid' => 0, 'revision_uid' => 0, @@ -786,9 +792,10 @@ function node_user_predelete($account) { ->condition('uid', $account->id()) ->accessCheck(FALSE) ->execute(); - entity_delete_multiple('node', $nids); // Delete old revisions. - $storage_controller = \Drupal::entityManager()->getStorage('node'); + $storage_controller = \Drupal::entityTypeManager()->getStorage('node'); + $nodes = $storage_controller->loadMultiple($nids); + $storage_controller->delete($nodes); $revisions = $storage_controller->userRevisionIds($account); foreach ($revisions as $revision) { node_revision_delete($revision); @@ -850,9 +857,17 @@ function node_get_recent($number = 10) { * * @return array * An array as expected by \Drupal\Core\Render\RendererInterface::render(). + * + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. + * Use \Drupal::entityTypeManager()->getViewBuilder('node')->view() instead. + * + * @see https://www.drupal.org/node/3033656 */ function node_view(NodeInterface $node, $view_mode = 'full', $langcode = NULL) { - return entity_view($node, $view_mode, $langcode); + @trigger_error("node_view() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal::entityTypeManager()->getViewBuilder('node')->view() instead. See https://www.drupal.org/node/3033656", E_USER_DEPRECATED); + return \Drupal::entityTypeManager() + ->getViewBuilder('node') + ->view($node, $view_mode, $langcode); } /** @@ -869,9 +884,18 @@ function node_view(NodeInterface $node, $view_mode = 'full', $langcode = NULL) { * @return array * An array in the format expected by * \Drupal\Core\Render\RendererInterface::render(). + * + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Use + * \Drupal::entityTypeManager()->getViewBuilder('node')->viewMultiple() + * instead. + * + * @see https://www.drupal.org/node/3033656 */ function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) { - return entity_view_multiple($nodes, $view_mode, $langcode); + @trigger_error("node_view_multiple() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal::entityTypeManager()->getViewBuilder('node')->viewMultiple() instead. See https://www.drupal.org/node/3033656", E_USER_DEPRECATED); + return \Drupal::entityTypeManager() + ->getViewBuilder('node') + ->viewMultiple($nodes, $view_mode, $langcode); } /** @@ -1068,7 +1092,7 @@ function node_access_view_all_nodes($account = NULL) { $access[$account->id()] = TRUE; } else { - $access[$account->id()] = \Drupal::entityManager()->getAccessControlHandler('node')->checkAllGrants($account); + $access[$account->id()] = \Drupal::entityTypeManager()->getAccessControlHandler('node')->checkAllGrants($account); } return $access[$account->id()]; @@ -1195,8 +1219,8 @@ function node_access_needs_rebuild($rebuild = NULL) { * This rebuild is occasionally needed by modules that make system-wide changes * to access levels. When the rebuild is required by an admin-triggered action * (e.g module settings form), calling node_access_needs_rebuild(TRUE) instead - * of node_access_rebuild() lets the user perform his changes and actually - * rebuild only once he is done. + * of node_access_rebuild() lets the user perform changes and actually rebuild + * only once done. * * Note : As of Drupal 6, node access modules are not required to (and actually * should not) call node_access_rebuild() in hook_install/uninstall anymore. @@ -1212,9 +1236,9 @@ function node_access_needs_rebuild($rebuild = NULL) { * @see node_access_needs_rebuild() */ function node_access_rebuild($batch_mode = FALSE) { - $node_storage = \Drupal::entityManager()->getStorage('node'); + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); /** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */ - $access_control_handler = \Drupal::entityManager()->getAccessControlHandler('node'); + $access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); $access_control_handler->deleteGrants(); // Only recalculate if the site is using a node_access module. if (count(\Drupal::moduleHandler()->getImplementations('node_grants'))) { @@ -1278,7 +1302,7 @@ function node_access_rebuild($batch_mode = FALSE) { * An array of contextual key/value information for rebuild batch process. */ function _node_access_rebuild_batch_operation(&$context) { - $node_storage = \Drupal::entityManager()->getStorage('node'); + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); if (empty($context['sandbox'])) { // Initiate multistep processing. $context['sandbox']['progress'] = 0; @@ -1305,7 +1329,7 @@ function _node_access_rebuild_batch_operation(&$context) { // loads successfully. if (!empty($node)) { /** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */ - $access_control_handler = \Drupal::entityManager()->getAccessControlHandler('node'); + $access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); $grants = $access_control_handler->acquireGrants($node); \Drupal::service('node.grant_storage')->write($node, $grants); } @@ -1385,7 +1409,7 @@ function node_modules_uninstalled($modules) { */ function node_configurable_language_delete(ConfigurableLanguageInterface $language) { // On nodes with this language, unset the language. - \Drupal::entityManager()->getStorage('node')->clearRevisionsLanguage($language); + \Drupal::entityTypeManager()->getStorage('node')->clearRevisionsLanguage($language); } /** @@ -1397,7 +1421,7 @@ function node_configurable_language_delete(ConfigurableLanguageInterface $langua function node_reindex_node_search($nid) { if (\Drupal::moduleHandler()->moduleExists('search')) { // Reindex node context indexed by the node module search plugin. - search_mark_for_reindex('node_search', $nid); + \Drupal::service('search.index')->markForReindex('node_search', $nid); } } diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml index d592bd1a1..ac958ea24 100644 --- a/core/modules/node/node.routing.yml +++ b/core/modules/node/node.routing.yml @@ -27,7 +27,7 @@ node.add_page: node.add: path: '/node/add/{node_type}' defaults: - _controller: '\Drupal\node\Controller\NodeController::add' + _entity_form: 'node.default' _title_callback: '\Drupal\node\Controller\NodeController::addPageTitle' requirements: _node_add_access: 'node:{node_type}' @@ -35,6 +35,7 @@ node.add: _node_operation_route: TRUE parameters: node_type: + type: entity:node_type with_config_overrides: TRUE entity.node.preview: @@ -137,6 +138,6 @@ entity.node_type.delete_form: node.configure_rebuild_confirm: path: '/admin/reports/status/rebuild' defaults: - _form: 'Drupal\node\Form\RebuildPermissionsForm' + _form: '\Drupal\node\Form\RebuildPermissionsForm' requirements: _permission: 'access administration pages' diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc index b44343b66..30bd1c6b9 100644 --- a/core/modules/node/node.tokens.inc +++ b/core/modules/node/node.tokens.inc @@ -143,7 +143,9 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta // Get the 'trim_length' size used for the 'teaser' mode, if // present, or use the default trim_length size. - $display_options = entity_get_display('node', $node->getType(), 'teaser')->getComponent('body'); + $display_options = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', $node->getType(), 'teaser') + ->getComponent('body'); if (isset($display_options['settings']['trim_length'])) { $length = $display_options['settings']['trim_length']; } diff --git a/core/modules/node/src/Access/NodeAddAccessCheck.php b/core/modules/node/src/Access/NodeAddAccessCheck.php index 3ae796b52..5e6a04682 100644 --- a/core/modules/node/src/Access/NodeAddAccessCheck.php +++ b/core/modules/node/src/Access/NodeAddAccessCheck.php @@ -48,8 +48,8 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager) { * (optional) The node type. If not specified, access is allowed if there * exists at least one node type for which the user may create a node. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. */ public function access(AccountInterface $account, NodeTypeInterface $node_type = NULL) { $access_control_handler = $this->entityTypeManager->getAccessControlHandler('node'); diff --git a/core/modules/node/src/Access/NodePreviewAccessCheck.php b/core/modules/node/src/Access/NodePreviewAccessCheck.php index 651557730..3b3c051ab 100644 --- a/core/modules/node/src/Access/NodePreviewAccessCheck.php +++ b/core/modules/node/src/Access/NodePreviewAccessCheck.php @@ -46,8 +46,8 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager) { * @param \Drupal\node\NodeInterface $node_preview * The node that is being previewed. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. */ public function access(AccountInterface $account, NodeInterface $node_preview) { if ($node_preview->isNew()) { diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php index 58712f372..385d2bc5a 100644 --- a/core/modules/node/src/Controller/NodeController.php +++ b/core/modules/node/src/Controller/NodeController.php @@ -7,6 +7,7 @@ use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Link; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Url; use Drupal\node\NodeStorageInterface; @@ -120,8 +121,13 @@ public function addPage() { * * @return array * A node submission form. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Define + * entity form routes through the _entity_form instead through the + * _controller directive. */ public function add(NodeTypeInterface $node_type) { + @trigger_error(__METHOD__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Define entity form routes through the _entity_form instead through the _controller directive. See https://www.drupal.org/node/3084856', E_USER_DEPRECATED); $node = $this->entityTypeManager()->getStorage('node')->create([ 'type' => $node_type->id(), ]); @@ -211,7 +217,7 @@ public function revisionOverview(NodeInterface $node) { // this case. $is_current_revision = $vid == $default_revision || (!$current_revision_displayed && $revision->wasDefaultRevision()); if (!$is_current_revision) { - $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); + $link = Link::fromTextAndUrl($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid]))->toString(); } else { $link = $node->toLink($date)->toString(); diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index b3d19d4e8..fb54e1fb8 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -144,7 +144,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // is new. if ($this->isDefaultRevision()) { /** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */ - $access_control_handler = \Drupal::entityManager()->getAccessControlHandler('node'); + $access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); $grants = $access_control_handler->acquireGrants($this); \Drupal::service('node.grant_storage')->write($this, $grants, NULL, $update); } @@ -163,9 +163,11 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie parent::preDelete($storage, $entities); // Ensure that all nodes deleted are removed from the search index. - if (\Drupal::moduleHandler()->moduleExists('search')) { + if (\Drupal::hasService('search.index')) { + /** @var \Drupal\search\SearchIndexInterface $search_index */ + $search_index = \Drupal::service('search.index'); foreach ($entities as $entity) { - search_index_clear('node_search', $entity->nid->value); + $search_index->clear('node_search', $entity->nid->value); } } } @@ -257,6 +259,7 @@ public function setSticky($sticky) { * {@inheritdoc} */ public function getRevisionAuthor() { + @trigger_error(__NAMESPACE__ . '\Node::getRevisionAuthor is deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Entity\RevisionLogInterface::getRevisionUser() instead. See https://www.drupal.org/node/3069750', E_USER_DEPRECATED); return $this->getRevisionUser(); } @@ -264,8 +267,8 @@ public function getRevisionAuthor() { * {@inheritdoc} */ public function setRevisionAuthorId($uid) { - $this->setRevisionUserId($uid); - return $this; + @trigger_error(__NAMESPACE__ . '\Node::setRevisionAuthorId is deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Entity\RevisionLogInterface::setRevisionUserId() instead. See https://www.drupal.org/node/3069750', E_USER_DEPRECATED); + return $this->setRevisionUserId($uid); } /** diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php index 3338b93f1..60bfc7df7 100644 --- a/core/modules/node/src/Entity/NodeType.php +++ b/core/modules/node/src/Entity/NodeType.php @@ -125,7 +125,8 @@ public function isLocked() { * {@inheritdoc} */ public function isNewRevision() { - return $this->new_revision; + @trigger_error('NodeType::isNewRevision is deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use Drupal\Core\Entity\RevisionableEntityBundleInterface::shouldCreateNewRevision() instead. See https://www.drupal.org/node/3067365', E_USER_DEPRECATED); + return $this->shouldCreateNewRevision(); } /** @@ -216,7 +217,7 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti * {@inheritdoc} */ public function shouldCreateNewRevision() { - return $this->isNewRevision(); + return $this->new_revision; } } diff --git a/core/modules/node/src/Form/NodeRevisionDeleteForm.php b/core/modules/node/src/Form/NodeRevisionDeleteForm.php index 3d5807af3..aefefde6c 100644 --- a/core/modules/node/src/Form/NodeRevisionDeleteForm.php +++ b/core/modules/node/src/Form/NodeRevisionDeleteForm.php @@ -11,7 +11,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides a form for reverting a node revision. + * Provides a form for deleting a node revision. * * @internal */ @@ -75,10 +75,10 @@ public function __construct(EntityStorageInterface $node_storage, EntityStorageI * {@inheritdoc} */ public static function create(ContainerInterface $container) { - $entity_manager = $container->get('entity.manager'); + $entity_type_manager = $container->get('entity_type.manager'); return new static( - $entity_manager->getStorage('node'), - $entity_manager->getStorage('node_type'), + $entity_type_manager->getStorage('node'), + $entity_type_manager->getStorage('node_type'), $container->get('database'), $container->get('date.formatter') ); diff --git a/core/modules/node/src/Form/NodeRevisionRevertForm.php b/core/modules/node/src/Form/NodeRevisionRevertForm.php index cbd7e868f..da5cba023 100644 --- a/core/modules/node/src/Form/NodeRevisionRevertForm.php +++ b/core/modules/node/src/Form/NodeRevisionRevertForm.php @@ -67,7 +67,7 @@ public function __construct(EntityStorageInterface $node_storage, DateFormatterI */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager')->getStorage('node'), + $container->get('entity_type.manager')->getStorage('node'), $container->get('date.formatter'), $container->get('datetime.time') ); diff --git a/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php b/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php index a6a4d85d8..c4f78c312 100644 --- a/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php +++ b/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php @@ -53,7 +53,7 @@ public function __construct(EntityStorageInterface $node_storage, DateFormatterI */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager')->getStorage('node'), + $container->get('entity_type.manager')->getStorage('node'), $container->get('date.formatter'), $container->get('language_manager'), $container->get('datetime.time') diff --git a/core/modules/node/src/NodeAccessControlHandler.php b/core/modules/node/src/NodeAccessControlHandler.php index 7fd27dea0..b742f9c05 100644 --- a/core/modules/node/src/NodeAccessControlHandler.php +++ b/core/modules/node/src/NodeAccessControlHandler.php @@ -137,7 +137,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_ if ($account->hasPermission('administer nodes')) { return AccessResult::allowed()->cachePerPermissions(); } - return AccessResult::allowedIf($items->getEntity()->type->entity->isNewRevision())->cachePerPermissions(); + return AccessResult::allowedIf($items->getEntity()->type->entity->shouldCreateNewRevision())->cachePerPermissions(); } return parent::checkFieldAccess($operation, $field_definition, $account, $items); } diff --git a/core/modules/node/src/NodeAccessControlHandlerInterface.php b/core/modules/node/src/NodeAccessControlHandlerInterface.php index 7d407c1f1..4cf84d7a7 100644 --- a/core/modules/node/src/NodeAccessControlHandlerInterface.php +++ b/core/modules/node/src/NodeAccessControlHandlerInterface.php @@ -44,7 +44,7 @@ public function acquireGrants(NodeInterface $node); * purposes, and assumes the caller has already performed a mass delete of * some form. Defaults to TRUE. * - * @deprecated in Drupal 8.x, will be removed before Drupal 9.0. + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. * Use \Drupal\node\NodeAccessControlHandlerInterface::acquireGrants(). */ public function writeGrants(NodeInterface $node, $delete = TRUE); diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php index f5380846b..52c0a02df 100644 --- a/core/modules/node/src/NodeForm.php +++ b/core/modules/node/src/NodeForm.php @@ -223,7 +223,7 @@ public function form(array $form, FormStateInterface $form_state) { * * @see \Drupal\node\NodeForm::form() * - * @deprecated in Drupal 8.4.x, will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. * The "Publish" button was removed. */ public function updateStatus($entity_type_id, NodeInterface $node, array $form, FormStateInterface $form_state) { @@ -251,8 +251,9 @@ protected function actions(array $form, FormStateInterface $form_state) { '#submit' => ['::submitForm', '::preview'], ]; - $element['delete']['#access'] = $node->access('delete'); - $element['delete']['#weight'] = 100; + if (array_key_exists('delete', $element)) { + $element['delete']['#weight'] = 100; + } return $element; } diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php index b4947c9f1..d273359be 100644 --- a/core/modules/node/src/NodeGrantDatabaseStorage.php +++ b/core/modules/node/src/NodeGrantDatabaseStorage.php @@ -156,6 +156,12 @@ public function alterQuery($query, array $tables, $op, AccountInterface $account // more than once in the query, and could be aliased. Join each one to // the node_access table. $grants = node_access_grants($op, $account); + // If any grant exists for the specified user, then user has access to the + // node for the specified operation. + $grant_conditions = static::buildGrantsQueryCondition($grants); + $grants_exist = count($grant_conditions->conditions()) > 0; + + $is_multilingual = \Drupal::languageManager()->isMultilingual(); foreach ($tables as $nalias => $tableinfo) { $table = $tableinfo['table']; if (!($table instanceof SelectInterface) && $table == $base_table) { @@ -163,18 +169,14 @@ public function alterQuery($query, array $tables, $op, AccountInterface $account $subquery = $this->database->select('node_access', 'na') ->fields('na', ['nid']); - // If any grant exists for the specified user, then user has access to the - // node for the specified operation. - $grant_conditions = static::buildGrantsQueryCondition($grants); - - // Attach conditions to the subquery for nodes. - if (count($grant_conditions->conditions())) { + // Attach conditions to the sub-query for nodes. + if ($grants_exist) { $subquery->condition($grant_conditions); } $subquery->condition('na.grant_' . $op, 1, '>='); // Add langcode-based filtering if this is a multilingual site. - if (\Drupal::languageManager()->isMultilingual()) { + if ($is_multilingual) { // If no specific langcode to check for is given, use the grant entry // which is set as a fallback. // If a specific langcode is given, use the grant entry for it. diff --git a/core/modules/node/src/NodeInterface.php b/core/modules/node/src/NodeInterface.php index 87b68927c..fc88507d0 100644 --- a/core/modules/node/src/NodeInterface.php +++ b/core/modules/node/src/NodeInterface.php @@ -65,7 +65,7 @@ public function getTitle(); * @param string $title * The node title. * - * @return \Drupal\node\NodeInterface + * @return $this * The called node entity. */ public function setTitle($title); @@ -84,7 +84,7 @@ public function getCreatedTime(); * @param int $timestamp * The node creation timestamp. * - * @return \Drupal\node\NodeInterface + * @return $this * The called node entity. */ public function setCreatedTime($timestamp); @@ -103,7 +103,7 @@ public function isPromoted(); * @param bool $promoted * TRUE to set this node to promoted, FALSE to set it to not promoted. * - * @return \Drupal\node\NodeInterface + * @return $this * The called node entity. */ public function setPromoted($promoted); @@ -122,7 +122,7 @@ public function isSticky(); * @param bool $sticky * TRUE to set this node to sticky, FALSE to set it to not sticky. * - * @return \Drupal\node\NodeInterface + * @return $this * The called node entity. */ public function setSticky($sticky); @@ -141,7 +141,7 @@ public function getRevisionCreationTime(); * @param int $timestamp * The UNIX timestamp of when this revision was created. * - * @return \Drupal\node\NodeInterface + * @return $this * The called node entity. */ public function setRevisionCreationTime($timestamp); @@ -152,8 +152,10 @@ public function setRevisionCreationTime($timestamp); * @return \Drupal\user\UserInterface * The user entity for the revision author. * - * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * \Drupal\Core\Entity\RevisionLogInterface::getRevisionUser() instead. + * + * @see https://www.drupal.org/node/3069750 */ public function getRevisionAuthor(); @@ -163,11 +165,13 @@ public function getRevisionAuthor(); * @param int $uid * The user ID of the revision author. * - * @return \Drupal\node\NodeInterface + * @return $this * The called node entity. * - * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * \Drupal\Core\Entity\RevisionLogInterface::setRevisionUserId() instead. + * + * @see https://www.drupal.org/node/3069750 */ public function setRevisionAuthorId($uid); diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/NodeTypeForm.php index 753c5adfb..502c478ae 100644 --- a/core/modules/node/src/NodeTypeForm.php +++ b/core/modules/node/src/NodeTypeForm.php @@ -148,7 +148,7 @@ public function form(array $form, FormStateInterface $form_state) { 'status' => $node->status->value, 'promote' => $node->promote->value, 'sticky' => $node->sticky->value, - 'revision' => $type->isNewRevision(), + 'revision' => $type->shouldCreateNewRevision(), ]; // Prepare workflow options to be used for 'checkboxes' form element. $keys = array_keys(array_filter($workflow_options)); @@ -203,7 +203,6 @@ public function form(array $form, FormStateInterface $form_state) { protected function actions(array $form, FormStateInterface $form_state) { $actions = parent::actions($form, $form_state); $actions['submit']['#value'] = t('Save content type'); - $actions['delete']['#value'] = t('Delete content type'); return $actions; } diff --git a/core/modules/node/src/NodeTypeInterface.php b/core/modules/node/src/NodeTypeInterface.php index df4831e56..17dcd63db 100644 --- a/core/modules/node/src/NodeTypeInterface.php +++ b/core/modules/node/src/NodeTypeInterface.php @@ -24,9 +24,11 @@ public function isLocked(); * @return bool * TRUE if a new revision should be created by default. * - * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * Drupal\Core\Entity\RevisionableEntityBundleInterface::shouldCreateNewRevision() * instead. + * + * @see https://www.drupal.org/node/3067365 */ public function isNewRevision(); diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php index a3a5b7b16..f36cd4863 100644 --- a/core/modules/node/src/NodeViewBuilder.php +++ b/core/modules/node/src/NodeViewBuilder.php @@ -4,11 +4,13 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityViewBuilder; +use Drupal\Core\Render\Element\Link; +use Drupal\Core\Security\TrustedCallbackInterface; /** * View builder handler for nodes. */ -class NodeViewBuilder extends EntityViewBuilder { +class NodeViewBuilder extends EntityViewBuilder implements TrustedCallbackInterface { /** * {@inheritdoc} @@ -87,7 +89,7 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) { public static function renderLinks($node_entity_id, $view_mode, $langcode, $is_in_preview, $revision_id = NULL) { $links = [ '#theme' => 'links__node', - '#pre_render' => ['drupal_pre_render_links'], + '#pre_render' => [[Link::class, 'preRenderLinks']], '#attributes' => ['class' => ['links', 'inline']], ]; @@ -146,4 +148,13 @@ protected static function buildLinks(NodeInterface $entity, $view_mode) { ]; } + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + $callbacks = parent::trustedCallbacks(); + $callbacks[] = 'renderLinks'; + return $callbacks; + } + } diff --git a/core/modules/node/src/NodeViewsData.php b/core/modules/node/src/NodeViewsData.php index ef7d5d3ef..86db01c34 100644 --- a/core/modules/node/src/NodeViewsData.php +++ b/core/modules/node/src/NodeViewsData.php @@ -243,8 +243,12 @@ public function getViewsData() { $data['node_field_revision']['langcode']['help'] = $this->t('The language the original content is in.'); - $data['node_revision']['revision_uid']['help'] = $this->t('Relate a content revision to the user who created the revision.'); + $data['node_revision']['revision_uid']['help'] = $this->t('The user who created the revision.'); $data['node_revision']['revision_uid']['relationship']['label'] = $this->t('revision user'); + $data['node_revision']['revision_uid']['filter']['id'] = 'user_name'; + + $data['node_revision']['table']['join']['node_field_data']['left_field'] = 'vid'; + $data['node_revision']['table']['join']['node_field_data']['field'] = 'vid'; $data['node_field_revision']['table']['wizard_id'] = 'node_field_revision'; diff --git a/core/modules/node/src/Plugin/Action/DeleteNode.php b/core/modules/node/src/Plugin/Action/DeleteNode.php index 64fbe120c..0213ccf51 100644 --- a/core/modules/node/src/Plugin/Action/DeleteNode.php +++ b/core/modules/node/src/Plugin/Action/DeleteNode.php @@ -10,7 +10,7 @@ /** * Redirects to a node deletion form. * - * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. * Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. * * @see \Drupal\Core\Action\Plugin\Action\DeleteAction diff --git a/core/modules/node/src/Plugin/Action/PublishNode.php b/core/modules/node/src/Plugin/Action/PublishNode.php index adecc7de8..6e5222976 100644 --- a/core/modules/node/src/Plugin/Action/PublishNode.php +++ b/core/modules/node/src/Plugin/Action/PublishNode.php @@ -8,7 +8,7 @@ /** * Publishes a node. * - * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0. + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. * Use \Drupal\Core\Action\Plugin\Action\PublishAction instead. * * @see \Drupal\Core\Action\Plugin\Action\PublishAction diff --git a/core/modules/node/src/Plugin/Action/SaveNode.php b/core/modules/node/src/Plugin/Action/SaveNode.php index d9eea0c92..f321c9127 100644 --- a/core/modules/node/src/Plugin/Action/SaveNode.php +++ b/core/modules/node/src/Plugin/Action/SaveNode.php @@ -9,7 +9,7 @@ /** * Provides an action that can save any entity. * - * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0. + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. * Use \Drupal\Core\Action\Plugin\Action\SaveAction instead. * * @see \Drupal\Core\Action\Plugin\Action\SaveAction diff --git a/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php index 91b60c204..94abf8b63 100644 --- a/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php +++ b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php @@ -22,9 +22,12 @@ class UnpublishByKeywordNode extends ConfigurableActionBase { * {@inheritdoc} */ public function execute($node = NULL) { + $elements = \Drupal::entityTypeManager() + ->getViewBuilder('node') + ->view(clone $node); + $render = \Drupal::service('renderer')->render($elements); foreach ($this->configuration['keywords'] as $keyword) { - $elements = node_view(clone $node); - if (strpos(\Drupal::service('renderer')->render($elements), $keyword) !== FALSE || strpos($node->label(), $keyword) !== FALSE) { + if (strpos($render, $keyword) !== FALSE || strpos($node->label(), $keyword) !== FALSE) { $node->setUnpublished(); $node->save(); break; diff --git a/core/modules/node/src/Plugin/Action/UnpublishNode.php b/core/modules/node/src/Plugin/Action/UnpublishNode.php index bb188d700..7f4cdd8d7 100644 --- a/core/modules/node/src/Plugin/Action/UnpublishNode.php +++ b/core/modules/node/src/Plugin/Action/UnpublishNode.php @@ -8,7 +8,7 @@ /** * Unpublishes a node. * - * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0. + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. * Use \Drupal\Core\Action\Plugin\Action\UnpublishAction instead. * * @see \Drupal\Core\Action\Plugin\Action\UnpublishAction diff --git a/core/modules/node/src/Plugin/Condition/NodeType.php b/core/modules/node/src/Plugin/Condition/NodeType.php index 93e18c09a..ba8726d53 100644 --- a/core/modules/node/src/Plugin/Condition/NodeType.php +++ b/core/modules/node/src/Plugin/Condition/NodeType.php @@ -53,7 +53,7 @@ public function __construct(EntityStorageInterface $entity_storage, array $confi */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( - $container->get('entity.manager')->getStorage('node_type'), + $container->get('entity_type.manager')->getStorage('node_type'), $configuration, $plugin_id, $plugin_definition diff --git a/core/modules/node/src/Plugin/Search/NodeSearch.php b/core/modules/node/src/Plugin/Search/NodeSearch.php index e4d5a0e7f..078620124 100644 --- a/core/modules/node/src/Plugin/Search/NodeSearch.php +++ b/core/modules/node/src/Plugin/Search/NodeSearch.php @@ -2,26 +2,28 @@ namespace Drupal\node\Plugin\Search; +use Drupal\Core\Access\AccessibleInterface; use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\Config; use Drupal\Core\Database\Connection; -use Drupal\Core\Database\Database; +use Drupal\Core\Database\Query\Condition; use Drupal\Core\Database\Query\SelectExtender; use Drupal\Core\Database\StatementInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Messenger\MessengerInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\Core\Access\AccessibleInterface; -use Drupal\Core\Database\Query\Condition; use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Security\TrustedCallbackInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\node\NodeInterface; use Drupal\search\Plugin\ConfigurableSearchPluginBase; use Drupal\search\Plugin\SearchIndexingInterface; +use Drupal\search\SearchIndexInterface; use Drupal\Search\SearchQuery; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -33,7 +35,13 @@ * title = @Translation("Content") * ) */ -class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInterface, SearchIndexingInterface { +class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInterface, SearchIndexingInterface, TrustedCallbackInterface { + use DeprecatedServicePropertyTrait; + + /** + * {@inheritdoc} + */ + protected $deprecatedProperties = ['entityManager' => 'entity.manager']; /** * The current database connection. @@ -50,11 +58,11 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter protected $databaseReplica; /** - * An entity manager object. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * A module manager object. @@ -91,6 +99,13 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter */ protected $renderer; + /** + * The search index. + * + * @var \Drupal\search\SearchIndexInterface + */ + protected $searchIndex; + /** * An array of additional rankings from hook_ranking(). * @@ -139,14 +154,15 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->get('database'), - $container->get('entity.manager'), + $container->get('entity_type.manager'), $container->get('module_handler'), $container->get('config.factory')->get('search.settings'), $container->get('language_manager'), $container->get('renderer'), $container->get('messenger'), $container->get('current_user'), - $container->get('database.replica') + $container->get('database.replica'), + $container->get('search.index') ); } @@ -161,8 +177,8 @@ public static function create(ContainerInterface $container, array $configuratio * The plugin implementation definition. * @param \Drupal\Core\Database\Connection $database * The current database connection. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * An entity manager object. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * A module manager object. * @param \Drupal\Core\Config\Config $search_settings @@ -177,11 +193,13 @@ public static function create(ContainerInterface $container, array $configuratio * The $account object to use for checking for access to advanced search. * @param \Drupal\Core\Database\Connection|null $database_replica * (Optional) the replica database connection. + * @param \Drupal\search\SearchIndexInterface $search_index + * The search index. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, Config $search_settings, LanguageManagerInterface $language_manager, RendererInterface $renderer, MessengerInterface $messenger, AccountInterface $account = NULL, Connection $database_replica = NULL) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, Config $search_settings, LanguageManagerInterface $language_manager, RendererInterface $renderer, MessengerInterface $messenger, AccountInterface $account = NULL, Connection $database_replica = NULL, SearchIndexInterface $search_index = NULL) { $this->database = $database; $this->databaseReplica = $database_replica ?: $database; - $this->entityManager = $entity_manager; + $this->entityTypeManager = $entity_type_manager; $this->moduleHandler = $module_handler; $this->searchSettings = $search_settings; $this->languageManager = $language_manager; @@ -191,6 +209,11 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition parent::__construct($configuration, $plugin_id, $plugin_definition); $this->addCacheTags(['node_list']); + if (!$search_index) { + @trigger_error('Calling NodeSearch::__construct() without the $search_index argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/3075696', E_USER_DEPRECATED); + $search_index = \Drupal::service('search.index'); + } + $this->searchIndex = $search_index; } /** @@ -339,8 +362,8 @@ protected function findResults() { protected function prepareResults(StatementInterface $found) { $results = []; - $node_storage = $this->entityManager->getStorage('node'); - $node_render = $this->entityManager->getViewBuilder('node'); + $node_storage = $this->entityTypeManager->getStorage('node'); + $node_render = $this->entityTypeManager->getViewBuilder('node'); $keys = $this->keywords; foreach ($found as $item) { @@ -350,7 +373,7 @@ protected function prepareResults(StatementInterface $found) { $build = $node_render->view($node, 'search_result', $item->langcode); /** @var \Drupal\node\NodeTypeInterface $type*/ - $type = $this->entityManager->getStorage('node_type')->load($node->bundle()); + $type = $this->entityTypeManager->getStorage('node_type')->load($node->bundle()); unset($build['#theme']); $build['#pre_render'][] = [$this, 'removeSubmittedInfo']; @@ -470,9 +493,15 @@ public function updateIndex() { return; } - $node_storage = $this->entityManager->getStorage('node'); - foreach ($node_storage->loadMultiple($nids) as $node) { - $this->indexNode($node); + $node_storage = $this->entityTypeManager->getStorage('node'); + $words = []; + try { + foreach ($node_storage->loadMultiple($nids) as $node) { + $words += $this->indexNode($node); + } + } + finally { + $this->searchIndex->updateWordWeights($words); } } @@ -481,10 +510,14 @@ public function updateIndex() { * * @param \Drupal\node\NodeInterface $node * The node to index. + * + * @return array + * An array of words to update after indexing. */ protected function indexNode(NodeInterface $node) { + $words = []; $languages = $node->getTranslationLanguages(); - $node_render = $this->entityManager->getViewBuilder('node'); + $node_render = $this->entityTypeManager->getViewBuilder('node'); foreach ($languages as $language) { $node = $node->getTranslation($language->getId()); @@ -509,8 +542,9 @@ protected function indexNode(NodeInterface $node) { } // Update index, using search index "type" equal to the plugin ID. - search_index($this->getPluginId(), $node->id(), $language->getId(), $text); + $words += $this->searchIndex->index($this->getPluginId(), $node->id(), $language->getId(), $text, FALSE); } + return $words; } /** @@ -519,7 +553,7 @@ protected function indexNode(NodeInterface $node) { public function indexClear() { // All NodeSearch pages share a common search index "type" equal to // the plugin ID. - search_index_clear($this->getPluginId()); + $this->searchIndex->clear($this->getPluginId()); } /** @@ -528,7 +562,7 @@ public function indexClear() { public function markForReindex() { // All NodeSearch pages share a common search index "type" equal to // the plugin ID. - search_mark_for_reindex($this->getPluginId()); + $this->searchIndex->markForReindex($this->getPluginId()); } /** @@ -844,4 +878,11 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s } } + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['removeSubmittedInfo']; + } + } diff --git a/core/modules/node/src/Plugin/migrate/source/d6/Node.php b/core/modules/node/src/Plugin/migrate/source/d6/Node.php index d20545f2e..2d43c3a1c 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/Node.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/Node.php @@ -3,7 +3,7 @@ namespace Drupal\node\Plugin\migrate\source\d6; use Drupal\Core\Database\Query\SelectInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandler; use Drupal\Core\State\StateInterface; use Drupal\migrate\Plugin\MigrationInterface; @@ -51,8 +51,8 @@ class Node extends DrupalSqlBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager, ModuleHandler $module_handler) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager); + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager, ModuleHandler $module_handler) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager); $this->moduleHandler = $module_handler; } @@ -66,7 +66,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_definition, $migration, $container->get('state'), - $container->get('entity.manager'), + $container->get('entity_type.manager'), $container->get('module_handler') ); } @@ -295,7 +295,7 @@ protected function getFieldData(array $field, Row $node) { /** * Retrieves raw field data for a node. * - * @deprecated in Drupal 8.2.x, to be removed in Drupal 9.0.x. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * getFieldData() instead. * * @param array $field diff --git a/core/modules/node/src/Plugin/migrate/source/d6/ViewMode.php b/core/modules/node/src/Plugin/migrate/source/d6/ViewMode.php index 434007a0a..2a938af47 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/ViewMode.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/ViewMode.php @@ -71,7 +71,7 @@ public function getIds() { public function calculateDependencies() { $this->dependencies = parent::calculateDependencies(); if (isset($this->configuration['constants']['targetEntityType'])) { - $this->addDependency('module', $this->entityManager->getDefinition($this->configuration['constants']['targetEntityType'])->getProvider()); + $this->addDependency('module', $this->entityTypeManager->getDefinition($this->configuration['constants']['targetEntityType'])->getProvider()); } return $this->dependencies; } diff --git a/core/modules/node/src/Plugin/migrate/source/d7/Node.php b/core/modules/node/src/Plugin/migrate/source/d7/Node.php index 931a2b943..4a8cce440 100644 --- a/core/modules/node/src/Plugin/migrate/source/d7/Node.php +++ b/core/modules/node/src/Plugin/migrate/source/d7/Node.php @@ -6,8 +6,7 @@ use Drupal\migrate\Row; use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity; use Drupal\Core\Database\Query\SelectInterface; -use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Extension\ModuleHandler; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\migrate\Plugin\MigrationInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -31,8 +30,8 @@ class Node extends FieldableEntity { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager); + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager); $this->moduleHandler = $module_handler; } @@ -46,7 +45,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_definition, $migration, $container->get('state'), - $container->get('entity.manager'), + $container->get('entity_type.manager'), $container->get('module_handler') ); } diff --git a/core/modules/node/src/Plugin/views/argument/Nid.php b/core/modules/node/src/Plugin/views/argument/Nid.php index 3680ee738..382b66643 100644 --- a/core/modules/node/src/Plugin/views/argument/Nid.php +++ b/core/modules/node/src/Plugin/views/argument/Nid.php @@ -44,7 +44,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('entity.manager')->getStorage('node') + $container->get('entity_type.manager')->getStorage('node') ); } diff --git a/core/modules/node/src/Plugin/views/argument/Type.php b/core/modules/node/src/Plugin/views/argument/Type.php index 1a78c2faf..135e3952c 100644 --- a/core/modules/node/src/Plugin/views/argument/Type.php +++ b/core/modules/node/src/Plugin/views/argument/Type.php @@ -42,12 +42,12 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - $entity_manager = $container->get('entity.manager'); + $entity_type_manager = $container->get('entity_type.manager'); return new static( $configuration, $plugin_id, $plugin_definition, - $entity_manager->getStorage('node_type') + $entity_type_manager->getStorage('node_type') ); } diff --git a/core/modules/node/src/Plugin/views/argument/Vid.php b/core/modules/node/src/Plugin/views/argument/Vid.php index 32de757c6..ff1c0aca6 100644 --- a/core/modules/node/src/Plugin/views/argument/Vid.php +++ b/core/modules/node/src/Plugin/views/argument/Vid.php @@ -58,7 +58,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->get('database'), - $container->get('entity.manager')->getStorage('node') + $container->get('entity_type.manager')->getStorage('node') ); } diff --git a/core/modules/node/src/Plugin/views/field/Path.php b/core/modules/node/src/Plugin/views/field/Path.php index d64d25041..75b87606b 100644 --- a/core/modules/node/src/Plugin/views/field/Path.php +++ b/core/modules/node/src/Plugin/views/field/Path.php @@ -18,7 +18,7 @@ * * @ViewsField("node_path") * - * @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. * Use @ViewsField("entity_link") with 'output_url_as_text' set. */ class Path extends FieldPluginBase { diff --git a/core/modules/node/src/Plugin/views/row/Rss.php b/core/modules/node/src/Plugin/views/row/Rss.php index b98b98fee..80fac8b60 100644 --- a/core/modules/node/src/Plugin/views/row/Rss.php +++ b/core/modules/node/src/Plugin/views/row/Rss.php @@ -129,7 +129,9 @@ public function render($row) { $build_mode = $display_mode; - $build = node_view($node, $build_mode); + $build = \Drupal::entityTypeManager() + ->getViewBuilder('node') + ->view($node, $build_mode); unset($build['#theme']); if (!empty($node->rss_namespaces)) { diff --git a/core/modules/node/src/Plugin/views/wizard/Node.php b/core/modules/node/src/Plugin/views/wizard/Node.php index 0fd93675a..5f53d971b 100644 --- a/core/modules/node/src/Plugin/views/wizard/Node.php +++ b/core/modules/node/src/Plugin/views/wizard/Node.php @@ -2,8 +2,13 @@ namespace Drupal\node\Plugin\views\wizard; +use Drupal\Core\Entity\EntityDisplayRepositoryInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\views\wizard\WizardPluginBase; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * @todo: replace numbers with constants. @@ -27,6 +32,65 @@ class Node extends WizardPluginBase { */ protected $createdColumn = 'node_field_data-created'; + /** + * The entity display repository. + * + * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface + */ + protected $entityDisplayRepository; + + /** + * The entity field manager. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + protected $entityFieldManager; + + /** + * Node constructor. + * + * @param array $configuration + * The plugin configuration. + * @param string $plugin_id + * The plugin ID. + * @param mixed $plugin_definition + * The plugin definition. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info_service + * The entity bundle info service. + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository + * The entity display repository service. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $bundle_info_service, EntityDisplayRepositoryInterface $entity_display_repository = NULL, EntityFieldManagerInterface $entity_field_manager = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $bundle_info_service); + + if (!$entity_display_repository) { + @trigger_error('The entity_display.repository service must be passed to ' . __METHOD__ . ', it is required before Drupal 9.0.0. See https://www.drupal.org/node/2835616.', E_USER_DEPRECATED); + $entity_display_repository = \Drupal::service('entity_display.repository'); + } + $this->entityDisplayRepository = $entity_display_repository; + if (!$entity_field_manager) { + @trigger_error('The entity_field.manager service must be passed to ' . __METHOD__ . ', it is required before Drupal 9.0.0. See https://www.drupal.org/node/2835616.', E_USER_DEPRECATED); + $entity_field_manager = \Drupal::service('entity_field.manager'); + } + $this->entityFieldManager = $entity_field_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.bundle.info'), + $container->get('entity_display.repository'), + $container->get('entity_field.manager') + ); + } + /** * Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::getAvailableSorts(). * @@ -213,8 +277,8 @@ protected function buildFilters(&$form, FormStateInterface $form_state) { } $tag_fields = []; foreach ($bundles as $bundle) { - $display = entity_get_form_display($this->entityTypeId, $bundle, 'default'); - $taxonomy_fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($this->entityTypeId, $bundle), function ($field_definition) { + $display = $this->entityDisplayRepository->getFormDisplay($this->entityTypeId, $bundle); + $taxonomy_fields = array_filter($this->entityFieldManager->getFieldDefinitions($this->entityTypeId, $bundle), function (FieldDefinitionInterface $field_definition) { return $field_definition->getType() == 'entity_reference' && $field_definition->getSetting('target_type') == 'taxonomy_term'; }); foreach ($taxonomy_fields as $field_name => $field) { diff --git a/core/modules/node/src/Tests/AssertButtonsTrait.php b/core/modules/node/src/Tests/AssertButtonsTrait.php index 97016dc2e..db57fe40d 100644 --- a/core/modules/node/src/Tests/AssertButtonsTrait.php +++ b/core/modules/node/src/Tests/AssertButtonsTrait.php @@ -7,7 +7,7 @@ /** * Asserts that buttons are present on a page. * - * @deprecated Scheduled for removal before Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\node\Functional\AssertButtonsTrait instead. */ trait AssertButtonsTrait { diff --git a/core/modules/node/src/Tests/NodeTestBase.php b/core/modules/node/src/Tests/NodeTestBase.php index e520b3283..ba619ac23 100644 --- a/core/modules/node/src/Tests/NodeTestBase.php +++ b/core/modules/node/src/Tests/NodeTestBase.php @@ -4,6 +4,7 @@ @trigger_error(__NAMESPACE__ . '\NodeTestBase is deprecated for removal before Drupal 9.0.0. Use Drupal\Tests\node\Functional\NodeTestBase instead. See https://www.drupal.org/node/2999939', E_USER_DEPRECATED); +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Session\AccountInterface; use Drupal\node\NodeInterface; use Drupal\simpletest\WebTestBase; @@ -11,7 +12,7 @@ /** * Sets up page and article content types. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\node\Functional\NodeTestBase instead. * * @see https://www.drupal.org/node/2999939 @@ -47,7 +48,7 @@ protected function setUp() { ]); $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); } - $this->accessHandler = \Drupal::entityManager()->getAccessControlHandler('node'); + $this->accessHandler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); } /** @@ -104,7 +105,7 @@ public function assertNodeCreateAccess($bundle, $result, AccountInterface $accou * about the node access permission test that was performed. */ public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) { - return format_string( + return new FormattableMarkup( 'Node access returns @result with operation %op, language code %langcode.', [ '@result' => $result ? 'true' : 'false', diff --git a/core/modules/node/src/Tests/Views/NodeTestBase.php b/core/modules/node/src/Tests/Views/NodeTestBase.php index cb765d4c5..da5297fde 100644 --- a/core/modules/node/src/Tests/Views/NodeTestBase.php +++ b/core/modules/node/src/Tests/Views/NodeTestBase.php @@ -10,7 +10,7 @@ /** * Base class for all node tests. * - * @deprecated Scheduled for removal before Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\node\Functional\Views\NodeTestBase instead. */ abstract class NodeTestBase extends ViewTestBase { diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.info.yml b/core/modules/node/tests/modules/node_access_test/node_access_test.info.yml index 667d75d8a..f63cdd659 100644 --- a/core/modules/node/tests/modules/node_access_test/node_access_test.info.yml +++ b/core/modules/node/tests/modules/node_access_test/node_access_test.info.yml @@ -2,11 +2,5 @@ name: 'Node module access tests' type: module description: 'Support module for node permission testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module index 9fca50c4e..589accef2 100644 --- a/core/modules/node/tests/modules/node_access_test/node_access_test.module +++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module @@ -135,7 +135,8 @@ function node_access_test_add_field(NodeTypeInterface $type) { $field->save(); // Assign widget settings for the 'default' form mode. - entity_get_form_display('node', $type->id(), 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('node', $type->id()) ->setComponent('private', [ 'type' => 'number', ]) diff --git a/core/modules/node/tests/modules/node_access_test_empty/node_access_test_empty.info.yml b/core/modules/node/tests/modules/node_access_test_empty/node_access_test_empty.info.yml index 6056affdc..8daafc25a 100644 --- a/core/modules/node/tests/modules/node_access_test_empty/node_access_test_empty.info.yml +++ b/core/modules/node/tests/modules/node_access_test_empty/node_access_test_empty.info.yml @@ -2,11 +2,5 @@ name: 'Node module empty access tests' type: module description: 'Support module for node permission testing. Provides empty grants hook implementations.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info.yml b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info.yml index e114fcd5b..aeec34892 100644 --- a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info.yml +++ b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info.yml @@ -2,13 +2,7 @@ name: 'Node module access tests language' type: module description: 'Support module for language-aware node access testing.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:options - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/node/tests/modules/node_display_configurable_test/node_display_configurable_test.info.yml b/core/modules/node/tests/modules/node_display_configurable_test/node_display_configurable_test.info.yml index 51a608a10..c6b478d27 100644 --- a/core/modules/node/tests/modules/node_display_configurable_test/node_display_configurable_test.info.yml +++ b/core/modules/node/tests/modules/node_display_configurable_test/node_display_configurable_test.info.yml @@ -2,11 +2,5 @@ name: 'Node configurable display module tests' type: module description: 'Support module for node \Drupal\Core\Field\BaseFieldDefinition::setDisplayConfigurable() testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/node/tests/modules/node_test/node_test.info.yml b/core/modules/node/tests/modules/node_test/node_test.info.yml index 1d24c7a3b..952456fe2 100644 --- a/core/modules/node/tests/modules/node_test/node_test.info.yml +++ b/core/modules/node/tests/modules/node_test/node_test.info.yml @@ -2,11 +2,5 @@ name: 'Node module tests' type: module description: 'Support module for node related testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/node/tests/modules/node_test_config/node_test_config.info.yml b/core/modules/node/tests/modules/node_test_config/node_test_config.info.yml index a014aaf1a..c7e766570 100644 --- a/core/modules/node/tests/modules/node_test_config/node_test_config.info.yml +++ b/core/modules/node/tests/modules/node_test_config/node_test_config.info.yml @@ -1,12 +1,6 @@ name: 'Node configuration tests' type: module description: 'Support module for node configuration tests.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/node/tests/modules/node_test_exception/node_test_exception.info.yml b/core/modules/node/tests/modules/node_test_exception/node_test_exception.info.yml index a4aa0092c..fac3fe797 100644 --- a/core/modules/node/tests/modules/node_test_exception/node_test_exception.info.yml +++ b/core/modules/node/tests/modules/node_test_exception/node_test_exception.info.yml @@ -2,11 +2,5 @@ name: 'Node module exception tests' type: module description: 'Support module for node related exception testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/node/tests/modules/node_test_views/node_test_views.info.yml b/core/modules/node/tests/modules/node_test_views/node_test_views.info.yml index b47bfa701..a6ded4659 100644 --- a/core/modules/node/tests/modules/node_test_views/node_test_views.info.yml +++ b/core/modules/node/tests/modules/node_test_views/node_test_views.info.yml @@ -2,15 +2,9 @@ name: 'Node test views' type: module description: 'Provides default views for views node tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:node - drupal:views - drupal:language - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_argument_node_uid_revision.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_argument_node_uid_revision.yml index 00ee678fc..91ad37d92 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_argument_node_uid_revision.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_argument_node_uid_revision.yml @@ -11,7 +11,6 @@ description: '' tag: default base_table: node_field_data base_field: nid -core: 8.0-dev display: default: display_options: diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_contextual_links.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_contextual_links.yml index 37b3f8dab..3edaf70d6 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_contextual_links.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_contextual_links.yml @@ -11,7 +11,6 @@ description: '' tag: default base_table: node_field_data base_field: nid -core: 8.x display: default: display_options: diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml index fcfc4f096..1c419c1ca 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_access.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_access.yml index 6663fa826..a80e6976a 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_access.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_access.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_uid_revision.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_uid_revision.yml index 4f8ba5026..5770f0718 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_uid_revision.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_filter_node_uid_revision.yml @@ -11,7 +11,6 @@ description: '' tag: default base_table: node_field_data base_field: nid -core: 8.0-dev display: default: display_options: diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml index d95efc061..b2bbe805a 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml index f52109371..59f0103b0 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml index 7469b7c43..d2962c24e 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_path_plugin.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_path_plugin.yml index 747de3578..fc09130d2 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_path_plugin.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_path_plugin.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_links.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_links.yml index a17157426..984a66287 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_links.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_links.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: node_field_revision base_field: vid -core: '8' display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml index 8c5d4cb4b..ba7adc366 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: node_field_revision base_field: nid -core: '8' display: default: display_options: diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml index a0ba6eb85..f22a278b5 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: node_field_revision base_field: vid -core: '8' display: default: display_options: diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_uid.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_uid.yml new file mode 100644 index 000000000..19d5b7f2d --- /dev/null +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_uid.yml @@ -0,0 +1,389 @@ +langcode: en +status: true +dependencies: + module: + - node + - user +id: test_node_revision_uid +label: 'Test node revision uid' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: none + options: + offset: 0 + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + nid: + id: nid + table: node_field_revision + field: nid + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: nid + plugin_id: field + vid: + id: vid + table: node_field_revision + field: vid + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: vid + plugin_id: field + uid: + id: uid + table: node_field_data + field: uid + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: uid + plugin_id: field + revision_uid: + id: revision_uid + table: node_revision + field: revision_uid + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: revision_uid + plugin_id: field + filters: + revision_uid: + id: revision_uid + table: node_revision + field: revision_uid + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: revision_uid_op + label: 'Revision user' + description: '' + use_operator: false + operator: revision_uid_op + operator_limit_selection: false + operator_list: { } + identifier: revision_uid + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: node + entity_field: revision_uid + plugin_id: user_name + sorts: { } + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + filter_groups: + operator: AND + groups: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml index 3c9aac56f..a623a008a 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: node_field_revision base_field: nid -core: '8' display: default: display_options: diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_row_plugin.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_row_plugin.yml index ea4bd5020..feac67b4e 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_row_plugin.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_row_plugin.yml @@ -11,7 +11,6 @@ description: '' tag: default base_table: node_field_data base_field: nid -core: '8' display: default: display_options: diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_tokens.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_tokens.yml index 26517fc21..398afccdf 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_tokens.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_tokens.yml @@ -14,7 +14,6 @@ description: 'Verifies tokens provided by the Node module are replaced correctly tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_view.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_view.yml index 2fa971787..835630e30 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_view.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_view.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_status_extra.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_status_extra.yml index 8c6f7cfde..a5ee2a194 100644 --- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_status_extra.yml +++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_status_extra.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/node/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.info.yml b/core/modules/node/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.info.yml index 5b3e94c89..49a990d75 100644 --- a/core/modules/node/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.info.yml +++ b/core/modules/node/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.info.yml @@ -2,11 +2,5 @@ name: 'Node module access automatic cacheability bubbling tests' type: module description: 'Support module for node permission testing. Provides a route which does a node access query without explicitly specifying the corresponding cache context.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/node/tests/src/Functional/Hal/NodeHalJsonAnonTest.php b/core/modules/node/tests/src/Functional/Hal/NodeHalJsonAnonTest.php index f474c54f7..10f101eb3 100644 --- a/core/modules/node/tests/src/Functional/Hal/NodeHalJsonAnonTest.php +++ b/core/modules/node/tests/src/Functional/Hal/NodeHalJsonAnonTest.php @@ -20,6 +20,11 @@ class NodeHalJsonAnonTest extends NodeResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Hal/NodeHalJsonBasicAuthTest.php b/core/modules/node/tests/src/Functional/Hal/NodeHalJsonBasicAuthTest.php index 0c973a168..fd238757c 100644 --- a/core/modules/node/tests/src/Functional/Hal/NodeHalJsonBasicAuthTest.php +++ b/core/modules/node/tests/src/Functional/Hal/NodeHalJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class NodeHalJsonBasicAuthTest extends NodeHalJsonAnonTest { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Hal/NodeHalJsonCookieTest.php b/core/modules/node/tests/src/Functional/Hal/NodeHalJsonCookieTest.php index e29a58786..1c49e78c7 100644 --- a/core/modules/node/tests/src/Functional/Hal/NodeHalJsonCookieTest.php +++ b/core/modules/node/tests/src/Functional/Hal/NodeHalJsonCookieTest.php @@ -16,4 +16,9 @@ class NodeHalJsonCookieTest extends NodeHalJsonAnonTest { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonAnonTest.php b/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonAnonTest.php index 68f531c89..8a7e8d9b6 100644 --- a/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonAnonTest.php +++ b/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonAnonTest.php @@ -17,6 +17,11 @@ class NodeTypeHalJsonAnonTest extends NodeTypeResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonBasicAuthTest.php b/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonBasicAuthTest.php index fb7098bdc..2c8760ba7 100644 --- a/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonBasicAuthTest.php +++ b/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class NodeTypeHalJsonBasicAuthTest extends NodeTypeResourceTestBase { */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonCookieTest.php b/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonCookieTest.php index 9d7f5a9a3..96c7046d0 100644 --- a/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonCookieTest.php +++ b/core/modules/node/tests/src/Functional/Hal/NodeTypeHalJsonCookieTest.php @@ -17,6 +17,11 @@ class NodeTypeHalJsonCookieTest extends NodeTypeResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/MultiStepNodeFormBasicOptionsTest.php b/core/modules/node/tests/src/Functional/MultiStepNodeFormBasicOptionsTest.php index 93c225786..837d96ce3 100644 --- a/core/modules/node/tests/src/Functional/MultiStepNodeFormBasicOptionsTest.php +++ b/core/modules/node/tests/src/Functional/MultiStepNodeFormBasicOptionsTest.php @@ -12,6 +12,11 @@ */ class MultiStepNodeFormBasicOptionsTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The field name to create. * @@ -43,7 +48,8 @@ public function testMultiStepNodeFormBasicOptions() { 'bundle' => 'page', 'label' => $this->randomMachineName() . '_label', ])->save(); - entity_get_form_display('node', 'page', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('node', 'page') ->setComponent($this->fieldName, [ 'type' => 'text_textfield', ]) diff --git a/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php b/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php index 20b5f63eb..1cc590eb7 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessBaseTableTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\node\Functional; +use Drupal\Core\Database\Database; use Drupal\node\Entity\NodeType; /** @@ -18,6 +19,11 @@ class NodeAccessBaseTableTest extends NodeTestBase { */ public static $modules = ['node_access_test', 'views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The installation profile to use with this test. * @@ -122,10 +128,11 @@ public function testNodeAccessBasic() { $this->nodesByUser[$this->webUser->id()][$node->id()] = $is_private; } } - $this->publicTid = db_query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', [':name' => 'public'])->fetchField(); - $this->privateTid = db_query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', [':name' => 'private'])->fetchField(); - $this->assertTrue($this->publicTid, 'Public tid was found'); - $this->assertTrue($this->privateTid, 'Private tid was found'); + $connection = Database::getConnection(); + $this->publicTid = $connection->query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', [':name' => 'public'])->fetchField(); + $this->privateTid = $connection->query('SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1', [':name' => 'private'])->fetchField(); + $this->assertNotEmpty($this->publicTid, 'Public tid was found'); + $this->assertNotEmpty($this->privateTid, 'Private tid was found'); foreach ($simple_users as $this->webUser) { $this->drupalLogin($this->webUser); // Check own nodes to see that all are readable. @@ -198,7 +205,7 @@ protected function assertTaxonomyPage($is_admin) { $this->nidsVisible = []; foreach ($this->xpath("//a[text()='Read more']") as $link) { // See also testTranslationRendering() in NodeTranslationUITest. - $this->assertTrue(preg_match('|node/(\d+)$|', $link->getAttribute('href'), $matches), 'Read more points to a node'); + $this->assertEquals(1, preg_match('|node/(\d+)$|', $link->getAttribute('href'), $matches), 'Read more points to a node'); $this->nidsVisible[$matches[1]] = TRUE; } foreach ($this->nodesByUser as $uid => $data) { diff --git a/core/modules/node/tests/src/Functional/NodeAccessCacheabilityTest.php b/core/modules/node/tests/src/Functional/NodeAccessCacheabilityTest.php index 84af1ff18..abaee259a 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessCacheabilityTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessCacheabilityTest.php @@ -23,6 +23,11 @@ class NodeAccessCacheabilityTest extends NodeTestBase { */ public static $modules = ['node_access_test', 'node_access_test_auto_bubbling']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/NodeAccessFieldTest.php b/core/modules/node/tests/src/Functional/NodeAccessFieldTest.php index ab6f28df9..d92b5ef8a 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessFieldTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessFieldTest.php @@ -19,6 +19,11 @@ class NodeAccessFieldTest extends NodeTestBase { */ public static $modules = ['node_access_test', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to bypass access content. * @@ -61,10 +66,12 @@ protected function setUp() { 'entity_type' => 'node', 'bundle' => 'page', ])->save(); - entity_get_display('node', 'page', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $display_repository->getViewDisplay('node', 'page') ->setComponent($this->fieldName) ->save(); - entity_get_form_display('node', 'page', 'default') + $display_repository->getFormDisplay('node', 'page') ->setComponent($this->fieldName) ->save(); } diff --git a/core/modules/node/tests/src/Functional/NodeAccessGrantsCacheContextTest.php b/core/modules/node/tests/src/Functional/NodeAccessGrantsCacheContextTest.php index e0a466e21..ba9f2bb15 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessGrantsCacheContextTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessGrantsCacheContextTest.php @@ -19,6 +19,11 @@ class NodeAccessGrantsCacheContextTest extends NodeTestBase { */ public static $modules = ['node_access_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * User with permission to view content. */ diff --git a/core/modules/node/tests/src/Functional/NodeAccessLanguageFallbackTest.php b/core/modules/node/tests/src/Functional/NodeAccessLanguageFallbackTest.php index 4beacc342..b53e36666 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessLanguageFallbackTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessLanguageFallbackTest.php @@ -18,6 +18,11 @@ class NodeAccessLanguageFallbackTest extends NodeTestBase { */ public static $modules = ['language', 'node_access_test', 'content_translation']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/NodeAccessMenuLinkTest.php b/core/modules/node/tests/src/Functional/NodeAccessMenuLinkTest.php index 5d987b36a..54c912345 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessMenuLinkTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessMenuLinkTest.php @@ -18,6 +18,11 @@ class NodeAccessMenuLinkTest extends NodeTestBase { */ public static $modules = ['menu_ui', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to manage menu links and create nodes. * diff --git a/core/modules/node/tests/src/Functional/NodeAccessPagerTest.php b/core/modules/node/tests/src/Functional/NodeAccessPagerTest.php index ad6cc5494..76acb9f9f 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessPagerTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessPagerTest.php @@ -23,6 +23,11 @@ class NodeAccessPagerTest extends BrowserTestBase { */ public static $modules = ['node_access_test', 'comment', 'forum']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -71,12 +76,12 @@ public function testCommentPager() { public function testForumPager() { // Look up the forums vocabulary ID. $vid = $this->config('forum.settings')->get('vocabulary'); - $this->assertTrue($vid, 'Forum navigation vocabulary ID is set.'); + $this->assertNotEmpty($vid, 'Forum navigation vocabulary ID is set.'); // Look up the general discussion term. - $tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid, 0, 1); + $tree = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid, 0, 1); $tid = reset($tree)->tid; - $this->assertTrue($tid, 'General discussion term is found in the forum vocabulary.'); + $this->assertNotEmpty($tid, 'General discussion term is found in the forum vocabulary.'); // Create 30 nodes. for ($i = 0; $i < 30; $i++) { diff --git a/core/modules/node/tests/src/Functional/NodeAccessRebuildNodeGrantsTest.php b/core/modules/node/tests/src/Functional/NodeAccessRebuildNodeGrantsTest.php index 7ed8413fc..8c61404b8 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessRebuildNodeGrantsTest.php +++ b/core/modules/node/tests/src/Functional/NodeAccessRebuildNodeGrantsTest.php @@ -12,6 +12,11 @@ */ class NodeAccessRebuildNodeGrantsTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user to create nodes that only it has access to. * @@ -60,7 +65,7 @@ public function testNodeAccessRebuildNodeGrants() { $grant_storage = \Drupal::service('node.grant_storage'); // Default realm access and node records are present. foreach ($nodes as $node) { - $this->assertTrue($node->private->value); + $this->assertNotEmpty($node->private->value); $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the node author.'); $this->assertTrue($grant_storage->access($node, 'view', $this->adminUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the admin user.'); } @@ -82,7 +87,7 @@ public function testNodeAccessRebuildNodeGrants() { $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.'); $this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.'); } - $this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record'); + $this->assertEmpty(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record'); // Test an anonymous node access rebuild from code. $this->drupalLogout(); @@ -91,7 +96,7 @@ public function testNodeAccessRebuildNodeGrants() { $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.'); $this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.'); } - $this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record'); + $this->assertEmpty(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record'); } /** @@ -102,7 +107,7 @@ public function testNodeAccessRebuildNoAccessModules() { $this->assertEqual(1, \Drupal::service('node.grant_storage')->count(), 'There is an all realm access record'); // No need to rebuild permissions. - $this->assertFalse(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt'); + $this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt'); // Rebuild permissions. $this->drupalGet('admin/reports/status'); diff --git a/core/modules/node/tests/src/Functional/NodeAccessRecordsTest.php b/core/modules/node/tests/src/Functional/NodeAccessRecordsTest.php deleted file mode 100644 index 259ed83ef..000000000 --- a/core/modules/node/tests/src/Functional/NodeAccessRecordsTest.php +++ /dev/null @@ -1,84 +0,0 @@ -drupalCreateNode(['type' => 'article']); - $this->assertTrue(Node::load($node1->id()), 'Article node created.'); - - // Check to see if grants added by node_test_node_access_records made it in. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node1->id()])->fetchAll(); - $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); - $this->assertEqual($records[0]->realm, 'test_article_realm', 'Grant with article_realm acquired for node without alteration.'); - $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.'); - - // Create an unpromoted "Basic page" node. - $node2 = $this->drupalCreateNode(['type' => 'page', 'promote' => 0]); - $this->assertTrue(Node::load($node2->id()), 'Unpromoted basic page node created.'); - - // Check to see if grants added by node_test_node_access_records made it in. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node2->id()])->fetchAll(); - $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); - $this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.'); - $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.'); - - // Create an unpromoted, unpublished "Basic page" node. - $node3 = $this->drupalCreateNode(['type' => 'page', 'promote' => 0, 'status' => 0]); - $this->assertTrue(Node::load($node3->id()), 'Unpromoted, unpublished basic page node created.'); - - // Check to see if grants added by node_test_node_access_records made it in. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node3->id()])->fetchAll(); - $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); - $this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.'); - $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.'); - - // Create a promoted "Basic page" node. - $node4 = $this->drupalCreateNode(['type' => 'page', 'promote' => 1]); - $this->assertTrue(Node::load($node4->id()), 'Promoted basic page node created.'); - - // Check to see if grant added by node_test_node_access_records was altered - // by node_test_node_access_records_alter. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node4->id()])->fetchAll(); - $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); - $this->assertEqual($records[0]->realm, 'test_alter_realm', 'Altered grant with alter_realm acquired for node.'); - $this->assertEqual($records[0]->gid, 2, 'Altered grant with gid = 2 acquired for node.'); - - // Check to see if we can alter grants with hook_node_grants_alter(). - $operations = ['view', 'update', 'delete']; - // Create a user that is allowed to access content. - $web_user = $this->drupalCreateUser(['access content']); - foreach ($operations as $op) { - $grants = node_test_node_grants($web_user, $op); - $altered_grants = $grants; - \Drupal::moduleHandler()->alter('node_grants', $altered_grants, $web_user, $op); - $this->assertNotEqual($grants, $altered_grants, format_string('Altered the %op grant for a user.', ['%op' => $op])); - } - - // Check that core does not grant access to an unpublished node when an - // empty $grants array is returned. - $node6 = $this->drupalCreateNode(['status' => 0, 'disable_node_access' => TRUE]); - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node6->id()])->fetchAll(); - $this->assertEqual(count($records), 0, 'Returned no records for unpublished node.'); - } - -} diff --git a/core/modules/node/tests/src/Functional/NodeActionsConfigurationTest.php b/core/modules/node/tests/src/Functional/NodeActionsConfigurationTest.php index d026902f5..c9f094969 100644 --- a/core/modules/node/tests/src/Functional/NodeActionsConfigurationTest.php +++ b/core/modules/node/tests/src/Functional/NodeActionsConfigurationTest.php @@ -19,6 +19,11 @@ class NodeActionsConfigurationTest extends BrowserTestBase { */ public static $modules = ['action', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests configuration of the node_assign_owner_action action. */ @@ -77,7 +82,7 @@ public function testAssignOwnerNodeActionConfiguration() { $this->assertNoText($new_action_label, 'The label for the node_assign_owner_action action does not appear on the actions administration page after deleting.'); $action = Action::load($action_id); - $this->assertFalse($action, 'The node_assign_owner_action action is not available after being deleted.'); + $this->assertNull($action, 'The node_assign_owner_action action is not available after being deleted.'); } } diff --git a/core/modules/node/tests/src/Functional/NodeAdminTest.php b/core/modules/node/tests/src/Functional/NodeAdminTest.php index 24c62dda6..9cbb42d9d 100644 --- a/core/modules/node/tests/src/Functional/NodeAdminTest.php +++ b/core/modules/node/tests/src/Functional/NodeAdminTest.php @@ -11,6 +11,12 @@ * @group node */ class NodeAdminTest extends NodeTestBase { + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A user with permission to bypass access content. * diff --git a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php index cc8e5703b..b0b0aae17 100644 --- a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php +++ b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php @@ -17,6 +17,11 @@ class NodeBlockFunctionalTest extends NodeTestBase { use AssertPageCacheContextsAndTagsTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An administrative user for testing. * @@ -98,7 +103,7 @@ public function testRecentNodeBlock() { $this->assertText($node3->label(), 'Node found in block.'); // Check to make sure nodes are in the right order. - $this->assertTrue($this->xpath('//div[@id="block-test-block"]//div[@class="item-list"]/ul/li[1]/div/span/a[text() = "' . $node3->label() . '"]'), 'Nodes were ordered correctly in block.'); + $this->assertNotEmpty($this->xpath('//div[@id="block-test-block"]//div[@class="item-list"]/ul/li[1]/div/span/a[text() = "' . $node3->label() . '"]'), 'Nodes were ordered correctly in block.'); $this->drupalLogout(); $this->drupalLogin($this->adminUser); diff --git a/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php b/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php index cae058bd5..14c02669d 100644 --- a/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php +++ b/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php @@ -19,6 +19,11 @@ class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/NodeContextualLinksTest.php b/core/modules/node/tests/src/Functional/NodeContextualLinksTest.php index 0d4aabe6a..a27bdc0de 100644 --- a/core/modules/node/tests/src/Functional/NodeContextualLinksTest.php +++ b/core/modules/node/tests/src/Functional/NodeContextualLinksTest.php @@ -18,6 +18,11 @@ class NodeContextualLinksTest extends NodeTestBase { 'contextual', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests contextual links. */ diff --git a/core/modules/node/tests/src/Functional/NodeCreationTest.php b/core/modules/node/tests/src/Functional/NodeCreationTest.php index 1bee2b6e7..c64ace0af 100644 --- a/core/modules/node/tests/src/Functional/NodeCreationTest.php +++ b/core/modules/node/tests/src/Functional/NodeCreationTest.php @@ -23,6 +23,11 @@ class NodeCreationTest extends NodeTestBase { */ public static $modules = ['node_test_exception', 'dblog', 'test_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -34,7 +39,7 @@ protected function setUp() { * Creates a "Basic page" node and verifies its consistency in the database. */ public function testNodeCreation() { - $node_type_storage = \Drupal::entityManager()->getStorage('node_type'); + $node_type_storage = \Drupal::entityTypeManager()->getStorage('node_type'); // Test /node/add page with only one content type. $node_type_storage->load('article')->delete(); @@ -56,7 +61,7 @@ public function testNodeCreation() { // Check that the node exists in the database. $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); - $this->assertTrue($node, 'Node found in database.'); + $this->assertNotEmpty($node, 'Node found in database.'); // Verify that pages do not show submitted information by default. $this->drupalGet('node/' . $node->id()); @@ -97,10 +102,10 @@ public function testFailedPageCreation() { // An exception is generated by node_test_exception_node_insert() if the // title is 'testing_transaction_exception'. Node::create($edit)->save(); - $this->fail(t('Expected exception has not been thrown.')); + $this->fail('Expected exception has not been thrown.'); } catch (\Exception $e) { - $this->pass(t('Expected exception has been thrown.')); + $this->pass('Expected exception has been thrown.'); } if (Database::getConnection()->supportsTransactions()) { @@ -131,7 +136,7 @@ public function testUnpublishedNodeCreation() { $this->config('system.site')->set('page.front', '/test-page')->save(); // Set "Basic page" content type to be unpublished by default. - $fields = \Drupal::entityManager()->getFieldDefinitions('node', 'page'); + $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page'); $fields['status']->getConfig('page') ->setDefaultValue(FALSE) ->save(); @@ -248,7 +253,7 @@ public function testNodeAddWithoutContentTypes() { $this->assertNoLinkByHref('/admin/structure/types/add'); // Test /node/add page without content types. - foreach (\Drupal::entityManager()->getStorage('node_type')->loadMultiple() as $entity) { + foreach (\Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple() as $entity) { $entity->delete(); } @@ -274,7 +279,7 @@ protected static function getWatchdogIdsForTestExceptionRollback() { // PostgreSQL doesn't support bytea LIKE queries, so we need to unserialize // first to check for the rollback exception message. $matches = []; - $query = db_query("SELECT wid, variables FROM {watchdog}"); + $query = Database::getConnection()->query("SELECT wid, variables FROM {watchdog}"); foreach ($query as $row) { $variables = (array) unserialize($row->variables); if (isset($variables['@message']) && $variables['@message'] === 'Test exception for rollback.') { @@ -292,7 +297,7 @@ protected static function getWatchdogIdsForTestExceptionRollback() { * records with the explicit rollback failed exception message. */ protected static function getWatchdogIdsForFailedExplicitRollback() { - return db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll(); + return Database::getConnection()->query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll(); } } diff --git a/core/modules/node/tests/src/Functional/NodeDisplayConfigurableTest.php b/core/modules/node/tests/src/Functional/NodeDisplayConfigurableTest.php index 5bac9abfa..9193b6efc 100644 --- a/core/modules/node/tests/src/Functional/NodeDisplayConfigurableTest.php +++ b/core/modules/node/tests/src/Functional/NodeDisplayConfigurableTest.php @@ -18,6 +18,11 @@ class NodeDisplayConfigurableTest extends NodeTestBase { */ public static $modules = ['quickedit', 'rdf']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Sets base fields to configurable display and check settings are respected. */ diff --git a/core/modules/node/tests/src/Functional/NodeEditFormTest.php b/core/modules/node/tests/src/Functional/NodeEditFormTest.php index 89f891a64..62d0a6100 100644 --- a/core/modules/node/tests/src/Functional/NodeEditFormTest.php +++ b/core/modules/node/tests/src/Functional/NodeEditFormTest.php @@ -12,6 +12,11 @@ */ class NodeEditFormTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A normal logged in user. * @@ -47,7 +52,7 @@ protected function setUp() { $this->adminUser = $this->drupalCreateUser(['bypass node access', 'administer nodes']); $this->drupalPlaceBlock('local_tasks_block'); - $this->nodeStorage = $this->container->get('entity.manager')->getStorage('node'); + $this->nodeStorage = $this->container->get('entity_type.manager')->getStorage('node'); } /** @@ -66,7 +71,7 @@ public function testNodeEdit() { // Check that the node exists in the database. $node = $this->drupalGetNodeByTitle($edit[$title_key]); - $this->assertTrue($node, 'Node found in database.'); + $this->assertNotEmpty($node, 'Node found in database.'); // Check that "edit" link points to correct page. $this->clickLink(t('Edit')); @@ -167,7 +172,7 @@ public function testNodeEditAuthoredBy() { // Now test with the Autocomplete (Tags) field widget. /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */ - $form_display = \Drupal::entityManager()->getStorage('entity_form_display')->load('node.page.default'); + $form_display = \Drupal::entityTypeManager()->getStorage('entity_form_display')->load('node.page.default'); $widget = $form_display->getComponent('uid'); $widget['type'] = 'entity_reference_autocomplete_tags'; $widget['settings'] = [ diff --git a/core/modules/node/tests/src/Functional/NodeEntityViewModeAlterTest.php b/core/modules/node/tests/src/Functional/NodeEntityViewModeAlterTest.php index be0f1c062..c51af5d42 100644 --- a/core/modules/node/tests/src/Functional/NodeEntityViewModeAlterTest.php +++ b/core/modules/node/tests/src/Functional/NodeEntityViewModeAlterTest.php @@ -19,6 +19,11 @@ class NodeEntityViewModeAlterTest extends NodeTestBase { */ public static $modules = ['node_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Create a "Basic page" node and verify its consistency in the database. */ diff --git a/core/modules/node/tests/src/Functional/NodeFieldMultilingualTest.php b/core/modules/node/tests/src/Functional/NodeFieldMultilingualTest.php index 771426d02..2d1f97472 100644 --- a/core/modules/node/tests/src/Functional/NodeFieldMultilingualTest.php +++ b/core/modules/node/tests/src/Functional/NodeFieldMultilingualTest.php @@ -22,6 +22,11 @@ class NodeFieldMultilingualTest extends BrowserTestBase { */ public static $modules = ['node', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected function setUp() { parent::setUp(); @@ -71,7 +76,7 @@ public function testMultilingualNodeForm() { // Check that the node exists in the database. $node = $this->drupalGetNodeByTitle($edit[$title_key]); - $this->assertTrue($node, 'Node found in database.'); + $this->assertNotEmpty($node, 'Node found in database.'); $this->assertTrue($node->language()->getId() == $langcode && $node->body->value == $body_value, 'Field language correctly set.'); // Change node language. @@ -83,7 +88,7 @@ public function testMultilingualNodeForm() { ]; $this->drupalPostForm(NULL, $edit, t('Save')); $node = $this->drupalGetNodeByTitle($edit[$title_key], TRUE); - $this->assertTrue($node, 'Node found in database.'); + $this->assertNotEmpty($node, 'Node found in database.'); $this->assertTrue($node->language()->getId() == $langcode && $node->body->value == $body_value, 'Field language correctly changed.'); // Enable content language URL detection. @@ -115,7 +120,7 @@ public function testMultilingualDisplaySettings() { // Check that the node exists in the database. $node = $this->drupalGetNodeByTitle($edit[$title_key]); - $this->assertTrue($node, 'Node found in database.'); + $this->assertNotEmpty($node, 'Node found in database.'); // Check if node body is showed. $this->drupalGet('node/' . $node->id()); diff --git a/core/modules/node/tests/src/Functional/NodeFormSaveChangedTimeTest.php b/core/modules/node/tests/src/Functional/NodeFormSaveChangedTimeTest.php index 607e68cf0..6dfd5a73c 100644 --- a/core/modules/node/tests/src/Functional/NodeFormSaveChangedTimeTest.php +++ b/core/modules/node/tests/src/Functional/NodeFormSaveChangedTimeTest.php @@ -20,6 +20,11 @@ class NodeFormSaveChangedTimeTest extends BrowserTestBase { 'node', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An user with permissions to create and edit articles. * diff --git a/core/modules/node/tests/src/Functional/NodeHelpTest.php b/core/modules/node/tests/src/Functional/NodeHelpTest.php index 4dad41618..971030078 100644 --- a/core/modules/node/tests/src/Functional/NodeHelpTest.php +++ b/core/modules/node/tests/src/Functional/NodeHelpTest.php @@ -18,6 +18,11 @@ class NodeHelpTest extends BrowserTestBase { */ public static $modules = ['block', 'node', 'help']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The name of the test node type to create. * diff --git a/core/modules/node/tests/src/Functional/NodeLinksTest.php b/core/modules/node/tests/src/Functional/NodeLinksTest.php index 7bd725b46..31b222d5a 100644 --- a/core/modules/node/tests/src/Functional/NodeLinksTest.php +++ b/core/modules/node/tests/src/Functional/NodeLinksTest.php @@ -18,6 +18,11 @@ class NodeLinksTest extends NodeTestBase { */ public static $modules = ['views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that the links can be hidden in the view display settings. */ @@ -33,7 +38,8 @@ public function testHideLinks() { $this->assertLink('Read more'); // Hide links. - entity_get_display('node', 'article', 'teaser') + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article', 'teaser') ->removeComponent('links') ->save(); diff --git a/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php b/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php index 2da5d1bbe..fa2b3dece 100644 --- a/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php +++ b/core/modules/node/tests/src/Functional/NodeLoadMultipleTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\node\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\node\Entity\Node; /** @@ -18,6 +19,11 @@ class NodeLoadMultipleTest extends NodeTestBase { */ public static $modules = ['views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $web_user = $this->drupalCreateUser(['create article content', 'create page content']); @@ -45,12 +51,12 @@ public function testNodeMultipleLoad() { $this->assertEqual($node3->label(), $nodes[$node3->id()]->label(), 'Node was loaded.'); $this->assertEqual($node4->label(), $nodes[$node4->id()]->label(), 'Node was loaded.'); $count = count($nodes); - $this->assertTrue($count == 2, format_string('@count nodes loaded.', ['@count' => $count])); + $this->assertTrue($count == 2, new FormattableMarkup('@count nodes loaded.', ['@count' => $count])); // Load nodes by nid. Nodes 1, 2 and 4 will be loaded. $nodes = Node::loadMultiple([1, 2, 4]); $count = count($nodes); - $this->assertTrue(count($nodes) == 3, format_string('@count nodes loaded', ['@count' => $count])); + $this->assertTrue(count($nodes) == 3, new FormattableMarkup('@count nodes loaded', ['@count' => $count])); $this->assertTrue(isset($nodes[$node1->id()]), 'Node is correctly keyed in the array'); $this->assertTrue(isset($nodes[$node2->id()]), 'Node is correctly keyed in the array'); $this->assertTrue(isset($nodes[$node4->id()]), 'Node is correctly keyed in the array'); diff --git a/core/modules/node/tests/src/Functional/NodePostSettingsTest.php b/core/modules/node/tests/src/Functional/NodePostSettingsTest.php index 4716b3006..c46558ef7 100644 --- a/core/modules/node/tests/src/Functional/NodePostSettingsTest.php +++ b/core/modules/node/tests/src/Functional/NodePostSettingsTest.php @@ -10,6 +10,11 @@ */ class NodePostSettingsTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/node/tests/src/Functional/NodePreviewAnonymousTest.php b/core/modules/node/tests/src/Functional/NodePreviewAnonymousTest.php index f10d8b738..ff86d237d 100644 --- a/core/modules/node/tests/src/Functional/NodePreviewAnonymousTest.php +++ b/core/modules/node/tests/src/Functional/NodePreviewAnonymousTest.php @@ -20,6 +20,11 @@ class NodePreviewAnonymousTest extends BrowserTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/NodeQueryAlterTest.php b/core/modules/node/tests/src/Functional/NodeQueryAlterTest.php index 6b9306703..d7a0054ed 100644 --- a/core/modules/node/tests/src/Functional/NodeQueryAlterTest.php +++ b/core/modules/node/tests/src/Functional/NodeQueryAlterTest.php @@ -18,6 +18,11 @@ class NodeQueryAlterTest extends NodeTestBase { */ public static $modules = ['node_access_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * User with permission to view content. */ @@ -65,7 +70,7 @@ public function testNodeQueryAlterLowLevelWithAccess() { $this->assertEqual(count($result), 4, 'User with access can see correct nodes'); } catch (\Exception $e) { - $this->fail(t('Altered query is malformed')); + $this->fail('Altered query is malformed'); } } @@ -106,7 +111,7 @@ public function testNodeQueryAlterLowLevelNoAccess() { $this->assertEqual(count($result), 0, 'User with no access cannot see nodes'); } catch (\Exception $e) { - $this->fail(t('Altered query is malformed')); + $this->fail('Altered query is malformed'); } } @@ -131,7 +136,7 @@ public function testNodeQueryAlterLowLevelEditAccess() { catch (\Exception $e) { $this->fail($e->getMessage()); $this->fail((string) $query); - $this->fail(t('Altered query is malformed')); + $this->fail('Altered query is malformed'); } } @@ -170,7 +175,7 @@ public function testNodeQueryAlterOverride() { $this->assertEqual(count($result), 0, 'User view privileges are not overridden'); } catch (\Exception $e) { - $this->fail(t('Altered query is malformed')); + $this->fail('Altered query is malformed'); } // Have node_test_node_grants return a node_access_all privilege, @@ -192,7 +197,7 @@ public function testNodeQueryAlterOverride() { $this->assertEqual(count($result), 4, 'User view privileges are overridden'); } catch (\Exception $e) { - $this->fail(t('Altered query is malformed')); + $this->fail('Altered query is malformed'); } \Drupal::state()->delete('node_access_test.no_access_uid'); } diff --git a/core/modules/node/tests/src/Functional/NodeRSSContentTest.php b/core/modules/node/tests/src/Functional/NodeRSSContentTest.php index 0efa336ad..6dd159222 100644 --- a/core/modules/node/tests/src/Functional/NodeRSSContentTest.php +++ b/core/modules/node/tests/src/Functional/NodeRSSContentTest.php @@ -22,6 +22,11 @@ class NodeRSSContentTest extends NodeTestBase { */ public static $modules = ['node_test', 'views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/node/tests/src/Functional/NodeRevisionPermissionsTest.php b/core/modules/node/tests/src/Functional/NodeRevisionPermissionsTest.php index d0aa88efb..3418bcb26 100644 --- a/core/modules/node/tests/src/Functional/NodeRevisionPermissionsTest.php +++ b/core/modules/node/tests/src/Functional/NodeRevisionPermissionsTest.php @@ -13,6 +13,11 @@ class NodeRevisionPermissionsTest extends NodeTestBase { use GeneratePermutationsTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The node revisions. * @@ -102,9 +107,10 @@ public function testNodeRevisionAccessAnyType() { $permutations = $this->generatePermutations($parameters); $node_revision_access = \Drupal::service('access_check.node.revision'); + $connection = \Drupal::database(); foreach ($permutations as $case) { // Skip this test if there are no revisions for the node. - if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', [':nid' => $revision->id()])->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { + if (!($revision->isDefaultRevision() && ($connection->query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', [':nid' => $revision->id()])->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { if (!empty($case['account']->is_admin) || $case['account']->hasPermission($this->map[$case['op']])) { $this->assertTrue($node_revision_access->checkAccess($revision, $case['account'], $case['op']), "{$this->map[$case['op']]} granted."); } @@ -150,9 +156,10 @@ public function testNodeRevisionAccessPerType() { $permutations = $this->generatePermutations($parameters); $node_revision_access = \Drupal::service('access_check.node.revision'); + $connection = \Drupal::database(); foreach ($permutations as $case) { // Skip this test if there are no revisions for the node. - if (!($revision->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', [':nid' => $revision->id()])->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { + if (!($revision->isDefaultRevision() && ($connection->query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid', [':nid' => $revision->id()])->fetchField() == 1 || $case['op'] == 'update' || $case['op'] == 'delete'))) { if (!empty($case['account']->is_admin) || $case['account']->hasPermission($this->typeMap[$case['op']])) { $this->assertTrue($node_revision_access->checkAccess($revision, $case['account'], $case['op']), "{$this->typeMap[$case['op']]} granted."); } diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsAllTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsAllTest.php index e7f7e88ae..85440bbfc 100644 --- a/core/modules/node/tests/src/Functional/NodeRevisionsAllTest.php +++ b/core/modules/node/tests/src/Functional/NodeRevisionsAllTest.php @@ -13,6 +13,11 @@ */ class NodeRevisionsAllTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A list of nodes created to be used as starting point of different tests. * @@ -109,7 +114,7 @@ protected function createNodeRevision(NodeInterface $node) { * Checks node revision operations. */ public function testRevisions() { - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $nodes = $this->nodes; $logs = $this->revisionLogs; @@ -179,7 +184,7 @@ public function testRevisions() { ]), 'Revision deleted.'); $connection = Database::getConnection(); - $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', + $this->assertTrue($connection->query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', [':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0, 'Revision not found.'); diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsTest.php index d25bb8bf8..55d6f8672 100644 --- a/core/modules/node/tests/src/Functional/NodeRevisionsTest.php +++ b/core/modules/node/tests/src/Functional/NodeRevisionsTest.php @@ -19,6 +19,11 @@ */ class NodeRevisionsTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An array of node revisions. * @@ -134,7 +139,7 @@ protected function setUp() { * Checks node revision related operations. */ public function testRevisions() { - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $nodes = $this->nodes; $logs = $this->revisionLogs; @@ -186,8 +191,8 @@ public function testRevisions() { '%title' => $nodes[1]->label(), ]), 'Revision deleted.'); $connection = Database::getConnection(); - $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', [':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0, 'Revision not found.'); - $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid and vid = :vid', [':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0, 'Field revision not found.'); + $this->assertTrue($connection->query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', [':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0, 'Revision not found.'); + $this->assertTrue($connection->query('SELECT COUNT(vid) FROM {node_field_revision} WHERE nid = :nid and vid = :vid', [':nid' => $node->id(), ':vid' => $nodes[1]->getRevisionId()])->fetchField() == 0, 'Field revision not found.'); // Set the revision timestamp to an older date to make sure that the // confirmation message correctly displays the stored revision date. @@ -305,7 +310,7 @@ public function testRevisions() { * Checks that revisions are correctly saved without log messages. */ public function testNodeRevisionWithoutLogMessage() { - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); // Create a node with an initial log message. $revision_log = $this->randomMachineName(10); $node = $this->drupalCreateNode(['revision_log' => $revision_log]); @@ -406,7 +411,7 @@ public function testRevisionTranslationRevert() { ]); $this->drupalPostForm($revert_translation_url, [], t('Revert')); /** @var \Drupal\node\NodeStorage $node_storage */ - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $node_storage->resetCache(); /** @var \Drupal\node\NodeInterface $node */ $node = $node_storage->load($node->id()); diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php index c7a84f86d..d63bb7dca 100644 --- a/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php +++ b/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php @@ -15,6 +15,11 @@ */ class NodeRevisionsUiBypassAccessTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * User with bypass node access permission. * @@ -50,7 +55,7 @@ public function testDisplayRevisionTab() { $this->drupalPlaceBlock('local_tasks_block'); $this->drupalLogin($this->editor); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); // Set page revision setting 'create new revision'. This will mean new // revisions are created by default when the node is edited. diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php index 4b04a77b1..861833a45 100644 --- a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php +++ b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\node\Functional; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; @@ -13,6 +14,11 @@ */ class NodeRevisionsUiTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * @var \Drupal\user\Entity\User */ @@ -38,7 +44,7 @@ protected function setUp() { */ public function testNodeFormSaveWithoutRevision() { $this->drupalLogin($this->editor); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); // Set page revision setting 'create new revision'. This will mean new // revisions are created by default when the node is edited. @@ -114,7 +120,7 @@ public function testNodeRevisionDoubleEscapeFix() { // Assert the old revision message. $date = $this->container->get('date.formatter')->format($nodes[0]->revision_timestamp->value, 'short'); $url = new Url('entity.node.revision', ['node' => $nodes[0]->id(), 'node_revision' => $nodes[0]->getRevisionId()]); - $this->assertRaw(\Drupal::l($date, $url) . ' by ' . $editor); + $this->assertRaw(Link::fromTextAndUrl($date, $url)->toString() . ' by ' . $editor); // Assert the current revision message. $date = $this->container->get('date.formatter')->format($nodes[1]->revision_timestamp->value, 'short'); diff --git a/core/modules/node/tests/src/Functional/NodeSaveTest.php b/core/modules/node/tests/src/Functional/NodeSaveTest.php index 6c508aee6..c4b5243f7 100644 --- a/core/modules/node/tests/src/Functional/NodeSaveTest.php +++ b/core/modules/node/tests/src/Functional/NodeSaveTest.php @@ -25,6 +25,11 @@ class NodeSaveTest extends NodeTestBase { */ public static $modules = ['node_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -44,7 +49,7 @@ protected function setUp() { */ public function testImport() { // Node ID must be a number that is not in the database. - $nids = \Drupal::entityManager()->getStorage('node')->getQuery() + $nids = \Drupal::entityTypeManager()->getStorage('node')->getQuery() ->sort('nid', 'DESC') ->range(0, 1) ->execute(); @@ -67,10 +72,10 @@ public function testImport() { $node->save(); // Test the import. $node_by_nid = Node::load($test_nid); - $this->assertTrue($node_by_nid, 'Node load by node ID.'); + $this->assertNotEmpty($node_by_nid, 'Node load by node ID.'); $node_by_title = $this->drupalGetNodeByTitle($title); - $this->assertTrue($node_by_title, 'Node load by node title.'); + $this->assertNotEmpty($node_by_title, 'Node load by node title.'); } /** diff --git a/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php b/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php index c1daef94c..f39bb7845 100644 --- a/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php +++ b/core/modules/node/tests/src/Functional/NodeSyndicateBlockTest.php @@ -16,6 +16,11 @@ class NodeSyndicateBlockTest extends NodeTestBase { */ public static $modules = ['block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/node/tests/src/Functional/NodeTemplateSuggestionsTest.php b/core/modules/node/tests/src/Functional/NodeTemplateSuggestionsTest.php index dcadf934d..1e73f7a9e 100644 --- a/core/modules/node/tests/src/Functional/NodeTemplateSuggestionsTest.php +++ b/core/modules/node/tests/src/Functional/NodeTemplateSuggestionsTest.php @@ -9,6 +9,11 @@ */ class NodeTemplateSuggestionsTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests if template_preprocess_node() generates the correct suggestions. */ @@ -18,7 +23,7 @@ public function testNodeThemeHookSuggestions() { $view_mode = 'full'; // Simulate theming of the node. - $build = \Drupal::entityManager()->getViewBuilder('node')->view($node, $view_mode); + $build = \Drupal::entityTypeManager()->getViewBuilder('node')->view($node, $view_mode); $variables['elements'] = $build; $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_node', [$variables]); @@ -27,7 +32,7 @@ public function testNodeThemeHookSuggestions() { // Change the view mode. $view_mode = 'node.my_custom_view_mode'; - $build = \Drupal::entityManager()->getViewBuilder('node')->view($node, $view_mode); + $build = \Drupal::entityTypeManager()->getViewBuilder('node')->view($node, $view_mode); $variables['elements'] = $build; $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_node', [$variables]); diff --git a/core/modules/node/tests/src/Functional/NodeTestBase.php b/core/modules/node/tests/src/Functional/NodeTestBase.php index e52708632..a05552ccc 100644 --- a/core/modules/node/tests/src/Functional/NodeTestBase.php +++ b/core/modules/node/tests/src/Functional/NodeTestBase.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\node\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Session\AccountInterface; use Drupal\node\NodeInterface; use Drupal\Tests\BrowserTestBase; @@ -40,7 +41,7 @@ protected function setUp() { ]); $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); } - $this->accessHandler = \Drupal::entityManager()->getAccessControlHandler('node'); + $this->accessHandler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); } /** @@ -97,7 +98,7 @@ public function assertNodeCreateAccess($bundle, $result, AccountInterface $accou * about the node access permission test that was performed. */ public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) { - return format_string( + return new FormattableMarkup( 'Node access returns @result with operation %op, language code %langcode.', [ '@result' => $result ? 'true' : 'false', diff --git a/core/modules/node/tests/src/Functional/NodeTitleTest.php b/core/modules/node/tests/src/Functional/NodeTitleTest.php index 7985f65ea..0e490320e 100644 --- a/core/modules/node/tests/src/Functional/NodeTitleTest.php +++ b/core/modules/node/tests/src/Functional/NodeTitleTest.php @@ -21,6 +21,11 @@ class NodeTitleTest extends NodeTestBase { */ public static $modules = ['comment', 'views', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A user with permission to bypass access content. * diff --git a/core/modules/node/tests/src/Functional/NodeTitleXSSTest.php b/core/modules/node/tests/src/Functional/NodeTitleXSSTest.php index 4b750875a..a5302e704 100644 --- a/core/modules/node/tests/src/Functional/NodeTitleXSSTest.php +++ b/core/modules/node/tests/src/Functional/NodeTitleXSSTest.php @@ -12,6 +12,11 @@ */ class NodeTitleXSSTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests XSS functionality with a node entity. */ diff --git a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php index 3348ad9d9..2201d727d 100644 --- a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php +++ b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\node\Functional; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Database\Database; use Drupal\Core\Entity\EntityInterface; use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase; use Drupal\Core\Language\LanguageInterface; @@ -16,6 +18,11 @@ */ class NodeTranslationUITest extends ContentTranslationUITestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {inheritdoc} */ @@ -231,7 +238,7 @@ public function testTranslationLinkTheme() { $article = $this->drupalCreateNode(['type' => 'article', 'langcode' => $this->langcodes[0]]); // Set up Seven as the admin theme and use it for node editing. - $this->container->get('theme_handler')->install(['seven']); + $this->container->get('theme_installer')->install(['seven']); $edit = []; $edit['admin_theme'] = 'seven'; $edit['use_admin_theme'] = TRUE; @@ -264,7 +271,7 @@ public function testDisabledBundle() { ]); // Make sure that nothing was inserted into the {content_translation} table. - $rows = db_query('SELECT nid, count(nid) AS count FROM {node_field_data} WHERE type <> :type GROUP BY nid HAVING count(nid) >= 2', [':type' => $this->bundle])->fetchAll(); + $rows = Database::getConnection()->query('SELECT nid, count(nid) AS count FROM {node_field_data} WHERE type <> :type GROUP BY nid HAVING count(nid) >= 2', [':type' => $this->bundle])->fetchAll(); $this->assertEqual(0, count($rows)); // Ensure the translation tab is not accessible. @@ -279,7 +286,7 @@ public function testTranslationRendering() { $default_langcode = $this->langcodes[0]; $values[$default_langcode] = $this->getNewEntityValues($default_langcode); $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode); - $node = \Drupal::entityManager()->getStorage($this->entityTypeId)->load($this->entityId); + $node = \Drupal::entityTypeManager()->getStorage($this->entityTypeId)->load($this->entityId); $node->setPromoted(TRUE); // Create translations. @@ -298,7 +305,7 @@ public function testTranslationRendering() { $this->doTestTranslations('node', $values); // Enable the translation language renderer. - $view = \Drupal::entityManager()->getStorage('view')->load('frontpage'); + $view = \Drupal::entityTypeManager()->getStorage('view')->load('frontpage'); $display = &$view->getDisplay('default'); $display['display_options']['rendering_language'] = '***LANGUAGE_entity_translation***'; $view->save(); @@ -371,7 +378,7 @@ protected function doTestTranslations($path, array $values) { $languages = $this->container->get('language_manager')->getLanguages(); foreach ($this->langcodes as $langcode) { $this->drupalGet($path, ['language' => $languages[$langcode]]); - $this->assertText($values[$langcode]['title'][0]['value'], format_string('The %langcode node translation is correctly displayed.', ['%langcode' => $langcode])); + $this->assertText($values[$langcode]['title'][0]['value'], new FormattableMarkup('The %langcode node translation is correctly displayed.', ['%langcode' => $langcode])); } } @@ -395,7 +402,7 @@ protected function doTestAlternateHreflangLinks(Url $url) { // Retrieve desired link elements from the HTML head. $links = $this->xpath('head/link[@rel = "alternate" and @href = :href and @hreflang = :hreflang]', [':href' => $language_url->toString(), ':hreflang' => $alternate_langcode]); - $this->assert(isset($links[0]), format_string('The %langcode node translation has the correct alternate hreflang link for %alternate_langcode: %link.', ['%langcode' => $langcode, '%alternate_langcode' => $alternate_langcode, '%link' => $url->toString()])); + $this->assert(isset($links[0]), new FormattableMarkup('The %langcode node translation has the correct alternate hreflang link for %alternate_langcode: %link.', ['%langcode' => $langcode, '%alternate_langcode' => $alternate_langcode, '%link' => $url->toString()])); } } } diff --git a/core/modules/node/tests/src/Functional/NodeTypeInitialLanguageTest.php b/core/modules/node/tests/src/Functional/NodeTypeInitialLanguageTest.php index 9c442fd5d..8d970e7d9 100644 --- a/core/modules/node/tests/src/Functional/NodeTypeInitialLanguageTest.php +++ b/core/modules/node/tests/src/Functional/NodeTypeInitialLanguageTest.php @@ -18,6 +18,11 @@ class NodeTypeInitialLanguageTest extends NodeTestBase { */ public static $modules = ['language', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -97,7 +102,7 @@ public function testLanguageFieldVisibility() { ]; $this->drupalPostForm('node/add/article', $edit, t('Save')); $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); - $this->assertTrue($node, 'Node found in database.'); + $this->assertNotEmpty($node, 'Node found in database.'); // Loads node page and check if Language field is hidden by default. $this->drupalGet('node/' . $node->id()); diff --git a/core/modules/node/tests/src/Functional/NodeTypeTest.php b/core/modules/node/tests/src/Functional/NodeTypeTest.php index 84c549e8d..2977c5970 100644 --- a/core/modules/node/tests/src/Functional/NodeTypeTest.php +++ b/core/modules/node/tests/src/Functional/NodeTypeTest.php @@ -25,6 +25,11 @@ class NodeTypeTest extends NodeTestBase { */ public static $modules = ['field_ui', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Ensures that node type functions (node_type_get_*) work correctly. * @@ -219,7 +224,7 @@ public function testNodeTypeFieldUiPermissions() { $admin_user_1 = $this->drupalCreateUser(['administer content types', 'administer node fields']); $this->drupalLogin($admin_user_1); - // Test that the user only sees the actions available to him. + // Test that the user only sees the actions available to them. $this->drupalGet('admin/structure/types'); $this->assertLinkByHref('admin/structure/types/manage/article/fields'); $this->assertNoLinkByHref('admin/structure/types/manage/article/display'); @@ -228,7 +233,7 @@ public function testNodeTypeFieldUiPermissions() { $admin_user_2 = $this->drupalCreateUser(['administer content types', 'administer node display']); $this->drupalLogin($admin_user_2); - // Test that the user only sees the actions available to him. + // Test that the user only sees the actions available to them. $this->drupalGet('admin/structure/types'); $this->assertNoLinkByHref('admin/structure/types/manage/article/fields'); $this->assertLinkByHref('admin/structure/types/manage/article/display'); diff --git a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php index 973955c5d..925a5f5fc 100644 --- a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php +++ b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php @@ -28,6 +28,11 @@ class NodeTypeTranslationTest extends BrowserTestBase { 'node', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The default language code to use in this test. * diff --git a/core/modules/node/tests/src/Functional/NodeViewLanguageTest.php b/core/modules/node/tests/src/Functional/NodeViewLanguageTest.php index 0ebedfb8f..5876175a7 100644 --- a/core/modules/node/tests/src/Functional/NodeViewLanguageTest.php +++ b/core/modules/node/tests/src/Functional/NodeViewLanguageTest.php @@ -18,6 +18,11 @@ class NodeViewLanguageTest extends NodeTestBase { */ public static $modules = ['node', 'datetime', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the language extra field display. */ @@ -26,7 +31,8 @@ public function testViewLanguage() { ConfigurableLanguage::createFromLangcode('es')->save(); // Set language field visible. - entity_get_display('node', 'page', 'full') + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'page', 'full') ->setComponent('langcode') ->save(); diff --git a/core/modules/node/tests/src/Functional/NodeViewTest.php b/core/modules/node/tests/src/Functional/NodeViewTest.php index 913a1485f..19bb251fd 100644 --- a/core/modules/node/tests/src/Functional/NodeViewTest.php +++ b/core/modules/node/tests/src/Functional/NodeViewTest.php @@ -12,6 +12,11 @@ */ class NodeViewTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Tests the html head links. */ @@ -25,10 +30,10 @@ public function testHtmlHeadLinks() { // Link relations are checked for access for anonymous users. $result = $this->xpath('//link[@rel = "version-history"]'); - $this->assertFalse($result, 'Version history not present for anonymous users without access.'); + $this->assertEmpty($result, 'Version history not present for anonymous users without access.'); $result = $this->xpath('//link[@rel = "edit-form"]'); - $this->assertFalse($result, 'Edit form not present for anonymous users without access.'); + $this->assertEmpty($result, 'Edit form not present for anonymous users without access.'); $this->drupalLogin($this->createUser(['access content'])); $this->drupalGet($node->toUrl()); @@ -59,7 +64,7 @@ public function testHtmlHeadLinks() { $this->assertEqual($result[0]->getAttribute('href'), $node->toUrl()->setAbsolute()->toString()); $result = $this->xpath('//link[@rel = "version-history"]'); - $this->assertFalse($result, 'Version history not present for anonymous users without access.'); + $this->assertEmpty($result, 'Version history not present for anonymous users without access.'); $result = $this->xpath('//link[@rel = "edit-form"]'); $this->assertEqual($result[0]->getAttribute('href'), $node->toUrl('edit-form')->setAbsolute()->toString()); @@ -79,7 +84,7 @@ public function testLinkHeader() { $this->drupalGet($node->toUrl()); - $links = $this->drupalGetHeaders()['Link']; + $links = $this->getSession()->getResponseHeaders()['Link']; $this->assertEqual($links, $expected); } diff --git a/core/modules/node/tests/src/Functional/PagePreviewTest.php b/core/modules/node/tests/src/Functional/PagePreviewTest.php index 30ca678ee..428ae121a 100644 --- a/core/modules/node/tests/src/Functional/PagePreviewTest.php +++ b/core/modules/node/tests/src/Functional/PagePreviewTest.php @@ -34,6 +34,11 @@ class PagePreviewTest extends NodeTestBase { */ public static $modules = ['node', 'taxonomy', 'comment', 'image', 'file', 'text', 'node_test', 'menu_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * The name of the created field. * @@ -100,32 +105,35 @@ protected function setUp() { ]; $this->createEntityReferenceField('node', 'page', $this->fieldName, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); - entity_get_form_display('node', 'page', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + + $display_repository->getFormDisplay('node', 'page') ->setComponent($this->fieldName, [ 'type' => 'entity_reference_autocomplete_tags', ]) ->save(); // Show on default display and teaser. - entity_get_display('node', 'page', 'default') + $display_repository->getViewDisplay('node', 'page') ->setComponent($this->fieldName, [ 'type' => 'entity_reference_label', ]) ->save(); - entity_get_display('node', 'page', 'teaser') + $display_repository->getViewDisplay('node', 'page', 'teaser') ->setComponent($this->fieldName, [ 'type' => 'entity_reference_label', ]) ->save(); - entity_get_form_display('node', 'page', 'default') + $display_repository->getFormDisplay('node', 'page') ->setComponent('field_image', [ 'type' => 'image_image', 'settings' => [], ]) ->save(); - entity_get_display('node', 'page', 'default') + $display_repository->getViewDisplay('node', 'page') ->setComponent('field_image') ->save(); @@ -145,13 +153,13 @@ protected function setUp() { 'bundle' => 'page', ])->save(); - entity_get_form_display('node', 'page', 'default') + $display_repository->getFormDisplay('node', 'page') ->setComponent('field_test_multi', [ 'type' => 'text_textfield', ]) ->save(); - entity_get_display('node', 'page', 'default') + $display_repository->getViewDisplay('node', 'page') ->setComponent('field_test_multi', [ 'type' => 'string', ]) @@ -199,7 +207,8 @@ public function testPagePreview() { $uuid = array_pop($paths); // Switch view mode. We'll remove the body from the teaser view mode. - entity_get_display('node', 'page', 'teaser') + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'page', 'teaser') ->removeComponent('body') ->save(); diff --git a/core/modules/node/tests/src/Functional/PageViewTest.php b/core/modules/node/tests/src/Functional/PageViewTest.php index 1f796d83c..63ac3f733 100644 --- a/core/modules/node/tests/src/Functional/PageViewTest.php +++ b/core/modules/node/tests/src/Functional/PageViewTest.php @@ -11,13 +11,18 @@ */ class PageViewTest extends NodeTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests an anonymous and unpermissioned user attempting to edit the node. */ public function testPageView() { // Create a node to view. $node = $this->drupalCreateNode(); - $this->assertTrue(Node::load($node->id()), 'Node created.'); + $this->assertNotEmpty(Node::load($node->id()), 'Node created.'); // Try to edit with anonymous user. $this->drupalGet("node/" . $node->id() . "/edit"); diff --git a/core/modules/node/tests/src/Functional/Rest/NodeJsonAnonTest.php b/core/modules/node/tests/src/Functional/Rest/NodeJsonAnonTest.php index fa44d1386..f434b3b87 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeJsonAnonTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeJsonAnonTest.php @@ -21,4 +21,9 @@ class NodeJsonAnonTest extends NodeResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/node/tests/src/Functional/Rest/NodeJsonBasicAuthTest.php b/core/modules/node/tests/src/Functional/Rest/NodeJsonBasicAuthTest.php index d4ce479c2..a0bbdfef0 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeJsonBasicAuthTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class NodeJsonBasicAuthTest extends NodeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Rest/NodeJsonCookieTest.php b/core/modules/node/tests/src/Functional/Rest/NodeJsonCookieTest.php index 0e070bc4d..3cb12de77 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeJsonCookieTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeJsonCookieTest.php @@ -26,4 +26,9 @@ class NodeJsonCookieTest extends NodeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonAnonTest.php b/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonAnonTest.php index ab30b17fe..1b8970820 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonAnonTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonAnonTest.php @@ -21,4 +21,9 @@ class NodeTypeJsonAnonTest extends NodeTypeResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonBasicAuthTest.php b/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonBasicAuthTest.php index e1874e4cc..870f0d0be 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonBasicAuthTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class NodeTypeJsonBasicAuthTest extends NodeTypeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonCookieTest.php b/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonCookieTest.php index 26608fc70..a280615a5 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonCookieTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeTypeJsonCookieTest.php @@ -26,4 +26,9 @@ class NodeTypeJsonCookieTest extends NodeTypeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlAnonTest.php b/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlAnonTest.php index c21a9d458..24b2f66e9 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlAnonTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlAnonTest.php @@ -23,4 +23,9 @@ class NodeTypeXmlAnonTest extends NodeTypeResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlBasicAuthTest.php b/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlBasicAuthTest.php index 29413dc9c..05fb962d7 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlBasicAuthTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class NodeTypeXmlBasicAuthTest extends NodeTypeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlCookieTest.php b/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlCookieTest.php index 1326f291f..61b21bb98 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlCookieTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeTypeXmlCookieTest.php @@ -28,4 +28,9 @@ class NodeTypeXmlCookieTest extends NodeTypeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/node/tests/src/Functional/Rest/NodeXmlAnonTest.php b/core/modules/node/tests/src/Functional/Rest/NodeXmlAnonTest.php index 83e98a171..5e7716436 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeXmlAnonTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeXmlAnonTest.php @@ -23,6 +23,11 @@ class NodeXmlAnonTest extends NodeResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Rest/NodeXmlBasicAuthTest.php b/core/modules/node/tests/src/Functional/Rest/NodeXmlBasicAuthTest.php index ce809747a..9c738ee6b 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeXmlBasicAuthTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class NodeXmlBasicAuthTest extends NodeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Rest/NodeXmlCookieTest.php b/core/modules/node/tests/src/Functional/Rest/NodeXmlCookieTest.php index 288d5a143..dc479c932 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeXmlCookieTest.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeXmlCookieTest.php @@ -28,6 +28,11 @@ class NodeXmlCookieTest extends NodeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Functional/Views/BulkFormAccessTest.php b/core/modules/node/tests/src/Functional/Views/BulkFormAccessTest.php index 19c409719..1b440cf2f 100644 --- a/core/modules/node/tests/src/Functional/Views/BulkFormAccessTest.php +++ b/core/modules/node/tests/src/Functional/Views/BulkFormAccessTest.php @@ -24,6 +24,11 @@ class BulkFormAccessTest extends NodeTestBase { */ public static $modules = ['node_test_views', 'node_access_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * @@ -47,7 +52,7 @@ protected function setUp($import_test_views = TRUE) { // Create Article node type. $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); - $this->accessHandler = \Drupal::entityManager()->getAccessControlHandler('node'); + $this->accessHandler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); node_access_test_add_field(NodeType::load('article')); diff --git a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php index eded04530..92eabee8a 100644 --- a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php +++ b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php @@ -21,6 +21,11 @@ class BulkFormTest extends NodeTestBase { */ public static $modules = ['node_test_views', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * @@ -271,7 +276,7 @@ public function testBulkDeletion() { $node = $this->loadNode(4); $this->assertNull($node, '4: Node has been deleted'); $node = $this->loadNode(5); - $this->assertTrue($node, '5: Node has not been deleted'); + $this->assertNotEmpty($node, '5: Node has not been deleted'); $this->assertText('Deleted 8 content items.'); } @@ -287,7 +292,7 @@ public function testBulkDeletion() { */ protected function loadNode($id) { /** @var \Drupal\node\NodeStorage $storage */ - $storage = $this->container->get('entity.manager')->getStorage('node'); + $storage = $this->container->get('entity_type.manager')->getStorage('node'); $storage->resetCache([$id]); return $storage->load($id); } diff --git a/core/modules/node/tests/src/Functional/Views/FilterNodeAccessTest.php b/core/modules/node/tests/src/Functional/Views/FilterNodeAccessTest.php index f51959062..e5597d07b 100644 --- a/core/modules/node/tests/src/Functional/Views/FilterNodeAccessTest.php +++ b/core/modules/node/tests/src/Functional/Views/FilterNodeAccessTest.php @@ -24,6 +24,11 @@ class FilterNodeAccessTest extends NodeTestBase { */ public static $modules = ['node_access_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * diff --git a/core/modules/node/tests/src/Functional/Views/FilterUidRevisionTest.php b/core/modules/node/tests/src/Functional/Views/FilterUidRevisionTest.php deleted file mode 100644 index 2b3f3b30e..000000000 --- a/core/modules/node/tests/src/Functional/Views/FilterUidRevisionTest.php +++ /dev/null @@ -1,54 +0,0 @@ -drupalCreateUser(); - $no_author = $this->drupalCreateUser(); - - $expected_result = []; - // Create one node, with the author as the node author. - $node = $this->drupalCreateNode(['uid' => $author->id()]); - $expected_result[] = ['nid' => $node->id()]; - // Create one node of which an additional revision author will be the - // author. - $node = $this->drupalCreateNode(['revision_uid' => $no_author->id()]); - $expected_result[] = ['nid' => $node->id()]; - $revision = clone $node; - // Force to add a new revision. - $revision->set('vid', NULL); - $revision->set('revision_uid', $author->id()); - $revision->save(); - - // Create one node on which the author has neither authorship of revisions - // or the main node. - $this->drupalCreateNode(['uid' => $no_author->id()]); - - $view = Views::getView('test_filter_node_uid_revision'); - $view->initHandlers(); - $view->filter['uid_revision']->value = [$author->id()]; - - $this->executeView($view); - $this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid'], 'Make sure that the view only returns nodes which match either the node or the revision author.'); - } - -} diff --git a/core/modules/node/tests/src/Functional/Views/FrontPageTest.php b/core/modules/node/tests/src/Functional/Views/FrontPageTest.php index 618119c06..5671d1ead 100644 --- a/core/modules/node/tests/src/Functional/Views/FrontPageTest.php +++ b/core/modules/node/tests/src/Functional/Views/FrontPageTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\node\Functional\Views; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Cache\Cache; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Url; @@ -20,6 +21,11 @@ class FrontPageTest extends ViewTestBase { use AssertViewsCacheTagsTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -45,7 +51,7 @@ class FrontPageTest extends ViewTestBase { protected function setUp($import_test_views = TRUE) { parent::setUp($import_test_views); - $this->nodeStorage = $this->container->get('entity.manager') + $this->nodeStorage = $this->container->get('entity_type.manager') ->getStorage('node'); } @@ -77,7 +83,7 @@ public function testFrontPage() { $this->executeView($view); $view->preview(); - $this->assertEqual($view->getTitle(), format_string('Welcome to @site_name', ['@site_name' => $site_name]), 'The welcome title is used for the empty view.'); + $this->assertEqual($view->getTitle(), new FormattableMarkup('Welcome to @site_name', ['@site_name' => $site_name]), 'The welcome title is used for the empty view.'); $view->destroy(); // Create some nodes on the frontpage view. Add more than 10 nodes in order @@ -165,7 +171,7 @@ protected function assertNotInResultSet(ViewExecutable $view, array $not_expecte $found_nids = array_filter($view->result, function ($row) use ($not_expected_nids) { return in_array($row->nid, $not_expected_nids); }); - $this->assertFalse($found_nids, $message); + $this->assertEmpty($found_nids, $message); } /** diff --git a/core/modules/node/tests/src/Functional/Views/NodeContextualLinksTest.php b/core/modules/node/tests/src/Functional/Views/NodeContextualLinksTest.php index 73ccfef75..9dad4459c 100644 --- a/core/modules/node/tests/src/Functional/Views/NodeContextualLinksTest.php +++ b/core/modules/node/tests/src/Functional/Views/NodeContextualLinksTest.php @@ -18,6 +18,11 @@ class NodeContextualLinksTest extends NodeTestBase { */ public static $modules = ['contextual']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests if the node page works if Contextual Links is disabled. * diff --git a/core/modules/node/tests/src/Functional/Views/NodeFieldFilterTest.php b/core/modules/node/tests/src/Functional/Views/NodeFieldFilterTest.php index c2ee2f276..43cf14e22 100644 --- a/core/modules/node/tests/src/Functional/Views/NodeFieldFilterTest.php +++ b/core/modules/node/tests/src/Functional/Views/NodeFieldFilterTest.php @@ -16,6 +16,11 @@ class NodeFieldFilterTest extends NodeTestBase { */ public static $modules = ['language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * @@ -95,7 +100,10 @@ public function testFilters() { */ protected function assertPageCounts($path, $counts, $message) { // Disable read more links. - entity_get_display('node', 'page', 'teaser')->removeComponent('links')->save(); + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'page', 'teaser') + ->removeComponent('links') + ->save(); // Get the text of the page. $this->drupalGet($path); diff --git a/core/modules/node/tests/src/Functional/Views/NodeFieldTokensTest.php b/core/modules/node/tests/src/Functional/Views/NodeFieldTokensTest.php index fc547ed37..4e32b723a 100644 --- a/core/modules/node/tests/src/Functional/Views/NodeFieldTokensTest.php +++ b/core/modules/node/tests/src/Functional/Views/NodeFieldTokensTest.php @@ -20,6 +20,11 @@ class NodeFieldTokensTest extends NodeTestBase { */ public static $testViews = ['test_node_tokens']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests token replacement for Views tokens supplied by the Node module. */ diff --git a/core/modules/node/tests/src/Functional/Views/NodeIntegrationTest.php b/core/modules/node/tests/src/Functional/Views/NodeIntegrationTest.php index 0f855d728..965e2b3b9 100644 --- a/core/modules/node/tests/src/Functional/Views/NodeIntegrationTest.php +++ b/core/modules/node/tests/src/Functional/Views/NodeIntegrationTest.php @@ -16,6 +16,11 @@ class NodeIntegrationTest extends NodeTestBase { */ public static $testViews = ['test_node_view']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests basic node view with a node type argument. */ diff --git a/core/modules/node/tests/src/Functional/Views/NodeLanguageTest.php b/core/modules/node/tests/src/Functional/Views/NodeLanguageTest.php index 31e085213..012f6dd57 100644 --- a/core/modules/node/tests/src/Functional/Views/NodeLanguageTest.php +++ b/core/modules/node/tests/src/Functional/Views/NodeLanguageTest.php @@ -21,6 +21,11 @@ class NodeLanguageTest extends NodeTestBase { */ public static $modules = ['language', 'node_test_views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * diff --git a/core/modules/node/tests/src/Functional/Views/NodeRevisionWizardTest.php b/core/modules/node/tests/src/Functional/Views/NodeRevisionWizardTest.php index 3a3c5723a..b7af626a0 100644 --- a/core/modules/node/tests/src/Functional/Views/NodeRevisionWizardTest.php +++ b/core/modules/node/tests/src/Functional/Views/NodeRevisionWizardTest.php @@ -13,13 +13,18 @@ */ class NodeRevisionWizardTest extends WizardTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests creating a node revision view. */ public function testViewAdd() { $this->drupalCreateContentType(['type' => 'article']); // Create two nodes with two revision. - $node_storage = \Drupal::entityManager()->getStorage('node'); + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); /** @var \Drupal\node\NodeInterface $node */ $node = $node_storage->create(['title' => $this->randomString(), 'type' => 'article', 'changed' => REQUEST_TIME + 40]); $node->save(); @@ -68,7 +73,7 @@ public function testViewAdd() { // Check for the default filters. $this->assertEqual($view->filter['status']->table, 'node_field_revision'); $this->assertEqual($view->filter['status']->field, 'status'); - $this->assertTrue($view->filter['status']->value); + $this->assertEquals('1', $view->filter['status']->value); $this->assertEquals('node_field_data', $view->filter['type']->table); $this->executeView($view); @@ -102,8 +107,8 @@ public function testViewAdd() { // Check for the default filters. $this->assertEqual($view->filter['status']->table, 'node_field_revision'); $this->assertEqual($view->filter['status']->field, 'status'); - $this->assertTrue($view->filter['status']->value); - $this->assertTrue(empty($view->filter['type'])); + $this->assertEquals('1', $view->filter['status']->value); + $this->assertArrayNotHasKey('type', $view->filter); $this->executeView($view); diff --git a/core/modules/node/tests/src/Functional/Views/PathPluginTest.php b/core/modules/node/tests/src/Functional/Views/PathPluginTest.php index b1da9fa4b..ee9b2acf6 100644 --- a/core/modules/node/tests/src/Functional/Views/PathPluginTest.php +++ b/core/modules/node/tests/src/Functional/Views/PathPluginTest.php @@ -18,6 +18,11 @@ class PathPluginTest extends NodeTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * diff --git a/core/modules/node/tests/src/Functional/Views/RevisionLinkTest.php b/core/modules/node/tests/src/Functional/Views/RevisionLinkTest.php index b5a4829b0..34f1f7f7f 100644 --- a/core/modules/node/tests/src/Functional/Views/RevisionLinkTest.php +++ b/core/modules/node/tests/src/Functional/Views/RevisionLinkTest.php @@ -20,6 +20,11 @@ class RevisionLinkTest extends NodeTestBase { */ public static $testViews = ['test_node_revision_links']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests revision links. */ diff --git a/core/modules/node/tests/src/Functional/Views/RowPluginTest.php b/core/modules/node/tests/src/Functional/Views/RowPluginTest.php index fdd4d45ad..a8627524d 100644 --- a/core/modules/node/tests/src/Functional/Views/RowPluginTest.php +++ b/core/modules/node/tests/src/Functional/Views/RowPluginTest.php @@ -19,6 +19,11 @@ class RowPluginTest extends NodeTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * diff --git a/core/modules/node/tests/src/Functional/Views/StatusExtraTest.php b/core/modules/node/tests/src/Functional/Views/StatusExtraTest.php index 0bccbdd2c..0347038d3 100644 --- a/core/modules/node/tests/src/Functional/Views/StatusExtraTest.php +++ b/core/modules/node/tests/src/Functional/Views/StatusExtraTest.php @@ -17,6 +17,11 @@ class StatusExtraTest extends NodeTestBase { */ public static $modules = ['node_test_views', 'content_moderation']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * @@ -61,7 +66,7 @@ public function testStatusExtra() { $this->assertText($node_unpublished2->label()); $this->assertText($node_unpublished3->label()); - // The node author should see the published node and his own node. + // The node author should see the published node and their own node. $this->drupalLogin($node_author); $this->drupalGet('test_status_extra'); $this->assertText($node_published->label()); @@ -77,7 +82,7 @@ public function testStatusExtra() { $this->assertNoText($node_unpublished2->label()); $this->assertNoText($node_unpublished3->label()); - // The author without the permission to see his own unpublished node should + // The author without the permission to see their own unpublished node should // just see the published node. $this->drupalLogin($node_author_not_unpublished); $this->drupalGet('test_status_extra'); diff --git a/core/modules/node/tests/src/FunctionalJavascript/ContextualLinksTest.php b/core/modules/node/tests/src/FunctionalJavascript/ContextualLinksTest.php index 98051262e..bf45eb6e1 100644 --- a/core/modules/node/tests/src/FunctionalJavascript/ContextualLinksTest.php +++ b/core/modules/node/tests/src/FunctionalJavascript/ContextualLinksTest.php @@ -28,6 +28,11 @@ class ContextualLinksTest extends WebDriverTestBase { */ protected static $modules = ['node', 'contextual']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -111,7 +116,7 @@ public function testRevisionContextualLinks() { $this->toggleContextualTriggerVisibility('main'); $contextual_button = $page->find('css', 'main .contextual button'); - $this->assertEmpty(0, $contextual_button); + $this->assertEmpty(0, $contextual_button ?: ''); } } diff --git a/core/modules/node/tests/src/FunctionalJavascript/NodePreviewLinkTest.php b/core/modules/node/tests/src/FunctionalJavascript/NodePreviewLinkTest.php index e1c185000..ca2f81fe9 100644 --- a/core/modules/node/tests/src/FunctionalJavascript/NodePreviewLinkTest.php +++ b/core/modules/node/tests/src/FunctionalJavascript/NodePreviewLinkTest.php @@ -17,6 +17,11 @@ class NodePreviewLinkTest extends WebDriverTestBase { */ public static $modules = ['node', 'filter']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/FunctionalJavascript/TestSettingSummariesContentType.php b/core/modules/node/tests/src/FunctionalJavascript/TestSettingSummariesContentType.php index aef0c1e27..9aa6b8dc4 100644 --- a/core/modules/node/tests/src/FunctionalJavascript/TestSettingSummariesContentType.php +++ b/core/modules/node/tests/src/FunctionalJavascript/TestSettingSummariesContentType.php @@ -11,6 +11,11 @@ */ class TestSettingSummariesContentType extends WebDriverTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/node/tests/src/Kernel/Action/UnpublishByKeywordActionTest.php b/core/modules/node/tests/src/Kernel/Action/UnpublishByKeywordActionTest.php index 88eb122bb..01f30b55d 100644 --- a/core/modules/node/tests/src/Kernel/Action/UnpublishByKeywordActionTest.php +++ b/core/modules/node/tests/src/Kernel/Action/UnpublishByKeywordActionTest.php @@ -2,7 +2,10 @@ namespace Drupal\Tests\node\Kernel\Action; +use Drupal\Core\Render\RenderContext; use Drupal\KernelTests\KernelTestBase; +use Drupal\node\Entity\Node; +use Drupal\node\Entity\NodeType; use Drupal\system\Entity\Action; /** @@ -13,19 +16,55 @@ class UnpublishByKeywordActionTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['action', 'node', 'system', 'user']; + public static $modules = ['action', 'node', 'system', 'user', 'field']; + + protected function setUp() { + parent::setUp(); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installSchema('node', ['node_access']); + // Install system's configuration as default date formats are needed. + $this->installConfig(['system']); + // Create a node type for testing. + $type = NodeType::create(['type' => 'page', 'name' => 'page', 'display_submitted' => FALSE]); + $type->save(); + } /** * Tests creating an action using the node_unpublish_by_keyword_action plugin. - * - * @see https://www.drupal.org/node/2578519 */ public function testUnpublishByKeywordAction() { - Action::create([ + /** @var \Drupal\node\Plugin\Action\UnpublishByKeywordNode $action */ + $action = Action::create([ 'id' => 'foo', 'label' => 'Foobaz', 'plugin' => 'node_unpublish_by_keyword_action', - ])->save(); + 'configuration' => [ + 'keywords' => ['test'], + ], + ]); + $action->save(); + $node1 = Node::create([ + 'type' => 'page', + 'title' => 'test', + 'uid' => 1, + ]); + $node1->setPublished(); + $node1->save(); + $node2 = Node::create([ + 'type' => 'page', + 'title' => 'Another node', + 'uid' => 1, + ]); + $node2->setPublished(); + $node2->save(); + + $this->container->get('renderer')->executeInRenderContext(new RenderContext(), function () use (&$node1, &$node2, $action) { + $action->execute([$node1, $node2]); + }); + + $this->assertFalse($node1->isPublished()); + $this->assertTrue($node2->isPublished()); } } diff --git a/core/modules/node/tests/src/Kernel/Config/NodeImportCreateTest.php b/core/modules/node/tests/src/Kernel/Config/NodeImportCreateTest.php index 4df971906..342651522 100644 --- a/core/modules/node/tests/src/Kernel/Config/NodeImportCreateTest.php +++ b/core/modules/node/tests/src/Kernel/Config/NodeImportCreateTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\node\Kernel\Config; +use Drupal\Core\Site\Settings; use Drupal\field\Entity\FieldConfig; use Drupal\node\Entity\NodeType; use Drupal\KernelTests\KernelTestBase; @@ -38,13 +39,13 @@ public function testImportCreateDefault() { $node_type_id = 'default'; // Check that the content type does not exist yet. - $this->assertFalse(NodeType::load($node_type_id)); + $this->assertNull(NodeType::load($node_type_id)); // Enable node_test_config module and check that the content type // shipped in the module's default config is created. $this->container->get('module_installer')->install(['node_test_config']); $node_type = NodeType::load($node_type_id); - $this->assertTrue($node_type, 'The default content type was created.'); + $this->assertNotEmpty($node_type, 'The default content type was created.'); } /** @@ -60,16 +61,16 @@ public function testImportCreate() { $this->copyConfig($active, $sync); // Manually add new node type. $src_dir = __DIR__ . '/../../../modules/node_test_config/sync'; - $target_dir = config_get_config_directory(CONFIG_SYNC_DIRECTORY); - $this->assertTrue(\Drupal::service('file_system')->copy("$src_dir/$node_type_config_name.yml", "$target_dir/$node_type_config_name.yml")); + $target_dir = Settings::get('config_sync_directory'); + $this->assertNotFalse(\Drupal::service('file_system')->copy("$src_dir/$node_type_config_name.yml", "$target_dir/$node_type_config_name.yml")); // Import the content of the sync directory. $this->configImporter()->import(); // Check that the content type was created. $node_type = NodeType::load($node_type_id); - $this->assertTrue($node_type, 'Import node type from sync was created.'); - $this->assertFalse(FieldConfig::loadByName('node', $node_type_id, 'body')); + $this->assertNotEmpty($node_type, 'Import node type from sync was created.'); + $this->assertNull(FieldConfig::loadByName('node', $node_type_id, 'body')); } } diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php index 47366b788..9dbfb5412 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\node\Kernel\Migrate\d6; +use Drupal\node\NodeInterface; + /** * Node content revisions migration. * @@ -9,6 +11,13 @@ */ class MigrateNodeRevisionTest extends MigrateNodeTestBase { + /** + * The entity storage for node. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $nodeStorage; + /** * {@inheritdoc} */ @@ -20,13 +29,39 @@ class MigrateNodeRevisionTest extends MigrateNodeTestBase { protected function setUp() { parent::setUp(); $this->executeMigrations(['d6_node', 'd6_node_revision']); + $this->nodeStorage = $this->container->get('entity_type.manager') + ->getStorage('node'); + } + + /** + * Asserts various aspects of a node revision. + * + * @param int $id + * The revision ID. + * @param string $langcode + * The revision language. + * @param string $title + * The expected title. + * @param string $log + * The revision log message. + * @param int $timestamp + * The revision's time stamp. + */ + protected function assertRevision($id, $langcode, $title, $log, $timestamp) { + /* @var \Drupal\node\NodeInterface $revision */ + $revision = $this->nodeStorage->loadRevision($id) + ->getTranslation($langcode); + $this->assertInstanceOf(NodeInterface::class, $revision); + $this->assertSame($title, $revision->getTitle()); + $this->assertSame($log, $revision->revision_log->value); + $this->assertIdentical($timestamp, $revision->getRevisionCreationTime()); } /** * Test node revisions migration from Drupal 6 to 8. */ public function testNodeRevision() { - $node = \Drupal::entityManager()->getStorage('node')->loadRevision(2001); + $node = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(2001); /** @var \Drupal\node\NodeInterface $node */ $this->assertIdentical('1', $node->id()); $this->assertIdentical('2001', $node->getRevisionId()); @@ -38,12 +73,34 @@ public function testNodeRevision() { $this->assertIdentical('modified rev 2', $node->revision_log->value); $this->assertIdentical('1390095702', $node->getRevisionCreationTime()); - $node = \Drupal::entityManager()->getStorage('node')->loadRevision(5); - $this->assertIdentical('1', $node->id()); - $this->assertIdentical('body test rev 3', $node->body->value); - $this->assertIdentical('1', $node->getRevisionUser()->id()); - $this->assertIdentical('modified rev 3', $node->revision_log->value); - $this->assertIdentical('1390095703', $node->getRevisionCreationTime()); + $this->assertRevision(1, 'und', 'Test title', NULL, '1420861423'); + $this->assertRevision(3, 'und', 'Test title rev 3', NULL, '1420718386'); + $this->assertRevision(4, 'und', 'Test page title rev 4', NULL, '1390095701'); + $this->assertRevision(5, 'und', 'Test title rev 3', 'modified rev 3', '1390095703'); + $this->assertRevision(6, 'und', 'Node 4', NULL, '1390095701'); + $this->assertRevision(7, 'und', 'Node 5', NULL, '1390095701'); + $this->assertRevision(8, 'und', 'Node 6', NULL, '1390095701'); + $this->assertRevision(9, 'und', 'Node 7', NULL, '1390095701'); + $this->assertRevision(10, 'und', 'Node 8', NULL, '1390095701'); + $this->assertRevision(11, 'und', 'Node 9', NULL, '1390095701'); + $this->assertRevision(12, 'und', 'Once upon a time', NULL, '1444671588'); + $this->assertRevision(13, 'en', 'The Real McCoy', NULL, '1444238808'); + $this->assertRevision(15, 'zu', 'Abantu zulu', NULL, '1444238808'); + $this->assertRevision(17, 'und', 'United Federation of Planets', NULL, '1493066668'); + $this->assertRevision(18, 'und', 'Klingon Empire', NULL, '1493066677'); + $this->assertRevision(19, 'und', 'Romulan Empire', NULL, '1493066684'); + $this->assertRevision(20, 'und', 'Ferengi Commerce Authority', NULL, '1493066693'); + $this->assertRevision(21, 'und', 'Ambassador Sarek', NULL, '1494966544'); + $this->assertRevision(22, 'und', 'New Forum Topic', NULL, '1501955771'); + $this->assertRevision(2001, 'und', 'Test title rev 2', 'modified rev 2', '1390095702'); + $this->assertRevision(2002, 'en', 'John Smith - EN', NULL, '1534014650'); + + // Test that the revision translations are not migrated and there should not + // be a revision with id of 2003. + $ids = [2, 14, 16, 23, 2003]; + foreach ($ids as $id) { + $this->assertNull($this->nodeStorage->loadRevision($id)); + } } } diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php index 997df83b5..c390af2db 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php @@ -60,7 +60,7 @@ public function testNode() { $this->assertIdentical('1420861423', $node->getRevisionCreationTime()); /** @var \Drupal\node\NodeInterface $node_revision */ - $node_revision = \Drupal::entityManager()->getStorage('node')->loadRevision(1); + $node_revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(1); $this->assertIdentical('Test title', $node_revision->getTitle()); $this->assertIdentical('1', $node_revision->getRevisionUser()->id(), 'Node revision has the correct user'); $this->assertSame('1', $node_revision->id(), 'Node 1 loaded.'); diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTypeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTypeTest.php index f36aebda0..4975c493a 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTypeTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTypeTest.php @@ -36,9 +36,9 @@ public function testNodeType() { $node_type_page = NodeType::load('test_page'); $this->assertIdentical('test_page', $node_type_page->id(), 'Node type test_page loaded'); $this->assertIdentical(TRUE, $node_type_page->displaySubmitted()); - $this->assertIdentical(FALSE, $node_type_page->isNewRevision()); + $this->assertIdentical(FALSE, $node_type_page->shouldCreateNewRevision()); $this->assertIdentical(DRUPAL_OPTIONAL, $node_type_page->getPreviewMode()); - $this->assertIdentical($id_map->lookupDestinationId(['test_page']), ['test_page']); + $this->assertIdentical($id_map->lookupDestinationIds(['test_page']), [['test_page']]); // Test we have a body field. $field = FieldConfig::loadByName('node', 'test_page', 'body'); @@ -55,9 +55,9 @@ public function testNodeType() { $this->assertIdentical('test_story', $node_type_story->id(), 'Node type test_story loaded'); $this->assertIdentical(TRUE, $node_type_story->displaySubmitted()); - $this->assertIdentical(FALSE, $node_type_story->isNewRevision()); + $this->assertIdentical(FALSE, $node_type_story->shouldCreateNewRevision()); $this->assertIdentical(DRUPAL_OPTIONAL, $node_type_story->getPreviewMode()); - $this->assertIdentical($id_map->lookupDestinationId(['test_story']), ['test_story']); + $this->assertIdentical($id_map->lookupDestinationIds(['test_story']), [['test_story']]); // Test we don't have a body field. $field = FieldConfig::loadByName('node', 'test_story', 'body'); @@ -74,9 +74,9 @@ public function testNodeType() { $this->assertIdentical('test_event', $node_type_event->id(), 'Node type test_event loaded'); $this->assertIdentical(TRUE, $node_type_event->displaySubmitted()); - $this->assertIdentical(TRUE, $node_type_event->isNewRevision()); + $this->assertIdentical(TRUE, $node_type_event->shouldCreateNewRevision()); $this->assertIdentical(DRUPAL_OPTIONAL, $node_type_event->getPreviewMode()); - $this->assertIdentical($id_map->lookupDestinationId(['test_event']), ['test_event']); + $this->assertIdentical($id_map->lookupDestinationIds(['test_event']), [['test_event']]); // Test we have a body field. $field = FieldConfig::loadByName('node', 'test_event', 'body'); diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateViewModesTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateViewModesTest.php index 83de9b0fd..5320aef9e 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateViewModesTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateViewModesTest.php @@ -29,7 +29,7 @@ public function testViewModes() { $this->assertIdentical(FALSE, is_null($view_mode), 'Preview view mode loaded.'); $this->assertIdentical('Preview', $view_mode->label(), 'View mode has correct label.'); // Test the ID map. - $this->assertIdentical(['node', 'preview'], $this->getMigration('d6_view_modes')->getIdMap()->lookupDestinationId([1])); + $this->assertIdentical([['node', 'preview']], $this->getMigration('d6_view_modes')->getIdMap()->lookupDestinationIds([1])); } } diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeDeriverTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeDeriverTest.php index 437126803..50b4a4f66 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeDeriverTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeDeriverTest.php @@ -35,4 +35,28 @@ public function testTranslations() { $this->assertTrue($this->container->get('plugin.manager.migration')->hasDefinition('d7_node_translation:article'), "Node translation migrations exist after content_translation installed"); } + /** + * Tests the d7_node node driver. + * + * @group node + */ + public function testBuilder() { + $process = $this->getMigration('d7_node:test_content_type')->getProcess(); + $this->assertIdentical('field_boolean', $process['field_boolean'][0]['source']); + $this->assertIdentical('field_email', $process['field_email'][0]['source']); + $this->assertIdentical('field_phone', $process['field_phone'][0]['source']); + $this->assertIdentical('field_date', $process['field_date'][0]['source']); + $this->assertIdentical('field_date_with_end_time', $process['field_date_with_end_time'][0]['source']); + $this->assertIdentical('field_file', $process['field_file'][0]['source']); + $this->assertIdentical('field_float', $process['field_float'][0]['source']); + $this->assertIdentical('field_images', $process['field_images'][0]['source']); + $this->assertIdentical('field_integer', $process['field_integer'][0]['source']); + $this->assertIdentical('field_link', $process['field_link'][0]['source']); + $this->assertIdentical('field_text_list', $process['field_text_list'][0]['source']); + $this->assertIdentical('field_integer_list', $process['field_integer_list'][0]['source']); + $this->assertIdentical('field_long_text', $process['field_long_text'][0]['source']); + $this->assertIdentical('field_term_reference', $process['field_term_reference'][0]['source']); + $this->assertIdentical('field_text', $process['field_text'][0]['source']); + } + } diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTest.php new file mode 100644 index 000000000..563bcf9be --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTest.php @@ -0,0 +1,129 @@ +fileMigrationSetup(); + + $this->installEntitySchema('node'); + $this->installEntitySchema('comment'); + $this->installEntitySchema('taxonomy_term'); + $this->installSchema('comment', ['comment_entity_statistics']); + $this->installSchema('node', ['node_access']); + + $this->migrateUsers(); + $this->migrateFields(); + $this->executeMigrations([ + 'language', + 'd7_language_content_settings', + 'd7_comment_field', + 'd7_comment_field_instance', + 'd7_node', + 'd7_node_translation', + 'd7_node_revision', + ]); + $this->nodeStorage = $this->container->get('entity_type.manager') + ->getStorage('node'); + } + + /** + * {@inheritdoc} + */ + protected function getFileMigrationInfo() { + return [ + 'path' => 'public://sites/default/files/cube.jpeg', + 'size' => '3620', + 'base_path' => 'public://', + 'plugin_id' => 'd7_file', + ]; + } + + /** + * Asserts various aspects of a node revision. + * + * @param int $id + * The revision ID. + * @param string $langcode + * The revision language. + * @param string $title + * The expected title. + * @param string $log + * The revision log message. + * @param int $timestamp + * The revision's time stamp. + */ + protected function assertRevision($id, $langcode, $title, $log, $timestamp) { + $revision = $this->nodeStorage->loadRevision($id); + $this->assertInstanceOf(NodeInterface::class, $revision); + $this->assertSame($title, $revision->getTitle()); + $this->assertSame($langcode, $revision->language()->getId()); + $this->assertSame($log, $revision->revision_log->value); + $this->assertIdentical($timestamp, $revision->getRevisionCreationTime()); + } + + /** + * Tests the migration of node revisions with translated nodes. + */ + public function testNodeRevisions() { + $this->assertRevision(1, 'en', 'An English Node', NULL, '1441032132'); + $this->assertRevision(2, 'en', 'The thing about Deep Space 9', NULL, '1471428152'); + $this->assertRevision(4, 'is', 'is - The thing about Firefly', NULL, '1478755314'); + $this->assertRevision(6, 'en', 'Comments are closed :-(', NULL, '1504715414'); + $this->assertRevision(7, 'en', 'Comments are open :-)', NULL, '1504715432'); + $this->assertRevision(8, 'en', 'The number 47', NULL, '1552126363'); + + // Test that the revision translation are not migrated and there should not + // be a revision with id of 9. + $ids = [3, 5, 9]; + foreach ($ids as $id) { + $this->assertNull($this->nodeStorage->loadRevision($id)); + } + } + +} diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php index 0e6600603..25aa75654 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\node\Kernel\Migrate\d7; use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; +use Drupal\taxonomy\Entity\Term; use Drupal\Tests\file\Kernel\Migrate\d7\FileMigrationSetupTrait; use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; use Drupal\node\Entity\Node; @@ -51,14 +52,19 @@ protected function setUp() { $this->migrateUsers(); $this->migrateFields(); + $this->migrateTaxonomyTerms(); $this->executeMigrations([ 'language', + 'd7_language_content_taxonomy_vocabulary_settings', + 'd7_taxonomy_term_localized_translation', + 'd7_taxonomy_term_translation', 'd7_language_content_settings', 'd7_comment_field', 'd7_comment_field_instance', 'd7_node', 'd7_node_translation', 'd7_entity_translation_settings', + 'd7_taxonomy_term_entity_translation', 'd7_node_entity_translation', ]); } @@ -131,7 +137,7 @@ protected function assertEntity($id, $type, $langcode, $title, $uid, $status, $c * The revision's time stamp. */ protected function assertRevision($id, $title, $uid, $log, $timestamp) { - $revision = \Drupal::entityManager()->getStorage('node')->loadRevision($id); + $revision = \Drupal::entityTypeManager()->getStorage('node')->loadRevision($id); $this->assertInstanceOf(NodeInterface::class, $revision); $this->assertEquals($title, $revision->getTitle()); $this->assertEquals($uid, $revision->getRevisionUser()->id()); @@ -147,16 +153,19 @@ public function testNode() { $this->assertRevision(1, 'An English Node', '1', NULL, '1441032132'); $node = Node::load(1); - $this->assertTrue($node->field_boolean->value); + $this->assertNotEmpty($node->field_boolean->value); $this->assertEquals('99-99-99-99', $node->field_phone->value); - $this->assertEquals('1', $node->field_float->value); + $this->assertSame('2015-01-20T04:15:00', $node->field_date->value); + $this->assertSame('2015-01-20', $node->field_date_without_time->value); + $this->assertSame('2015-01-20', $node->field_datetime_without_time->value); + $this->assertEquals(1, $node->field_float->value); $this->assertEquals('5', $node->field_integer->value); $this->assertEquals('Some more text', $node->field_text_list[0]->value); $this->assertEquals('7', $node->field_integer_list[0]->value); $this->assertEquals('qwerty', $node->field_text->value); $this->assertEquals('2', $node->field_file->target_id); $this->assertEquals('file desc', $node->field_file->description); - $this->assertTrue($node->field_file->display); + $this->assertNotEmpty($node->field_file->display); $this->assertEquals('1', $node->field_images->target_id); $this->assertEquals('alt text', $node->field_images->alt); $this->assertEquals('title text', $node->field_images->title); @@ -177,6 +186,16 @@ public function testNode() { $this->assertEquals('internal:/', $node->field_link->uri); $this->assertEquals('Home', $node->field_link->title); $this->assertEquals(CommentItemInterface::OPEN, $node->comment_node_article->status); + $term_ref = $node->get('field_vocab_localize')->target_id; + $this->assertSame('20', $term_ref); + $this->assertSame('DS9', Term::load($term_ref)->getName()); + + $term_ref = $node->get('field_vocab_translate')->target_id; + $this->assertSame('21', $term_ref); + $this->assertSame('High council', Term::load($term_ref)->getName()); + + $term_ref = $node->get('field_vocab_fixed')->target_id; + $this->assertSame('24', $term_ref); $this->assertTrue($node->hasTranslation('is'), "Node 2 has an Icelandic translation"); $translation = $node->getTranslation('is'); @@ -186,6 +205,16 @@ public function testNode() { $this->assertEquals('internal:/', $translation->field_link->uri); $this->assertEquals(CommentItemInterface::OPEN, $translation->comment_node_article->status); $this->assertEquals('Home', $translation->field_link->title); + $term_ref = $translation->get('field_vocab_localize')->target_id; + $this->assertSame('20', $term_ref); + $this->assertSame('DS9', Term::load($term_ref)->getName()); + + $term_ref = $translation->get('field_vocab_translate')->target_id; + $this->assertSame('23', $term_ref); + $this->assertSame('is - High council', Term::load($term_ref)->getName()); + + $term_ref = $translation->get('field_vocab_fixed')->target_id; + $this->assertNulL($term_ref); // Test that content_translation_source is set. $manager = $this->container->get('content_translation.manager'); @@ -207,6 +236,16 @@ public function testNode() { $node = Node::load(7); $this->assertEquals(CommentItemInterface::OPEN, $node->comment_forum->status); + + // Test synchronized field. + $value = 'Kai Opaka'; + $node = Node::load(2); + $this->assertSame($value, $node->field_text_plain->value); + $this->assertArrayNotHasKey('field_text_plain', $node->getTranslatableFields()); + + $node = $node->getTranslation('is'); + $this->assertSame($value, $node->field_text_plain->value); + } /** diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTypeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTypeTest.php index 282106b20..82512568b 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTypeTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTypeTest.php @@ -54,7 +54,7 @@ protected function assertEntity($id, $label, $description, $help, $display_submi $this->assertIdentical($help, $entity->getHelp()); $this->assertIdentical($display_submitted, $entity->displaySubmitted(), 'Submission info is displayed'); - $this->assertIdentical($new_revision, $entity->isNewRevision(), 'Is a new revision'); + $this->assertIdentical($new_revision, $entity->shouldCreateNewRevision(), 'Is a new revision'); if ($body_label) { /** @var \Drupal\field\FieldConfigInterface $body */ diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/NodeMigrateDeriverTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/NodeMigrateDeriverTest.php deleted file mode 100644 index 96ec69426..000000000 --- a/core/modules/node/tests/src/Kernel/Migrate/d7/NodeMigrateDeriverTest.php +++ /dev/null @@ -1,35 +0,0 @@ -getMigration('d7_node:test_content_type')->getProcess(); - $this->assertIdentical('field_boolean', $process['field_boolean'][0]['source']); - $this->assertIdentical('field_email', $process['field_email'][0]['source']); - $this->assertIdentical('field_phone', $process['field_phone'][0]['source']); - $this->assertIdentical('field_date', $process['field_date'][0]['source']); - $this->assertIdentical('field_date_with_end_time', $process['field_date_with_end_time'][0]['source']); - $this->assertIdentical('field_file', $process['field_file'][0]['source']); - $this->assertIdentical('field_float', $process['field_float'][0]['source']); - $this->assertIdentical('field_images', $process['field_images'][0]['source']); - $this->assertIdentical('field_integer', $process['field_integer'][0]['source']); - $this->assertIdentical('field_link', $process['field_link'][0]['source']); - $this->assertIdentical('field_text_list', $process['field_text_list'][0]['source']); - $this->assertIdentical('field_integer_list', $process['field_integer_list'][0]['source']); - $this->assertIdentical('field_long_text', $process['field_long_text'][0]['source']); - $this->assertIdentical('field_term_reference', $process['field_term_reference'][0]['source']); - $this->assertIdentical('field_text', $process['field_text'][0]['source']); - } - -} diff --git a/core/modules/node/tests/src/Functional/NodeAccessLanguageAwareCombinationTest.php b/core/modules/node/tests/src/Kernel/NodeAccessLanguageAwareCombinationTest.php similarity index 96% rename from core/modules/node/tests/src/Functional/NodeAccessLanguageAwareCombinationTest.php rename to core/modules/node/tests/src/Kernel/NodeAccessLanguageAwareCombinationTest.php index 3aee6cb3f..6886f22db 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessLanguageAwareCombinationTest.php +++ b/core/modules/node/tests/src/Kernel/NodeAccessLanguageAwareCombinationTest.php @@ -1,6 +1,6 @@ assertEqual(count($nids), 4, 'db_select() returns 4 nodes when no langcode is specified.'); + $this->assertEqual(count($nids), 4, 'Query returns 4 nodes when no langcode is specified.'); $this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is full public node.'); $this->assertTrue(array_key_exists($this->nodes['public_ca_private']->id(), $nids), 'Returned node ID is Hungarian public only node.'); $this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.'); @@ -277,7 +277,7 @@ public function testNodeAccessLanguageAwareCombination() { $nids = $select->execute()->fetchAllAssoc('nid'); // Three nodes should be returned (with public Hungarian translations). - $this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.'); + $this->assertEqual(count($nids), 3, 'Query returns 3 nodes.'); $this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is both public node.'); $this->assertTrue(array_key_exists($this->nodes['public_ca_private']->id(), $nids), 'Returned node ID is Hungarian public only node.'); $this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.'); @@ -291,7 +291,7 @@ public function testNodeAccessLanguageAwareCombination() { $nids = $select->execute()->fetchAllAssoc('nid'); // Three nodes should be returned (with public Catalan translations). - $this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.'); + $this->assertEqual(count($nids), 3, 'Query returns 3 nodes.'); $this->assertTrue(array_key_exists($this->nodes['public_both_public']->id(), $nids), 'Returned node ID is both public node.'); $this->assertTrue(array_key_exists($this->nodes['public_hu_private']->id(), $nids), 'Returned node ID is Catalan public only node.'); $this->assertTrue(array_key_exists($this->nodes['private_both_public']->id(), $nids), 'Returned node ID is both public non-language-aware private only node.'); @@ -305,7 +305,7 @@ public function testNodeAccessLanguageAwareCombination() { $nids = $select->execute()->fetchAllAssoc('nid'); // There are no nodes with German translations, so no results are returned. - $this->assertTrue(empty($nids), 'db_select() returns an empty result.'); + $this->assertTrue(empty($nids), 'Query returns an empty result.'); // Query the nodes table as admin user (full access) with the node access // tag and no specific langcode. @@ -316,7 +316,7 @@ public function testNodeAccessLanguageAwareCombination() { $nids = $select->execute()->fetchAllAssoc('nid'); // All nodes are returned. - $this->assertEqual(count($nids), 10, 'db_select() returns all nodes.'); + $this->assertEqual(count($nids), 10, 'Query returns all nodes.'); // Query the nodes table as admin user (full access) with the node access // tag and langcode de. @@ -329,7 +329,7 @@ public function testNodeAccessLanguageAwareCombination() { // Even though there is no German translation, all nodes are returned // because node access filtering does not occur when the user is user 1. - $this->assertEqual(count($nids), 10, 'db_select() returns all nodes.'); + $this->assertEqual(count($nids), 10, 'Query returns all nodes.'); } } diff --git a/core/modules/node/tests/src/Functional/NodeAccessLanguageAwareTest.php b/core/modules/node/tests/src/Kernel/NodeAccessLanguageAwareTest.php similarity index 94% rename from core/modules/node/tests/src/Functional/NodeAccessLanguageAwareTest.php rename to core/modules/node/tests/src/Kernel/NodeAccessLanguageAwareTest.php index 996681007..2576060ef 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessLanguageAwareTest.php +++ b/core/modules/node/tests/src/Kernel/NodeAccessLanguageAwareTest.php @@ -1,6 +1,6 @@ assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.'); + $this->assertEqual(count($nids), 2, 'Query returns 2 nodes when the hu langcode is specified.'); $this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.'); $this->assertTrue(array_key_exists($this->nodes['ca_private']->id(), $nids), 'The node with only the Catalan translation private is returned.'); @@ -234,7 +234,7 @@ public function testNodeAccessLanguageAware() { // Two nodes should be returned: the node with both translations public, and // the node with only the Hungarian translation marked as private. - $this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.'); + $this->assertEqual(count($nids), 2, 'Query returns 2 nodes when the hu langcode is specified.'); $this->assertTrue(array_key_exists($this->nodes['both_public']->id(), $nids), 'The node with both translations public is returned.'); $this->assertTrue(array_key_exists($this->nodes['hu_private']->id(), $nids), 'The node with only the Hungarian translation private is returned.'); @@ -247,7 +247,7 @@ public function testNodeAccessLanguageAware() { $nids = $select->execute()->fetchAllAssoc('nid'); // There are no nodes with German translations, so no results are returned. - $this->assertTrue(empty($nids), 'db_select() returns an empty result when the de langcode is specified.'); + $this->assertTrue(empty($nids), 'Query returns an empty result when the de langcode is specified.'); // Query the nodes table as admin user (full access) with the node access // tag and no specific langcode. @@ -258,7 +258,7 @@ public function testNodeAccessLanguageAware() { $nids = $select->execute()->fetchAllAssoc('nid'); // All nodes are returned. - $this->assertEqual(count($nids), 6, 'db_select() returns all nodes.'); + $this->assertEqual(count($nids), 6, 'Query returns all nodes.'); // Query the nodes table as admin user (full access) with the node access // tag and langcode de. @@ -271,7 +271,7 @@ public function testNodeAccessLanguageAware() { // Even though there is no German translation, all nodes are returned // because node access filtering does not occur when the user is user 1. - $this->assertEqual(count($nids), 6, 'db_select() returns all nodes.'); + $this->assertEqual(count($nids), 6, 'Query returns all nodes.'); } } diff --git a/core/modules/node/tests/src/Functional/NodeAccessLanguageTest.php b/core/modules/node/tests/src/Kernel/NodeAccessLanguageTest.php similarity index 92% rename from core/modules/node/tests/src/Functional/NodeAccessLanguageTest.php rename to core/modules/node/tests/src/Kernel/NodeAccessLanguageTest.php index a3d9c4d94..269240670 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessLanguageTest.php +++ b/core/modules/node/tests/src/Kernel/NodeAccessLanguageTest.php @@ -1,6 +1,6 @@ assertNodeAccess($expected_node_access, $node_public_no_language, $web_user); // Reset the node access cache and turn on our test node access code. - \Drupal::entityManager()->getAccessControlHandler('node')->resetCache(); + \Drupal::entityTypeManager()->getAccessControlHandler('node')->resetCache(); \Drupal::state()->set('node_access_test_secret_catalan', 1); $node_public_ca = $this->drupalCreateNode(['body' => [[]], 'langcode' => 'ca', 'private' => FALSE]); $this->assertTrue($node_public_ca->language()->getId() == 'ca', 'Node created as Catalan.'); @@ -96,7 +97,7 @@ public function testNodeAccess() { $this->assertNodeAccess($expected_node_access_no_access, $node_public_ca, $web_user); $this->assertNodeAccess($expected_node_access_no_access, $node_public_ca->getTranslation('ca'), $web_user); - \Drupal::entityManager()->getAccessControlHandler('node')->resetCache(); + \Drupal::entityTypeManager()->getAccessControlHandler('node')->resetCache(); // Tests that access is granted if requested with no language. $this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user); @@ -141,7 +142,7 @@ public function testNodeAccessPrivate() { $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user); // Reset the node access cache and turn on our test node access code. - \Drupal::entityManager()->getAccessControlHandler('node')->resetCache(); + \Drupal::entityTypeManager()->getAccessControlHandler('node')->resetCache(); \Drupal::state()->set('node_access_test_secret_catalan', 1); // Tests that access is not granted if requested with no language. @@ -159,7 +160,7 @@ public function testNodeAccessPrivate() { $this->assertNodeAccess($expected_node_access_no_access, $node_private_ca, $private_ca_user); $this->assertNodeAccess($expected_node_access_no_access, $node_private_ca->getTranslation('ca'), $private_ca_user); - \Drupal::entityManager()->getAccessControlHandler('node')->resetCache(); + \Drupal::entityTypeManager()->getAccessControlHandler('node')->resetCache(); \Drupal::state()->set('node_access_test_secret_catalan', 0); // Tests that Catalan is still not accessible for a user with no access to @@ -174,7 +175,7 @@ public function testNodeAccessPrivate() { } /** - * Tests db_select() with a 'node_access' tag and langcode metadata. + * Tests select queries with a 'node_access' tag and langcode metadata. */ public function testNodeAccessQueryTag() { // Create a normal authenticated user. @@ -213,7 +214,7 @@ public function testNodeAccessQueryTag() { // The public node and no language node should be returned. Because no // langcode is given it will use the fallback node. - $this->assertEqual(count($nids), 2, 'db_select() returns 2 node'); + $this->assertEqual(count($nids), 2, 'Query returns 2 node'); $this->assertTrue(array_key_exists($node_public->id(), $nids), 'Returned node ID is public node.'); $this->assertTrue(array_key_exists($node_no_language->id(), $nids), 'Returned node ID is no language node.'); @@ -227,7 +228,7 @@ public function testNodeAccessQueryTag() { $nids = $select->execute()->fetchAllAssoc('nid'); // Because no nodes are created in German, no nodes are returned. - $this->assertTrue(empty($nids), 'db_select() returns an empty result.'); + $this->assertTrue(empty($nids), 'Query returns an empty result.'); // Query the nodes table as admin user (full access) with the node access // tag and no specific langcode. @@ -238,7 +239,7 @@ public function testNodeAccessQueryTag() { $nids = $select->execute()->fetchAllAssoc('nid'); // All nodes are returned. - $this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.'); + $this->assertEqual(count($nids), 3, 'Query returns all three nodes.'); // Query the nodes table as admin user (full access) with the node access // tag and langcode de. @@ -251,7 +252,7 @@ public function testNodeAccessQueryTag() { // All nodes are returned because node access tag is not invoked when the // user is user 1. - $this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.'); + $this->assertEqual(count($nids), 3, 'Query returns all three nodes.'); } } diff --git a/core/modules/node/tests/src/Kernel/NodeAccessRecordsTest.php b/core/modules/node/tests/src/Kernel/NodeAccessRecordsTest.php new file mode 100644 index 000000000..714f18cb4 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/NodeAccessRecordsTest.php @@ -0,0 +1,87 @@ +drupalCreateNode(['type' => 'article']); + $this->assertNotEmpty(Node::load($node1->id()), 'Article node created.'); + + // Check to see if grants added by node_test_node_access_records made it in. + $connection = Database::getConnection(); + $records = $connection->query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node1->id()])->fetchAll(); + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'test_article_realm', 'Grant with article_realm acquired for node without alteration.'); + $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.'); + + // Create an unpromoted "Basic page" node. + $node2 = $this->drupalCreateNode(['type' => 'page', 'promote' => 0]); + $this->assertNotEmpty(Node::load($node2->id()), 'Unpromoted basic page node created.'); + + // Check to see if grants added by node_test_node_access_records made it in. + $records = $connection->query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node2->id()])->fetchAll(); + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.'); + $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.'); + + // Create an unpromoted, unpublished "Basic page" node. + $node3 = $this->drupalCreateNode(['type' => 'page', 'promote' => 0, 'status' => 0]); + $this->assertNotEmpty(Node::load($node3->id()), 'Unpromoted, unpublished basic page node created.'); + + // Check to see if grants added by node_test_node_access_records made it in. + $records = $connection->query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node3->id()])->fetchAll(); + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'test_page_realm', 'Grant with page_realm acquired for node without alteration.'); + $this->assertEqual($records[0]->gid, 1, 'Grant with gid = 1 acquired for node without alteration.'); + + // Create a promoted "Basic page" node. + $node4 = $this->drupalCreateNode(['type' => 'page', 'promote' => 1]); + $this->assertNotEmpty(Node::load($node4->id()), 'Promoted basic page node created.'); + + // Check to see if grant added by node_test_node_access_records was altered + // by node_test_node_access_records_alter. + $records = $connection->query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node4->id()])->fetchAll(); + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'test_alter_realm', 'Altered grant with alter_realm acquired for node.'); + $this->assertEqual($records[0]->gid, 2, 'Altered grant with gid = 2 acquired for node.'); + + // Check to see if we can alter grants with hook_node_grants_alter(). + $operations = ['view', 'update', 'delete']; + // Create a user that is allowed to access content. + $web_user = $this->drupalCreateUser(['access content']); + foreach ($operations as $op) { + $grants = node_test_node_grants($web_user, $op); + $altered_grants = $grants; + \Drupal::moduleHandler()->alter('node_grants', $altered_grants, $web_user, $op); + $this->assertNotEqual($grants, $altered_grants, new FormattableMarkup('Altered the %op grant for a user.', ['%op' => $op])); + } + + // Check that core does not grant access to an unpublished node when an + // empty $grants array is returned. + $node6 = $this->drupalCreateNode(['status' => 0, 'disable_node_access' => TRUE]); + $records = $connection->query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', [':nid' => $node6->id()])->fetchAll(); + $this->assertEqual(count($records), 0, 'Returned no records for unpublished node.'); + } + +} diff --git a/core/modules/node/tests/src/Kernel/NodeAccessTest.php b/core/modules/node/tests/src/Kernel/NodeAccessTest.php index 2a5155e46..c983667bc 100644 --- a/core/modules/node/tests/src/Kernel/NodeAccessTest.php +++ b/core/modules/node/tests/src/Kernel/NodeAccessTest.php @@ -2,83 +2,12 @@ namespace Drupal\Tests\node\Kernel; -use Drupal\Component\Render\FormattableMarkup; -use Drupal\Core\Session\AccountInterface; -use Drupal\KernelTests\KernelTestBase; -use Drupal\node\NodeInterface; -use Drupal\Tests\node\Traits\ContentTypeCreationTrait; -use Drupal\Tests\node\Traits\NodeCreationTrait; -use Drupal\Tests\user\Traits\UserCreationTrait; -use Drupal\user\RoleInterface; - /** * Tests basic node_access functionality. * * @group node */ -class NodeAccessTest extends KernelTestBase { - - use NodeCreationTrait { - getNodeByTitle as drupalGetNodeByTitle; - createNode as drupalCreateNode; - } - use UserCreationTrait { - createUser as drupalCreateUser; - createRole as drupalCreateRole; - createAdminRole as drupalCreateAdminRole; - } - use ContentTypeCreationTrait { - createContentType as drupalCreateContentType; - } - - /** - * {@inheritdoc} - */ - public static $modules = [ - 'node', - 'datetime', - 'user', - 'system', - 'filter', - 'field', - 'text', - ]; - - /** - * Access handler. - * - * @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface - */ - protected $accessHandler; - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - $this->installSchema('system', 'sequences'); - $this->installSchema('node', 'node_access'); - $this->installEntitySchema('user'); - $this->installEntitySchema('node'); - $this->installConfig('filter'); - $this->installConfig('node'); - $this->accessHandler = $this->container->get('entity_type.manager') - ->getAccessControlHandler('node'); - // Clear permissions for authenticated users. - $this->config('user.role.' . RoleInterface::AUTHENTICATED_ID) - ->set('permissions', []) - ->save(); - - // Create user 1 who has special permissions. - $this->drupalCreateUser(); - - // Create a node type. - $this->drupalCreateContentType([ - 'type' => 'page', - 'name' => 'Basic page', - 'display_submitted' => FALSE, - ]); - } +class NodeAccessTest extends NodeAccessTestBase { /** * Runs basic tests for node_access function. @@ -194,69 +123,4 @@ public function testUnsupportedOperation() { $this->assertNodeAccess(['random_operation' => FALSE], $node, $web_user); } - /** - * Asserts that node access correctly grants or denies access. - * - * @param array $ops - * An associative array of the expected node access grants for the node - * and account, with each key as the name of an operation (e.g. 'view', - * 'delete') and each value a Boolean indicating whether access to that - * operation should be granted. - * @param \Drupal\node\NodeInterface $node - * The node object to check. - * @param \Drupal\Core\Session\AccountInterface $account - * The user account for which to check access. - */ - public function assertNodeAccess(array $ops, NodeInterface $node, AccountInterface $account) { - foreach ($ops as $op => $result) { - $this->assertEquals($result, $this->accessHandler->access($node, $op, $account), $this->nodeAccessAssertMessage($op, $result, $node->language() - ->getId())); - } - } - - /** - * Asserts that node create access correctly grants or denies access. - * - * @param string $bundle - * The node bundle to check access to. - * @param bool $result - * Whether access should be granted or not. - * @param \Drupal\Core\Session\AccountInterface $account - * The user account for which to check access. - * @param string|null $langcode - * (optional) The language code indicating which translation of the node - * to check. If NULL, the untranslated (fallback) access is checked. - */ - public function assertNodeCreateAccess($bundle, $result, AccountInterface $account, $langcode = NULL) { - $this->assertEquals($result, $this->accessHandler->createAccess($bundle, $account, [ - 'langcode' => $langcode, - ]), $this->nodeAccessAssertMessage('create', $result, $langcode)); - } - - /** - * Constructs an assert message to display which node access was tested. - * - * @param string $operation - * The operation to check access for. - * @param bool $result - * Whether access should be granted or not. - * @param string|null $langcode - * (optional) The language code indicating which translation of the node - * to check. If NULL, the untranslated (fallback) access is checked. - * - * @return string - * An assert message string which contains information in plain English - * about the node access permission test that was performed. - */ - public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) { - return new FormattableMarkup( - 'Node access returns @result with operation %op, language code %langcode.', - [ - '@result' => $result ? 'true' : 'false', - '%op' => $operation, - '%langcode' => !empty($langcode) ? $langcode : 'empty', - ] - ); - } - } diff --git a/core/modules/node/tests/src/Kernel/NodeAccessTestBase.php b/core/modules/node/tests/src/Kernel/NodeAccessTestBase.php new file mode 100644 index 000000000..3ee8ba16f --- /dev/null +++ b/core/modules/node/tests/src/Kernel/NodeAccessTestBase.php @@ -0,0 +1,147 @@ +installSchema('system', 'sequences'); + $this->installSchema('node', 'node_access'); + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installConfig('filter'); + $this->installConfig('node'); + + $this->accessHandler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); + + // Clear permissions for authenticated users. + $this->config('user.role.' . RoleInterface::AUTHENTICATED_ID) + ->set('permissions', []) + ->save(); + + // Create user 1 who has special permissions. + $this->drupalCreateUser(); + + // Create a node type. + $this->drupalCreateContentType([ + 'type' => 'page', + 'name' => 'Basic page', + 'display_submitted' => FALSE, + ]); + } + + /** + * Asserts that node access correctly grants or denies access. + * + * @param array $ops + * An associative array of the expected node access grants for the node + * and account, with each key as the name of an operation (e.g. 'view', + * 'delete') and each value a Boolean indicating whether access to that + * operation should be granted. + * @param \Drupal\node\NodeInterface $node + * The node object to check. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account for which to check access. + */ + public function assertNodeAccess(array $ops, NodeInterface $node, AccountInterface $account) { + foreach ($ops as $op => $result) { + $this->assertEquals($result, $this->accessHandler->access($node, $op, $account), $this->nodeAccessAssertMessage($op, $result, $node->language() + ->getId())); + } + } + + /** + * Asserts that node create access correctly grants or denies access. + * + * @param string $bundle + * The node bundle to check access to. + * @param bool $result + * Whether access should be granted or not. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account for which to check access. + * @param string|null $langcode + * (optional) The language code indicating which translation of the node + * to check. If NULL, the untranslated (fallback) access is checked. + */ + public function assertNodeCreateAccess($bundle, $result, AccountInterface $account, $langcode = NULL) { + $this->assertEquals($result, $this->accessHandler->createAccess($bundle, $account, [ + 'langcode' => $langcode, + ]), $this->nodeAccessAssertMessage('create', $result, $langcode)); + } + + /** + * Constructs an assert message to display which node access was tested. + * + * @param string $operation + * The operation to check access for. + * @param bool $result + * Whether access should be granted or not. + * @param string|null $langcode + * (optional) The language code indicating which translation of the node + * to check. If NULL, the untranslated (fallback) access is checked. + * + * @return string + * An assert message string which contains information in plain English + * about the node access permission test that was performed. + */ + public function nodeAccessAssertMessage($operation, $result, $langcode = NULL) { + return new FormattableMarkup( + 'Node access returns @result with operation %op, language code %langcode.', + [ + '@result' => $result ? 'true' : 'false', + '%op' => $operation, + '%langcode' => !empty($langcode) ? $langcode : 'empty', + ] + ); + } + +} diff --git a/core/modules/node/tests/src/Kernel/NodeBodyFieldStorageTest.php b/core/modules/node/tests/src/Kernel/NodeBodyFieldStorageTest.php index 3dd13496d..219618e19 100644 --- a/core/modules/node/tests/src/Kernel/NodeBodyFieldStorageTest.php +++ b/core/modules/node/tests/src/Kernel/NodeBodyFieldStorageTest.php @@ -36,7 +36,7 @@ protected function setUp() { */ public function testFieldOverrides() { $field_storage = FieldStorageConfig::loadByName('node', 'body'); - $this->assertTrue($field_storage, 'Node body field storage exists.'); + $this->assertNotEmpty($field_storage, 'Node body field storage exists.'); $type = NodeType::create(['name' => 'Ponies', 'type' => 'ponies']); $type->save(); node_add_body_field($type); @@ -48,7 +48,7 @@ public function testFieldOverrides() { $this->assertTrue(count($field_storage->getBundles()) == 0, 'Node body field storage exists after deleting the only instance of a field.'); \Drupal::service('module_installer')->uninstall(['node']); $field_storage = FieldStorageConfig::loadByName('node', 'body'); - $this->assertFalse($field_storage, 'Node body field storage does not exist after uninstalling the Node module.'); + $this->assertNull($field_storage, 'Node body field storage does not exist after uninstalling the Node module.'); } } diff --git a/core/modules/node/tests/src/Kernel/NodeConditionTest.php b/core/modules/node/tests/src/Kernel/NodeConditionTest.php index a30867cb8..6dc21babb 100644 --- a/core/modules/node/tests/src/Kernel/NodeConditionTest.php +++ b/core/modules/node/tests/src/Kernel/NodeConditionTest.php @@ -31,7 +31,7 @@ protected function setUp() { * Tests conditions. */ public function testConditions() { - $manager = $this->container->get('plugin.manager.condition', $this->container->get('container.namespaces')); + $manager = $this->container->get('plugin.manager.condition'); $this->createUser(); // Get some nodes of various types to check against. diff --git a/core/modules/node/tests/src/Kernel/NodeFieldOverridesTest.php b/core/modules/node/tests/src/Kernel/NodeFieldOverridesTest.php index 1d781c6f5..1fdfea720 100644 --- a/core/modules/node/tests/src/Kernel/NodeFieldOverridesTest.php +++ b/core/modules/node/tests/src/Kernel/NodeFieldOverridesTest.php @@ -50,7 +50,7 @@ public function testFieldOverrides() { if ($override) { $override->delete(); } - $uid_field = \Drupal::entityManager()->getBaseFieldDefinitions('node')['uid']; + $uid_field = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('node')['uid']; $config = $uid_field->getConfig('ponies'); $config->save(); $this->assertEquals($config->get('default_value_callback'), 'Drupal\node\Entity\Node::getDefaultEntityOwner'); diff --git a/core/modules/node/tests/src/Kernel/NodeLegacyTest.php b/core/modules/node/tests/src/Kernel/NodeLegacyTest.php index fe25f457e..faefee14a 100644 --- a/core/modules/node/tests/src/Kernel/NodeLegacyTest.php +++ b/core/modules/node/tests/src/Kernel/NodeLegacyTest.php @@ -63,4 +63,56 @@ public function testEntityLegacyCode() { $this->assertInstanceOf(NodeTypeInterface::class, node_type_load('page')); } + /** + * @expectedDeprecation node_view() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal::entityTypeManager()->getViewBuilder('node')->view() instead. See https://www.drupal.org/node/3033656 + * @expectedDeprecation node_view_multiple() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal::entityTypeManager()->getViewBuilder('node')->viewMultiple() instead. See https://www.drupal.org/node/3033656 + */ + public function testNodeView() { + $entity = Node::create(['type' => 'page']); + $this->assertNotEmpty(node_view($entity)); + $entities = [ + Node::create(['type' => 'page']), + Node::create(['type' => 'page']), + ]; + $this->assertEquals(4, count(node_view_multiple($entities))); + } + + /** + * Tests that NodeType::isNewRevision() triggers a deprecation error. + * + * @expectedDeprecation NodeType::isNewRevision is deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use Drupal\Core\Entity\RevisionableEntityBundleInterface::shouldCreateNewRevision() instead. See https://www.drupal.org/node/3067365 + */ + public function testNodeTypeIsNewRevision() { + $type = NodeType::load('page'); + $this->assertSame($type->shouldCreateNewRevision(), $type->isNewRevision()); + } + + /** + * Tests that Node::setRevisionAuthorId() triggers a deprecation error. + * + * @expectedDeprecation Drupal\node\Entity\Node::setRevisionAuthorId is deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Entity\RevisionLogInterface::setRevisionUserId() instead. See https://www.drupal.org/node/3069750 + */ + public function testNodeSetRevisionAuthorId() { + $user = $this->createUser(['uid' => 2, 'name' => 'Test']); + $entity = Node::create([ + 'type' => 'page', + ]); + $entity->setRevisionAuthorId($user->id()); + $this->assertSame($user->id(), $entity->getRevisionUser()->id()); + } + + /** + * Tests that Node::getRevisionAuthor() triggers a deprecation error. + * + * @expectedDeprecation Drupal\node\Entity\Node::getRevisionAuthor is deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Entity\RevisionLogInterface::getRevisionUser() instead. See https://www.drupal.org/node/3069750 + */ + public function testNodeGetRevisionAuthor() { + $user = $this->createUser(['uid' => 2, 'name' => 'Test']); + $entity = Node::create([ + 'type' => 'page', + ]); + $entity->setRevisionUser($user); + $this->assertSame($user->id(), $entity->getRevisionAuthor()->id()); + } + } diff --git a/core/modules/node/tests/src/Kernel/NodeListBuilderTest.php b/core/modules/node/tests/src/Kernel/NodeListBuilderTest.php index 420b30661..8949d0b4c 100644 --- a/core/modules/node/tests/src/Kernel/NodeListBuilderTest.php +++ b/core/modules/node/tests/src/Kernel/NodeListBuilderTest.php @@ -28,7 +28,7 @@ protected function setUp() { */ public function testCacheContexts() { /** @var \Drupal\Core\Entity\EntityListBuilderInterface $list_builder */ - $list_builder = $this->container->get('entity.manager')->getListBuilder('node'); + $list_builder = $this->container->get('entity_type.manager')->getListBuilder('node'); $build = $list_builder->render(); $this->container->get('renderer')->renderRoot($build); diff --git a/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php b/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php index 016807897..aaf5da715 100644 --- a/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php +++ b/core/modules/node/tests/src/Kernel/NodeTokenReplaceTest.php @@ -104,7 +104,7 @@ public function testNodeTokenReplacement() { foreach ($tests as $input => $expected) { $bubbleable_metadata = new BubbleableMetadata(); $output = $this->tokenService->replace($input, ['node' => $node], ['langcode' => $this->interfaceLanguage->getId()], $bubbleable_metadata); - $this->assertEqual($output, $expected, format_string('Node token %token replaced.', ['%token' => $input])); + $this->assertEqual($output, $expected, new FormattableMarkup('Node token %token replaced.', ['%token' => $input])); $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]); } diff --git a/core/modules/node/tests/src/Kernel/NodeViewBuilderTest.php b/core/modules/node/tests/src/Kernel/NodeViewBuilderTest.php index d92bd8fd7..3ef609b77 100644 --- a/core/modules/node/tests/src/Kernel/NodeViewBuilderTest.php +++ b/core/modules/node/tests/src/Kernel/NodeViewBuilderTest.php @@ -48,8 +48,8 @@ class NodeViewBuilderTest extends EntityKernelTestBase { protected function setUp() { parent::setUp(); - $this->storage = $this->entityManager->getStorage('node'); - $this->viewBuilder = $this->entityManager->getViewBuilder('node'); + $this->storage = $this->entityTypeManager->getStorage('node'); + $this->viewBuilder = $this->entityTypeManager->getViewBuilder('node'); $this->renderer = $this->container->get('renderer'); $type = NodeType::create([ diff --git a/core/modules/node/tests/src/Kernel/SummaryLengthTest.php b/core/modules/node/tests/src/Kernel/SummaryLengthTest.php index 3c69ff764..15d65a2ab 100644 --- a/core/modules/node/tests/src/Kernel/SummaryLengthTest.php +++ b/core/modules/node/tests/src/Kernel/SummaryLengthTest.php @@ -86,7 +86,7 @@ public function testSummaryLength() { 'promote' => 1, ]; $node = $this->drupalCreateNode($settings); - $this->assertTrue(Node::load($node->id()), 'Node created.'); + $this->assertNotEmpty(Node::load($node->id()), 'Node created.'); // Render the node as a teaser. $content = $this->drupalBuildEntityView($node, 'teaser'); @@ -99,7 +99,8 @@ public function testSummaryLength() { $this->assertRaw($expected); // Change the teaser length for "Basic page" content type. - $display = entity_get_display('node', $node->getType(), 'teaser'); + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', $node->getType(), 'teaser'); $display_options = $display->getComponent('body'); $display_options['settings']['trim_length'] = 200; $display->setComponent('body', $display_options) diff --git a/core/modules/node/tests/src/Kernel/Views/ArgumentUidRevisionTest.php b/core/modules/node/tests/src/Kernel/Views/ArgumentUidRevisionTest.php index de7ab9072..e658bb2fe 100644 --- a/core/modules/node/tests/src/Kernel/Views/ArgumentUidRevisionTest.php +++ b/core/modules/node/tests/src/Kernel/Views/ArgumentUidRevisionTest.php @@ -64,13 +64,13 @@ public function testArgument() { 'type' => 'default', 'title' => $this->randomMachineName(), ]); - $node2->setRevisionAuthorId($no_author->id()); + $node2->setRevisionUserId($no_author->id()); $node2->save(); $expected_result[] = ['nid' => $node2->id()]; // Force to add a new revision. $node2->setNewRevision(TRUE); - $node2->setRevisionAuthorId($author->id()); + $node2->setRevisionUserId($author->id()); $node2->save(); // Create one node on which the author has neither authorship of revisions diff --git a/core/modules/node/tests/src/Kernel/Views/FilterUidRevisionTest.php b/core/modules/node/tests/src/Kernel/Views/FilterUidRevisionTest.php new file mode 100644 index 000000000..84dfbacb7 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Views/FilterUidRevisionTest.php @@ -0,0 +1,83 @@ +installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installEntitySchema('view'); + $this->installSchema('system', ['sequences']); + $this->installSchema('node', ['node_access']); + $this->installConfig(['filter']); + ViewTestData::createTestViews(static::class, ['node_test_views']); + + $author = $this->createUser(); + $no_author = $this->createUser(); + + $expected_result = []; + // Create one node, with the author as the node author. + $node = $this->createNode(['uid' => $author->id()]); + $expected_result[] = ['nid' => $node->id()]; + // Create one node of which an additional revision author will be the + // author. + $node = $this->createNode(['revision_uid' => $no_author->id()]); + $expected_result[] = ['nid' => $node->id()]; + $revision = clone $node; + // Force to add a new revision. + $revision->set('vid', NULL); + $revision->set('revision_uid', $author->id()); + $revision->save(); + + // Create one node on which the author has neither authorship of revisions + // or the main node. + $this->createNode(['uid' => $no_author->id()]); + + $view = Views::getView('test_filter_node_uid_revision'); + $view->initHandlers(); + $view->filter['uid_revision']->value = [$author->id()]; + + $view->preview(); + $this->assertIdenticalResultset($view, $expected_result, ['nid' => 'nid'], 'Make sure that the view only returns nodes which match either the node or the revision author.'); + } + +} diff --git a/core/modules/node/tests/src/Kernel/Views/RevisionUidTest.php b/core/modules/node/tests/src/Kernel/Views/RevisionUidTest.php new file mode 100644 index 000000000..c83a540e8 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Views/RevisionUidTest.php @@ -0,0 +1,156 @@ + 'nid', + 'vid' => 'vid', + 'uid' => 'uid', + 'revision_uid' => 'revision_uid', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp($import_test_views = TRUE) { + parent::setUp($import_test_views); + + $this->installSchema('node', 'node_access'); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + + if ($import_test_views) { + ViewTestData::createTestViews(get_class($this), ['node_test_views']); + } + } + + /** + * Tests the node_revision_uid relationship. + */ + public function testRevisionUid() { + $primary_author = $this->createUser(); + $secondary_author = $this->createUser(); + + $node_type = NodeType::create([ + 'type' => 'page', + ]); + $node_type->save(); + $node = Node::create([ + 'title' => 'Test node', + 'type' => 'page', + 'uid' => $primary_author->id(), + ]); + $node->save(); + $view = Views::getView('test_node_revision_uid'); + $this->executeView($view); + $this->assertIdenticalResultset($view, [ + [ + 'nid' => 1, + 'vid' => 1, + 'uid' => $primary_author->id(), + 'revision_uid' => $primary_author->id(), + ], + ], static::$columnMap); + + // Test results shows the original author as well as the revision author. + $node->setRevisionUser($secondary_author); + $node->setNewRevision(); + $node->save(); + + $view = Views::getView('test_node_revision_uid'); + $this->executeView($view); + $this->assertIdenticalResultset($view, [ + [ + 'nid' => 1, + 'vid' => 2, + 'uid' => $primary_author->id(), + 'revision_uid' => $secondary_author->id(), + ], + ], static::$columnMap); + + // Build a larger dataset to allow filtering. + $node2_title = $this->randomString(); + $node2 = Node::create([ + 'title' => $node2_title, + 'type' => 'page', + 'uid' => $primary_author->id(), + ]); + $node2->save(); + $node2->setRevisionUser($primary_author); + $node2->setNewRevision(); + $node2->save(); + + $view = Views::getView('test_node_revision_uid'); + $this->executeView($view); + $this->assertIdenticalResultset($view, [ + [ + 'nid' => 1, + 'vid' => 2, + 'uid' => $primary_author->id(), + 'revision_uid' => $secondary_author->id(), + ], + [ + 'nid' => 2, + 'vid' => 4, + 'uid' => $primary_author->id(), + 'revision_uid' => $primary_author->id(), + ], + ], static::$columnMap); + + // Test filter by revision_uid. + $view = Views::getView('test_node_revision_uid'); + $view->initHandlers(); + $view->filter['revision_uid']->value = [$secondary_author->id()]; + $this->executeView($view); + $this->assertIdenticalResultset($view, [ + [ + 'nid' => 1, + 'vid' => 2, + 'uid' => $primary_author->id(), + 'revision_uid' => $secondary_author->id(), + ], + ], static::$columnMap); + } + +} diff --git a/core/modules/node/tests/src/Traits/NodeCreationTrait.php b/core/modules/node/tests/src/Traits/NodeCreationTrait.php index 714ae2fe6..bec9f2009 100644 --- a/core/modules/node/tests/src/Traits/NodeCreationTrait.php +++ b/core/modules/node/tests/src/Traits/NodeCreationTrait.php @@ -40,10 +40,11 @@ public function getNodeByTitle($title, $reset = FALSE) { /** * Creates a node based on default settings. * - * @param array $settings - * (optional) An associative array of settings for the node, as used in - * entity_create(). Override the defaults by specifying the key and value + * @param array $values + * (optional) An associative array of values for the node, as used in + * creation of entity. Override the defaults by specifying the key and value * in the array, for example: + * * @code * $this->drupalCreateNode(array( * 'title' => t('Hello, world!'), @@ -53,7 +54,7 @@ public function getNodeByTitle($title, $reset = FALSE) { * The following defaults are provided: * - body: Random string using the default filter format: * @code - * $settings['body'][0] = array( + * $values['body'][0] = array( * 'value' => $this->randomMachineName(32), * 'format' => filter_default_format(), * ); @@ -65,9 +66,9 @@ public function getNodeByTitle($title, $reset = FALSE) { * @return \Drupal\node\NodeInterface * The created node entity. */ - protected function createNode(array $settings = []) { + protected function createNode(array $values = []) { // Populate defaults array. - $settings += [ + $values += [ 'body' => [ [ 'value' => $this->randomMachineName(32), @@ -78,22 +79,22 @@ protected function createNode(array $settings = []) { 'type' => 'page', ]; - if (!array_key_exists('uid', $settings)) { + if (!array_key_exists('uid', $values)) { $user = User::load(\Drupal::currentUser()->id()); if ($user) { - $settings['uid'] = $user->id(); + $values['uid'] = $user->id(); } elseif (method_exists($this, 'setUpCurrentUser')) { /** @var \Drupal\user\UserInterface $user */ $user = $this->setUpCurrentUser(); - $settings['uid'] = $user->id(); + $values['uid'] = $user->id(); } else { - $settings['uid'] = 0; + $values['uid'] = 0; } } - $node = Node::create($settings); + $node = Node::create($values); $node->save(); return $node; diff --git a/core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php b/core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php index 42ab01d82..2a78d2565 100644 --- a/core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php +++ b/core/modules/node/tests/src/Unit/PageCache/DenyNodePreviewTest.php @@ -38,12 +38,12 @@ class DenyNodePreviewTest extends UnitTestCase { /** * The current route match. * - * @var \Drupal\Core\Routing\RouteMatch|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Routing\RouteMatch|\PHPUnit\Framework\MockObject\MockObject */ protected $routeMatch; protected function setUp() { - $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $this->routeMatch = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); $this->policy = new DenyNodePreview($this->routeMatch); $this->response = new Response(); $this->request = new Request(); diff --git a/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php b/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php index d6716c9d8..3b94bb8bd 100644 --- a/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php +++ b/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php @@ -30,20 +30,20 @@ public function testConstructor() { $actions = []; for ($i = 1; $i <= 2; $i++) { - $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface'); + $action = $this->createMock('\Drupal\system\ActionConfigEntityInterface'); $action->expects($this->any()) ->method('getType') ->will($this->returnValue('node')); $actions[$i] = $action; } - $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface'); + $action = $this->createMock('\Drupal\system\ActionConfigEntityInterface'); $action->expects($this->any()) ->method('getType') ->will($this->returnValue('user')); $actions[] = $action; - $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $entity_storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $entity_storage->expects($this->any()) ->method('loadMultiple') ->will($this->returnValue($actions)); @@ -56,9 +56,9 @@ public function testConstructor() { $entity_repository = $this->createMock(EntityRepositoryInterface::class); - $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $language_manager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface'); - $messenger = $this->getMock('Drupal\Core\Messenger\MessengerInterface'); + $messenger = $this->createMock('Drupal\Core\Messenger\MessengerInterface'); $views_data = $this->getMockBuilder('Drupal\views\ViewsData') ->disableOriginalConstructor() @@ -72,7 +72,7 @@ public function testConstructor() { $container->set('string_translation', $this->getStringTranslationStub()); \Drupal::setContainer($container); - $storage = $this->getMock('Drupal\views\ViewEntityInterface'); + $storage = $this->createMock('Drupal\views\ViewEntityInterface'); $storage->expects($this->any()) ->method('get') ->with('base_table') diff --git a/core/modules/options/migrations/state/options.migrate_drupal.yml b/core/modules/options/migrations/state/options.migrate_drupal.yml new file mode 100644 index 000000000..a523c6432 --- /dev/null +++ b/core/modules/options/migrations/state/options.migrate_drupal.yml @@ -0,0 +1,6 @@ +finished: + 6: + optionwidgets: options + 7: + options: options + list: options diff --git a/core/modules/options/options.info.yml b/core/modules/options/options.info.yml index c6701a591..41c5374a4 100644 --- a/core/modules/options/options.info.yml +++ b/core/modules/options/options.info.yml @@ -2,14 +2,8 @@ name: Options type: module description: 'Defines selection, check box and radio button widgets for text and numeric fields.' package: Field types -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:field - drupal:text - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php b/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php index b6b5516aa..65de5e32a 100644 --- a/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php +++ b/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php @@ -1,6 +1,6 @@ 'entity_test_rev', 'required' => TRUE, ])->save(); - entity_get_form_display('entity_test_rev', 'entity_test_rev', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('entity_test_rev', 'entity_test_rev') ->setComponent($field_name, [ 'type' => 'options_select', ]) diff --git a/core/modules/options/tests/src/Functional/OptionsDynamicValuesValidationTest.php b/core/modules/options/tests/src/Functional/OptionsDynamicValuesValidationTest.php index 7ef535c96..d44ec3941 100644 --- a/core/modules/options/tests/src/Functional/OptionsDynamicValuesValidationTest.php +++ b/core/modules/options/tests/src/Functional/OptionsDynamicValuesValidationTest.php @@ -9,6 +9,11 @@ */ class OptionsDynamicValuesValidationTest extends OptionsDynamicValuesTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test that allowed values function gets the entity. */ diff --git a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php index b61748397..97e150407 100644 --- a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php +++ b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\options\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\field\Functional\FieldTestBase; @@ -20,6 +21,11 @@ class OptionsFieldUITest extends FieldTestBase { */ public static $modules = ['node', 'options', 'field_test', 'taxonomy', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The name of the created content type. * @@ -274,7 +280,10 @@ protected function createOptionsField($type) { 'bundle' => $this->type, ])->save(); - entity_get_form_display('node', $this->type, 'default')->setComponent($this->fieldName)->save(); + \Drupal::service('entity_display.repository') + ->getFormDisplay('node', $this->type) + ->setComponent($this->fieldName) + ->save(); $this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName . '/storage'; } @@ -322,7 +331,7 @@ public function testNodeDisplay() { ]; $this->drupalPostForm($this->adminPath, $edit, t('Save field settings')); - $this->assertText(format_string('Updated field @field_name field settings.', ['@field_name' => $this->fieldName]), "The 'On' and 'Off' form fields work for boolean fields."); + $this->assertText(new FormattableMarkup('Updated field @field_name field settings.', ['@field_name' => $this->fieldName]), "The 'On' and 'Off' form fields work for boolean fields."); // Select a default value. $edit = [ diff --git a/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php b/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php index d4d6afff9..556bc9ba2 100644 --- a/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php +++ b/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php @@ -20,6 +20,11 @@ class OptionsFloatFieldImportTest extends FieldTestBase { */ public static $modules = ['node', 'options', 'field_ui', 'config', 'options_config_install_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/options/tests/src/Functional/OptionsSelectDynamicValuesTest.php b/core/modules/options/tests/src/Functional/OptionsSelectDynamicValuesTest.php index 0fb357be0..6738dbf68 100644 --- a/core/modules/options/tests/src/Functional/OptionsSelectDynamicValuesTest.php +++ b/core/modules/options/tests/src/Functional/OptionsSelectDynamicValuesTest.php @@ -9,6 +9,11 @@ */ class OptionsSelectDynamicValuesTest extends OptionsDynamicValuesTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the 'options_select' widget (single select). */ @@ -27,7 +32,7 @@ public function testSelectListDynamic() { foreach ($options as $option) { $value = $option->getValue(); if ($value != '_none') { - $this->assertTrue(array_search($value, $this->test)); + $this->assertContains($value, $this->test); } } } diff --git a/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php b/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php index 8363467c9..5902682c0 100644 --- a/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php +++ b/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php @@ -21,6 +21,11 @@ class OptionsWidgetsTest extends FieldTestBase { */ public static $modules = ['node', 'options', 'entity_test', 'options_test', 'taxonomy', 'field_ui']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A field storage with cardinality 1 to use in this test class. * @@ -113,7 +118,8 @@ public function testRadioButtons() { 'bundle' => 'entity_test', ]); $field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->card1->getName(), [ 'type' => 'options_buttons', ]) @@ -170,7 +176,8 @@ public function testCheckBoxes() { 'bundle' => 'entity_test', ]); $field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->card2->getName(), [ 'type' => 'options_buttons', ]) @@ -260,7 +267,8 @@ public function testSelectListSingle() { 'required' => TRUE, ]); $field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->card1->getName(), [ 'type' => 'options_select', ]) @@ -277,7 +285,7 @@ public function testSelectListSingle() { // Display form. $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit'); // A required field without any value has a "none" option. - $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', [':id' => 'edit-card-1', ':label' => '- Select a value -']), 'A required select list has a "Select a value" choice.'); + $this->assertNotEmpty($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', [':id' => 'edit-card-1', ':label' => '- Select a value -']), 'A required select list has a "Select a value" choice.'); // With no field data, nothing is selected. $this->assertTrue($this->assertSession()->optionExists('card_1', '_none')->isSelected()); @@ -299,7 +307,7 @@ public function testSelectListSingle() { // Display form: check that the right options are selected. $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit'); // A required field with a value has no 'none' option. - $this->assertFalse($this->xpath('//select[@id=:id]//option[@value="_none"]', [':id' => 'edit-card-1']), 'A required select list with an actual value has no "none" choice.'); + $this->assertEmpty($this->xpath('//select[@id=:id]//option[@value="_none"]', [':id' => 'edit-card-1']), 'A required select list with an actual value has no "none" choice.'); $this->assertTrue($this->assertSession()->optionExists('card_1', 0)->isSelected()); $this->assertFalse($this->assertSession()->optionExists('card_1', 1)->isSelected()); $this->assertFalse($this->assertSession()->optionExists('card_1', 2)->isSelected()); @@ -311,7 +319,7 @@ public function testSelectListSingle() { // Display form. $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit'); // A non-required field has a 'none' option. - $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', [':id' => 'edit-card-1', ':label' => '- None -']), 'A non-required select list has a "None" choice.'); + $this->assertNotEmpty($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', [':id' => 'edit-card-1', ':label' => '- None -']), 'A non-required select list has a "None" choice.'); // Submit form: Unselect the option. $edit = ['card_1' => '_none']; $this->drupalPostForm('entity_test/manage/' . $entity->id() . '/edit', $edit, t('Save')); @@ -359,7 +367,8 @@ public function testSelectListMultiple() { 'bundle' => 'entity_test', ]); $field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->card2->getName(), [ 'type' => 'options_select', ]) @@ -430,7 +439,7 @@ public function testSelectListMultiple() { $field->setRequired(TRUE); $field->save(); $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit'); - $this->assertFalse($this->xpath('//select[@id=:id]//option[@value=""]', [':id' => 'edit-card-2']), 'A required select list does not have an empty key.'); + $this->assertEmpty($this->xpath('//select[@id=:id]//option[@value=""]', [':id' => 'edit-card-2']), 'A required select list does not have an empty key.'); // We do not have to test that a required select list with one option is // auto-selected because the browser does it for us. @@ -528,8 +537,11 @@ public function testEmptyValue() { ]); $field->save(); + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + // Change it to the check boxes/radio buttons widget. - entity_get_form_display('entity_test', 'entity_test', 'default') + $display_repository->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->card1->getName(), [ 'type' => 'options_buttons', ]) @@ -544,11 +556,11 @@ public function testEmptyValue() { // Display form: check that _none options are present and has label. $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit'); - $this->assertTrue($this->xpath('//div[@id=:id]//input[@value=:value]', [':id' => 'edit-card-1', ':value' => '_none']), 'A test radio button has a "None" choice.'); - $this->assertTrue($this->xpath('//div[@id=:id]//label[@for=:for and text()=:label]', [':id' => 'edit-card-1', ':for' => 'edit-card-1-none', ':label' => 'N/A']), 'A test radio button has a "N/A" choice.'); + $this->assertNotEmpty($this->xpath('//div[@id=:id]//input[@value=:value]', [':id' => 'edit-card-1', ':value' => '_none']), 'A test radio button has a "None" choice.'); + $this->assertNotEmpty($this->xpath('//div[@id=:id]//label[@for=:for and text()=:label]', [':id' => 'edit-card-1', ':for' => 'edit-card-1-none', ':label' => 'N/A']), 'A test radio button has a "N/A" choice.'); // Change it to the select widget. - entity_get_form_display('entity_test', 'entity_test', 'default') + $display_repository->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->card1->getName(), [ 'type' => 'options_select', ]) @@ -557,7 +569,7 @@ public function testEmptyValue() { // Display form: check that _none options are present and has label. $this->drupalGet('entity_test/manage/' . $entity->id() . '/edit'); // A required field without any value has a "none" option. - $this->assertTrue($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', [':id' => 'edit-card-1', ':label' => '- None -']), 'A test select has a "None" choice.'); + $this->assertNotEmpty($this->xpath('//select[@id=:id]//option[@value="_none" and text()=:label]', [':id' => 'edit-card-1', ':label' => '- None -']), 'A test select has a "None" choice.'); } } diff --git a/core/modules/options/tests/src/Kernel/OptionsFieldTest.php b/core/modules/options/tests/src/Kernel/OptionsFieldTest.php index 401d6bc1d..6ed2fda2c 100644 --- a/core/modules/options/tests/src/Kernel/OptionsFieldTest.php +++ b/core/modules/options/tests/src/Kernel/OptionsFieldTest.php @@ -40,10 +40,10 @@ public function testUpdateAllowedValues() { $this->fieldStorage->setSetting('allowed_values', [2 => 'Two']); try { $this->fieldStorage->save(); - $this->fail(t('Cannot update a list field storage to not include keys with existing data.')); + $this->fail('Cannot update a list field storage to not include keys with existing data.'); } catch (FieldStorageDefinitionUpdateForbiddenException $e) { - $this->pass(t('Cannot update a list field storage to not include keys with existing data.')); + $this->pass('Cannot update a list field storage to not include keys with existing data.'); } // Empty the value, so that we can actually remove the option. unset($entity->{$this->fieldName}); @@ -80,7 +80,8 @@ public function testUpdateAllowedValues() { 'bundle' => 'entity_test', 'required' => TRUE, ])->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->fieldName, [ 'type' => 'options_buttons', ]) diff --git a/core/modules/options/tests/src/Kernel/OptionsFieldUnitTestBase.php b/core/modules/options/tests/src/Kernel/OptionsFieldUnitTestBase.php index 9098b818b..05f0c64a4 100644 --- a/core/modules/options/tests/src/Kernel/OptionsFieldUnitTestBase.php +++ b/core/modules/options/tests/src/Kernel/OptionsFieldUnitTestBase.php @@ -71,7 +71,8 @@ protected function setUp() { ]); $this->field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay('entity_test', 'entity_test') ->setComponent($this->fieldName, [ 'type' => 'options_buttons', ]) diff --git a/core/modules/page_cache/page_cache.info.yml b/core/modules/page_cache/page_cache.info.yml index 76cedf00b..ba18ed638 100644 --- a/core/modules/page_cache/page_cache.info.yml +++ b/core/modules/page_cache/page_cache.info.yml @@ -2,11 +2,5 @@ name: Internal Page Cache type: module description: 'Caches pages for anonymous users. Use when an external page cache is not available.' package: Core -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/page_cache/tests/modules/page_cache_form_test.info.yml b/core/modules/page_cache/tests/modules/page_cache_form_test.info.yml index d26773a2f..e18bb5349 100644 --- a/core/modules/page_cache/tests/modules/page_cache_form_test.info.yml +++ b/core/modules/page_cache/tests/modules/page_cache_form_test.info.yml @@ -1,12 +1,6 @@ name: 'Page Cache Form Test' type: module description: 'Support module for the Page Cache module tests.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php b/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php index 40cefba95..dd41bc25d 100644 --- a/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php +++ b/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php @@ -111,7 +111,6 @@ public function testPageCacheTags() { 'user:' . $author_1->id(), 'config:filter.format.basic_html', 'config:color.theme.bartik', - 'config:search.settings', 'config:system.menu.account', 'config:system.menu.tools', 'config:system.menu.footer', @@ -152,7 +151,6 @@ public function testPageCacheTags() { 'user:' . $author_2->id(), 'config:color.theme.bartik', 'config:filter.format.full_html', - 'config:search.settings', 'config:system.menu.account', 'config:system.menu.tools', 'config:system.menu.footer', diff --git a/core/modules/page_cache/tests/src/Functional/PageCacheTest.php b/core/modules/page_cache/tests/src/Functional/PageCacheTest.php index 88c5311fd..fc0421de0 100644 --- a/core/modules/page_cache/tests/src/Functional/PageCacheTest.php +++ b/core/modules/page_cache/tests/src/Functional/PageCacheTest.php @@ -29,6 +29,11 @@ class PageCacheTest extends BrowserTestBase { */ public static $modules = ['test_page_test', 'system_test', 'entity_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -228,7 +233,7 @@ public function testConditionalRequests() { $this->drupalLogin($user); $this->drupalGet('', [], ['If-Modified-Since' => $last_modified, 'If-None-Match' => $etag]); $this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.'); } /** @@ -268,8 +273,8 @@ public function testPageCache() { $user = $this->drupalCreateUser(); $this->drupalLogin($user); $this->drupalGet('system-test/set-header', ['query' => ['name' => 'Foo', 'value' => 'bar']]); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); - $this->assertTrue(strpos(strtolower($this->drupalGetHeader('Vary')), 'cookie') === FALSE, 'Vary: Cookie header was not sent.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); + $this->assertNotContains('cookie', $this->drupalGetHeader('Vary'), 'Vary: Cookie header was not sent.', TRUE); $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent.'); $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); @@ -436,13 +441,13 @@ public function testPageCacheWithoutVaryCookie() { // Fill the cache. $this->drupalGet(''); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); - $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.'); + $this->assertNotContains('cookie', $this->drupalGetHeader('Vary'), 'Vary: Cookie header was not sent.', TRUE); $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); // Check cache. $this->drupalGet(''); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.'); + $this->assertNotContains('cookie', $this->drupalGetHeader('Vary'), 'Vary: Cookie header was not sent.', TRUE); $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); } @@ -488,22 +493,22 @@ public function testCacheableResponseResponses() { // GET a URL, which would be marked as a cache miss if it were cacheable. $this->drupalGet('/system-test/respond-reponse'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent'); // GET it again, verify it's still not cached. $this->drupalGet('/system-test/respond-reponse'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent'); // GET a URL, which would be marked as a cache miss if it were cacheable. $this->drupalGet('/system-test/respond-public-response'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=60, public', 'Cache-Control header was sent'); // GET it again, verify it's still not cached. $this->drupalGet('/system-test/respond-public-response'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=60, public', 'Cache-Control header was sent'); // GET a URL, which should be marked as a cache miss. @@ -523,7 +528,7 @@ public function testCacheableResponseResponses() { // GET a URL that was cached by Page Cache before, it should not be now. $this->drupalGet('/respond-cacheable-reponse'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); } /** diff --git a/core/modules/path/config/optional/language.content_settings.path_alias.path_alias.yml b/core/modules/path/config/optional/language.content_settings.path_alias.path_alias.yml new file mode 100644 index 000000000..daacf92e0 --- /dev/null +++ b/core/modules/path/config/optional/language.content_settings.path_alias.path_alias.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - path_alias +id: path_alias.path_alias +target_entity_type_id: path_alias +target_bundle: path_alias +default_langcode: und +language_alterable: true diff --git a/core/modules/path/migrations/d6_url_alias.yml b/core/modules/path/migrations/d6_url_alias.yml index 6263ae2a0..747a06805 100644 --- a/core/modules/path/migrations/d6_url_alias.yml +++ b/core/modules/path/migrations/d6_url_alias.yml @@ -8,7 +8,10 @@ source: constants: slash: '/' process: - source: + # If you are using this file to build a custom migration consider removing + # the id field to allow incremental migrations. + id: pid + _path: plugin: concat source: - constants/slash @@ -18,9 +21,7 @@ process: source: - constants/slash - dst - langcode: - plugin: d6_url_alias_language - source: language + node_translation: - plugin: explode @@ -35,5 +36,19 @@ process: - plugin: migration_lookup migration: d6_node_translation + langcode: + - + plugin: null_coalesce + source: + - '@node_translation/1' + - language + - + plugin: default_value + default_value: 'und' + path: + plugin: path_set_translated + source: + - '@_path' + - '@node_translation' destination: - plugin: url_alias + plugin: entity:path_alias diff --git a/core/modules/path/migrations/d7_url_alias.yml b/core/modules/path/migrations/d7_url_alias.yml index a4172dcf2..b49686a7f 100644 --- a/core/modules/path/migrations/d7_url_alias.yml +++ b/core/modules/path/migrations/d7_url_alias.yml @@ -8,7 +8,10 @@ source: constants: slash: '/' process: - source: + # If you are using this file to build a custom migration consider removing + # the id field to allow incremental migrations. + id: pid + _path: plugin: concat source: - constants/slash @@ -18,7 +21,6 @@ process: source: - constants/slash - alias - langcode: language node_translation: - plugin: explode @@ -33,5 +35,15 @@ process: - plugin: migration_lookup migration: d7_node_translation + langcode: + plugin: null_coalesce + source: + - '@node_translation/1' + - language + path: + plugin: path_set_translated + source: + - '@_path' + - '@node_translation' destination: - plugin: url_alias + plugin: entity:path_alias diff --git a/core/modules/path/migrations/state/path.migrate_drupal.yml b/core/modules/path/migrations/state/path.migrate_drupal.yml new file mode 100644 index 000000000..d8066b950 --- /dev/null +++ b/core/modules/path/migrations/state/path.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + path: path + 7: + path: path diff --git a/core/modules/path/path.api.php b/core/modules/path/path.api.php index 5df539b52..dcce82df7 100644 --- a/core/modules/path/path.api.php +++ b/core/modules/path/path.api.php @@ -17,7 +17,10 @@ * The array structure is identical to that of the return value of * \Drupal\Core\Path\AliasStorageInterface::save(). * - * @see \Drupal\Core\Path\AliasStorageInterface::save() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * hook_path_alias_insert() instead. + * + * @see https://www.drupal.org/node/3013865 */ function hook_path_insert($path) { \Drupal::database()->insert('mytable') @@ -35,7 +38,10 @@ function hook_path_insert($path) { * The array structure is identical to that of the return value of * \Drupal\Core\Path\AliasStorageInterface::save(). * - * @see \Drupal\Core\Path\AliasStorageInterface::save() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * hook_path_alias_update() instead. + * + * @see https://www.drupal.org/node/3013865 */ function hook_path_update($path) { if ($path['alias'] != $path['original']['alias']) { @@ -53,7 +59,10 @@ function hook_path_update($path) { * The array structure is identical to that of the return value of * \Drupal\Core\Path\AliasStorageInterface::save(). * - * @see \Drupal\Core\Path\AliasStorageInterface::delete() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * hook_path_alias_delete() instead. + * + * @see https://www.drupal.org/node/3013865 */ function hook_path_delete($path) { \Drupal::database()->delete('mytable') diff --git a/core/modules/path/path.info.yml b/core/modules/path/path.info.yml index e4a64f8db..2075f77b4 100644 --- a/core/modules/path/path.info.yml +++ b/core/modules/path/path.info.yml @@ -2,12 +2,8 @@ name: Path type: module description: 'Allows users to rename URLs.' package: Core -# version: VERSION -# core: 8.x -configure: path.admin_overview - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x +configure: entity.path_alias.collection +dependencies: + - drupal:path_alias diff --git a/core/modules/path/path.links.action.yml b/core/modules/path/path.links.action.yml index 9c5898480..985482ec3 100644 --- a/core/modules/path/path.links.action.yml +++ b/core/modules/path/path.links.action.yml @@ -1,5 +1,5 @@ -path.admin_add: - route_name: path.admin_add +entity.path_alias.add_form: + route_name: entity.path_alias.add_form title: 'Add alias' appears_on: - - path.admin_overview + - entity.path_alias.collection diff --git a/core/modules/path/path.links.menu.yml b/core/modules/path/path.links.menu.yml index 4f394a055..549a4eb92 100644 --- a/core/modules/path/path.links.menu.yml +++ b/core/modules/path/path.links.menu.yml @@ -1,6 +1,6 @@ -path.admin_overview: +entity.path_alias.collection: title: 'URL aliases' description: 'Add custom URLs to existing paths.' - route_name: path.admin_overview + route_name: entity.path_alias.collection parent: system.admin_config_search weight: -5 diff --git a/core/modules/path/path.links.task.yml b/core/modules/path/path.links.task.yml index bc59857a4..e9328fe87 100644 --- a/core/modules/path/path.links.task.yml +++ b/core/modules/path/path.links.task.yml @@ -1,4 +1,4 @@ -path.admin_overview: +entity.path_alias.collection: title: List - route_name: path.admin_overview - base_route: path.admin_overview + route_name: entity.path_alias.collection + base_route: entity.path_alias.collection diff --git a/core/modules/path/path.module b/core/modules/path/path.module index f344c74b6..40323b0d2 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -6,10 +6,16 @@ */ use Drupal\Core\Url; +use Drupal\Core\Entity\ContentEntityDeleteForm; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\path\PathAliasForm; +use Drupal\path\PathAliasListBuilder; /** * Implements hook_help(). @@ -23,20 +29,71 @@ function path_help($route_name, RouteMatchInterface $route_match) { $output .= '

' . t('Uses') . '

'; $output .= '
'; $output .= '
' . t('Creating aliases') . '
'; - $output .= '
' . t('If you create or edit a taxonomy term you can add an alias (for example music/jazz) in the field "URL alias". When creating or editing content you can add an alias (for example about-us/team) under the section "URL path settings" in the field "URL alias". Aliases for any other path can be added through the page URL aliases. To add aliases a user needs the permission Create and edit URL aliases.', [':aliases' => Url::fromRoute('path.admin_overview')->toString(), ':permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-path'])->toString()]) . '
'; + $output .= '
' . t('If you create or edit a taxonomy term you can add an alias (for example music/jazz) in the field "URL alias". When creating or editing content you can add an alias (for example about-us/team) under the section "URL path settings" in the field "URL alias". Aliases for any other path can be added through the page URL aliases. To add aliases a user needs the permission Create and edit URL aliases.', [':aliases' => Url::fromRoute('entity.path_alias.collection')->toString(), ':permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-path'])->toString()]) . '
'; $output .= '
' . t('Managing aliases') . '
'; - $output .= '
' . t('The Path module provides a way to search and view a list of all aliases that are in use on your website. Aliases can be added, edited and deleted through this list.', [':aliases' => Url::fromRoute('path.admin_overview')->toString()]) . '
'; + $output .= '
' . t('The Path module provides a way to search and view a list of all aliases that are in use on your website. Aliases can be added, edited and deleted through this list.', [':aliases' => Url::fromRoute('entity.path_alias.collection')->toString()]) . '
'; $output .= '
'; return $output; - case 'path.admin_overview': + case 'entity.path_alias.collection': return '

' . t("An alias defines a different name for an existing URL path - for example, the alias 'about' for the URL path 'node/1'. A URL path can have multiple aliases.") . '

'; - case 'path.admin_add': + case 'entity.path_alias.add_form': return '

' . t('Enter the path you wish to create the alias for, followed by the name of the new alias.') . '

'; } } +/** + * Implements hook_entity_type_alter(). + */ +function path_entity_type_alter(array &$entity_types) { + // @todo Remove the conditional once core fully supports "path_alias" as an + // optional module. See https://drupal.org/node/3092090. + /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */ + if (isset($entity_types['path_alias'])) { + $entity_types['path_alias']->setFormClass('default', PathAliasForm::class); + $entity_types['path_alias']->setFormClass('delete', ContentEntityDeleteForm::class); + $entity_types['path_alias']->setHandlerClass('route_provider', ['html' => AdminHtmlRouteProvider::class]); + $entity_types['path_alias']->setListBuilderClass(PathAliasListBuilder::class); + $entity_types['path_alias']->setLinkTemplate('collection', '/admin/config/search/path'); + $entity_types['path_alias']->setLinkTemplate('add-form', '/admin/config/search/path/add'); + $entity_types['path_alias']->setLinkTemplate('edit-form', '/admin/config/search/path/edit/{path_alias}'); + $entity_types['path_alias']->setLinkTemplate('delete-form', '/admin/config/search/path/delete/{path_alias}'); + } +} + +/** + * Implements hook_entity_base_field_info_alter(). + */ +function path_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) { + /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */ + if ($entity_type->id() === 'path_alias') { + $fields['langcode']->setDisplayOptions('form', [ + 'type' => 'language_select', + 'weight' => 0, + 'settings' => [ + 'include_locked' => FALSE, + ], + ]); + + $fields['path']->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => 5, + 'settings' => [ + 'size' => 45, + ], + ]); + + $fields['alias']->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => 10, + 'settings' => [ + 'size' => 45, + ], + ]); + } +} + /** * Implements hook_entity_base_field_info(). */ @@ -69,3 +126,33 @@ function path_entity_translation_create(ContentEntityInterface $translation) { } } } + +/** + * Implements hook_field_widget_form_alter(). + */ +function path_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) { + $field_definition = $context['items']->getFieldDefinition(); + $field_name = $field_definition->getName(); + $entity_type = $field_definition->getTargetEntityTypeId(); + $widget_name = $context['widget']->getPluginId(); + + if ($entity_type === 'path_alias') { + if (($field_name === 'path' || $field_name === 'alias') && $widget_name === 'string_textfield') { + $element['value']['#field_prefix'] = \Drupal::service('router.request_context')->getCompleteBaseUrl(); + } + + if ($field_name === 'langcode') { + $element['value']['#description'] = t('A path alias set for a specific language will always be used when displaying this page in that language, and takes precedence over path aliases set as - Not specified -.'); + $element['value']['#empty_value'] = LanguageInterface::LANGCODE_NOT_SPECIFIED; + $element['value']['#empty_option'] = t('- Not specified -'); + } + + if ($field_name === 'path') { + $element['value']['#description'] = t('Specify the existing path you wish to alias. For example: /node/28, /forum/1, /taxonomy/term/1.'); + } + + if ($field_name === 'alias') { + $element['value']['#description'] = t('Specify an alternative path by which this data can be accessed. For example, type "/about" when writing an about page.'); + } + } +} diff --git a/core/modules/path/path.post_update.php b/core/modules/path/path.post_update.php new file mode 100644 index 000000000..b11ec8f93 --- /dev/null +++ b/core/modules/path/path.post_update.php @@ -0,0 +1,23 @@ +getEntityType('language_content_settings')) { + ContentLanguageSettings::loadByEntityTypeBundle('path_alias', 'path_alias') + ->setDefaultLangcode(LanguageInterface::LANGCODE_NOT_SPECIFIED) + ->setLanguageAlterable(TRUE) + ->trustData() + ->save(); + } +} diff --git a/core/modules/path/path.routing.yml b/core/modules/path/path.routing.yml deleted file mode 100644 index 96345415c..000000000 --- a/core/modules/path/path.routing.yml +++ /dev/null @@ -1,40 +0,0 @@ -path.delete: - path: '/admin/config/search/path/delete/{pid}' - defaults: - _form: '\Drupal\path\Form\DeleteForm' - _title: 'Delete alias' - requirements: - _permission: 'administer url aliases' - -path.admin_overview: - path: '/admin/config/search/path' - defaults: - _title: 'URL aliases' - _controller: '\Drupal\path\Controller\PathController::adminOverview' - keys: NULL - requirements: - _permission: 'administer url aliases' - -path.admin_overview_filter: - path: '/admin/config/search/path/filter' - defaults: - _title: 'URL aliases' - _controller: '\Drupal\path\Controller\PathController::adminOverview' - requirements: - _permission: 'administer url aliases' - -path.admin_add: - path: '/admin/config/search/path/add' - defaults: - _title: 'Add alias' - _form: '\Drupal\path\Form\AddForm' - requirements: - _permission: 'administer url aliases' - -path.admin_edit: - path: '/admin/config/search/path/edit/{pid}' - defaults: - _title: 'Edit alias' - _form: '\Drupal\path\Form\EditForm' - requirements: - _permission: 'administer url aliases' diff --git a/core/modules/path/path.services.yml b/core/modules/path/path.services.yml new file mode 100644 index 000000000..a2486eed7 --- /dev/null +++ b/core/modules/path/path.services.yml @@ -0,0 +1,10 @@ +services: + path.route_subscriber_bc: + class: Drupal\path\Routing\RouteSubscriber + tags: + - { name: event_subscriber } + path.route_processor_bc: + class: Drupal\path\Routing\RouteProcessor + arguments: ['@router.route_provider'] + tags: + - { name: route_processor_outbound } diff --git a/core/modules/path/src/Controller/PathController.php b/core/modules/path/src/Controller/PathController.php deleted file mode 100644 index 26fe85171..000000000 --- a/core/modules/path/src/Controller/PathController.php +++ /dev/null @@ -1,134 +0,0 @@ -aliasStorage = $alias_storage; - $this->aliasManager = $alias_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('path.alias_storage'), - $container->get('path.alias_manager') - ); - } - - /** - * Displays the path administration overview page. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * - * @return array - * A render array as expected by - * \Drupal\Core\Render\RendererInterface::render(). - */ - public function adminOverview(Request $request) { - $keys = $request->query->get('search'); - // Add the filter form above the overview table. - $build['path_admin_filter_form'] = $this->formBuilder()->getForm('Drupal\path\Form\PathFilterForm', $keys); - // Enable language column if language.module is enabled or if we have any - // alias with a language. - $multilanguage = ($this->moduleHandler()->moduleExists('language') || $this->aliasStorage->languageAliasExists()); - - $header = []; - $header[] = ['data' => $this->t('Alias'), 'field' => 'alias', 'sort' => 'asc']; - $header[] = ['data' => $this->t('System'), 'field' => 'source']; - if ($multilanguage) { - $header[] = ['data' => $this->t('Language'), 'field' => 'langcode']; - } - $header[] = $this->t('Operations'); - - $rows = []; - $destination = $this->getDestinationArray(); - foreach ($this->aliasStorage->getAliasesForAdminListing($header, $keys) as $data) { - $row = []; - // @todo Should Path module store leading slashes? See - // https://www.drupal.org/node/2430593. - $row['data']['alias'] = $this->l(Unicode::truncate($data->alias, 50, FALSE, TRUE), Url::fromUserInput($data->source, [ - 'attributes' => ['title' => $data->alias], - ])); - $row['data']['source'] = $this->l(Unicode::truncate($data->source, 50, FALSE, TRUE), Url::fromUserInput($data->source, [ - 'alias' => TRUE, - 'attributes' => ['title' => $data->source], - ])); - if ($multilanguage) { - $row['data']['language_name'] = $this->languageManager()->getLanguageName($data->langcode); - } - - $operations = []; - $operations['edit'] = [ - 'title' => $this->t('Edit'), - 'url' => Url::fromRoute('path.admin_edit', ['pid' => $data->pid], ['query' => $destination]), - ]; - $operations['delete'] = [ - 'title' => $this->t('Delete'), - 'url' => Url::fromRoute('path.delete', ['pid' => $data->pid], ['query' => $destination]), - ]; - $row['data']['operations'] = [ - 'data' => [ - '#type' => 'operations', - '#links' => $operations, - ], - ]; - - // If the system path maps to a different URL alias, highlight this table - // row to let the user know of old aliases. - if ($data->alias != $this->aliasManager->getAliasByPath($data->source, $data->langcode)) { - $row['class'] = ['warning']; - } - - $rows[] = $row; - } - - $build['path_table'] = [ - '#type' => 'table', - '#header' => $header, - '#rows' => $rows, - '#empty' => $this->t('No URL aliases available. Add URL alias.', [':link' => $this->url('path.admin_add')]), - ]; - $build['path_pager'] = ['#type' => 'pager']; - - return $build; - } - -} diff --git a/core/modules/path/src/Form/AddForm.php b/core/modules/path/src/Form/AddForm.php deleted file mode 100644 index 59db6f9c1..000000000 --- a/core/modules/path/src/Form/AddForm.php +++ /dev/null @@ -1,33 +0,0 @@ - '', - 'alias' => '', - 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, - 'pid' => NULL, - ]; - } - -} diff --git a/core/modules/path/src/Form/DeleteForm.php b/core/modules/path/src/Form/DeleteForm.php deleted file mode 100644 index aac413f2d..000000000 --- a/core/modules/path/src/Form/DeleteForm.php +++ /dev/null @@ -1,92 +0,0 @@ -aliasStorage = $alias_storage; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('path.alias_storage') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'path_alias_delete'; - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - return t('Are you sure you want to delete path alias %title?', ['%title' => $this->pathAlias['alias']]); - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - return new Url('path.admin_overview'); - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, $pid = NULL) { - $this->pathAlias = $this->aliasStorage->load(['pid' => $pid]); - - $form = parent::buildForm($form, $form_state); - - return $form; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $this->aliasStorage->delete(['pid' => $this->pathAlias['pid']]); - - $form_state->setRedirect('path.admin_overview'); - } - -} diff --git a/core/modules/path/src/Form/EditForm.php b/core/modules/path/src/Form/EditForm.php deleted file mode 100644 index 9638b57ab..000000000 --- a/core/modules/path/src/Form/EditForm.php +++ /dev/null @@ -1,61 +0,0 @@ -aliasStorage->load(['pid' => $pid]); - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, $pid = NULL) { - $form = parent::buildForm($form, $form_state, $pid); - - $form['#title'] = $this->path['alias']; - $form['pid'] = [ - '#type' => 'hidden', - '#value' => $this->path['pid'], - ]; - - $url = new Url('path.delete', [ - 'pid' => $this->path['pid'], - ]); - - if ($this->getRequest()->query->has('destination')) { - $url->setOption('query', $this->getDestinationArray()); - } - - $form['actions']['delete'] = [ - '#type' => 'link', - '#title' => $this->t('Delete'), - '#url' => $url, - '#attributes' => [ - 'class' => ['button', 'button--danger'], - ], - ]; - - return $form; - } - -} diff --git a/core/modules/path/src/Form/PathFilterForm.php b/core/modules/path/src/Form/PathFilterForm.php index 52b0ffbb9..9ff0c34f2 100644 --- a/core/modules/path/src/Form/PathFilterForm.php +++ b/core/modules/path/src/Form/PathFilterForm.php @@ -56,7 +56,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $keys = N * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $form_state->setRedirect('path.admin_overview_filter', [], [ + $form_state->setRedirect('entity.path_alias.collection', [], [ 'query' => ['search' => trim($form_state->getValue('filter'))], ]); } @@ -65,7 +65,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { * Resets the filter selections. */ public function resetForm(array &$form, FormStateInterface $form_state) { - $form_state->setRedirect('path.admin_overview'); + $form_state->setRedirect('entity.path_alias.collection'); } } diff --git a/core/modules/path/src/Form/PathFormBase.php b/core/modules/path/src/Form/PathFormBase.php deleted file mode 100644 index 4b1cbd591..000000000 --- a/core/modules/path/src/Form/PathFormBase.php +++ /dev/null @@ -1,219 +0,0 @@ -aliasStorage = $alias_storage; - $this->aliasManager = $alias_manager; - $this->pathValidator = $path_validator; - $this->requestContext = $request_context; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('path.alias_storage'), - $container->get('path.alias_manager'), - $container->get('path.validator'), - $container->get('router.request_context') - ); - } - - /** - * Builds the path used by the form. - * - * @param int|null $pid - * Either the unique path ID, or NULL if a new one is being created. - */ - abstract protected function buildPath($pid); - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, $pid = NULL) { - $this->path = $this->buildPath($pid); - $form['source'] = [ - '#type' => 'textfield', - '#title' => $this->t('Existing system path'), - '#default_value' => $this->path['source'], - '#maxlength' => 255, - '#size' => 45, - '#description' => $this->t('Specify the existing path you wish to alias. For example: /node/28, /forum/1, /taxonomy/term/1.'), - '#field_prefix' => $this->requestContext->getCompleteBaseUrl(), - '#required' => TRUE, - ]; - $form['alias'] = [ - '#type' => 'textfield', - '#title' => $this->t('Path alias'), - '#default_value' => $this->path['alias'], - '#maxlength' => 255, - '#size' => 45, - '#description' => $this->t('Specify an alternative path by which this data can be accessed. For example, type "/about" when writing an about page.'), - '#field_prefix' => $this->requestContext->getCompleteBaseUrl(), - '#required' => TRUE, - ]; - - // A hidden value unless language.module is enabled. - if (\Drupal::moduleHandler()->moduleExists('language')) { - $languages = \Drupal::languageManager()->getLanguages(); - $language_options = []; - foreach ($languages as $langcode => $language) { - $language_options[$langcode] = $language->getName(); - } - - $form['langcode'] = [ - '#type' => 'select', - '#title' => $this->t('Language'), - '#options' => $language_options, - '#empty_value' => LanguageInterface::LANGCODE_NOT_SPECIFIED, - '#empty_option' => $this->t('- None -'), - '#default_value' => $this->path['langcode'], - '#weight' => -10, - '#description' => $this->t('A path alias set for a specific language will always be used when displaying this page in that language, and takes precedence over path aliases set as - None -.'), - ]; - } - else { - $form['langcode'] = [ - '#type' => 'value', - '#value' => $this->path['langcode'], - ]; - } - - $form['actions'] = ['#type' => 'actions']; - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save'), - '#button_type' => 'primary', - ]; - - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state) { - $source = &$form_state->getValue('source'); - $source = $this->aliasManager->getPathByAlias($source); - $alias = &$form_state->getValue('alias'); - - // Trim the submitted value of whitespace and slashes. Ensure to not trim - // the slash on the left side. - $alias = rtrim(trim(trim($alias), ''), "\\/"); - - if ($source[0] !== '/') { - $form_state->setErrorByName('source', 'The source path has to start with a slash.'); - } - if ($alias[0] !== '/') { - $form_state->setErrorByName('alias', 'The alias path has to start with a slash.'); - } - - // Language is only set if language.module is enabled, otherwise save for all - // languages. - $langcode = $form_state->getValue('langcode', LanguageInterface::LANGCODE_NOT_SPECIFIED); - - if ($this->aliasStorage->aliasExists($alias, $langcode, $this->path['source'])) { - $stored_alias = $this->aliasStorage->load(['alias' => $alias, 'langcode' => $langcode]); - if ($stored_alias['alias'] !== $alias) { - // The alias already exists with different capitalization as the default - // implementation of AliasStorageInterface::aliasExists is - // case-insensitive. - $form_state->setErrorByName('alias', t('The alias %alias could not be added because it is already in use in this language with different capitalization: %stored_alias.', [ - '%alias' => $alias, - '%stored_alias' => $stored_alias['alias'], - ])); - } - else { - $form_state->setErrorByName('alias', t('The alias %alias is already in use in this language.', ['%alias' => $alias])); - } - } - - if (!$this->pathValidator->isValid(trim($source, '/'))) { - $form_state->setErrorByName('source', t("Either the path '@link_path' is invalid or you do not have access to it.", ['@link_path' => $source])); - } - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - // Remove unnecessary values. - $form_state->cleanValues(); - - $pid = $form_state->getValue('pid', 0); - $source = $form_state->getValue('source'); - $alias = $form_state->getValue('alias'); - // Language is only set if language.module is enabled, otherwise save for all - // languages. - $langcode = $form_state->getValue('langcode', LanguageInterface::LANGCODE_NOT_SPECIFIED); - - $this->aliasStorage->save($source, $alias, $langcode, $pid); - - $this->messenger()->addStatus($this->t('The alias has been saved.')); - $form_state->setRedirect('path.admin_overview'); - } - -} diff --git a/core/modules/path/src/PathAliasForm.php b/core/modules/path/src/PathAliasForm.php new file mode 100644 index 000000000..3ce2da630 --- /dev/null +++ b/core/modules/path/src/PathAliasForm.php @@ -0,0 +1,32 @@ +messenger()->addStatus($this->t('The alias has been saved.')); + $form_state->setRedirect('entity.path_alias.collection'); + } + +} diff --git a/core/modules/path/src/PathAliasListBuilder.php b/core/modules/path/src/PathAliasListBuilder.php new file mode 100644 index 000000000..4c6ca4622 --- /dev/null +++ b/core/modules/path/src/PathAliasListBuilder.php @@ -0,0 +1,195 @@ +currentRequest = $current_request; + $this->formBuilder = $form_builder; + $this->languageManager = $language_manager; + $this->aliasManager = $alias_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity_type.manager')->getStorage($entity_type->id()), + $container->get('request_stack')->getCurrentRequest(), + $container->get('form_builder'), + $container->get('language_manager'), + $container->get('path_alias.manager') + ); + } + + /** + * {@inheritdoc} + */ + protected function getEntityIds() { + $query = $this->getStorage()->getQuery(); + + $search = $this->currentRequest->query->get('search'); + if ($search) { + $query->condition('alias', $search, 'CONTAINS'); + } + + // Only add the pager if a limit is specified. + if ($this->limit) { + $query->pager($this->limit); + } + + // Allow the entity query to sort using the table header. + $header = $this->buildHeader(); + $query->tableSort($header); + + return $query->execute(); + } + + /** + * {@inheritdoc} + */ + public function render() { + $keys = $this->currentRequest->query->get('search'); + $build['path_admin_filter_form'] = $this->formBuilder->getForm(PathFilterForm::class, $keys); + $build += parent::render(); + + $build['table']['#empty'] = $this->t('No path aliases available. Add URL alias.', [':link' => Url::fromRoute('entity.path_alias.add_form')->toString()]); + + return $build; + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header = [ + 'alias' => [ + 'data' => $this->t('Alias'), + 'field' => 'alias', + 'specifier' => 'alias', + 'sort' => 'asc', + ], + 'path' => [ + 'data' => $this->t('System path'), + 'field' => 'path', + 'specifier' => 'path', + ], + ]; + + // Enable language column and filter if multiple languages are added. + if ($this->languageManager->isMultilingual()) { + $header['language_name'] = [ + 'data' => $this->t('Language'), + 'field' => 'langcode', + 'specifier' => 'langcode', + 'class' => [RESPONSIVE_PRIORITY_MEDIUM], + ]; + } + + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /** @var \Drupal\Core\Path\Entity\PathAlias $entity */ + $langcode = $entity->language()->getId(); + $alias = $entity->getAlias(); + $path = $entity->getPath(); + $url = Url::fromUserInput($path); + + $row['data']['alias']['data'] = [ + '#type' => 'link', + '#title' => Unicode::truncate($alias, 50, FALSE, TRUE), + '#url' => $url->setOption('attributes', ['title' => $alias]), + ]; + $row['data']['path']['data'] = [ + '#type' => 'link', + '#title' => Unicode::truncate($path, 50, FALSE, TRUE), + '#url' => $url->setOption('attributes', ['title' => $path]), + ]; + + if ($this->languageManager->isMultilingual()) { + $row['data']['language_name'] = $this->languageManager->getLanguageName($langcode); + } + + $row['data']['operations']['data'] = $this->buildOperations($entity); + + // If the system path maps to a different URL alias, highlight this table + // row to let the user know of old aliases. + if ($alias != $this->aliasManager->getAliasByPath($path, $langcode)) { + $row['class'] = ['warning']; + } + + return $row; + } + +} diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php b/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php index 1985499de..0836df17a 100644 --- a/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php +++ b/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php @@ -4,7 +4,6 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Field\FieldItemList; -use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\ComputedItemListTrait; @@ -27,21 +26,15 @@ protected function computeValue() { $entity = $this->getEntity(); if (!$entity->isNew()) { - $conditions = [ - 'source' => '/' . $entity->toUrl()->getInternalPath(), - 'langcode' => $this->getLangcode(), - ]; - $alias = \Drupal::service('path.alias_storage')->load($conditions); - if ($alias === FALSE) { - // Fall back to non-specific language. - if ($this->getLangcode() !== LanguageInterface::LANGCODE_NOT_SPECIFIED) { - $conditions['langcode'] = LanguageInterface::LANGCODE_NOT_SPECIFIED; - $alias = \Drupal::service('path.alias_storage')->load($conditions); - } - } + /** @var \Drupal\path_alias\AliasRepositoryInterface $path_alias_repository */ + $path_alias_repository = \Drupal::service('path_alias.repository'); - if ($alias) { - $value = $alias; + if ($path_alias = $path_alias_repository->lookupBySystemPath('/' . $entity->toUrl()->getInternalPath(), $this->getLangcode())) { + $value = [ + 'alias' => $path_alias['alias'], + 'pid' => $path_alias['id'], + 'langcode' => $path_alias['langcode'], + ]; } } @@ -64,11 +57,12 @@ public function defaultAccess($operation = 'view', AccountInterface $account = N public function delete() { // Delete all aliases associated with this entity in the current language. $entity = $this->getEntity(); - $conditions = [ - 'source' => '/' . $entity->toUrl()->getInternalPath(), + $path_alias_storage = \Drupal::entityTypeManager()->getStorage('path_alias'); + $entities = $path_alias_storage->loadByProperties([ + 'path' => '/' . $entity->toUrl()->getInternalPath(), 'langcode' => $entity->language()->getId(), - ]; - \Drupal::service('path.alias_storage')->delete($conditions); + ]); + $path_alias_storage->delete($entities); } } diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php index 0fb913b9a..ba57f2467 100644 --- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php @@ -63,28 +63,42 @@ public function preSave() { * {@inheritdoc} */ public function postSave($update) { + $path_alias_storage = \Drupal::entityTypeManager()->getStorage('path_alias'); + $entity = $this->getEntity(); + // If specified, rely on the langcode property for the language, so that the // existing language of an alias can be kept. That could for example be // unspecified even if the field/entity has a specific langcode. $alias_langcode = ($this->langcode && $this->pid) ? $this->langcode : $this->getLangcode(); - if (!$update) { - if ($this->alias) { - $entity = $this->getEntity(); - if ($path = \Drupal::service('path.alias_storage')->save('/' . $entity->toUrl()->getInternalPath(), $this->alias, $alias_langcode)) { - $this->pid = $path['pid']; + // If we have an alias, we need to create or update a path alias entity. + if ($this->alias) { + if (!$update || !$this->pid) { + $path_alias = $path_alias_storage->create([ + 'path' => '/' . $entity->toUrl()->getInternalPath(), + 'alias' => $this->alias, + 'langcode' => $alias_langcode, + ]); + $path_alias->save(); + $this->pid = $path_alias->id(); + } + elseif ($this->pid) { + $path_alias = $path_alias_storage->load($this->pid); + + if ($this->alias != $path_alias->getAlias()) { + $path_alias->setAlias($this->alias); + $path_alias->save(); } } } - else { - // Delete old alias if user erased it. - if ($this->pid && !$this->alias) { - \Drupal::service('path.alias_storage')->delete(['pid' => $this->pid]); + elseif ($this->pid && !$this->alias) { + // Otherwise, delete the old alias if the user erased it. + $path_alias = $path_alias_storage->load($this->pid); + if ($entity->isDefaultRevision()) { + $path_alias_storage->delete([$path_alias]); } - // Only save a non-empty alias. - elseif ($this->alias) { - $entity = $this->getEntity(); - \Drupal::service('path.alias_storage')->save('/' . $entity->toUrl()->getInternalPath(), $this->alias, $alias_langcode, $this->pid); + else { + $path_alias_storage->deleteRevision($path_alias->getRevisionID()); } } } diff --git a/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php b/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php index 090edd4a9..5ddc00902 100644 --- a/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php +++ b/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php @@ -87,15 +87,22 @@ public static function validateFormElement(array &$element, FormStateInterface $ if (!empty($alias)) { $form_state->setValueForElement($element['alias'], $alias); - // Validate that the submitted alias does not exist yet. - $is_exists = \Drupal::service('path.alias_storage')->aliasExists($alias, $element['langcode']['#value'], $element['source']['#value']); - if ($is_exists) { - $form_state->setError($element['alias'], t('The alias is already in use.')); - } - } + /** @var \Drupal\path_alias\PathAliasInterface $path_alias */ + $path_alias = \Drupal::entityTypeManager()->getStorage('path_alias')->create([ + 'path' => $element['source']['#value'], + 'alias' => $alias, + 'langcode' => $element['langcode']['#value'], + ]); + $violations = $path_alias->validate(); - if ($alias && $alias[0] !== '/') { - $form_state->setError($element['alias'], t('The alias needs to start with a slash.')); + foreach ($violations as $violation) { + // Newly created entities do not have a system path yet, so we need to + // disregard some violations. + if (!$path_alias->getPath() && $violation->getPropertyPath() === 'path') { + continue; + } + $form_state->setError($element['alias'], $violation->getMessage()); + } } } diff --git a/core/modules/path/src/Plugin/migrate/destination/UrlAlias.php b/core/modules/path/src/Plugin/migrate/destination/UrlAlias.php index a845fb390..99517c67b 100644 --- a/core/modules/path/src/Plugin/migrate/destination/UrlAlias.php +++ b/core/modules/path/src/Plugin/migrate/destination/UrlAlias.php @@ -2,100 +2,51 @@ namespace Drupal\path\Plugin\migrate\destination; -use Drupal\Core\Path\AliasStorage; -use Drupal\migrate\Plugin\MigrationInterface; +use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; use Drupal\migrate\Row; -use Drupal\migrate\Plugin\migrate\destination\DestinationBase; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; + +@trigger_error('UrlAlias is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the entity:path_alias destination instead. See https://www.drupal.org/node/3013865', E_USER_DEPRECATED); /** + * Legacy destination class for non-entity path aliases. + * * @MigrateDestination( * id = "url_alias" * ) + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * the entity:path_alias destination instead. + * + * @see https://www.drupal.org/node/3013865 */ -class UrlAlias extends DestinationBase implements ContainerFactoryPluginInterface { - - /** - * The alias storage service. - * - * @var \Drupal\Core\Path\AliasStorage - */ - protected $aliasStorage; - - /** - * Constructs an entity destination plugin. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin_id for the plugin instance. - * @param mixed $plugin_definition - * The plugin implementation definition. - * @param \Drupal\migrate\Plugin\MigrationInterface $migration - * The migration. - * @param \Drupal\Core\Path\AliasStorage $alias_storage - * The alias storage service. - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, AliasStorage $alias_storage) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration); - $this->aliasStorage = $alias_storage; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $migration, - $container->get('path.alias_storage') - ); - } +class UrlAlias extends EntityContentBase { /** * {@inheritdoc} */ public function import(Row $row, array $old_destination_id_values = []) { - $source = $row->getDestinationProperty('source'); - $alias = $row->getDestinationProperty('alias'); - $langcode = $row->getDestinationProperty('langcode'); - $pid = $old_destination_id_values ? $old_destination_id_values[0] : NULL; + if ($row->getDestinationProperty('source')) { + $row->setDestinationProperty('path', $row->getDestinationProperty('source')); + } + $path = $row->getDestinationProperty('path'); // Check if this alias is for a node and if that node is a translation. - if (preg_match('/^\/node\/\d+$/', $source) && $row->hasDestinationProperty('node_translation')) { + if (preg_match('/^\/node\/\d+$/', $path) && $row->hasDestinationProperty('node_translation')) { // Replace the alias source with the translation source path. $node_translation = $row->getDestinationProperty('node_translation'); - $source = '/node/' . $node_translation[0]; - $langcode = $node_translation[1]; + $row->setDestinationProperty('path', '/node/' . $node_translation[0]); + $row->setDestinationProperty('langcode', $node_translation[1]); } - $path = $this->aliasStorage->save($source, $alias, $langcode, $pid); - - return [$path['pid']]; - } - - /** - * {@inheritdoc} - */ - public function getIds() { - $ids['pid']['type'] = 'integer'; - return $ids; + return parent::import($row, $old_destination_id_values); } /** * {@inheritdoc} */ - public function fields(MigrationInterface $migration = NULL) { - return [ - 'pid' => 'The path id', - 'source' => 'The source path.', - 'alias' => 'The URL alias.', - 'langcode' => 'The language code for the URL.', - ]; + protected static function getEntityTypeId($plugin_id) { + return 'path_alias'; } } diff --git a/core/modules/path/src/Plugin/migrate/process/PathSetTranslated.php b/core/modules/path/src/Plugin/migrate/process/PathSetTranslated.php new file mode 100644 index 000000000..74ed9652d --- /dev/null +++ b/core/modules/path/src/Plugin/migrate/process/PathSetTranslated.php @@ -0,0 +1,75 @@ +' will pass through unchanged, as will any inputs with invalid + * or missing translated nodes. + * + * This plugin will return the correct path for the translated node if the above + * conditions are met, and will return the original path otherwise. + * + * Example: + * node_translation: + * - + * plugin: explode + * source: source + * delimiter: / + * - + * # If the source path has no slashes return a dummy default value. + * plugin: extract + * default: 'INVALID_NID' + * index: + * - 1 + * - + * plugin: migration_lookup + * migration: d7_node_translation + * _path: + * plugin: concat + * source: + * - constants/slash + * - source + * path: + * plugin: path_set_translated + * source: + * - '@_path' + * - '@node_translation' + * + * In the example above, if the node_translation lookup succeeds and the + * original path is of the format '/node/', then the new path + * will be set to '/node/' + * + * @MigrateProcessPlugin( + * id = "path_set_translated" + * ) + */ +class PathSetTranslated extends ProcessPluginBase { + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + if (!is_array($value)) { + throw new MigrateException("The input value should be an array."); + } + + $path = isset($value[0]) ? $value[0] : ''; + $nid = (is_array($value[1]) && isset($value[1][0])) ? $value[1][0] : FALSE; + if (preg_match('/^\/node\/\d+$/', $path) && $nid) { + return '/node/' . $nid; + } + return $path; + } + +} diff --git a/core/modules/path/src/Plugin/migrate/source/UrlAliasBase.php b/core/modules/path/src/Plugin/migrate/source/UrlAliasBase.php index 7714cbd1c..79e640035 100644 --- a/core/modules/path/src/Plugin/migrate/source/UrlAliasBase.php +++ b/core/modules/path/src/Plugin/migrate/source/UrlAliasBase.php @@ -14,9 +14,9 @@ abstract class UrlAliasBase extends DrupalSqlBase { */ public function query() { // The order of the migration is significant since - // \Drupal\Core\Path\AliasStorage::lookupPathAlias() orders by pid before - // returning a result. Postgres does not automatically order by primary key - // therefore we need to add a specific order by. + // \Drupal\path_alias\AliasRepository::lookupPathAlias() orders by pid + // before returning a result. Postgres does not automatically order by + // primary key therefore we need to add a specific order by. return $this->select('url_alias', 'ua')->fields('ua')->orderBy('pid'); } diff --git a/core/modules/path/src/Routing/RouteProcessor.php b/core/modules/path/src/Routing/RouteProcessor.php new file mode 100644 index 000000000..aacd537f5 --- /dev/null +++ b/core/modules/path/src/Routing/RouteProcessor.php @@ -0,0 +1,70 @@ +routeProvider = $route_provider; + } + + /** + * {@inheritdoc} + */ + public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) { + $redirected_route_names = [ + 'path.admin_add' => 'entity.path_alias.add_form', + 'path.admin_edit' => 'entity.path_alias.edit_form', + 'path.delete' => 'entity.path_alias.delete_form', + 'path.admin_overview' => 'entity.path_alias.collection', + 'path.admin_overview_filter' => 'entity.path_alias.collection', + ]; + + if (in_array($route_name, array_keys($redirected_route_names), TRUE)) { + @trigger_error("The '{$route_name}' route is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the '{$redirected_route_names[$route_name]}' route instead. See https://www.drupal.org/node/3013865", E_USER_DEPRECATED); + static::overwriteRoute($route, $this->routeProvider->getRouteByName($redirected_route_names[$route_name])); + } + } + + /** + * Overwrites one route's metadata with the other's. + * + * @param \Symfony\Component\Routing\Route $target_route + * The route whose metadata to overwrite. + * @param \Symfony\Component\Routing\Route $source_route + * The route whose metadata to read from. + * + * @see \Symfony\Component\Routing\Route + */ + protected static function overwriteRoute(Route $target_route, Route $source_route) { + $target_route->setPath($source_route->getPath()); + $target_route->setDefaults($source_route->getDefaults()); + $target_route->setRequirements($source_route->getRequirements()); + $target_route->setOptions($source_route->getOptions()); + $target_route->setHost($source_route->getHost()); + $target_route->setSchemes($source_route->getSchemes()); + $target_route->setMethods($source_route->getMethods()); + } + +} diff --git a/core/modules/path/src/Routing/RouteSubscriber.php b/core/modules/path/src/Routing/RouteSubscriber.php new file mode 100644 index 000000000..077539523 --- /dev/null +++ b/core/modules/path/src/Routing/RouteSubscriber.php @@ -0,0 +1,39 @@ +getRouteCollection(); + + $route_collection->add('path.admin_add', new BcRoute()); + $route_collection->add('path.admin_edit', new BcRoute()); + $route_collection->add('path.delete', new BcRoute()); + $route_collection->add('path.admin_overview', new BcRoute()); + $route_collection->add('path.admin_overview_filter', new BcRoute()); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[RoutingEvents::DYNAMIC][] = ['onDynamicRouteEvent', 0]; + return $events; + } + +} diff --git a/core/modules/path/src/Tests/PathTestBase.php b/core/modules/path/src/Tests/PathTestBase.php index 78b7b8589..7041ea210 100644 --- a/core/modules/path/src/Tests/PathTestBase.php +++ b/core/modules/path/src/Tests/PathTestBase.php @@ -9,7 +9,7 @@ /** * Provides a base class for testing the Path module. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\path\Functional\PathTestBase instead. * * @see https://www.drupal.org/node/2999939 diff --git a/core/modules/path/tests/src/Functional/PathAdminTest.php b/core/modules/path/tests/src/Functional/PathAdminTest.php index a43a1f8de..2ac11efa6 100644 --- a/core/modules/path/tests/src/Functional/PathAdminTest.php +++ b/core/modules/path/tests/src/Functional/PathAdminTest.php @@ -16,6 +16,11 @@ class PathAdminTest extends PathTestBase { */ public static $modules = ['path']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -36,22 +41,22 @@ public function testPathFiltering() { // Create aliases. $alias1 = '/' . $this->randomMachineName(8); $edit = [ - 'source' => '/node/' . $node1->id(), - 'alias' => $alias1, + 'path[0][value]' => '/node/' . $node1->id(), + 'alias[0][value]' => $alias1, ]; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $alias2 = '/' . $this->randomMachineName(8); $edit = [ - 'source' => '/node/' . $node2->id(), - 'alias' => $alias2, + 'path[0][value]' => '/node/' . $node2->id(), + 'alias[0][value]' => $alias2, ]; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $alias3 = '/' . $this->randomMachineName(4) . '/' . $this->randomMachineName(4); $edit = [ - 'source' => '/node/' . $node3->id(), - 'alias' => $alias3, + 'path[0][value]' => '/node/' . $node3->id(), + 'alias[0][value]' => $alias3, ]; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); diff --git a/core/modules/path/tests/src/Functional/PathAliasTest.php b/core/modules/path/tests/src/Functional/PathAliasTest.php index 19115cd27..0ef6b9930 100644 --- a/core/modules/path/tests/src/Functional/PathAliasTest.php +++ b/core/modules/path/tests/src/Functional/PathAliasTest.php @@ -21,6 +21,11 @@ class PathAliasTest extends PathTestBase { */ public static $modules = ['path']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -38,8 +43,8 @@ public function testPathCache() { // Create alias. $edit = []; - $edit['source'] = '/node/' . $node1->id(); - $edit['alias'] = '/' . $this->randomMachineName(8); + $edit['path[0][value]'] = '/node/' . $node1->id(); + $edit['alias[0][value]'] = '/' . $this->randomMachineName(8); $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); // Check the path alias whitelist cache. @@ -51,15 +56,15 @@ public function testPathCache() { // created. \Drupal::cache('data')->deleteAll(); // Make sure the path is not converted to the alias. - $this->drupalGet(trim($edit['source'], '/'), ['alias' => TRUE]); - $this->assertTrue(\Drupal::cache('data')->get('preload-paths:' . $edit['source']), 'Cache entry was created.'); + $this->drupalGet(trim($edit['path[0][value]'], '/'), ['alias' => TRUE]); + $this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:' . $edit['path[0][value]']), 'Cache entry was created.'); // Visit the alias for the node and confirm a cache entry is created. \Drupal::cache('data')->deleteAll(); // @todo Remove this once https://www.drupal.org/node/2480077 lands. Cache::invalidateTags(['rendered']); - $this->drupalGet(trim($edit['alias'], '/')); - $this->assertTrue(\Drupal::cache('data')->get('preload-paths:' . $edit['source']), 'Cache entry was created.'); + $this->drupalGet(trim($edit['alias[0][value]'], '/')); + $this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:' . $edit['path[0][value]']), 'Cache entry was created.'); } /** @@ -71,29 +76,29 @@ public function testAdminAlias() { // Create alias. $edit = []; - $edit['source'] = '/node/' . $node1->id(); - $edit['alias'] = '/' . $this->getRandomGenerator()->word(8); + $edit['path[0][value]'] = '/node/' . $node1->id(); + $edit['alias[0][value]'] = '/' . $this->getRandomGenerator()->word(8); $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); // Confirm that the alias works. - $this->drupalGet($edit['alias']); + $this->drupalGet($edit['alias[0][value]']); $this->assertText($node1->label(), 'Alias works.'); $this->assertResponse(200); // Confirm that the alias works in a case-insensitive way. - $this->assertTrue(ctype_lower(ltrim($edit['alias'], '/'))); - $this->drupalGet($edit['alias']); + $this->assertTrue(ctype_lower(ltrim($edit['alias[0][value]'], '/'))); + $this->drupalGet($edit['alias[0][value]']); $this->assertText($node1->label(), 'Alias works lower case.'); $this->assertResponse(200); - $this->drupalGet(mb_strtoupper($edit['alias'])); + $this->drupalGet(mb_strtoupper($edit['alias[0][value]'])); $this->assertText($node1->label(), 'Alias works upper case.'); $this->assertResponse(200); // Change alias to one containing "exotic" characters. - $pid = $this->getPID($edit['alias']); + $pid = $this->getPID($edit['alias[0][value]']); - $previous = $edit['alias']; + $previous = $edit['alias[0][value]']; // Lower-case letters. - $edit['alias'] = '/alias' . + $edit['alias[0][value]'] = '/alias' . // "Special" ASCII characters. "- ._~!$'\"()*@[]?&+%#,;=:" . // Characters that look like a percent-escaped string. @@ -106,16 +111,16 @@ public function testAdminAlias() { // currently unable to find the upper-case versions of non-ASCII // characters. // @todo fix this in https://www.drupal.org/node/2607432 - $edit['alias'] .= "ïвβéø"; + $edit['alias[0][value]'] .= "ïвβéø"; } $this->drupalPostForm('admin/config/search/path/edit/' . $pid, $edit, t('Save')); // Confirm that the alias works. - $this->drupalGet(mb_strtoupper($edit['alias'])); + $this->drupalGet(mb_strtoupper($edit['alias[0][value]'])); $this->assertText($node1->label(), 'Changed alias works.'); $this->assertResponse(200); - $this->container->get('path.alias_manager')->cacheClear(); + $this->container->get('path_alias.manager')->cacheClear(); // Confirm that previous alias no longer works. $this->drupalGet($previous); $this->assertNoText($node1->label(), 'Previous alias no longer works.'); @@ -125,37 +130,37 @@ public function testAdminAlias() { $node2 = $this->drupalCreateNode(); // Set alias to second test node. - $edit['source'] = '/node/' . $node2->id(); + $edit['path[0][value]'] = '/node/' . $node2->id(); // leave $edit['alias'] the same $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); // Confirm no duplicate was created. - $this->assertRaw(t('The alias %alias is already in use in this language.', ['%alias' => $edit['alias']]), 'Attempt to move alias was rejected.'); + $this->assertRaw(t('The alias %alias is already in use in this language.', ['%alias' => $edit['alias[0][value]']]), 'Attempt to move alias was rejected.'); $edit_upper = $edit; - $edit_upper['alias'] = mb_strtoupper($edit['alias']); + $edit_upper['alias[0][value]'] = mb_strtoupper($edit['alias[0][value]']); $this->drupalPostForm('admin/config/search/path/add', $edit_upper, t('Save')); $this->assertRaw(t('The alias %alias could not be added because it is already in use in this language with different capitalization: %stored_alias.', [ - '%alias' => $edit_upper['alias'], - '%stored_alias' => $edit['alias'], + '%alias' => $edit_upper['alias[0][value]'], + '%stored_alias' => $edit['alias[0][value]'], ]), 'Attempt to move upper-case alias was rejected.'); // Delete alias. $this->drupalGet('admin/config/search/path/edit/' . $pid); $this->clickLink(t('Delete')); - $this->assertRaw(t('Are you sure you want to delete path alias %name?', ['%name' => $edit['alias']])); - $this->drupalPostForm(NULL, [], t('Confirm')); + $this->assertRaw(t('Are you sure you want to delete the URL alias %name?', ['%name' => $edit['alias[0][value]']])); + $this->drupalPostForm(NULL, [], t('Delete')); // Confirm that the alias no longer works. - $this->drupalGet($edit['alias']); + $this->drupalGet($edit['alias[0][value]']); $this->assertNoText($node1->label(), 'Alias was successfully deleted.'); $this->assertResponse(404); // Create a really long alias. $edit = []; - $edit['source'] = '/node/' . $node1->id(); + $edit['path[0][value]'] = '/node/' . $node1->id(); $alias = '/' . $this->randomMachineName(128); - $edit['alias'] = $alias; + $edit['alias[0][value]'] = $alias; // The alias is shortened to 50 characters counting the ellipsis. $truncated_alias = substr($alias, 0, 47); $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); @@ -168,9 +173,9 @@ public function testAdminAlias() { // Create absolute path alias. $edit = []; - $edit['source'] = '/node/' . $node3->id(); + $edit['path[0][value]'] = '/node/' . $node3->id(); $node3_alias = '/' . $this->randomMachineName(8); - $edit['alias'] = $node3_alias; + $edit['alias[0][value]'] = $node3_alias; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); // Create fourth test node. @@ -178,24 +183,24 @@ public function testAdminAlias() { // Create alias with trailing slash. $edit = []; - $edit['source'] = '/node/' . $node4->id(); + $edit['path[0][value]'] = '/node/' . $node4->id(); $node4_alias = '/' . $this->randomMachineName(8); - $edit['alias'] = $node4_alias . '/'; + $edit['alias[0][value]'] = $node4_alias . '/'; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); // Confirm that the alias with trailing slash is not found. - $this->assertNoText($edit['alias'], 'The absolute alias was not found.'); + $this->assertNoText($edit['alias[0][value]'], 'The absolute alias was not found.'); // The alias without trailing flash is found. - $this->assertText(trim($edit['alias'], '/'), 'The alias without trailing slash was found.'); + $this->assertText(trim($edit['alias[0][value]'], '/'), 'The alias without trailing slash was found.'); // Update an existing alias to point to a different source. $pid = $this->getPID($node4_alias); $edit = []; - $edit['alias'] = $node4_alias; - $edit['source'] = '/node/' . $node2->id(); + $edit['alias[0][value]'] = $node4_alias; + $edit['path[0][value]'] = '/node/' . $node2->id(); $this->drupalPostForm('admin/config/search/path/edit/' . $pid, $edit, t('Save')); $this->assertText('The alias has been saved.'); - $this->drupalGet($edit['alias']); + $this->drupalGet($edit['alias[0][value]']); $this->assertNoText($node4->label(), 'Previous alias no longer works.'); $this->assertText($node2->label(), 'Alias works.'); $this->assertResponse(200); @@ -203,18 +208,18 @@ public function testAdminAlias() { // Update an existing alias to use a duplicate alias. $pid = $this->getPID($node3_alias); $edit = []; - $edit['alias'] = $node4_alias; - $edit['source'] = '/node/' . $node3->id(); + $edit['alias[0][value]'] = $node4_alias; + $edit['path[0][value]'] = '/node/' . $node3->id(); $this->drupalPostForm('admin/config/search/path/edit/' . $pid, $edit, t('Save')); - $this->assertRaw(t('The alias %alias is already in use in this language.', ['%alias' => $edit['alias']])); + $this->assertRaw(t('The alias %alias is already in use in this language.', ['%alias' => $edit['alias[0][value]']])); // Create an alias without a starting slash. $node5 = $this->drupalCreateNode(); $edit = []; - $edit['source'] = 'node/' . $node5->id(); + $edit['path[0][value]'] = 'node/' . $node5->id(); $node5_alias = $this->randomMachineName(8); - $edit['alias'] = $node5_alias . '/'; + $edit['alias[0][value]'] = $node5_alias . '/'; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $this->assertUrl('admin/config/search/path/add'); @@ -283,7 +288,7 @@ public function testNodeAlias() { $this->drupalPostForm('node/' . $node2->id() . '/edit', $edit, t('Save')); // Confirm that the alias didn't make a duplicate. - $this->assertText(t('The alias is already in use.'), 'Attempt to moved alias was rejected.'); + $this->assertSession()->pageTextContains("The alias {$edit['path[0][alias]']} is already in use in this language."); // Delete alias. $this->drupalPostForm('node/' . $node1->id() . '/edit', ['path[0][alias]' => ''], t('Save')); @@ -326,8 +331,8 @@ public function testNodeAlias() { // Delete the node and check that the path alias is also deleted. $node5->delete(); - $path_alias = \Drupal::service('path.alias_storage')->lookupPathAlias('/node/' . $node5->id(), $node5->language()->getId()); - $this->assertFalse($path_alias, 'Alias was successfully deleted when the referenced node was deleted.'); + $path_alias = \Drupal::service('path_alias.repository')->lookUpBySystemPath('/node/' . $node5->id(), $node5->language()->getId()); + $this->assertNull($path_alias, 'Alias was successfully deleted when the referenced node was deleted.'); // Create sixth test node. $node6 = $this->drupalCreateNode(); @@ -368,7 +373,11 @@ public function testNodeAlias() { * Integer representing the path ID. */ public function getPID($alias) { - return db_query("SELECT pid FROM {url_alias} WHERE alias = :alias", [':alias' => $alias])->fetchField(); + $result = \Drupal::entityTypeManager()->getStorage('path_alias')->getQuery() + ->condition('alias', $alias, '=') + ->accessCheck(FALSE) + ->execute(); + return reset($result); } /** @@ -384,7 +393,7 @@ public function testDuplicateNodeAlias() { // Now create another node and try to set the same alias. $node_two = $this->drupalCreateNode(); $this->drupalPostForm('node/' . $node_two->id() . '/edit', $edit, t('Save')); - $this->assertText(t('The alias is already in use.')); + $this->assertSession()->pageTextContains("The alias {$edit['path[0][alias]']} is already in use in this language."); $this->assertFieldByXPath("//input[@name='path[0][alias]' and contains(@class, 'error')]", $edit['path[0][alias]'], 'Textfield exists and has the error class.'); // Behavior here differs with the inline_form_errors module enabled. @@ -395,7 +404,7 @@ public function testDuplicateNodeAlias() { // Attempt to edit the second node again, as before. $this->drupalPostForm('node/' . $node_two->id() . '/edit', $edit, t('Preview')); // This error should still be present next to the field. - $this->assertSession()->pageTextContains(t('The alias is already in use.'), 'Field error found with expected text.'); + $this->assertSession()->pageTextContains("The alias {$edit['path[0][alias]']} is already in use in this language."); // The validation error set for the page should include this text. $this->assertSession()->pageTextContains(t('1 error has been found: URL alias'), 'Form error found with expected text.'); // The text 'URL alias' should be a link. diff --git a/core/modules/path/tests/src/Functional/PathContentModerationTest.php b/core/modules/path/tests/src/Functional/PathContentModerationTest.php index b69a5dbe5..cae1928bb 100644 --- a/core/modules/path/tests/src/Functional/PathContentModerationTest.php +++ b/core/modules/path/tests/src/Functional/PathContentModerationTest.php @@ -28,6 +28,11 @@ class PathContentModerationTest extends BrowserTestBase { 'content_translation', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/path/tests/src/Functional/PathLanguageTest.php b/core/modules/path/tests/src/Functional/PathLanguageTest.php index eae266124..a5982e15d 100644 --- a/core/modules/path/tests/src/Functional/PathLanguageTest.php +++ b/core/modules/path/tests/src/Functional/PathLanguageTest.php @@ -16,6 +16,11 @@ class PathLanguageTest extends PathTestBase { */ public static $modules = ['path', 'locale', 'locale_test', 'content_translation']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An user with permissions to administer content types. * @@ -62,7 +67,7 @@ protected function setUp() { ]; $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration')); - $definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'page'); + $definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page'); $this->assertTrue($definitions['path']->isTranslatable(), 'Node path is translatable.'); $this->assertTrue($definitions['body']->isTranslatable(), 'Node body is translatable.'); } @@ -71,7 +76,7 @@ protected function setUp() { * Test alias functionality through the admin interfaces. */ public function testAliasTranslation() { - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $english_node = $this->drupalCreateNode(['type' => 'page', 'langcode' => 'en']); $english_alias = $this->randomMachineName(); @@ -96,7 +101,7 @@ public function testAliasTranslation() { $this->drupalPostForm(NULL, $edit, t('Save (this translation)')); // Clear the path lookup cache. - $this->container->get('path.alias_manager')->cacheClear(); + $this->container->get('path_alias.manager')->cacheClear(); // Languages are cached on many levels, and we need to clear those caches. $this->container->get('language_manager')->reset(); @@ -119,7 +124,7 @@ public function testAliasTranslation() { $languages = $this->container->get('language_manager')->getLanguages(); $url = $english_node_french_translation->toUrl('canonical', ['language' => $languages['fr']])->toString(); - $this->assertTrue(strpos($url, $edit['path[0][alias]']), 'URL contains the path alias.'); + $this->assertContains($edit['path[0][alias]'], $url, 'URL contains the path alias.'); // Confirm that the alias works even when changing language negotiation // options. Enable User language detection and selection over URL one. @@ -167,28 +172,28 @@ public function testAliasTranslation() { // The alias manager has an internal path lookup cache. Check to see that // it has the appropriate contents at this point. - $this->container->get('path.alias_manager')->cacheClear(); - $french_node_path = $this->container->get('path.alias_manager')->getPathByAlias('/' . $french_alias, 'fr'); + $this->container->get('path_alias.manager')->cacheClear(); + $french_node_path = $this->container->get('path_alias.manager')->getPathByAlias('/' . $french_alias, 'fr'); $this->assertEqual($french_node_path, '/node/' . $english_node_french_translation->id(), 'Normal path works.'); // Second call should return the same path. - $french_node_path = $this->container->get('path.alias_manager')->getPathByAlias('/' . $french_alias, 'fr'); + $french_node_path = $this->container->get('path_alias.manager')->getPathByAlias('/' . $french_alias, 'fr'); $this->assertEqual($french_node_path, '/node/' . $english_node_french_translation->id(), 'Normal path is the same.'); // Confirm that the alias works. - $french_node_alias = $this->container->get('path.alias_manager')->getAliasByPath('/node/' . $english_node_french_translation->id(), 'fr'); + $french_node_alias = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $english_node_french_translation->id(), 'fr'); $this->assertEqual($french_node_alias, '/' . $french_alias, 'Alias works.'); // Second call should return the same alias. - $french_node_alias = $this->container->get('path.alias_manager')->getAliasByPath('/node/' . $english_node_french_translation->id(), 'fr'); + $french_node_alias = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $english_node_french_translation->id(), 'fr'); $this->assertEqual($french_node_alias, '/' . $french_alias, 'Alias is the same.'); // Confirm that the alias is removed if the translation is deleted. $english_node->removeTranslation('fr'); $english_node->save(); - $this->assertFalse($this->container->get('path.alias_storage')->aliasExists('/' . $french_alias, 'fr'), 'Alias for French translation is removed when translation is deleted.'); + $this->assertPathAliasNotExists('/' . $french_alias, 'fr', NULL, 'Alias for French translation is removed when translation is deleted.'); // Check that the English alias still works. $this->drupalGet($english_alias); - $this->assertTrue($this->container->get('path.alias_storage')->aliasExists('/' . $english_alias, 'en'), 'English alias is not deleted when French translation is removed.'); + $this->assertPathAliasExists('/' . $english_alias, 'en', NULL, 'English alias is not deleted when French translation is removed.'); $this->assertText($english_node->body->value, 'English alias still works'); } diff --git a/core/modules/path/tests/src/Functional/PathLanguageUiTest.php b/core/modules/path/tests/src/Functional/PathLanguageUiTest.php index c76284515..6ab6e2c22 100644 --- a/core/modules/path/tests/src/Functional/PathLanguageUiTest.php +++ b/core/modules/path/tests/src/Functional/PathLanguageUiTest.php @@ -18,6 +18,11 @@ class PathLanguageUiTest extends PathTestBase { */ public static $modules = ['path', 'locale', 'locale_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -42,8 +47,8 @@ protected function setUp() { public function testLanguageNeutralUrl() { $name = $this->randomMachineName(8); $edit = []; - $edit['source'] = '/admin/config/search/path'; - $edit['alias'] = '/' . $name; + $edit['path[0][value]'] = '/admin/config/search/path'; + $edit['alias[0][value]'] = '/' . $name; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $this->drupalGet($name); @@ -56,9 +61,9 @@ public function testLanguageNeutralUrl() { public function testDefaultLanguageUrl() { $name = $this->randomMachineName(8); $edit = []; - $edit['source'] = '/admin/config/search/path'; - $edit['alias'] = '/' . $name; - $edit['langcode'] = 'en'; + $edit['path[0][value]'] = '/admin/config/search/path'; + $edit['alias[0][value]'] = '/' . $name; + $edit['langcode[0][value]'] = 'en'; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $this->drupalGet($name); @@ -71,9 +76,9 @@ public function testDefaultLanguageUrl() { public function testNonDefaultUrl() { $name = $this->randomMachineName(8); $edit = []; - $edit['source'] = '/admin/config/search/path'; - $edit['alias'] = '/' . $name; - $edit['langcode'] = 'fr'; + $edit['path[0][value]'] = '/admin/config/search/path'; + $edit['alias[0][value]'] = '/' . $name; + $edit['langcode[0][value]'] = 'fr'; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $this->drupalGet('fr/' . $name); @@ -90,14 +95,14 @@ public function testNotSpecifiedNode() { // Create a language-unspecific alias in the admin UI, ensure that is // displayed and the langcode is not changed when saving. $edit = [ - 'source' => '/node/' . $node->id(), - 'alias' => '/' . $this->getRandomGenerator()->word(8), - 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'path[0][value]' => '/node/' . $node->id(), + 'alias[0][value]' => '/' . $this->getRandomGenerator()->word(8), + 'langcode[0][value]' => LanguageInterface::LANGCODE_NOT_SPECIFIED, ]; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $this->drupalGet($node->toUrl('edit-form')); - $this->assertSession()->fieldValueEquals('path[0][alias]', $edit['alias']); + $this->assertSession()->fieldValueEquals('path[0][alias]', $edit['alias[0][value]']); $this->drupalPostForm(NULL, [], t('Save')); $this->drupalGet('admin/config/search/path'); diff --git a/core/modules/path/tests/src/Functional/PathMediaFormTest.php b/core/modules/path/tests/src/Functional/PathMediaFormTest.php index 3a9690081..9f34cd85a 100644 --- a/core/modules/path/tests/src/Functional/PathMediaFormTest.php +++ b/core/modules/path/tests/src/Functional/PathMediaFormTest.php @@ -16,6 +16,11 @@ class PathMediaFormTest extends PathTestBase { */ public static $modules = ['media', 'media_test_source']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -52,7 +57,7 @@ public function testMediaForm() { $assert_session->fieldExists('path[0][alias]'); // Disable the 'Path' field for this content type. - entity_get_form_display('media', $media_type_id, 'default') + \Drupal::service('entity_display.repository')->getFormDisplay('media', $media_type_id, 'default') ->removeComponent('path') ->save(); diff --git a/core/modules/path/tests/src/Functional/PathNodeFormTest.php b/core/modules/path/tests/src/Functional/PathNodeFormTest.php index 3245b17c5..66c6198cc 100644 --- a/core/modules/path/tests/src/Functional/PathNodeFormTest.php +++ b/core/modules/path/tests/src/Functional/PathNodeFormTest.php @@ -16,6 +16,11 @@ class PathNodeFormTest extends PathTestBase { */ public static $modules = ['node', 'path']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected function setUp() { parent::setUp(); @@ -37,7 +42,7 @@ public function testNodeForm() { $assert_session->fieldExists('path[0][alias]'); // Disable the 'Path' field for this content type. - entity_get_form_display('node', 'page', 'default') + \Drupal::service('entity_display.repository')->getFormDisplay('node', 'page', 'default') ->removeComponent('path') ->save(); diff --git a/core/modules/path/tests/src/Functional/PathTaxonomyTermTest.php b/core/modules/path/tests/src/Functional/PathTaxonomyTermTest.php index 80bedab75..1db6bb188 100644 --- a/core/modules/path/tests/src/Functional/PathTaxonomyTermTest.php +++ b/core/modules/path/tests/src/Functional/PathTaxonomyTermTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\path\Functional; +use Drupal\Core\Database\Database; use Drupal\taxonomy\Entity\Vocabulary; /** @@ -18,6 +19,11 @@ class PathTaxonomyTermTest extends PathTestBase { */ public static $modules = ['taxonomy']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -46,7 +52,7 @@ public function testTermAlias() { 'path[0][alias]' => '/' . $this->randomMachineName(), ]; $this->drupalPostForm('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add', $edit, t('Save')); - $tid = db_query("SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1", [':name' => $edit['name[0][value]']])->fetchField(); + $tid = Database::getConnection()->query("SELECT tid FROM {taxonomy_term_field_data} WHERE name = :name AND default_langcode = 1", [':name' => $edit['name[0][value]']])->fetchField(); // Confirm that the alias works. $this->drupalGet($edit['path[0][alias]']); diff --git a/core/modules/path/tests/src/Functional/PathTestBase.php b/core/modules/path/tests/src/Functional/PathTestBase.php index d4b8826d5..ca6f03c90 100644 --- a/core/modules/path/tests/src/Functional/PathTestBase.php +++ b/core/modules/path/tests/src/Functional/PathTestBase.php @@ -3,12 +3,15 @@ namespace Drupal\Tests\path\Functional; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\Traits\Core\PathAliasTestTrait; /** * Provides a base class for testing the Path module. */ abstract class PathTestBase extends BrowserTestBase { + use PathAliasTestTrait; + /** * Modules to enable. * diff --git a/core/modules/path/tests/src/Kernel/Migrate/d6/LegacyMigrateUrlAliasTest.php b/core/modules/path/tests/src/Kernel/Migrate/d6/LegacyMigrateUrlAliasTest.php new file mode 100644 index 000000000..9570f16b4 --- /dev/null +++ b/core/modules/path/tests/src/Kernel/Migrate/d6/LegacyMigrateUrlAliasTest.php @@ -0,0 +1,115 @@ + 'd6_url_alias', + 'label' => 'URL aliases', + 'migration_tags' => + [ + 0 => 'Drupal 6', + 1 => 'Content', + ], + 'source' => + [ + 'plugin' => 'd6_url_alias', + 'constants' => + [ + 'slash' => '/', + ], + ], + 'process' => + [ + 'source' => + [ + 'plugin' => 'concat', + 'source' => + [ + 0 => 'constants/slash', + 1 => 'src', + ], + ], + 'alias' => + [ + 'plugin' => 'concat', + 'source' => + [ + 0 => 'constants/slash', + 1 => 'dst', + ], + ], + 'langcode' => + [ + 'plugin' => 'd6_url_alias_language', + 'source' => 'language', + ], + 'node_translation' => + [ + 0 => + [ + 'plugin' => 'explode', + 'source' => 'src', + 'delimiter' => '/', + ], + 1 => + [ + 'plugin' => 'extract', + 'default' => 'INVALID_NID', + 'index' => + [ + 0 => 1, + ], + ], + 2 => + [ + 'plugin' => 'migration_lookup', + 'migration' => 'd6_node_translation', + ], + ], + ], + 'destination' => + [ + 'plugin' => 'url_alias', + ], + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + MigrateDrupal6TestBase::setUp(); + $this->installEntitySchema('node'); + $this->installEntitySchema('path_alias'); + $this->installConfig(['node']); + $this->installSchema('node', ['node_access']); + $this->migrateUsers(FALSE); + $this->migrateFields(); + + $this->executeMigrations([ + 'language', + 'd6_node_settings', + 'd6_node', + 'd6_node_translation', + ]); + $this->executeMigration(\Drupal::service('plugin.manager.migration')->createStubMigration($this->stubMigration)); + $this->expectDeprecation('UrlAlias is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the entity:path_alias destination instead. See https://www.drupal.org/node/3013865'); + } + +} diff --git a/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php b/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php index 28f22ecc9..617fe73f0 100644 --- a/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php +++ b/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php @@ -2,9 +2,11 @@ namespace Drupal\Tests\path\Kernel\Migrate\d6; +use Drupal\path_alias\PathAliasInterface; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\Core\Database\Database; use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase; +use Drupal\Tests\Traits\Core\PathAliasTestTrait; /** * URL alias migration. @@ -13,6 +15,8 @@ */ class MigrateUrlAliasTest extends MigrateDrupal6TestBase { + use PathAliasTestTrait; + /** * {@inheritdoc} */ @@ -20,6 +24,7 @@ class MigrateUrlAliasTest extends MigrateDrupal6TestBase { 'language', 'content_translation', 'path', + 'path_alias', 'menu_ui', // Required for translation migrations. 'migrate_drupal_multilingual', @@ -31,6 +36,7 @@ class MigrateUrlAliasTest extends MigrateDrupal6TestBase { protected function setUp() { parent::setUp(); $this->installEntitySchema('node'); + $this->installEntitySchema('path_alias'); $this->installConfig(['node']); $this->installSchema('node', ['node_access']); $this->migrateUsers(FALSE); @@ -46,20 +52,20 @@ protected function setUp() { } /** - * Assert a path. + * Asserts that a path alias matches a set of conditions. * - * @param string $pid - * The path id. + * @param int $pid + * The path alias ID. * @param array $conditions * The path conditions. - * @param array $path - * The path. + * @param \Drupal\path_alias\PathAliasInterface $path_alias + * The path alias. */ - private function assertPath($pid, $conditions, $path) { - $this->assertTrue($path, "Path alias for " . $conditions['source'] . " successfully loaded."); - $this->assertIdentical($conditions['alias'], $path['alias']); - $this->assertIdentical($conditions['langcode'], $path['langcode']); - $this->assertIdentical($conditions['source'], $path['source']); + private function assertPath($pid, $conditions, PathAliasInterface $path_alias) { + $this->assertSame($pid, (int) $path_alias->id()); + $this->assertSame($conditions['alias'], $path_alias->getAlias()); + $this->assertSame($conditions['langcode'], $path_alias->get('langcode')->value); + $this->assertSame($conditions['path'], $path_alias->getPath()); } /** @@ -69,21 +75,21 @@ public function testUrlAlias() { $id_map = $this->getMigration('d6_url_alias')->getIdMap(); // Test that the field exists. $conditions = [ - 'source' => '/node/1', + 'path' => '/node/1', 'alias' => '/alias-one', 'langcode' => 'af', ]; - $path = \Drupal::service('path.alias_storage')->load($conditions); - $this->assertPath('1', $conditions, $path); - $this->assertIdentical($id_map->lookupDestinationId([$path['pid']]), ['1'], "Test IdMap"); + $path_alias = $this->loadPathAliasByConditions($conditions); + $this->assertPath(1, $conditions, $path_alias); + $this->assertSame([['1']], $id_map->lookupDestinationIds([$path_alias->id()]), "Test IdMap"); $conditions = [ - 'source' => '/node/2', + 'path' => '/node/2', 'alias' => '/alias-two', 'langcode' => 'en', ]; - $path = \Drupal::service('path.alias_storage')->load($conditions); - $this->assertPath('2', $conditions, $path); + $path_alias = $this->loadPathAliasByConditions($conditions); + $this->assertPath(2, $conditions, $path_alias); // Test that we can re-import using the UrlAlias destination. Database::getConnection('default', 'migrate') @@ -99,54 +105,53 @@ public function testUrlAlias() { $migration = $this->getMigration('d6_url_alias'); $this->executeMigration($migration); - $path = \Drupal::service('path.alias_storage')->load(['pid' => $path['pid']]); + $path_alias = $this->loadPathAliasByConditions(['id' => $path_alias->id()]); $conditions['alias'] = '/new-url-alias'; - $this->assertPath('2', $conditions, $path); + $this->assertPath(2, $conditions, $path_alias); $conditions = [ - 'source' => '/node/3', + 'path' => '/node/3', 'alias' => '/alias-three', 'langcode' => 'und', ]; - $path = \Drupal::service('path.alias_storage')->load($conditions); - $this->assertPath('3', $conditions, $path); + $path_alias = $this->loadPathAliasByConditions($conditions); + $this->assertPath(3, $conditions, $path_alias); - $path = \Drupal::service('path.alias_storage')->load(['alias' => '/source-noslash']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/source-noslash']); $conditions = [ - 'source' => '/admin', + 'path' => '/admin', 'alias' => '/source-noslash', 'langcode' => 'und', ]; - $this->assertPath('2', $conditions, $path); + $this->assertPath(8, $conditions, $path_alias); } /** * Test the URL alias migration with translated nodes. */ public function testUrlAliasWithTranslatedNodes() { - $alias_storage = $this->container->get('path.alias_storage'); - // Alias for the 'The Real McCoy' node in English. - $path = $alias_storage->load(['alias' => '/the-real-mccoy']); - $this->assertSame('/node/10', $path['source']); - $this->assertSame('en', $path['langcode']); + + $path_alias = $this->loadPathAliasByConditions(['alias' => '/the-real-mccoy']); + $this->assertSame('/node/10', $path_alias->getPath()); + $this->assertSame('en', $path_alias->get('langcode')->value); // Alias for the 'The Real McCoy' French translation, // which should now point to node/10 instead of node/11. - $path = $alias_storage->load(['alias' => '/le-vrai-mccoy']); - $this->assertSame('/node/10', $path['source']); - $this->assertSame('fr', $path['langcode']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/le-vrai-mccoy']); + $this->assertSame('/node/10', $path_alias->getPath()); + $this->assertSame('fr', $path_alias->get('langcode')->value); // Alias for the 'Abantu zulu' node in Zulu. - $path = $alias_storage->load(['alias' => '/abantu-zulu']); - $this->assertSame('/node/12', $path['source']); - $this->assertSame('zu', $path['langcode']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/abantu-zulu']); + $this->assertSame('/node/12', $path_alias->getPath()); + $this->assertSame('zu', $path_alias->get('langcode')->value); // Alias for the 'Abantu zulu' English translation, // which should now point to node/12 instead of node/13. - $path = $alias_storage->load(['alias' => '/the-zulu-people']); - $this->assertSame('/node/12', $path['source']); - $this->assertSame('en', $path['langcode']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/the-zulu-people']); + $this->assertSame('/node/12', $path_alias->getPath()); + $this->assertSame('en', $path_alias->get('langcode')->value); } } diff --git a/core/modules/path/tests/src/Kernel/Migrate/d7/LegacyMigrateUrlAliasTest.php b/core/modules/path/tests/src/Kernel/Migrate/d7/LegacyMigrateUrlAliasTest.php new file mode 100644 index 000000000..27eb20215 --- /dev/null +++ b/core/modules/path/tests/src/Kernel/Migrate/d7/LegacyMigrateUrlAliasTest.php @@ -0,0 +1,110 @@ + 'd7_url_alias', + 'label' => 'URL aliases', + 'migration_tags' => + [ + 0 => 'Drupal 7', + 1 => 'Content', + ], + 'source' => + [ + 'plugin' => 'd7_url_alias', + 'constants' => + [ + 'slash' => '/', + ], + ], + 'process' => + [ + 'source' => + [ + 'plugin' => 'concat', + 'source' => + [ + 0 => 'constants/slash', + 1 => 'source', + ], + ], + 'alias' => + [ + 'plugin' => 'concat', + 'source' => + [ + 0 => 'constants/slash', + 1 => 'alias', + ], + ], + 'langcode' => 'language', + 'node_translation' => + [ + 0 => + [ + 'plugin' => 'explode', + 'source' => 'source', + 'delimiter' => '/', + ], + 1 => + [ + 'plugin' => 'extract', + 'default' => 'INVALID_NID', + 'index' => + [ + 0 => 1, + ], + ], + 2 => + [ + 'plugin' => 'migration_lookup', + 'migration' => 'd7_node_translation', + ], + ], + ], + 'destination' => + [ + 'plugin' => 'url_alias', + ], + ]; + + /** + * {@inheritdoc} + */ + protected function setUp() { + MigrateDrupal7TestBase::setUp(); + + $this->installEntitySchema('node'); + $this->installEntitySchema('path_alias'); + $this->installConfig('node'); + $this->installSchema('node', ['node_access']); + + $this->migrateUsers(FALSE); + $this->migrateContentTypes(); + $this->executeMigrations([ + 'language', + 'd7_node', + 'd7_node_translation', + ]); + $this->executeMigration(\Drupal::service('plugin.manager.migration')->createStubMigration($this->stubMigration)); + $this->expectDeprecation('UrlAlias is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the entity:path_alias destination instead. See https://www.drupal.org/node/3013865'); + } + +} diff --git a/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasNoTranslationTest.php b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasNoTranslationTest.php new file mode 100644 index 000000000..fc4cac676 --- /dev/null +++ b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasNoTranslationTest.php @@ -0,0 +1,20 @@ +executeMigration('d7_url_alias'); + } + +} diff --git a/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php index e0607b96d..e776a31f2 100644 --- a/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php +++ b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php @@ -2,27 +2,20 @@ namespace Drupal\Tests\path\Kernel\Migrate\d7; -use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; - /** * Tests URL alias migration. * * @group path */ -class MigrateUrlAliasTest extends MigrateDrupal7TestBase { +class MigrateUrlAliasTest extends MigrateUrlAliasTestBase { /** * {@inheritdoc} */ public static $modules = [ + 'path_alias', 'content_translation', - 'language', - 'menu_ui', - // Required for translation migrations. 'migrate_drupal_multilingual', - 'node', - 'path', - 'text', ]; /** @@ -30,67 +23,37 @@ class MigrateUrlAliasTest extends MigrateDrupal7TestBase { */ protected function setUp() { parent::setUp(); - - $this->installSchema('node', ['node_access']); - - $this->migrateUsers(FALSE); - $this->migrateContentTypes(); $this->executeMigrations([ - 'language', - 'd7_node', 'd7_node_translation', 'd7_url_alias', ]); } - /** - * Test the URL alias migration. - */ - public function testUrlAlias() { - $alias_storage = $this->container->get('path.alias_storage'); - - $path = $alias_storage->load([ - 'source' => '/taxonomy/term/4', - 'alias' => '/term33', - 'langcode' => 'und', - ]); - $this->assertIdentical('/taxonomy/term/4', $path['source']); - $this->assertIdentical('/term33', $path['alias']); - $this->assertIdentical('und', $path['langcode']); - - // Alias with no slash. - $path = $alias_storage->load(['alias' => '/source-noslash']); - $this->assertSame('/admin', $path['source']); - $this->assertSame('und', $path['langcode']); - } - /** * Test the URL alias migration with translated nodes. */ public function testUrlAliasWithTranslatedNodes() { - $alias_storage = $this->container->get('path.alias_storage'); - // Alias for the 'The thing about Deep Space 9' node in English. - $path = $alias_storage->load(['alias' => '/deep-space-9']); - $this->assertSame('/node/2', $path['source']); - $this->assertSame('en', $path['langcode']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/deep-space-9']); + $this->assertSame('/node/2', $path_alias->getPath()); + $this->assertSame('en', $path_alias->get('langcode')->value); // Alias for the 'The thing about Deep Space 9' Icelandic translation, // which should now point to node/2 instead of node/3. - $path = $alias_storage->load(['alias' => '/deep-space-9-is']); - $this->assertSame('/node/2', $path['source']); - $this->assertSame('is', $path['langcode']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/deep-space-9-is']); + $this->assertSame('/node/2', $path_alias->getPath()); + $this->assertSame('is', $path_alias->get('langcode')->value); // Alias for the 'The thing about Firefly' node in Icelandic. - $path = $alias_storage->load(['alias' => '/firefly-is']); - $this->assertSame('/node/4', $path['source']); - $this->assertSame('is', $path['langcode']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/firefly-is']); + $this->assertSame('/node/4', $path_alias->getPath()); + $this->assertSame('is', $path_alias->get('langcode')->value); // Alias for the 'The thing about Firefly' English translation, // which should now point to node/4 instead of node/5. - $path = $alias_storage->load(['alias' => '/firefly']); - $this->assertSame('/node/4', $path['source']); - $this->assertSame('en', $path['langcode']); + $path_alias = $this->loadPathAliasByConditions(['alias' => '/firefly']); + $this->assertSame('/node/4', $path_alias->getPath()); + $this->assertSame('en', $path_alias->get('langcode')->value); } } diff --git a/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTestBase.php b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTestBase.php new file mode 100644 index 000000000..86a11fe71 --- /dev/null +++ b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTestBase.php @@ -0,0 +1,67 @@ +installEntitySchema('node'); + $this->installEntitySchema('path_alias'); + $this->installConfig('node'); + $this->installSchema('node', ['node_access']); + + $this->migrateUsers(FALSE); + $this->migrateContentTypes(); + $this->executeMigrations([ + 'language', + 'd7_node', + ]); + } + + /** + * Test the URL alias migration. + */ + public function testUrlAlias() { + $path_alias = $this->loadPathAliasByConditions([ + 'path' => '/taxonomy/term/4', + 'alias' => '/term33', + 'langcode' => 'und', + ]); + $this->assertIdentical('/taxonomy/term/4', $path_alias->getPath()); + $this->assertIdentical('/term33', $path_alias->getAlias()); + $this->assertIdentical('und', $path_alias->language()->getId()); + + // Alias with no slash. + $path_alias = $this->loadPathAliasByConditions(['alias' => '/source-noslash']); + $this->assertSame('/admin', $path_alias->getPath()); + $this->assertSame('und', $path_alias->language()->getId()); + } + +} diff --git a/core/modules/path/tests/src/Kernel/PathItemTest.php b/core/modules/path/tests/src/Kernel/PathItemTest.php index 224a8420b..240b6cf54 100644 --- a/core/modules/path/tests/src/Kernel/PathItemTest.php +++ b/core/modules/path/tests/src/Kernel/PathItemTest.php @@ -19,7 +19,7 @@ class PathItemTest extends KernelTestBase { * * @var array */ - public static $modules = ['path', 'node', 'user', 'system', 'language', 'content_translation']; + public static $modules = ['path', 'path_alias', 'node', 'user', 'system', 'language', 'content_translation']; /** * {@inheritdoc} @@ -29,6 +29,7 @@ protected function setUp() { $this->installEntitySchema('node'); $this->installEntitySchema('user'); + $this->installEntitySchema('path_alias'); $this->installSchema('node', ['node_access']); @@ -43,9 +44,8 @@ protected function setUp() { * Test creating, loading, updating and deleting aliases through PathItem. */ public function testPathItem() { - - /** @var \Drupal\Core\Path\AliasStorageInterface $alias_storage */ - $alias_storage = \Drupal::service('path.alias_storage'); + /** @var \Drupal\path_alias\AliasRepositoryInterface $alias_repository */ + $alias_repository = \Drupal::service('path_alias.repository'); $node_storage = \Drupal::entityTypeManager()->getStorage('node'); @@ -61,8 +61,8 @@ public function testPathItem() { $this->assertFalse($node->get('path')->isEmpty()); $this->assertEquals('/foo', $node->get('path')->alias); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); - $this->assertEquals('/foo', $stored_alias); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); + $this->assertEquals('/foo', $stored_alias['alias']); $node_storage->resetCache(); @@ -96,10 +96,10 @@ public function testPathItem() { $translation = $loaded_node->getTranslation('de'); $this->assertEquals('/furchtbar', $translation->path->alias); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); - $this->assertEquals('/foo', $stored_alias); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $translation->language()->getId()); - $this->assertEquals('/furchtbar', $stored_alias); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); + $this->assertEquals('/foo', $stored_alias['alias']); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $translation->language()->getId()); + $this->assertEquals('/furchtbar', $stored_alias['alias']); $loaded_node->get('path')->alias = '/bar'; $this->assertFalse($loaded_node->get('path')->isEmpty()); @@ -122,11 +122,11 @@ public function testPathItem() { $this->assertFalse($loaded_node->get('path')->isEmpty()); $this->assertEquals('/bar', $loaded_node->get('path')->alias); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); - $this->assertEquals('/bar', $stored_alias); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); + $this->assertEquals('/bar', $stored_alias['alias']); - $old_alias = $alias_storage->lookupPathSource('/foo', $node->language()->getId()); - $this->assertFalse($old_alias); + $old_alias = $alias_repository->lookupByAlias('/foo', $node->language()->getId()); + $this->assertNull($old_alias); // Reload the node to make sure that it is possible to set a value // immediately after loading. @@ -139,19 +139,19 @@ public function testPathItem() { $loaded_node = $node_storage->load($node->id()); $this->assertFalse($loaded_node->get('path')->isEmpty()); $this->assertEquals('/foobar', $loaded_node->get('path')->alias); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); - $this->assertEquals('/foobar', $stored_alias); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); + $this->assertEquals('/foobar', $stored_alias['alias']); - $old_alias = $alias_storage->lookupPathSource('/bar', $node->language()->getId()); - $this->assertFalse($old_alias); + $old_alias = $alias_repository->lookupByAlias('/bar', $node->language()->getId()); + $this->assertNull($old_alias); $loaded_node->get('path')->alias = ''; $this->assertEquals('', $loaded_node->get('path')->alias); $loaded_node->save(); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); - $this->assertFalse($stored_alias); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); + $this->assertNull($stored_alias); // Check that reading, updating and reading the computed alias again in the // same request works without clearing any caches in between. @@ -161,16 +161,16 @@ public function testPathItem() { $this->assertFalse($loaded_node->get('path')->isEmpty()); $this->assertEquals('/foo', $loaded_node->get('path')->alias); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); - $this->assertEquals('/foo', $stored_alias); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); + $this->assertEquals('/foo', $stored_alias['alias']); $loaded_node->get('path')->alias = '/foobar'; $loaded_node->save(); $this->assertFalse($loaded_node->get('path')->isEmpty()); $this->assertEquals('/foobar', $loaded_node->get('path')->alias); - $stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); - $this->assertEquals('/foobar', $stored_alias); + $stored_alias = $alias_repository->lookupBySystemPath('/' . $node->toUrl()->getInternalPath(), $node->language()->getId()); + $this->assertEquals('/foobar', $stored_alias['alias']); // Check that \Drupal\Core\Field\FieldItemList::equals() for the path field // type. diff --git a/core/modules/path/tests/src/Kernel/PathLegacyRoutesKernelTest.php b/core/modules/path/tests/src/Kernel/PathLegacyRoutesKernelTest.php new file mode 100644 index 000000000..3cd8832b7 --- /dev/null +++ b/core/modules/path/tests/src/Kernel/PathLegacyRoutesKernelTest.php @@ -0,0 +1,56 @@ +assertNotEmpty(Url::fromRoute('path.admin_add')->toString()); + } + + /** + * @expectedDeprecation The 'path.admin_edit' route is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the 'entity.path_alias.edit_form' route instead. See https://www.drupal.org/node/3013865 + */ + public function testLegacyEditRoute() { + $this->assertNotEmpty(Url::fromRoute('path.admin_edit', ['path_alias' => 1])->toString()); + } + + /** + * @expectedDeprecation The 'path.delete' route is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the 'entity.path_alias.delete_form' route instead. See https://www.drupal.org/node/3013865 + */ + public function testLegacyDeleteRoute() { + $this->assertNotEmpty(Url::fromRoute('path.delete', ['path_alias' => 1])->toString()); + } + + /** + * @expectedDeprecation The 'path.admin_overview' route is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the 'entity.path_alias.collection' route instead. See https://www.drupal.org/node/3013865 + */ + public function testLegacyCollectionRoute() { + $this->assertNotEmpty(Url::fromRoute('path.admin_overview')->toString()); + } + + /** + * @expectedDeprecation The 'path.admin_overview_filter' route is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the 'entity.path_alias.collection' route instead. See https://www.drupal.org/node/3013865 + */ + public function testLegacyCollectionFilterRoute() { + $this->assertNotEmpty(Url::fromRoute('path.admin_overview_filter')->toString()); + } + +} diff --git a/core/modules/path/tests/src/Unit/migrate/process/PathSetTranslatedTest.php b/core/modules/path/tests/src/Unit/migrate/process/PathSetTranslatedTest.php new file mode 100644 index 000000000..1af7e53e4 --- /dev/null +++ b/core/modules/path/tests/src/Unit/migrate/process/PathSetTranslatedTest.php @@ -0,0 +1,71 @@ +assertSame($expected_result, $plugin->transform([$path, $node_translation], $this->migrateExecutable, $this->row, 'destination_property')); + } + + /** + * Provides data for the testTransform method. + * + * @return array + * The data. + */ + public function transformDataProvider() { + return [ + 'non-node-path' => [ + 'path' => '/non-node-path', + 'node_translation' => [1, 'en'], + 'expected_result' => '/non-node-path', + ], + 'no_translated_node_1' => [ + 'path' => '/node/1', + 'node_translation' => 'INVALID_NID', + 'expected_result' => '/node/1', + ], + 'no_translated_node_2' => [ + 'path' => '/node/1', + 'node_translation' => NULL, + 'expected_result' => '/node/1', + ], + 'no_translated_node_3' => [ + 'path' => '/node/1', + 'node_translation' => FALSE, + 'expected_result' => '/node/1', + ], + 'valid_transform' => [ + 'path' => '/node/1', + 'node_translation' => [3, 'en'], + 'expected_result' => '/node/3', + ], + ]; + } + +} diff --git a/core/modules/path_alias/path_alias.info.yml b/core/modules/path_alias/path_alias.info.yml new file mode 100644 index 000000000..76bdd35a4 --- /dev/null +++ b/core/modules/path_alias/path_alias.info.yml @@ -0,0 +1,7 @@ +name: Path alias +type: module +description: 'Provides the API allowing to rename URLs.' +package: Core +version: VERSION +required: true +hidden: true diff --git a/core/modules/path_alias/path_alias.services.yml b/core/modules/path_alias/path_alias.services.yml new file mode 100644 index 000000000..c36eee191 --- /dev/null +++ b/core/modules/path_alias/path_alias.services.yml @@ -0,0 +1,25 @@ +services: + path_alias.subscriber: + class: Drupal\path_alias\EventSubscriber\PathAliasSubscriber + tags: + - { name: event_subscriber } + arguments: ['@path_alias.manager', '@path.current'] + path_alias.path_processor: + class: Drupal\path_alias\PathProcessor\AliasPathProcessor + tags: + - { name: path_processor_inbound, priority: 100 } + - { name: path_processor_outbound, priority: 300 } + arguments: ['@path_alias.manager'] + path_alias.manager: + class: Drupal\path_alias\AliasManager + arguments: ['@path_alias.repository', '@path_alias.whitelist', '@language_manager', '@cache.data'] + path_alias.repository: + class: Drupal\path_alias\AliasRepository + arguments: ['@database'] + tags: + - { name: backend_overridable } + path_alias.whitelist: + class: Drupal\path_alias\AliasWhitelist + tags: + - { name: needs_destruction } + arguments: [path_alias_whitelist, '@cache.bootstrap', '@lock', '@state', '@path_alias.repository'] diff --git a/core/modules/path_alias/src/AliasManager.php b/core/modules/path_alias/src/AliasManager.php new file mode 100644 index 000000000..a169b402b --- /dev/null +++ b/core/modules/path_alias/src/AliasManager.php @@ -0,0 +1,10 @@ +setLabel(new TranslatableMarkup('System path')) + ->setDescription(new TranslatableMarkup('The path that this alias belongs to.')) + ->setRequired(TRUE) + ->setRevisionable(TRUE) + ->addPropertyConstraints('value', [ + 'Regex' => [ + 'pattern' => '/^\//i', + 'message' => new TranslatableMarkup('The source path has to start with a slash.'), + ], + ]) + ->addPropertyConstraints('value', ['ValidPath' => []]); + + $fields['alias'] = BaseFieldDefinition::create('string') + ->setLabel(new TranslatableMarkup('URL alias')) + ->setDescription(new TranslatableMarkup('An alias used with this path.')) + ->setRequired(TRUE) + ->setRevisionable(TRUE) + ->addPropertyConstraints('value', [ + 'Regex' => [ + 'pattern' => '/^\//i', + 'message' => new TranslatableMarkup('The alias path has to start with a slash.'), + ], + ]); + + $fields['langcode']->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED); + + // Add the published field. + $fields += static::publishedBaseFieldDefinitions($entity_type); + $fields['status']->setTranslatable(FALSE); + + return $fields; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + + // Trim the alias value of whitespace and slashes. Ensure to not trim the + // slash on the left side. + $alias = rtrim(trim($this->getAlias()), "\\/"); + $this->setAlias($alias); + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + + $alias_manager = \Drupal::service('path_alias.manager'); + $alias_manager->cacheClear($this->getPath()); + if ($update) { + $alias_manager->cacheClear($this->original->getPath()); + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageInterface $storage, array $entities) { + parent::postDelete($storage, $entities); + + $alias_manager = \Drupal::service('path_alias.manager'); + foreach ($entities as $entity) { + $alias_manager->cacheClear($entity->getPath()); + } + } + + /** + * {@inheritdoc} + */ + public function getPath() { + return $this->get('path')->value; + } + + /** + * {@inheritdoc} + */ + public function setPath($path) { + $this->set('path', $path); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAlias() { + return $this->get('alias')->value; + } + + /** + * {@inheritdoc} + */ + public function setAlias($alias) { + $this->set('alias', $alias); + return $this; + } + + /** + * {@inheritdoc} + */ + public function label() { + return $this->getAlias(); + } + + /** + * {@inheritdoc} + */ + public function getCacheTagsToInvalidate() { + return ['route_match']; + } + +} diff --git a/core/modules/path_alias/src/EventSubscriber/PathAliasSubscriber.php b/core/modules/path_alias/src/EventSubscriber/PathAliasSubscriber.php new file mode 100644 index 000000000..986448cb0 --- /dev/null +++ b/core/modules/path_alias/src/EventSubscriber/PathAliasSubscriber.php @@ -0,0 +1,10 @@ + $entity->id(), + 'source' => $entity->getPath(), + 'alias' => $entity->getAlias(), + 'langcode' => $entity->language()->getId(), + ]; + + if ($hook === 'update') { + $values['original'] = [ + 'pid' => $entity->id(), + 'source' => $entity->original->getPath(), + 'alias' => $entity->original->getAlias(), + 'langcode' => $entity->original->language()->getId(), + ]; + } + + $this->moduleHandler()->invokeAllDeprecated("It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_{$hook}() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865.", 'path_' . $hook, [$values]); + } + } + + /** + * {@inheritdoc} + */ + public function createWithSampleValues($bundle = FALSE, array $values = []) { + $entity = parent::createWithSampleValues($bundle, ['path' => '/'] + $values); + // Ensure the alias is only 255 characters long. + $entity->set('alias', substr('/' . $entity->get('alias')->value, 0, 255)); + return $entity; + } + +} diff --git a/core/modules/path_alias/src/PathAliasStorageSchema.php b/core/modules/path_alias/src/PathAliasStorageSchema.php new file mode 100644 index 000000000..53acc85c7 --- /dev/null +++ b/core/modules/path_alias/src/PathAliasStorageSchema.php @@ -0,0 +1,27 @@ +storage->getBaseTable()]['indexes'] += [ + 'path_alias__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'], + 'path_alias__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'], + ]; + + return $schema; + } + +} diff --git a/core/modules/path_alias/src/PathProcessor/AliasPathProcessor.php b/core/modules/path_alias/src/PathProcessor/AliasPathProcessor.php new file mode 100644 index 000000000..db43ce619 --- /dev/null +++ b/core/modules/path_alias/src/PathProcessor/AliasPathProcessor.php @@ -0,0 +1,10 @@ +aliasManager = $alias_manager; + } + + /** + * {@inheritdoc} + */ + public function getPathByAlias($alias, $langcode = NULL) { + $this->aliasManager->getPathByAlias($alias, $langcode); + } + + /** + * {@inheritdoc} + */ + public function getAliasByPath($path, $langcode = NULL) { + return $this->aliasManager->getAliasByPath($path, $langcode); + } + + /** + * {@inheritdoc} + */ + public function cacheClear($source = NULL) { + $this->aliasManager->cacheClear($source); + } + +} diff --git a/core/modules/path_alias/tests/modules/path_alias_deprecated_test/src/NewAliasManager.php b/core/modules/path_alias/tests/modules/path_alias_deprecated_test/src/NewAliasManager.php new file mode 100644 index 000000000..3adcb0ee9 --- /dev/null +++ b/core/modules/path_alias/tests/modules/path_alias_deprecated_test/src/NewAliasManager.php @@ -0,0 +1,30 @@ +getDefinition('path.alias_manager'); + $definition->setClass(static::$newClass); + } + + if (!static::$useDecorator) { + $decorator_id = 'path_alias_deprecated_test.path.alias_manager'; + if ($container->hasDefinition($decorator_id)) { + $container->removeDefinition($decorator_id); + } + } + } + +} diff --git a/core/modules/path_alias/tests/src/Functional/Hal/PathAliasHalJsonAnonTest.php b/core/modules/path_alias/tests/src/Functional/Hal/PathAliasHalJsonAnonTest.php new file mode 100644 index 000000000..60917af35 --- /dev/null +++ b/core/modules/path_alias/tests/src/Functional/Hal/PathAliasHalJsonAnonTest.php @@ -0,0 +1,35 @@ +applyHalFieldNormalization($default_normalization); + return $normalization + [ + '_links' => [ + 'self' => [ + 'href' => '', + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/path_alias/path_alias', + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + return parent::getNormalizedPostEntity() + [ + '_links' => [ + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/path_alias/path_alias', + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheContexts() { + return [ + 'url.site', + 'user.permissions', + ]; + } + +} diff --git a/core/modules/path_alias/tests/src/Functional/Rest/PathAliasJsonAnonTest.php b/core/modules/path_alias/tests/src/Functional/Rest/PathAliasJsonAnonTest.php new file mode 100644 index 000000000..1e1b4aa21 --- /dev/null +++ b/core/modules/path_alias/tests/src/Functional/Rest/PathAliasJsonAnonTest.php @@ -0,0 +1,31 @@ +grantPermissionsToTestedRole(['administer url aliases']); + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + $path_alias = PathAlias::create([ + 'path' => '/', + 'alias' => '/frontpage1', + ]); + $path_alias->save(); + return $path_alias; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedNormalizedEntity() { + return [ + 'id' => [ + [ + 'value' => 1, + ], + ], + 'revision_id' => [ + [ + 'value' => 1, + ], + ], + 'langcode' => [ + [ + 'value' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ], + ], + 'path' => [ + [ + 'value' => '/', + ], + ], + 'alias' => [ + [ + 'value' => '/frontpage1', + ], + ], + 'status' => [ + [ + 'value' => TRUE, + ], + ], + 'uuid' => [ + [ + 'value' => $this->entity->uuid(), + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + return [ + 'path' => [ + [ + 'value' => '/', + ], + ], + 'alias' => [ + [ + 'value' => '/frontpage1', + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheContexts() { + return ['user.permissions']; + } + +} diff --git a/core/modules/path_alias/tests/src/Functional/Rest/PathAliasXmlAnonTest.php b/core/modules/path_alias/tests/src/Functional/Rest/PathAliasXmlAnonTest.php new file mode 100644 index 000000000..a987e0dba --- /dev/null +++ b/core/modules/path_alias/tests/src/Functional/Rest/PathAliasXmlAnonTest.php @@ -0,0 +1,33 @@ +assertTrue(Database::getConnection()->schema()->tableExists('url_alias'), 'The url_alias table exists after Drupal installation.'); + // Ensure that the path_alias table exists after Drupal installation. + $this->assertTrue(Database::getConnection()->schema()->tableExists('path_alias'), 'The path_alias table exists after Drupal installation.'); // User names can have quotes and plus signs so we should ensure that URL // altering works with this. @@ -42,14 +51,13 @@ public function testUrlAlter() { $this->assertUrlOutboundAlter("/user/$uid", "/user/$name"); // Test that a path always uses its alias. - $path = ['source' => "/user/$uid/test1", 'alias' => '/alias/test1']; - $this->container->get('path.alias_storage')->save($path['source'], $path['alias']); + $this->createPathAlias("/user/$uid/test1", '/alias/test1'); $this->rebuildContainer(); $this->assertUrlInboundAlter('/alias/test1', "/user/$uid/test1"); $this->assertUrlOutboundAlter("/user/$uid/test1", '/alias/test1'); // Test adding an alias via the UI. - $edit = ['source' => "/user/$uid/edit", 'alias' => '/alias/test2']; + $edit = ['path[0][value]' => "/user/$uid/edit", 'alias[0][value]' => '/alias/test2']; $this->drupalPostForm('admin/config/search/path/add', $edit, t('Save')); $this->assertText(t('The alias has been saved.')); $this->drupalGet('alias/test2'); @@ -95,7 +103,7 @@ public function testUrlAlter() { protected function assertUrlOutboundAlter($original, $final) { // Test outbound altering. $result = $this->container->get('path_processor_manager')->processOutbound($original); - return $this->assertIdentical($result, $final, format_string('Altered outbound URL %original, expected %final, and got %result.', ['%original' => $original, '%final' => $final, '%result' => $result])); + return $this->assertIdentical($result, $final, new FormattableMarkup('Altered outbound URL %original, expected %final, and got %result.', ['%original' => $original, '%final' => $final, '%result' => $result])); } /** @@ -111,8 +119,8 @@ protected function assertUrlOutboundAlter($original, $final) { */ protected function assertUrlInboundAlter($original, $final) { // Test inbound altering. - $result = $this->container->get('path.alias_manager')->getPathByAlias($original); - return $this->assertIdentical($result, $final, format_string('Altered inbound URL %original, expected %final, and got %result.', ['%original' => $original, '%final' => $final, '%result' => $result])); + $result = $this->container->get('path_alias.manager')->getPathByAlias($original); + return $this->assertIdentical($result, $final, new FormattableMarkup('Altered inbound URL %original, expected %final, and got %result.', ['%original' => $original, '%final' => $final, '%result' => $result])); } } diff --git a/core/modules/path_alias/tests/src/Kernel/AliasTest.php b/core/modules/path_alias/tests/src/Kernel/AliasTest.php new file mode 100644 index 000000000..083a4cc2c --- /dev/null +++ b/core/modules/path_alias/tests/src/Kernel/AliasTest.php @@ -0,0 +1,226 @@ +set('router.path_roots', ['user', 'admin']); + + $this->installEntitySchema('path_alias'); + } + + /** + * @covers ::lookupBySystemPath + */ + public function testLookupBySystemPath() { + $this->createPathAlias('/test-source-Case', '/test-alias'); + + $path_alias_repository = $this->container->get('path_alias.repository'); + $this->assertEquals('/test-alias', $path_alias_repository->lookupBySystemPath('/test-source-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['alias']); + $this->assertEquals('/test-alias', $path_alias_repository->lookupBySystemPath('/test-source-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['alias']); + } + + /** + * @covers ::lookupByAlias + */ + public function testLookupByAlias() { + $this->createPathAlias('/test-source', '/test-alias-Case'); + + $path_alias_repository = $this->container->get('path_alias.repository'); + $this->assertEquals('/test-source', $path_alias_repository->lookupByAlias('/test-alias-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['path']); + $this->assertEquals('/test-source', $path_alias_repository->lookupByAlias('/test-alias-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['path']); + } + + /** + * @covers \Drupal\path_alias\AliasManager::getPathByAlias + * @covers \Drupal\path_alias\AliasManager::getAliasByPath + */ + public function testLookupPath() { + // Create AliasManager and Path object. + $aliasManager = $this->container->get('path_alias.manager'); + + // Test the situation where the source is the same for multiple aliases. + // Start with a language-neutral alias, which we will override. + $path_alias = $this->createPathAlias('/user/1', '/foo'); + $this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'Basic alias lookup works.'); + $this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'Basic source lookup works.'); + + // Create a language specific alias for the default language (English). + $path_alias = $this->createPathAlias('/user/1', '/users/Dries', 'en'); + + $this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'English alias overrides language-neutral alias.'); + $this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'English source overrides language-neutral source.'); + + // Create a language-neutral alias for the same path, again. + $path_alias = $this->createPathAlias('/user/1', '/bar'); + $this->assertEquals("/users/Dries", $aliasManager->getAliasByPath($path_alias->getPath()), 'English alias still returned after entering a language-neutral alias.'); + + // Create a language-specific (xx-lolspeak) alias for the same path. + $path_alias = $this->createPathAlias('/user/1', '/LOL', 'xx-lolspeak'); + $this->assertEquals("/users/Dries", $aliasManager->getAliasByPath($path_alias->getPath()), 'English alias still returned after entering a LOLspeak alias.'); + // The LOLspeak alias should be returned if we really want LOLspeak. + $this->assertEquals('/LOL', $aliasManager->getAliasByPath($path_alias->getPath(), 'xx-lolspeak'), 'LOLspeak alias returned if we specify xx-lolspeak to the alias manager.'); + + // Create a new alias for this path in English, which should override the + // previous alias for "user/1". + $path_alias = $this->createPathAlias('/user/1', '/users/my-new-path', 'en'); + $this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'Recently created English alias returned.'); + $this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'Recently created English source returned.'); + + // Remove the English aliases, which should cause a fallback to the most + // recently created language-neutral alias, 'bar'. + $path_alias_storage = $this->container->get('entity_type.manager')->getStorage('path_alias'); + $entities = $path_alias_storage->loadByProperties(['langcode' => 'en']); + $path_alias_storage->delete($entities); + $this->assertEquals('/bar', $aliasManager->getAliasByPath($path_alias->getPath()), 'Path lookup falls back to recently created language-neutral alias.'); + + // Test the situation where the alias and language are the same, but + // the source differs. The newer alias record should be returned. + $this->createPathAlias('/user/2', '/bar'); + $aliasManager->cacheClear(); + $this->assertEquals('/user/2', $aliasManager->getPathByAlias('/bar'), 'Newer alias record is returned when comparing two LanguageInterface::LANGCODE_NOT_SPECIFIED paths with the same alias.'); + } + + /** + * Tests the alias whitelist. + */ + public function testWhitelist() { + $memoryCounterBackend = new MemoryCounterBackend(); + + // Create AliasManager and Path object. + $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository')); + $aliasManager = new AliasManager($this->container->get('path_alias.repository'), $whitelist, $this->container->get('language_manager'), $memoryCounterBackend); + + // No alias for user and admin yet, so should be NULL. + $this->assertNull($whitelist->get('user')); + $this->assertNull($whitelist->get('admin')); + + // Non-existing path roots should be NULL too. Use a length of 7 to avoid + // possible conflict with random aliases below. + $this->assertNull($whitelist->get($this->randomMachineName())); + + // Add an alias for user/1, user should get whitelisted now. + $this->createPathAlias('/user/1', '/' . $this->randomMachineName()); + $aliasManager->cacheClear(); + $this->assertTrue($whitelist->get('user')); + $this->assertNull($whitelist->get('admin')); + $this->assertNull($whitelist->get($this->randomMachineName())); + + // Add an alias for admin, both should get whitelisted now. + $this->createPathAlias('/admin/something', '/' . $this->randomMachineName()); + $aliasManager->cacheClear(); + $this->assertTrue($whitelist->get('user')); + $this->assertTrue($whitelist->get('admin')); + $this->assertNull($whitelist->get($this->randomMachineName())); + + // Remove the user alias again, whitelist entry should be removed. + $path_alias_storage = $this->container->get('entity_type.manager')->getStorage('path_alias'); + $entities = $path_alias_storage->loadByProperties(['path' => '/user/1']); + $path_alias_storage->delete($entities); + $aliasManager->cacheClear(); + $this->assertNull($whitelist->get('user')); + $this->assertTrue($whitelist->get('admin')); + $this->assertNull($whitelist->get($this->randomMachineName())); + + // Destruct the whitelist so that the caches are written. + $whitelist->destruct(); + $this->assertEqual($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 1); + $memoryCounterBackend->resetCounter(); + + // Re-initialize the whitelist using the same cache backend, should load + // from cache. + $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository')); + $this->assertNull($whitelist->get('user')); + $this->assertTrue($whitelist->get('admin')); + $this->assertNull($whitelist->get($this->randomMachineName())); + $this->assertEqual($memoryCounterBackend->getCounter('get', 'path_alias_whitelist'), 1); + $this->assertEqual($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 0); + + // Destruct the whitelist, should not attempt to write the cache again. + $whitelist->destruct(); + $this->assertEqual($memoryCounterBackend->getCounter('get', 'path_alias_whitelist'), 1); + $this->assertEqual($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 0); + } + + /** + * Tests situation where the whitelist cache is deleted mid-request. + */ + public function testWhitelistCacheDeletionMidRequest() { + $memoryCounterBackend = new MemoryCounterBackend(); + + // Create AliasManager and Path object. + $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository')); + $aliasManager = new AliasManager($this->container->get('path_alias.repository'), $whitelist, $this->container->get('language_manager'), $memoryCounterBackend); + + // Whitelist cache should not exist at all yet. + $this->assertFalse($memoryCounterBackend->get('path_alias_whitelist')); + + // Add some aliases for both menu routes we have. + $this->createPathAlias('/admin/something', '/' . $this->randomMachineName()); + $this->createPathAlias('/user/something', '/' . $this->randomMachineName()); + + // Lookup admin path in whitelist. It will query the DB and figure out + // that it indeed has an alias, and add it to the internal whitelist and + // flag it to be persisted to cache. + $this->assertTrue($whitelist->get('admin')); + + // Destruct the whitelist so it persists its cache. + $whitelist->destruct(); + $this->assertEquals($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 1); + // Cache data should have data for 'user' and 'admin', even though just + // 'admin' was looked up. This is because the cache is primed with all + // menu router base paths. + $this->assertEquals(['user' => FALSE, 'admin' => TRUE], $memoryCounterBackend->get('path_alias_whitelist')->data); + $memoryCounterBackend->resetCounter(); + + // Re-initialize the the whitelist and lookup an alias for the 'user' path. + // Whitelist should load data from its cache, see that it hasn't done a + // check for 'user' yet, perform the check, then mark the result to be + // persisted to cache. + $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository')); + $this->assertTrue($whitelist->get('user')); + + // Delete the whitelist cache. This could happen from an outside process, + // like a code deployment that performs a cache rebuild. + $memoryCounterBackend->delete('path_alias_whitelist'); + + // Destruct whitelist so it attempts to save the whitelist data to cache. + // However it should recognize that the previous cache entry was deleted + // from underneath it and not save anything to cache, to protect from + // cache corruption. + $whitelist->destruct(); + $this->assertEquals($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 0); + $this->assertFalse($memoryCounterBackend->get('path_alias_whitelist')); + $memoryCounterBackend->resetCounter(); + } + +} diff --git a/core/modules/path_alias/tests/src/Kernel/DeprecatedServicesTest.php b/core/modules/path_alias/tests/src/Kernel/DeprecatedServicesTest.php new file mode 100644 index 000000000..1e0f4afa1 --- /dev/null +++ b/core/modules/path_alias/tests/src/Kernel/DeprecatedServicesTest.php @@ -0,0 +1,190 @@ +installEntitySchema('path_alias'); + } + + /** + * @expectedDeprecation The "path.alias_manager" service is deprecated. Use "path_alias.manager" instead. See https://drupal.org/node/3092086 + * @expectedDeprecation The "path_processor_alias" service is deprecated. Use "path_alias.path_processor" instead. See https://drupal.org/node/3092086 + * @expectedDeprecation The "path_subscriber" service is deprecated. Use "path_alias.subscriber" instead. See https://drupal.org/node/3092086 + */ + public function testAliasServicesDeprecation() { + $this->container->get('path.alias_manager'); + $this->container->get('path_processor_alias'); + $this->container->get('path_subscriber'); + } + + /** + * @expectedDeprecation The "path.alias_manager" service is deprecated. Use "path_alias.manager" instead. See https://drupal.org/node/3092086 + * @expectedDeprecation The \Drupal\Core\Path\AliasManager class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\AliasManager. See https://drupal.org/node/3092086 + */ + public function testOverriddenServiceImplementation() { + $class = $this->setServiceClass(OverriddenAliasManager::class); + $this->assertServiceClass('path.alias_manager', $class); + $this->assertServiceClass('path_alias.manager', AliasManager::class); + } + + /** + * @expectedDeprecation The "path.alias_manager" service is deprecated. Use "path_alias.manager" instead. See https://drupal.org/node/3092086 + */ + public function testNewServiceImplementation() { + $class = $this->setServiceClass(NewAliasManager::class); + $this->assertServiceClass('path.alias_manager', $class); + $this->assertServiceClass('path_alias.manager', AliasManager::class); + } + + /** + * @expectedDeprecation The "path_alias_deprecated_test.path.alias_manager.inner" service is deprecated. Use "path_alias.manager" instead. See https://drupal.org/node/3092086 + * @expectedDeprecation The \Drupal\Core\Path\AliasManager class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\AliasManager. See https://drupal.org/node/3092086 + */ + public function testDecoratorForOverriddenServiceImplementation() { + $this->setServiceClass(OverriddenAliasManager::class, TRUE); + $this->assertServiceClass('path.alias_manager', AliasManagerDecorator::class); + $this->assertServiceClass('path_alias.manager', AliasManager::class); + } + + /** + * @expectedDeprecation The "path_alias_deprecated_test.path.alias_manager.inner" service is deprecated. Use "path_alias.manager" instead. See https://drupal.org/node/3092086 + */ + public function testDecoratorForNewServiceImplementation() { + $this->setServiceClass(NewAliasManager::class, TRUE); + $this->assertServiceClass('path.alias_manager', AliasManagerDecorator::class); + $this->assertServiceClass('path_alias.manager', AliasManager::class); + } + + /** + * @expectedDeprecation The "path.alias_manager" service is deprecated. Use "path_alias.manager" instead. See https://drupal.org/node/3092086 + */ + public function testDefaultImplementations() { + $this->assertServiceClass('path.alias_manager', CoreAliasManager::class); + $this->assertServiceClass('path_alias.manager', AliasManager::class); + } + + /** + * No deprecation message expected. + */ + public function testRegularImplementation() { + $this->assertServiceClass('path_alias.manager', AliasManager::class); + } + + /** + * Test that the new alias manager and the legacy ones share the same state. + * + * @expectedDeprecation The \Drupal\Core\Path\AliasManager class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\AliasManager. See https://drupal.org/node/3092086 + */ + public function testAliasManagerSharedState() { + /** @var \Drupal\Core\Path\AliasManager $legacy_alias_manager */ + $legacy_alias_manager = $this->container->get('path.alias_manager'); + /** @var \Drupal\path_alias\AliasManager $alias_manager */ + $alias_manager = $this->container->get('path_alias.manager'); + + $cache_key = $this->randomMachineName(); + $alias_manager->setCacheKey($cache_key); + $this->assertSharedProperty('preload-paths:' . $cache_key, $legacy_alias_manager, 'cacheKey'); + + $invalid_alias = '/' . $this->randomMachineName(); + $alias_manager->getPathByAlias($invalid_alias); + $this->assertSharedProperty(['en' => [$invalid_alias => TRUE]], $legacy_alias_manager, 'noPath'); + + $this->assertSharedProperty(FALSE, $legacy_alias_manager, 'preloadedPathLookups'); + + /** @var \Drupal\path_alias\Entity\PathAlias $alias */ + $alias = PathAlias::create([ + 'path' => '/' . $this->randomMachineName(), + 'alias' => $invalid_alias . '2', + ]); + $alias->save(); + + $this->assertSharedProperty([], $legacy_alias_manager, 'preloadedPathLookups'); + + /** @var \Drupal\Core\State\StateInterface $state */ + $state = $this->container->get('state'); + $state->set('router.path_roots', [ltrim($alias->getPath(), '/')]); + + $alias_manager->getAliasByPath($alias->getPath()); + $this->assertSharedProperty(['en' => [$alias->getPath() => $alias->getAlias()]], $legacy_alias_manager, 'lookupMap'); + + $invalid_path = $alias->getPath() . '/' . $this->randomMachineName(); + $alias_manager->getAliasByPath($invalid_path); + $this->assertSharedProperty(['en' => [$invalid_path => TRUE]], $legacy_alias_manager, 'noAlias'); + } + + /** + * Asserts that a shared property has the expected value. + * + * @param mixed $expected + * The property expected value. + * @param \Drupal\Core\Path\AliasManager $legacy_alias_manager + * An instance of the legacy alias manager. + * @param string $property + * The property name. + */ + protected function assertSharedProperty($expected, CoreAliasManager $legacy_alias_manager, $property) { + $reflector = new \ReflectionProperty(get_class($legacy_alias_manager), $property); + $reflector->setAccessible(TRUE); + $this->assertSame($expected, $reflector->getValue($legacy_alias_manager)); + } + + /** + * Asserts that the specified service is implemented by the expected class. + * + * @param string $service_id + * A service ID. + * @param string $expected_class + * The name of the expected class. + */ + protected function assertServiceClass($service_id, $expected_class) { + $service = $this->container->get($service_id); + $this->assertSame(get_class($service), $expected_class); + } + + /** + * Sets the specified implementation for the service being tested. + * + * @param string $class + * The name of the implementation class. + * @param bool $use_decorator + * (optional) Whether using a decorator service to wrap the specified class. + * Defaults to no decorator. + * + * @return string + * The specified class name. + */ + protected function setServiceClass($class, $use_decorator = FALSE) { + PathAliasDeprecatedTestServiceProvider::$newClass = $class; + PathAliasDeprecatedTestServiceProvider::$useDecorator = $use_decorator; + $this->container->get('kernel')->rebuildContainer(); + return $class; + } + +} diff --git a/core/modules/path_alias/tests/src/Kernel/PathHooksTest.php b/core/modules/path_alias/tests/src/Kernel/PathHooksTest.php new file mode 100644 index 000000000..80e2683a4 --- /dev/null +++ b/core/modules/path_alias/tests/src/Kernel/PathHooksTest.php @@ -0,0 +1,71 @@ +installEntitySchema('path_alias'); + } + + /** + * Tests that the PathAlias entity clears caches correctly. + * + * @covers ::postSave + * @covers ::postDelete + */ + public function testPathHooks() { + $path_alias = PathAlias::create([ + 'path' => '/' . $this->randomMachineName(), + 'alias' => '/' . $this->randomMachineName(), + ]); + + // Check \Drupal\Core\Path\Entity\PathAlias::postSave() for new path alias + // entities. + $alias_manager = $this->prophesize(AliasManagerInterface::class); + $alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1); + $alias_manager->cacheClear($path_alias->getPath())->shouldBeCalledTimes(1); + \Drupal::getContainer()->set('path_alias.manager', $alias_manager->reveal()); + $path_alias->save(); + + $new_source = '/' . $this->randomMachineName(); + + // Check \Drupal\Core\Path\Entity\PathAlias::postSave() for existing path + // alias entities. + $alias_manager = $this->prophesize(AliasManagerInterface::class); + $alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(2); + $alias_manager->cacheClear($path_alias->getPath())->shouldBeCalledTimes(1); + $alias_manager->cacheClear($new_source)->shouldBeCalledTimes(1); + \Drupal::getContainer()->set('path_alias.manager', $alias_manager->reveal()); + $path_alias->setPath($new_source); + $path_alias->save(); + + // Check \Drupal\Core\Path\Entity\PathAlias::postDelete(). + $alias_manager = $this->prophesize(AliasManagerInterface::class); + $alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1); + $alias_manager->cacheClear($new_source)->shouldBeCalledTimes(1); + \Drupal::getContainer()->set('path_alias.manager', $alias_manager->reveal()); + $path_alias->delete(); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php b/core/modules/path_alias/tests/src/Unit/AliasManagerTest.php similarity index 83% rename from core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php rename to core/modules/path_alias/tests/src/Unit/AliasManagerTest.php index 109c7cc2c..e260fb683 100644 --- a/core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php +++ b/core/modules/path_alias/tests/src/Unit/AliasManagerTest.php @@ -1,50 +1,58 @@ aliasStorage = $this->getMock('Drupal\Core\Path\AliasStorageInterface'); - $this->aliasWhitelist = $this->getMock('Drupal\Core\Path\AliasWhitelistInterface'); - $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); - $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->aliasRepository = $this->createMock(AliasRepositoryInterface::class); + $this->aliasWhitelist = $this->createMock('Drupal\path_alias\AliasWhitelistInterface'); + $this->languageManager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); - $this->aliasManager = new AliasManager($this->aliasStorage, $this->aliasWhitelist, $this->languageManager, $this->cache); + $this->aliasManager = new AliasManager($this->aliasRepository, $this->aliasWhitelist, $this->languageManager, $this->cache); } @@ -92,8 +100,8 @@ public function testGetPathByAliasNoMatch() { ->with(LanguageInterface::TYPE_URL) ->will($this->returnValue($language)); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathSource') + $this->aliasRepository->expects($this->once()) + ->method('lookupByAlias') ->with($alias, $language->getId()) ->will($this->returnValue(NULL)); @@ -113,10 +121,10 @@ public function testGetPathByAliasNatch() { $language = $this->setUpCurrentLanguage(); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathSource') + $this->aliasRepository->expects($this->once()) + ->method('lookupByAlias') ->with($alias, $language->getId()) - ->will($this->returnValue($path)); + ->will($this->returnValue(['path' => $path])); $this->assertEquals($path, $this->aliasManager->getPathByAlias($alias)); // Call it twice to test the static cache. @@ -135,10 +143,10 @@ public function testGetPathByAliasLangcode() { $this->languageManager->expects($this->never()) ->method('getCurrentLanguage'); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathSource') + $this->aliasRepository->expects($this->once()) + ->method('lookupByAlias') ->with($alias, 'de') - ->will($this->returnValue($path)); + ->will($this->returnValue(['path' => $path])); $this->assertEquals($path, $this->aliasManager->getPathByAlias($alias, 'de')); // Call it twice to test the static cache. @@ -164,8 +172,8 @@ public function testGetAliasByPathWhitelist() { // The whitelist returns FALSE for that path part, so the storage should // never be called. - $this->aliasStorage->expects($this->never()) - ->method('lookupPathAlias'); + $this->aliasRepository->expects($this->never()) + ->method('lookupBySystemPath'); $this->assertEquals($path, $this->aliasManager->getAliasByPath($path)); } @@ -189,8 +197,8 @@ public function testGetAliasByPathNoMatch() { ->with($path_part1) ->will($this->returnValue(TRUE)); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathAlias') + $this->aliasRepository->expects($this->once()) + ->method('lookupBySystemPath') ->with($path, $language->getId()) ->will($this->returnValue(NULL)); @@ -227,10 +235,10 @@ public function testGetAliasByPathMatch() { ->with($path_part1) ->will($this->returnValue(TRUE)); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathAlias') + $this->aliasRepository->expects($this->once()) + ->method('lookupBySystemPath') ->with($path, $language->getId()) - ->will($this->returnValue($alias)); + ->will($this->returnValue(['alias' => $alias])); $this->assertEquals($alias, $this->aliasManager->getAliasByPath($path)); // Call it twice to test the static cache. @@ -272,14 +280,14 @@ public function testGetAliasByPathCachedMatch() { ->with($path_part1) ->will($this->returnValue(TRUE)); - $this->aliasStorage->expects($this->once()) + $this->aliasRepository->expects($this->once()) ->method('preloadPathAlias') ->with($cached_paths[$language->getId()], $language->getId()) ->will($this->returnValue([$path => $alias])); // LookupPathAlias should not be called. - $this->aliasStorage->expects($this->never()) - ->method('lookupPathAlias'); + $this->aliasRepository->expects($this->never()) + ->method('lookupBySystemPath'); $this->assertEquals($alias, $this->aliasManager->getAliasByPath($path)); // Call it twice to test the static cache. @@ -322,12 +330,12 @@ public function testGetAliasByPathCachedMissLanguage() { // The requested language is different than the cached, so this will // need to load. - $this->aliasStorage->expects($this->never()) + $this->aliasRepository->expects($this->never()) ->method('preloadPathAlias'); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathAlias') + $this->aliasRepository->expects($this->once()) + ->method('lookupBySystemPath') ->with($path, $language->getId()) - ->will($this->returnValue($alias)); + ->will($this->returnValue(['alias' => $alias])); $this->assertEquals($alias, $this->aliasManager->getAliasByPath($path)); // Call it twice to test the static cache. @@ -369,14 +377,14 @@ public function testGetAliasByPathCachedMissNoAlias() { ->with($path_part1) ->will($this->returnValue(TRUE)); - $this->aliasStorage->expects($this->once()) + $this->aliasRepository->expects($this->once()) ->method('preloadPathAlias') ->with($cached_paths[$language->getId()], $language->getId()) ->will($this->returnValue([$cached_path => $cached_alias])); // LookupPathAlias() should not be called. - $this->aliasStorage->expects($this->never()) - ->method('lookupPathAlias'); + $this->aliasRepository->expects($this->never()) + ->method('lookupBySystemPath'); $this->assertEquals($path, $this->aliasManager->getAliasByPath($path)); // Call it twice to test the static cache. @@ -417,13 +425,13 @@ public function testGetAliasByPathUncachedMissNoAlias() { ->with($path_part1) ->will($this->returnValue(TRUE)); - $this->aliasStorage->expects($this->once()) + $this->aliasRepository->expects($this->once()) ->method('preloadPathAlias') ->with($cached_paths[$language->getId()], $language->getId()) ->will($this->returnValue([$cached_path => $cached_alias])); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathAlias') + $this->aliasRepository->expects($this->once()) + ->method('lookupBySystemPath') ->with($path, $language->getId()) ->will($this->returnValue(NULL)); @@ -445,10 +453,10 @@ public function testCacheClear() { $path = '/path'; $alias = '/alias'; $language = $this->setUpCurrentLanguage(); - $this->aliasStorage->expects($this->exactly(2)) - ->method('lookupPathAlias') + $this->aliasRepository->expects($this->exactly(2)) + ->method('lookupBySystemPath') ->with($path, $language->getId()) - ->willReturn($alias); + ->willReturn(['alias' => $alias]); $this->aliasWhitelist->expects($this->any()) ->method('get') ->willReturn(TRUE); @@ -457,9 +465,8 @@ public function testCacheClear() { $this->assertEquals($alias, $this->aliasManager->getAliasByPath($path, $language->getId())); // Check that the cache is populated. - $original_storage = clone $this->aliasStorage; - $this->aliasStorage->expects($this->never()) - ->method('lookupPathSource'); + $this->aliasRepository->expects($this->never()) + ->method('lookupByAlias'); $this->assertEquals($path, $this->aliasManager->getPathByAlias($alias, $language->getId())); // Clear specific source. @@ -506,15 +513,15 @@ public function testGetAliasByPathUncachedMissWithAlias() { ->with($path_part1) ->will($this->returnValue(TRUE)); - $this->aliasStorage->expects($this->once()) + $this->aliasRepository->expects($this->once()) ->method('preloadPathAlias') ->with($cached_paths[$language->getId()], $language->getId()) ->will($this->returnValue([$cached_path => $cached_alias])); - $this->aliasStorage->expects($this->once()) - ->method('lookupPathAlias') + $this->aliasRepository->expects($this->once()) + ->method('lookupBySystemPath') ->with($path, $language->getId()) - ->will($this->returnValue($new_alias)); + ->will($this->returnValue(['alias' => $new_alias])); $this->assertEquals($new_alias, $this->aliasManager->getAliasByPath($path)); // Call it twice to test the static cache. diff --git a/core/modules/path_alias/tests/src/Unit/DeprecatedClassesTest.php b/core/modules/path_alias/tests/src/Unit/DeprecatedClassesTest.php new file mode 100644 index 000000000..1b5440303 --- /dev/null +++ b/core/modules/path_alias/tests/src/Unit/DeprecatedClassesTest.php @@ -0,0 +1,235 @@ +aliasManager = $this->prophesize(AliasManagerInterface::class) + ->reveal(); + $this->aliasRepository = $this->prophesize(AliasRepositoryInterface::class) + ->reveal(); + $this->aliasWhitelist = $this->prophesize(AliasWhitelistInterface::class) + ->reveal(); + $this->cache = $this->prophesize(CacheBackendInterface::class) + ->reveal(); + $this->currentPathStack = $this->prophesize(CurrentPathStack::class) + ->reveal(); + $this->languageManager = $this->prophesize(LanguageManagerInterface::class) + ->reveal(); + $this->lock = $this->prophesize(LockBackendInterface::class) + ->reveal(); + $this->state = $this->prophesize(StateInterface::class) + ->reveal(); + + /** @var \Prophecy\Prophecy\ObjectProphecy $container */ + $container = $this->prophesize(ContainerBuilder::class); + $container->get('path_alias.manager') + ->willReturn($this->aliasManager); + \Drupal::setContainer($container->reveal()); + } + + /** + * @covers \Drupal\Core\EventSubscriber\PathSubscriber::__construct + * + * @expectedDeprecation The \Drupal\Core\EventSubscriber\PathSubscriber class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\EventSubscriber\PathAliasSubscriber. See https://drupal.org/node/3092086 + */ + public function testPathSubscriber() { + new PathSubscriber($this->aliasManager, $this->currentPathStack); + } + + /** + * @covers \Drupal\path_alias\EventSubscriber\PathAliasSubscriber::__construct + */ + public function testPathAliasSubscriber() { + $object = new PathAliasSubscriber($this->aliasManager, $this->currentPathStack); + $this->assertInstanceOf(PathSubscriber::class, $object); + } + + /** + * @covers \Drupal\Core\PathProcessor\PathProcessorAlias::__construct + * + * @expectedDeprecation The \Drupal\Core\PathProcessor\PathProcessorAlias class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\PathProcessor\AliasPathProcessor. See https://drupal.org/node/3092086 + */ + public function testPathProcessorAlias() { + new PathProcessorAlias($this->aliasManager); + } + + /** + * @covers \Drupal\path_alias\PathProcessor\AliasPathProcessor::__construct + */ + public function testAliasPathProcessor() { + $object = new AliasPathProcessor($this->aliasManager); + $this->assertInstanceOf(PathProcessorAlias::class, $object); + } + + /** + * @covers \Drupal\Core\Path\AliasManager::__construct + * + * @expectedDeprecation The \Drupal\Core\Path\AliasManager class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\AliasManager. See https://drupal.org/node/3092086 + */ + public function testCoreAliasManager() { + new CoreAliasManager($this->aliasRepository, $this->aliasWhitelist, $this->languageManager, $this->cache); + } + + /** + * @covers \Drupal\path_alias\AliasManager::__construct + */ + public function testAliasManager() { + $object = new AliasManager($this->aliasRepository, $this->aliasWhitelist, $this->languageManager, $this->cache); + $this->assertInstanceOf(CoreAliasManager::class, $object); + } + + /** + * @covers \Drupal\Core\Path\AliasWhitelist::__construct + * + * @expectedDeprecation The \Drupal\Core\Path\AliasWhitelist class is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\AliasWhitelist. See https://drupal.org/node/3092086 + */ + public function testCoreAliasWhitelist() { + new CoreAliasWhitelist('path_alias_whitelist', $this->cache, $this->lock, $this->state, $this->aliasRepository); + } + + /** + * @covers \Drupal\path_alias\AliasWhitelist::__construct + */ + public function testAliasWhitelist() { + $object = new AliasWhitelist('path_alias_whitelist', $this->cache, $this->lock, $this->state, $this->aliasRepository); + $this->assertInstanceOf(CoreAliasWhitelist::class, $object); + } + + /** + * @covers \Drupal\system\Form\SiteInformationForm::__construct + * + * @expectedDeprecation Calling \Drupal\system\Form\SiteInformationForm::__construct with \Drupal\Core\Path\AliasManagerInterface instead of \Drupal\path_alias\AliasManagerInterface is deprecated in drupal:8.8.0. The new service will be required in drupal:9.0.0. See https://www.drupal.org/node/3092086 + */ + public function testDeprecatedSystemInformationFormConstructorParameters() { + $this->assertDeprecatedConstructorParameter(SiteInformationForm::class); + } + + /** + * @covers \Drupal\system\Plugin\Condition\RequestPath::__construct + * + * @expectedDeprecation Calling \Drupal\system\Plugin\Condition\RequestPath::__construct with \Drupal\Core\Path\AliasManagerInterface instead of \Drupal\path_alias\AliasManagerInterface is deprecated in drupal:8.8.0. The new service will be required in drupal:9.0.0. See https://www.drupal.org/node/3092086 + */ + public function testDeprecatedRequestPathConstructorParameters() { + $this->assertDeprecatedConstructorParameter(RequestPath::class); + } + + /** + * @covers \Drupal\views\Plugin\views\argument_default\Raw::__construct + * + * @expectedDeprecation Calling \Drupal\views\Plugin\views\argument_default\Raw::__construct with \Drupal\Core\Path\AliasManagerInterface instead of \Drupal\path_alias\AliasManagerInterface is deprecated in drupal:8.8.0. The new service will be required in drupal:9.0.0. See https://www.drupal.org/node/3092086 + */ + public function testDeprecatedRawConstructorParameters() { + $this->assertDeprecatedConstructorParameter(Raw::class); + } + + /** + * Test that deprecation for the \Drupal\Core\Path\AliasManagerInterface. + * + * @param string $tested_class_name + * The name of the tested class. + * + * @dataProvider deprecatedConstructorParametersProvider + * + * @expectedDeprecation The \Drupal\Core\Path\AliasManagerInterface interface is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, use \Drupal\path_alias\AliasManagerInterface. See https://drupal.org/node/3092086 + */ + public function assertDeprecatedConstructorParameter($tested_class_name) { + $tested_class = new \ReflectionClass($tested_class_name); + $parameters = $tested_class->getConstructor() + ->getParameters(); + + $args = []; + foreach ($parameters as $parameter) { + $name = $parameter->getName(); + if ($name === 'alias_manager') { + $class_name = CoreAliasManagerInterface::class; + } + else { + $type = $parameter->getType(); + $class_name = $type ? (string) $type : NULL; + } + $args[$name] = isset($class_name) && $class_name !== 'array' ? $this->prophesize($class_name)->reveal() : []; + } + + $instance = $tested_class->newInstanceArgs($args); + $property = $tested_class->getProperty('aliasManager'); + $property->setAccessible(TRUE); + $this->assertInstanceOf(AliasManagerInterface::class, $property->getValue($instance)); + } + +} diff --git a/core/modules/path_alias/tests/src/Unit/PathProcessor/AliasPathProcessorTest.php b/core/modules/path_alias/tests/src/Unit/PathProcessor/AliasPathProcessorTest.php new file mode 100644 index 000000000..4f28f6495 --- /dev/null +++ b/core/modules/path_alias/tests/src/Unit/PathProcessor/AliasPathProcessorTest.php @@ -0,0 +1,87 @@ +aliasManager = $this->createMock('Drupal\path_alias\AliasManagerInterface'); + $this->pathProcessor = new AliasPathProcessor($this->aliasManager); + } + + /** + * Tests the processInbound method. + * + * @see \Drupal\path_alias\PathProcessor\AliasPathProcessor::processInbound + */ + public function testProcessInbound() { + $this->aliasManager->expects($this->exactly(2)) + ->method('getPathByAlias') + ->will($this->returnValueMap([ + ['urlalias', NULL, 'internal-url'], + ['url', NULL, 'url'], + ])); + + $request = Request::create('/urlalias'); + $this->assertEquals('internal-url', $this->pathProcessor->processInbound('urlalias', $request)); + $request = Request::create('/url'); + $this->assertEquals('url', $this->pathProcessor->processInbound('url', $request)); + } + + /** + * @covers ::processOutbound + * + * @dataProvider providerTestProcessOutbound + */ + public function testProcessOutbound($path, array $options, $expected_path) { + $this->aliasManager->expects($this->any()) + ->method('getAliasByPath') + ->will($this->returnValueMap([ + ['internal-url', NULL, 'urlalias'], + ['url', NULL, 'url'], + ])); + + $bubbleable_metadata = new BubbleableMetadata(); + $this->assertEquals($expected_path, $this->pathProcessor->processOutbound($path, $options, NULL, $bubbleable_metadata)); + // Cacheability of paths replaced with path aliases is permanent. + // @todo https://www.drupal.org/node/2480077 + $this->assertEquals((new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT), $bubbleable_metadata); + } + + /** + * @return array + */ + public function providerTestProcessOutbound() { + return [ + ['internal-url', [], 'urlalias'], + ['internal-url', ['alias' => TRUE], 'internal-url'], + ['url', [], 'url'], + ]; + } + +} diff --git a/core/modules/quickedit/js/models/FieldModel.es6.js b/core/modules/quickedit/js/models/FieldModel.es6.js index fbb510cf7..e07a9faa5 100644 --- a/core/modules/quickedit/js/models/FieldModel.es6.js +++ b/core/modules/quickedit/js/models/FieldModel.es6.js @@ -316,7 +316,7 @@ // - Trigger: in-place editor. // - Guarantees: see 'candidate' and 'active'. // - Expected behavior: remain in 'invalid' state, let the user make more - // changes so that he can save it again, without validation errors. + // changes and then save it again, without validation errors. 'invalid', ], diff --git a/core/modules/quickedit/js/theme.es6.js b/core/modules/quickedit/js/theme.es6.js index a528404f1..17640feec 100644 --- a/core/modules/quickedit/js/theme.es6.js +++ b/core/modules/quickedit/js/theme.es6.js @@ -34,9 +34,7 @@ */ Drupal.theme.quickeditEntityToolbar = function(settings) { let html = ''; - html += `
`; + html += `
`; html += ''; html += '
'; html += diff --git a/core/modules/quickedit/js/views/AppView.es6.js b/core/modules/quickedit/js/views/AppView.es6.js index 5d2ccc2c6..9fe454a34 100644 --- a/core/modules/quickedit/js/views/AppView.es6.js +++ b/core/modules/quickedit/js/views/AppView.es6.js @@ -366,7 +366,7 @@ }, /** - * Asks the user to confirm whether he wants to stop editing via a modal. + * Asks the user if they want to stop editing via a modal. * * @param {Drupal.quickedit.EntityModel} entityModel * An instance of the EntityModel class. diff --git a/core/modules/quickedit/js/views/EditorView.es6.js b/core/modules/quickedit/js/views/EditorView.es6.js index 84211fabe..f08d8bbab 100644 --- a/core/modules/quickedit/js/views/EditorView.es6.js +++ b/core/modules/quickedit/js/views/EditorView.es6.js @@ -124,7 +124,7 @@ break; case 'activating': { - // The user has indicated he wants to do in-place editing: if + // The user is in the process of activating in-place editing: if // something needs to be loaded (CSS/JavaScript/server data/…), then // do so at this stage, and once the in-place editor is ready, // set the 'active' state. A "loading" indicator will be shown in the @@ -149,11 +149,10 @@ break; case 'saving': - // When the user has indicated he wants to save his changes to this - // field, this state will be entered. If the previous saving attempt - // resulted in validation errors, the previous state will be - // 'invalid'. Clean up those validation errors while the user is - // saving. + // When the user has triggered a save to this field, this state will + // be entered. If the previous saving attempt resulted in validation + // errors, the previous state will be 'invalid'. Clean up those + // validation errors while the user is saving. if (from === 'invalid') { this.removeValidationErrors(); } diff --git a/core/modules/quickedit/js/views/EntityToolbarView.es6.js b/core/modules/quickedit/js/views/EntityToolbarView.es6.js index 0c9628d07..eece46b5a 100644 --- a/core/modules/quickedit/js/views/EntityToolbarView.es6.js +++ b/core/modules/quickedit/js/views/EntityToolbarView.es6.js @@ -3,7 +3,7 @@ * A Backbone View that provides an entity level toolbar. */ -(function($, _, Backbone, Drupal, debounce) { +(function($, _, Backbone, Drupal, debounce, Popper) { Drupal.quickedit.EntityToolbarView = Backbone.View.extend( /** @lends Drupal.quickedit.EntityToolbarView# */ { /** @@ -198,7 +198,7 @@ }, /** - * Uses the jQuery.ui.position() method to position the entity toolbar. + * Uses the Popper() method to position the entity toolbar. * * @param {HTMLElement} [element] * The element against which the entity toolbar is positioned. @@ -291,82 +291,83 @@ } while (!of); /** - * Refines the positioning algorithm of jquery.ui.position(). + * Refines popper positioning. * - * Invoked as the 'using' callback of jquery.ui.position() in - * positionToolbar(). - * - * @param {*} view - * The view the positions will be calculated from. - * @param {object} suggested - * A hash of top and left values for the position that should be set. It - * can be forwarded to .css() or .animate(). - * @param {object} info - * The position and dimensions of both the 'my' element and the 'of' - * elements, as well as calculations to their relative position. This - * object contains the following properties: - * @param {object} info.element - * A hash that contains information about the HTML element that will be - * positioned. Also known as the 'my' element. - * @param {object} info.target - * A hash that contains information about the HTML element that the - * 'my' element will be positioned against. Also known as the 'of' - * element. + * @param {object} data + * Data object containing popper and target data. */ - function refinePosition(view, suggested, info) { + function refinePopper(data) { // Determine if the pointer should be on the top or bottom. - const isBelow = suggested.top > info.target.top; - info.element.element.toggleClass( + const isBelow = data.offsets.popper.top > data.offsets.reference.top; + const classListMethod = isBelow ? 'add' : 'remove'; + data.instance.popper.classList[classListMethod]( 'quickedit-toolbar-pointer-top', - isBelow, ); // Don't position the toolbar past the first or last editable field if // the entity is the target. - if (view.$entity[0] === info.target.element[0]) { + if (that.$entity[0] === data.instance.reference) { // Get the first or last field according to whether the toolbar is // above or below the entity. - const $field = view.$entity + const $field = that.$entity .find('.quickedit-editable') .eq(isBelow ? -1 : 0); if ($field.length > 0) { - suggested.top = isBelow + data.offsets.popper.top = isBelow ? $field.offset().top + $field.outerHeight(true) - : $field.offset().top - info.element.element.outerHeight(true); + : $field.offset().top - + $(data.instance.reference).outerHeight(true); } } // Don't let the toolbar go outside the fence. - const fenceTop = view.$fence.offset().top; - const fenceHeight = view.$fence.height(); - const toolbarHeight = info.element.element.outerHeight(true); - if (suggested.top < fenceTop) { - suggested.top = fenceTop; - } else if (suggested.top + toolbarHeight > fenceTop + fenceHeight) { - suggested.top = fenceTop + fenceHeight - toolbarHeight; + const fenceTop = that.$fence.offset().top; + const fenceHeight = that.$fence.height(); + const toolbarHeight = $(data.instance.popper).outerHeight(true); + if (data.offsets.popper.top < fenceTop) { + data.offsets.popper.top = fenceTop; + } else if ( + data.offsets.popper.top + toolbarHeight > + fenceTop + fenceHeight + ) { + data.offsets.popper.top = fenceTop + fenceHeight - toolbarHeight; } - // Position the toolbar. - info.element.element.css({ - left: Math.floor(suggested.left), - top: Math.floor(suggested.top), - }); } - /** - * Calls the jquery.ui.position() method on the $el of this view. + * Calls the Popper() method on the $el of this view. */ function positionToolbar() { + const popperElement = that.el; + const referenceElement = of; + const boundariesElement = that.$fence[0]; + const popperedge = edge === 'left' ? 'start' : 'end'; + if (referenceElement !== undefined) { + if (!popperElement.classList.contains('js-popper-processed')) { + that.popper = new Popper(referenceElement, popperElement, { + placement: `top-${popperedge}`, + modifiers: { + flip: { + behavior: ['top', 'bottom'], + }, + computeStyle: { + gpuAcceleration: false, + }, + preventOverflow: { + boundariesElement, + }, + }, + onCreate: refinePopper, + onUpdate: refinePopper, + }); + popperElement.classList.add('js-popper-processed'); + } else { + that.popper.options.placement = `top-${popperedge}`; + that.popper.reference = referenceElement[0] + ? referenceElement[0] + : referenceElement; + that.popper.update(); + } + } + that.$el - .position({ - my: `${edge} bottom`, - // Move the toolbar 1px towards the start edge of the 'of' element, - // plus any horizontal padding that may have been added to the - // element that is being added, to prevent unwanted horizontal - // movement. - at: `${edge}+${1 + horizontalPadding} top`, - of, - collision: 'flipfit', - using: refinePosition.bind(null, that), - within: that.$fence, - }) // Resize the toolbar to match the dimensions of the field, up to a // maximum width that is equal to 90% of the field's width. .css({ @@ -579,4 +580,4 @@ }, }, ); -})(jQuery, _, Backbone, Drupal, Drupal.debounce); +})(jQuery, _, Backbone, Drupal, Drupal.debounce, Popper); diff --git a/core/modules/quickedit/js/views/EntityToolbarView.js b/core/modules/quickedit/js/views/EntityToolbarView.js index 8b919b10e..30dd7fa2d 100644 --- a/core/modules/quickedit/js/views/EntityToolbarView.js +++ b/core/modules/quickedit/js/views/EntityToolbarView.js @@ -5,7 +5,7 @@ * @preserve **/ -(function ($, _, Backbone, Drupal, debounce) { +(function ($, _, Backbone, Drupal, debounce, Popper) { Drupal.quickedit.EntityToolbarView = Backbone.View.extend({ _fieldToolbarRoot: null, @@ -163,42 +163,60 @@ check++; } while (!of); - function refinePosition(view, suggested, info) { - var isBelow = suggested.top > info.target.top; - info.element.element.toggleClass('quickedit-toolbar-pointer-top', isBelow); + function refinePopper(data) { + var isBelow = data.offsets.popper.top > data.offsets.reference.top; + var classListMethod = isBelow ? 'add' : 'remove'; + data.instance.popper.classList[classListMethod]('quickedit-toolbar-pointer-top'); - if (view.$entity[0] === info.target.element[0]) { - var $field = view.$entity.find('.quickedit-editable').eq(isBelow ? -1 : 0); + if (that.$entity[0] === data.instance.reference) { + var $field = that.$entity.find('.quickedit-editable').eq(isBelow ? -1 : 0); if ($field.length > 0) { - suggested.top = isBelow ? $field.offset().top + $field.outerHeight(true) : $field.offset().top - info.element.element.outerHeight(true); + data.offsets.popper.top = isBelow ? $field.offset().top + $field.outerHeight(true) : $field.offset().top - $(data.instance.reference).outerHeight(true); } } - var fenceTop = view.$fence.offset().top; - var fenceHeight = view.$fence.height(); - var toolbarHeight = info.element.element.outerHeight(true); - if (suggested.top < fenceTop) { - suggested.top = fenceTop; - } else if (suggested.top + toolbarHeight > fenceTop + fenceHeight) { - suggested.top = fenceTop + fenceHeight - toolbarHeight; + var fenceTop = that.$fence.offset().top; + var fenceHeight = that.$fence.height(); + var toolbarHeight = $(data.instance.popper).outerHeight(true); + if (data.offsets.popper.top < fenceTop) { + data.offsets.popper.top = fenceTop; + } else if (data.offsets.popper.top + toolbarHeight > fenceTop + fenceHeight) { + data.offsets.popper.top = fenceTop + fenceHeight - toolbarHeight; } - - info.element.element.css({ - left: Math.floor(suggested.left), - top: Math.floor(suggested.top) - }); } function positionToolbar() { - that.$el.position({ - my: edge + ' bottom', - - at: edge + '+' + (1 + horizontalPadding) + ' top', - of: of, - collision: 'flipfit', - using: refinePosition.bind(null, that), - within: that.$fence - }).css({ + var popperElement = that.el; + var referenceElement = of; + var boundariesElement = that.$fence[0]; + var popperedge = edge === 'left' ? 'start' : 'end'; + if (referenceElement !== undefined) { + if (!popperElement.classList.contains('js-popper-processed')) { + that.popper = new Popper(referenceElement, popperElement, { + placement: 'top-' + popperedge, + modifiers: { + flip: { + behavior: ['top', 'bottom'] + }, + computeStyle: { + gpuAcceleration: false + }, + preventOverflow: { + boundariesElement: boundariesElement + } + }, + onCreate: refinePopper, + onUpdate: refinePopper + }); + popperElement.classList.add('js-popper-processed'); + } else { + that.popper.options.placement = 'top-' + popperedge; + that.popper.reference = referenceElement[0] ? referenceElement[0] : referenceElement; + that.popper.update(); + } + } + + that.$el.css({ 'max-width': document.documentElement.clientWidth < 450 ? document.documentElement.clientWidth : 450, 'min-width': document.documentElement.clientWidth < 240 ? document.documentElement.clientWidth : 240, @@ -292,4 +310,4 @@ this.$el.removeClass('quickedit-animate-invisible'); } }); -})(jQuery, _, Backbone, Drupal, Drupal.debounce); \ No newline at end of file +})(jQuery, _, Backbone, Drupal, Drupal.debounce, Popper); \ No newline at end of file diff --git a/core/modules/quickedit/quickedit.info.yml b/core/modules/quickedit/quickedit.info.yml index fc1aaf57f..b9fc765d2 100644 --- a/core/modules/quickedit/quickedit.info.yml +++ b/core/modules/quickedit/quickedit.info.yml @@ -2,15 +2,9 @@ name: Quick Edit type: module description: 'In-place content editing.' package: Core -# core: 8.x -# version: VERSION +core: 8.x +version: VERSION dependencies: - drupal:contextual - drupal:field - drupal:filter - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/quickedit/quickedit.libraries.yml b/core/modules/quickedit/quickedit.libraries.yml index 7c81563bd..8111f00ec 100644 --- a/core/modules/quickedit/quickedit.libraries.yml +++ b/core/modules/quickedit/quickedit.libraries.yml @@ -32,7 +32,6 @@ quickedit: - core/underscore - core/backbone - core/jquery.form - - core/jquery.ui.position - core/drupal - core/drupal.displace - core/drupal.form @@ -40,6 +39,7 @@ quickedit: - core/drupal.debounce - core/drupalSettings - core/drupal.dialog + - core/popperjs quickedit.inPlaceEditor.form: version: VERSION diff --git a/core/modules/quickedit/quickedit.module b/core/modules/quickedit/quickedit.module index a601f0626..351ac6987 100644 --- a/core/modules/quickedit/quickedit.module +++ b/core/modules/quickedit/quickedit.module @@ -12,9 +12,9 @@ */ use Drupal\Core\Url; -use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; +use Drupal\Core\Entity\RevisionableInterface; use Drupal\Core\Routing\RouteMatchInterface; /** @@ -79,8 +79,8 @@ function quickedit_library_info_alter(&$libraries, $extension) { // First let the base theme modify the library, then the actual theme. $alter_library = function (&$library, $theme) use (&$alter_library) { - if (isset($theme) && $theme_path = drupal_get_path('theme', $theme)) { - $info = system_get_info('theme', $theme); + if (!empty($theme) && $theme_path = drupal_get_path('theme', $theme)) { + $info = \Drupal::service('extension.list.theme')->getExtensionInfo($theme); // Recurse to process base theme(s) first. if (isset($info['base theme'])) { $alter_library($library, $info['base theme']); @@ -134,7 +134,7 @@ function quickedit_preprocess_field(&$variables) { /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $element['#object']; - if (!\Drupal::currentUser()->hasPermission('access in-place editing') || !$entity->isLatestRevision()) { + if (!\Drupal::currentUser()->hasPermission('access in-place editing') || ($entity instanceof RevisionableInterface && !$entity->isLatestRevision())) { return; } @@ -158,9 +158,12 @@ function quickedit_preprocess_field(&$variables) { * Implements hook_entity_view_alter(). */ function quickedit_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) { - /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + if (isset($build['#embed'])) { + return; + } + $build['#cache']['contexts'][] = 'user.permissions'; - if (!\Drupal::currentUser()->hasPermission('access in-place editing') || !$entity->isLatestRevision()) { + if (!\Drupal::currentUser()->hasPermission('access in-place editing') || ($entity instanceof RevisionableInterface && !$entity->isLatestRevision())) { return; } @@ -170,20 +173,20 @@ function quickedit_entity_view_alter(&$build, EntityInterface $entity, EntityVie /** * Check if a loaded entity is the latest revision. * - * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * @param \Drupal\Core\Entity\RevisionableInterface $entity * The entity to check. * * @return bool * TRUE if the loaded entity is the latest revision, FALSE otherwise. * - * @deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. Use * \Drupal\Core\Entity\RevisionableInterface::isLatestRevision() instead. * As internal API, _quickedit_entity_is_latest_revision() may also be removed * in a minor release. * * @internal */ -function _quickedit_entity_is_latest_revision(ContentEntityInterface $entity) { +function _quickedit_entity_is_latest_revision(RevisionableInterface $entity) { @trigger_error('_quickedit_entity_is_latest_revision() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Entity\RevisionableInterface::isLatestRevision() instead. As internal API, _quickedit_entity_is_latest_revision() may also be removed in a minor release.', E_USER_DEPRECATED); return $entity->isLatestRevision(); } diff --git a/core/modules/quickedit/src/Access/EditEntityFieldAccessCheck.php b/core/modules/quickedit/src/Access/EditEntityFieldAccessCheck.php index f93790863..f965d9a82 100644 --- a/core/modules/quickedit/src/Access/EditEntityFieldAccessCheck.php +++ b/core/modules/quickedit/src/Access/EditEntityFieldAccessCheck.php @@ -3,7 +3,7 @@ namespace Drupal\quickedit\Access; /** - * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. */ class EditEntityFieldAccessCheck extends QuickEditEntityFieldAccessCheck { diff --git a/core/modules/quickedit/src/Access/EditEntityFieldAccessCheckInterface.php b/core/modules/quickedit/src/Access/EditEntityFieldAccessCheckInterface.php index cfdb32df5..db6407f78 100644 --- a/core/modules/quickedit/src/Access/EditEntityFieldAccessCheckInterface.php +++ b/core/modules/quickedit/src/Access/EditEntityFieldAccessCheckInterface.php @@ -3,7 +3,7 @@ namespace Drupal\quickedit\Access; /** - * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. */ interface EditEntityFieldAccessCheckInterface extends QuickEditEntityFieldAccessCheckInterface { diff --git a/core/modules/quickedit/src/Form/QuickEditFieldForm.php b/core/modules/quickedit/src/Form/QuickEditFieldForm.php index 0f5aa8c8f..315da98c2 100644 --- a/core/modules/quickedit/src/Form/QuickEditFieldForm.php +++ b/core/modules/quickedit/src/Form/QuickEditFieldForm.php @@ -64,7 +64,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('tempstore.private'), $container->get('module_handler'), - $container->get('entity.manager')->getStorage('node_type') + $container->get('entity_type.manager')->getStorage('node_type') ); } @@ -122,7 +122,7 @@ protected function init(FormStateInterface $form_state, EntityInterface $entity, // once https://www.drupal.org/node/1863258 lands. if ($entity->getEntityTypeId() == 'node') { $node_type = $this->nodeTypeStorage->load($entity->bundle()); - $entity->setNewRevision($node_type->isNewRevision()); + $entity->setNewRevision($node_type->shouldCreateNewRevision()); $entity->revision_log = NULL; } diff --git a/core/modules/quickedit/tests/modules/quickedit_test.info.yml b/core/modules/quickedit/tests/modules/quickedit_test.info.yml index 46405d607..fe21dc5b9 100644 --- a/core/modules/quickedit/tests/modules/quickedit_test.info.yml +++ b/core/modules/quickedit/tests/modules/quickedit_test.info.yml @@ -1,12 +1,6 @@ name: 'Quick Edit test' type: module description: 'Support module for the Quick Edit module tests.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/quickedit/tests/modules/src/MockEditEntityFieldAccessCheck.php b/core/modules/quickedit/tests/modules/src/MockEditEntityFieldAccessCheck.php index 7596aa87c..6e03bffd9 100644 --- a/core/modules/quickedit/tests/modules/src/MockEditEntityFieldAccessCheck.php +++ b/core/modules/quickedit/tests/modules/src/MockEditEntityFieldAccessCheck.php @@ -3,7 +3,7 @@ namespace Drupal\quickedit_test; /** - * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. */ class MockEditEntityFieldAccessCheck extends MockQuickEditEntityFieldAccessCheck { diff --git a/core/modules/quickedit/tests/src/Functional/QuickEditCustomPipelineTest.php b/core/modules/quickedit/tests/src/Functional/QuickEditCustomPipelineTest.php index 8921a9cf6..90ae91687 100644 --- a/core/modules/quickedit/tests/src/Functional/QuickEditCustomPipelineTest.php +++ b/core/modules/quickedit/tests/src/Functional/QuickEditCustomPipelineTest.php @@ -22,6 +22,11 @@ class QuickEditCustomPipelineTest extends BrowserTestBase { 'node', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that Quick Edit works with custom render pipelines. */ @@ -93,10 +98,10 @@ public function testCustomPipeline() { $ajax_commands = Json::decode($response->getBody()); $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.'); $this->assertIdentical('quickeditFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldFormSaved command.'); - $this->assertTrue(strpos($ajax_commands[0]['data'], 'Fine thanks.'), 'Form value saved and printed back.'); - $this->assertTrue(strpos($ajax_commands[0]['data'], '
') !== FALSE, 'Custom render pipeline used to render the value.'); + $this->assertContains('Fine thanks.', $ajax_commands[0]['data'], 'Form value saved and printed back.'); + $this->assertContains('
', $ajax_commands[0]['data'], 'Custom render pipeline used to render the value.'); $this->assertIdentical(array_keys($ajax_commands[0]['other_view_modes']), ['full'], 'Field was also rendered in the "full" view mode.'); - $this->assertTrue(strpos($ajax_commands[0]['other_view_modes']['full'], 'Fine thanks.'), '"full" version of field contains the form value.'); + $this->assertContains('Fine thanks.', $ajax_commands[0]['other_view_modes']['full'], '"full" version of field contains the form value.'); } } diff --git a/core/modules/quickedit/tests/src/Functional/QuickEditEndPointAccessTest.php b/core/modules/quickedit/tests/src/Functional/QuickEditEndPointAccessTest.php index 8d30a9c94..461b0a7a1 100644 --- a/core/modules/quickedit/tests/src/Functional/QuickEditEndPointAccessTest.php +++ b/core/modules/quickedit/tests/src/Functional/QuickEditEndPointAccessTest.php @@ -22,6 +22,11 @@ class QuickEditEndPointAccessTest extends BrowserTestBase { 'node', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/quickedit/tests/src/Functional/QuickEditMinimalTest.php b/core/modules/quickedit/tests/src/Functional/QuickEditMinimalTest.php new file mode 100644 index 000000000..2242e97a2 --- /dev/null +++ b/core/modules/quickedit/tests/src/Functional/QuickEditMinimalTest.php @@ -0,0 +1,45 @@ +drupalCreateUser([ + 'access in-place editing', + ]); + $this->drupalLogin($editor_user); + $this->assertSame('', $this->config('system.theme')->get('admin'), 'There is no admin theme set on the site.'); + } + +} diff --git a/core/modules/quickedit/tests/src/FunctionalJavascript/FieldTest.php b/core/modules/quickedit/tests/src/FunctionalJavascript/FieldTest.php index 7555220ac..0b6505bd4 100644 --- a/core/modules/quickedit/tests/src/FunctionalJavascript/FieldTest.php +++ b/core/modules/quickedit/tests/src/FunctionalJavascript/FieldTest.php @@ -28,6 +28,11 @@ class FieldTest extends WebDriverTestBase { 'quickedit', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -92,22 +97,9 @@ public function testFieldWithCkeditor() { // Wait and click by "Save" button after body field was changed. $this->assertSession()->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click(); // Wait until the save occurs and the editor UI disappears. - $this->waitForNoElement('.cke_button.cke_button__blockquote'); + $this->assertSession()->assertNoElementAfterWait('css', '.cke_button.cke_button__blockquote'); // Ensure that the changes take effect. $assert->responseMatches("|
\s*$body_value\s*
|"); } - /** - * Waits for an element to be removed from the page. - * - * @param string $selector - * CSS selector. - * @param int $timeout - * (optional) Timeout in milliseconds, defaults to 10000. - */ - protected function waitForNoElement($selector, $timeout = 10000) { - $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; - $this->assertJsCondition($condition, $timeout); - } - } diff --git a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditAutocompleteTermTest.php b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditAutocompleteTermTest.php index 375528b2e..f08ea2d46 100644 --- a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditAutocompleteTermTest.php +++ b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditAutocompleteTermTest.php @@ -31,6 +31,11 @@ class QuickEditAutocompleteTermTest extends WebDriverTestBase { 'ckeditor', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Stores the node used for the tests. * @@ -97,20 +102,20 @@ protected function setUp() { ]; $this->createEntityReferenceField('node', 'article', $this->fieldName, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); - entity_get_form_display('node', 'article', 'default') + \Drupal::service('entity_display.repository')->getFormDisplay('node', 'article') ->setComponent($this->fieldName, [ 'type' => 'entity_reference_autocomplete_tags', 'weight' => -4, ]) ->save(); - entity_get_display('node', 'article', 'default') + \Drupal::service('entity_display.repository')->getViewDisplay('node', 'article') ->setComponent($this->fieldName, [ 'type' => 'entity_reference_label', 'weight' => 10, ]) ->save(); - entity_get_display('node', 'article', 'teaser') + \Drupal::service('entity_display.repository')->getViewDisplay('node', 'article', 'teaser') ->setComponent($this->fieldName, [ 'type' => 'entity_reference_label', 'weight' => 10, diff --git a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditFileTest.php b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditFileTest.php index a1de95c01..f21ba6d7a 100644 --- a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditFileTest.php +++ b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditFileTest.php @@ -23,6 +23,11 @@ class QuickEditFileTest extends QuickEditJavascriptTestBase { 'file', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php index 52d645f0f..24f36d440 100644 --- a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php +++ b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php @@ -31,6 +31,11 @@ class QuickEditIntegrationTest extends QuickEditJavascriptTestBase { 'hold_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A user with permissions to edit Articles and use Quick Edit. * diff --git a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditJavascriptTestBase.php b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditJavascriptTestBase.php index 8c939b326..8afc3242b 100644 --- a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditJavascriptTestBase.php +++ b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditJavascriptTestBase.php @@ -176,7 +176,7 @@ protected function assertQuickEditEntityToolbar($expected_entity_label, $expecte $this->assertSame($expected_field_label, $field_label); } else { - $this->assertFalse($quickedit_entity_toolbar->find('css', '.quickedit-toolbar-label > .field')); + $this->assertEmpty($quickedit_entity_toolbar->find('css', '.quickedit-toolbar-label > .field')); } } diff --git a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditLoadingTest.php b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditLoadingTest.php index 0ecd655a9..4f52c00ef 100644 --- a/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditLoadingTest.php +++ b/core/modules/quickedit/tests/src/FunctionalJavascript/QuickEditLoadingTest.php @@ -41,6 +41,11 @@ class QuickEditLoadingTest extends WebDriverTestBase { 'image', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * An user with permissions to create and edit articles. * @@ -138,7 +143,7 @@ public function testUserPermissions() { $nid = $this->testNode->id(); // There should be only one revision so far. $node = Node::load($nid); - $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node); + $vids = \Drupal::entityTypeManager()->getStorage('node')->revisionIds($node); $this->assertCount(1, $vids, 'The node has only one revision.'); $original_log = $node->revision_log->value; @@ -164,7 +169,7 @@ public function testUserPermissions() { $assert->assertWaitOnAjaxRequest(); $node = Node::load($nid); - $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node); + $vids = \Drupal::entityTypeManager()->getStorage('node')->revisionIds($node); $this->assertCount(1, $vids, 'The node has only one revision.'); $this->assertSame($original_log, $node->revision_log->value, 'The revision log message is unchanged.'); @@ -354,7 +359,7 @@ public function testImageField() { 'entity_type' => 'node', 'bundle' => 'article', ])->save(); - entity_get_form_display('node', 'article', 'default') + \Drupal::service('entity_display.repository')->getFormDisplay('node', 'article', 'default') ->setComponent('field_image', [ 'type' => 'image_image', ]) diff --git a/core/modules/quickedit/tests/src/Kernel/EditorSelectionTest.php b/core/modules/quickedit/tests/src/Kernel/EditorSelectionTest.php index 08a8fe04a..f117a714a 100644 --- a/core/modules/quickedit/tests/src/Kernel/EditorSelectionTest.php +++ b/core/modules/quickedit/tests/src/Kernel/EditorSelectionTest.php @@ -41,7 +41,9 @@ protected function getSelectedEditor($entity_id, $field_name, $view_mode = 'defa $storage->resetCache([$entity_id]); $entity = $storage->load($entity_id); $items = $entity->get($field_name); - $options = entity_get_display('entity_test', 'entity_test', $view_mode)->getComponent($field_name); + $options = \Drupal::service('entity_display.repository') + ->getViewDisplay('entity_test', 'entity_test', $view_mode) + ->getComponent($field_name); return $this->editorSelector->getEditor($options['type'], $items); } diff --git a/core/modules/quickedit/tests/src/Kernel/QuickEditTestBase.php b/core/modules/quickedit/tests/src/Kernel/QuickEditTestBase.php index 8cc2d2f8b..127ab62d8 100644 --- a/core/modules/quickedit/tests/src/Kernel/QuickEditTestBase.php +++ b/core/modules/quickedit/tests/src/Kernel/QuickEditTestBase.php @@ -86,14 +86,17 @@ protected function createFieldWithStorage($field_name, $type, $cardinality, $lab ]); $this->fields->$field->save(); - entity_get_form_display('entity_test', 'entity_test', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + + $display_repository->getFormDisplay('entity_test', 'entity_test') ->setComponent($field_name, [ 'type' => $widget_type, 'settings' => $widget_settings, ]) ->save(); - entity_get_display('entity_test', 'entity_test', 'default') + $display_repository->getViewDisplay('entity_test', 'entity_test') ->setComponent($field_name, [ 'label' => 'above', 'type' => $formatter_type, diff --git a/core/modules/quickedit/tests/src/Unit/Access/QuickEditEntityFieldAccessCheckTest.php b/core/modules/quickedit/tests/src/Unit/Access/QuickEditEntityFieldAccessCheckTest.php index 082743698..6dc8a5835 100644 --- a/core/modules/quickedit/tests/src/Unit/Access/QuickEditEntityFieldAccessCheckTest.php +++ b/core/modules/quickedit/tests/src/Unit/Access/QuickEditEntityFieldAccessCheckTest.php @@ -70,7 +70,7 @@ public function testAccess($entity_is_editable, $field_storage_is_accessible, Ac ->method('access') ->willReturn(AccessResult::allowedIf($entity_is_editable)->cachePerPermissions()); - $field_storage = $this->getMock('Drupal\field\FieldStorageConfigInterface'); + $field_storage = $this->createMock('Drupal\field\FieldStorageConfigInterface'); $field_storage->expects($this->any()) ->method('access') ->willReturn(AccessResult::allowedIf($field_storage_is_accessible)); @@ -88,7 +88,7 @@ public function testAccess($entity_is_editable, $field_storage_is_accessible, Ac ->with(LanguageInterface::LANGCODE_NOT_SPECIFIED) ->will($this->returnValue(TRUE)); - $account = $this->getMock('Drupal\Core\Session\AccountInterface'); + $account = $this->createMock('Drupal\Core\Session\AccountInterface'); $access = $this->editAccessCheck->access($entity_with_field, $field_name, LanguageInterface::LANGCODE_NOT_SPECIFIED, $account); $this->assertEquals($expected_result, $access); } @@ -99,7 +99,7 @@ public function testAccess($entity_is_editable, $field_storage_is_accessible, Ac * @dataProvider providerTestAccessForbidden */ public function testAccessForbidden($field_name, $langcode) { - $account = $this->getMock('Drupal\Core\Session\AccountInterface'); + $account = $this->createMock('Drupal\Core\Session\AccountInterface'); $entity = $this->createMockEntity(); $this->assertEquals(AccessResult::forbidden(), $this->editAccessCheck->access($entity, $field_name, $langcode, $account)); } @@ -123,7 +123,7 @@ public function providerTestAccessForbidden() { /** * Returns a mock entity. * - * @return \Drupal\Core\Entity\EntityInterface|\PHPUnit_Framework_MockObject_MockObject + * @return \Drupal\Core\Entity\EntityInterface|\PHPUnit\Framework\MockObject\MockObject */ protected function createMockEntity() { $entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest') diff --git a/core/modules/rdf/migrations/state/rdf.migrate_drupal.yml b/core/modules/rdf/migrations/state/rdf.migrate_drupal.yml new file mode 100644 index 000000000..8e9205d12 --- /dev/null +++ b/core/modules/rdf/migrations/state/rdf.migrate_drupal.yml @@ -0,0 +1,3 @@ +finished: + 7: + rdf: rdf diff --git a/core/modules/rdf/rdf.info.yml b/core/modules/rdf/rdf.info.yml index e7c9a7890..ec1b50905 100644 --- a/core/modules/rdf/rdf.info.yml +++ b/core/modules/rdf/rdf.info.yml @@ -2,11 +2,5 @@ name: RDF type: module description: 'Enriches your content with metadata to let other applications (e.g. search engines, aggregators) better understand its relationships and attributes.' package: Core -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index d231697f1..6dced6ada 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -242,7 +242,7 @@ function rdf_comment_storage_load($comments) { // bubbleable metadata, because it can be outside of a render context. $comment->rdf_data['entity_uri'] = $entity->toUrl()->toString(TRUE)->getGeneratedUrl(); if ($comment->hasParentComment()) { - $comment->rdf_data['pid_uri'] = $comment->getParentComment()->toUrl()->toString(); + $comment->rdf_data['pid_uri'] = $comment->getParentComment()->toUrl()->toString(TRUE)->getGeneratedUrl(); } } } diff --git a/core/modules/rdf/src/Entity/RdfMapping.php b/core/modules/rdf/src/Entity/RdfMapping.php index a4f268f81..e33f24a59 100644 --- a/core/modules/rdf/src/Entity/RdfMapping.php +++ b/core/modules/rdf/src/Entity/RdfMapping.php @@ -145,7 +145,7 @@ public function calculateDependencies() { parent::calculateDependencies(); // Create dependency on the bundle. - $entity_type = \Drupal::entityManager()->getDefinition($this->targetEntityType); + $entity_type = \Drupal::entityTypeManager()->getDefinition($this->targetEntityType); $this->addDependency('module', $entity_type->getProvider()); $bundle_config_dependency = $entity_type->getBundleConfigDependency($this->bundle); $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']); @@ -159,8 +159,8 @@ public function calculateDependencies() { public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - if (\Drupal::entityManager()->hasHandler($this->targetEntityType, 'view_builder')) { - \Drupal::entityManager()->getViewBuilder($this->targetEntityType)->resetCache(); + if (\Drupal::entityTypeManager()->hasHandler($this->targetEntityType, 'view_builder')) { + \Drupal::entityTypeManager()->getViewBuilder($this->targetEntityType)->resetCache(); } } diff --git a/core/modules/rdf/tests/rdf_conflicting_namespaces/rdf_conflicting_namespaces.info.yml b/core/modules/rdf/tests/rdf_conflicting_namespaces/rdf_conflicting_namespaces.info.yml index 58a9e3a6a..bef342f56 100644 --- a/core/modules/rdf/tests/rdf_conflicting_namespaces/rdf_conflicting_namespaces.info.yml +++ b/core/modules/rdf/tests/rdf_conflicting_namespaces/rdf_conflicting_namespaces.info.yml @@ -2,13 +2,7 @@ name: 'RDF module conflicting namespaces test' type: module description: 'Test conflicting namespace declaration.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:rdf - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/rdf/tests/rdf_test/rdf_test.info.yml b/core/modules/rdf/tests/rdf_test/rdf_test.info.yml index 8d4c789f6..9e6a16bcc 100644 --- a/core/modules/rdf/tests/rdf_test/rdf_test.info.yml +++ b/core/modules/rdf/tests/rdf_test/rdf_test.info.yml @@ -2,13 +2,7 @@ name: 'RDF test module' type: module description: 'Test functionality for the RDF module.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - rdf - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/rdf/tests/rdf_test_namespaces/rdf_test_namespaces.info.yml b/core/modules/rdf/tests/rdf_test_namespaces/rdf_test_namespaces.info.yml index b71cd4b35..0856b9702 100644 --- a/core/modules/rdf/tests/rdf_test_namespaces/rdf_test_namespaces.info.yml +++ b/core/modules/rdf/tests/rdf_test_namespaces/rdf_test_namespaces.info.yml @@ -2,13 +2,7 @@ name: 'RDF module namespaces test' type: module description: 'Test namespace declaration.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:rdf - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/rdf/tests/src/Functional/CommentAttributesTest.php b/core/modules/rdf/tests/src/Functional/CommentAttributesTest.php index 6daec72fd..2be1e85cc 100644 --- a/core/modules/rdf/tests/src/Functional/CommentAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/CommentAttributesTest.php @@ -23,6 +23,11 @@ class CommentAttributesTest extends CommentTestBase { */ public static $modules = ['views', 'node', 'comment', 'rdf']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * URI of the front page of the Drupal site. * diff --git a/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php b/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php index 42be42f6b..8c577c1e1 100644 --- a/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/EntityReferenceFieldAttributesTest.php @@ -20,6 +20,11 @@ class EntityReferenceFieldAttributesTest extends TaxonomyTestBase { */ public static $modules = ['rdf', 'field_test', 'file', 'image']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The name of the taxonomy term reference field used in the test. * @@ -51,10 +56,13 @@ protected function setUp() { ]; $this->createEntityReferenceField('node', 'article', $this->fieldName, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); - entity_get_form_display('node', 'article', 'default') + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + + $display_repository->getFormDisplay('node', 'article') ->setComponent($this->fieldName, ['type' => 'options_select']) ->save(); - entity_get_display('node', 'article', 'full') + $display_repository->getViewDisplay('node', 'article', 'full') ->setComponent($this->fieldName, ['type' => 'entity_reference_label']) ->save(); @@ -80,7 +88,8 @@ protected function setUp() { */ public function testNodeTeaser() { // Set the teaser display to show this field. - entity_get_display('node', 'article', 'teaser') + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article', 'teaser') ->setComponent($this->fieldName, ['type' => 'entity_reference_label']) ->save(); @@ -98,7 +107,9 @@ public function testNodeTeaser() { ]); // Render the node. - $node_render_array = entity_view_multiple([$node], 'teaser'); + $node_render_array = \Drupal::entityTypeManager() + ->getViewBuilder('node') + ->view($node, 'teaser'); $html = \Drupal::service('renderer')->renderRoot($node_render_array); // Parse the teaser. diff --git a/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php b/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php index c63db1af6..fb8eb96c7 100644 --- a/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php @@ -20,6 +20,11 @@ class FileFieldAttributesTest extends FileFieldTestBase { */ public static $modules = ['rdf', 'file']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The name of the file field used in the test. * @@ -43,14 +48,15 @@ class FileFieldAttributesTest extends FileFieldTestBase { protected function setUp() { parent::setUp(); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $this->fieldName = strtolower($this->randomMachineName()); $type_name = 'article'; $this->createFileField($this->fieldName, 'node', $type_name); // Set the teaser display to show this field. - entity_get_display('node', 'article', 'teaser') + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article', 'teaser') ->setComponent($this->fieldName, ['type' => 'file_default']) ->save(); @@ -76,7 +82,9 @@ protected function setUp() { */ public function testNodeTeaser() { // Render the teaser. - $node_render_array = entity_view_multiple([$this->node], 'teaser'); + $node_render_array = \Drupal::entityTypeManager() + ->getViewBuilder('node') + ->view($this->node, 'teaser'); $html = \Drupal::service('renderer')->renderRoot($node_render_array); // Parses front page where the node is displayed in its teaser form. diff --git a/core/modules/rdf/tests/src/Functional/GetRdfNamespacesTest.php b/core/modules/rdf/tests/src/Functional/GetRdfNamespacesTest.php index 0b162a4a9..aade6bc62 100644 --- a/core/modules/rdf/tests/src/Functional/GetRdfNamespacesTest.php +++ b/core/modules/rdf/tests/src/Functional/GetRdfNamespacesTest.php @@ -18,6 +18,11 @@ class GetRdfNamespacesTest extends BrowserTestBase { */ public static $modules = ['rdf', 'rdf_test_namespaces']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests getting RDF namespaces. */ diff --git a/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonAnonTest.php b/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonAnonTest.php index 2d241e996..17917981a 100644 --- a/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonAnonTest.php +++ b/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonAnonTest.php @@ -17,6 +17,11 @@ class RdfMappingHalJsonAnonTest extends RdfMappingResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonBasicAuthTest.php b/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonBasicAuthTest.php index 346ad229e..3c9969900 100644 --- a/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonBasicAuthTest.php +++ b/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class RdfMappingHalJsonBasicAuthTest extends RdfMappingResourceTestBase { */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonCookieTest.php b/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonCookieTest.php index ff90d902a..8c5c47c0f 100644 --- a/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonCookieTest.php +++ b/core/modules/rdf/tests/src/Functional/Hal/RdfMappingHalJsonCookieTest.php @@ -17,6 +17,11 @@ class RdfMappingHalJsonCookieTest extends RdfMappingResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php b/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php index b8b471967..bd7eab7d7 100644 --- a/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php @@ -27,6 +27,11 @@ class ImageFieldAttributesTest extends ImageFieldTestBase { */ public static $modules = ['rdf', 'image']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The name of the image field used in the test. * @@ -83,12 +88,15 @@ public function testNodeTeaser() { 'type' => 'image', 'settings' => ['image_style' => 'medium', 'image_link' => 'content'], ]; - $display = entity_get_display('node', 'article', 'teaser'); + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article', 'teaser'); $display->setComponent($this->fieldName, $display_options) ->save(); // Render the teaser. - $node_render_array = node_view($this->node, 'teaser'); + $node_render_array = \Drupal::entityTypeManager() + ->getViewBuilder('node') + ->view($this->node, 'teaser'); $html = \Drupal::service('renderer')->renderRoot($node_render_array); // Parse the teaser. diff --git a/core/modules/rdf/tests/src/Functional/NodeAttributesTest.php b/core/modules/rdf/tests/src/Functional/NodeAttributesTest.php index 7811b976b..00a4a405c 100644 --- a/core/modules/rdf/tests/src/Functional/NodeAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/NodeAttributesTest.php @@ -19,6 +19,11 @@ class NodeAttributesTest extends NodeTestBase { */ public static $modules = ['rdf']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonAnonTest.php b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonAnonTest.php index bb53cc762..faed79506 100644 --- a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonAnonTest.php +++ b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonAnonTest.php @@ -21,4 +21,9 @@ class RdfMappingJsonAnonTest extends RdfMappingResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonBasicAuthTest.php b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonBasicAuthTest.php index 953733678..7727fedfa 100644 --- a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonBasicAuthTest.php +++ b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class RdfMappingJsonBasicAuthTest extends RdfMappingResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonCookieTest.php b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonCookieTest.php index 392d45e9b..768aab6b9 100644 --- a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonCookieTest.php +++ b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingJsonCookieTest.php @@ -26,4 +26,9 @@ class RdfMappingJsonCookieTest extends RdfMappingResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlAnonTest.php b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlAnonTest.php index ab6922b8e..a1970f6bd 100644 --- a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlAnonTest.php +++ b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlAnonTest.php @@ -23,4 +23,9 @@ class RdfMappingXmlAnonTest extends RdfMappingResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlBasicAuthTest.php b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlBasicAuthTest.php index 9cf6b97d5..d7a8c1918 100644 --- a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlBasicAuthTest.php +++ b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class RdfMappingXmlBasicAuthTest extends RdfMappingResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlCookieTest.php b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlCookieTest.php index 1519e5ac7..c84fdc075 100644 --- a/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlCookieTest.php +++ b/core/modules/rdf/tests/src/Functional/Rest/RdfMappingXmlCookieTest.php @@ -28,4 +28,9 @@ class RdfMappingXmlCookieTest extends RdfMappingResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rdf/tests/src/Functional/StandardProfileTest.php b/core/modules/rdf/tests/src/Functional/StandardProfileTest.php index 8355c19c7..93d78a93a 100644 --- a/core/modules/rdf/tests/src/Functional/StandardProfileTest.php +++ b/core/modules/rdf/tests/src/Functional/StandardProfileTest.php @@ -18,6 +18,11 @@ */ class StandardProfileTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * The profile used during tests. * @@ -105,10 +110,6 @@ class StandardProfileTest extends BrowserTestBase { protected function setUp() { parent::setUp(); - // Use Classy theme for testing markup output. - \Drupal::service('theme_handler')->install(['classy']); - $this->config('system.theme')->set('default', 'classy')->save(); - $this->baseUri = Url::fromRoute('', [], ['absolute' => TRUE])->toString(); // Create two test users. diff --git a/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php b/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php index 7b0720dfa..529c99b35 100644 --- a/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/TaxonomyAttributesTest.php @@ -19,6 +19,11 @@ class TaxonomyAttributesTest extends TaxonomyTestBase { */ public static $modules = ['rdf', 'views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Vocabulary created for testing purposes. * diff --git a/core/modules/rdf/tests/src/Functional/UserAttributesTest.php b/core/modules/rdf/tests/src/Functional/UserAttributesTest.php index 0373e1b49..4a38c57f9 100644 --- a/core/modules/rdf/tests/src/Functional/UserAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/UserAttributesTest.php @@ -19,6 +19,11 @@ class UserAttributesTest extends BrowserTestBase { */ public static $modules = ['rdf', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); rdf_get_mapping('user', 'user') diff --git a/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php b/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php index 7d8e54bf7..2e30ccd0f 100644 --- a/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php +++ b/core/modules/rdf/tests/src/Kernel/Field/FieldRdfaTestBase.php @@ -87,10 +87,13 @@ protected function assertFormatterRdfa($formatter, $property, $expected_rdf_valu // The field formatter will be rendered inside the entity. Set the field // formatter in the entity display options before rendering the entity. - entity_get_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getViewDisplay('entity_test', 'entity_test') ->setComponent($this->fieldName, $formatter) ->save(); - $build = entity_view($this->entity, 'default'); + $build = \Drupal::entityTypeManager() + ->getViewBuilder($this->entity->getEntityTypeId()) + ->view($this->entity, 'default'); $output = \Drupal::service('renderer')->renderRoot($build); $graph = new \EasyRdf_Graph($this->uri, $output, 'rdfa'); $this->setRawContent($output); diff --git a/core/modules/rdf/tests/src/Kernel/Field/NumberFieldRdfaTest.php b/core/modules/rdf/tests/src/Kernel/Field/NumberFieldRdfaTest.php index 5c7d38569..fc566cc49 100644 --- a/core/modules/rdf/tests/src/Kernel/Field/NumberFieldRdfaTest.php +++ b/core/modules/rdf/tests/src/Kernel/Field/NumberFieldRdfaTest.php @@ -23,14 +23,14 @@ public function testIntegerFormatter() { // Test that the content attribute is not created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__items") and @content]'); - $this->assertFalse($result); + $this->assertEmpty($result); } /** * Tests the integer formatter with settings. */ public function testIntegerFormatterWithSettings() { - \Drupal::service('theme_handler')->install(['classy']); + \Drupal::service('theme_installer')->install(['classy']); $this->config('system.theme')->set('default', 'classy')->save(); $this->fieldType = 'integer'; $formatter = [ @@ -51,7 +51,7 @@ public function testIntegerFormatterWithSettings() { // Test that the content attribute is created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__item") and @content=:testValue]', [':testValue' => $testValue]); - $this->assertTrue($result); + $this->assertNotEmpty($result); } /** @@ -66,14 +66,14 @@ public function testFloatFormatter() { // Test that the content attribute is not created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__items") and @content]'); - $this->assertFalse($result); + $this->assertEmpty($result); } /** * Tests the float formatter with settings. */ public function testFloatFormatterWithSettings() { - \Drupal::service('theme_handler')->install(['classy']); + \Drupal::service('theme_installer')->install(['classy']); $this->config('system.theme')->set('default', 'classy')->save(); $this->fieldType = 'float'; $formatter = [ @@ -95,7 +95,7 @@ public function testFloatFormatterWithSettings() { // Test that the content attribute is created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__item") and @content=:testValue]', [':testValue' => $testValue]); - $this->assertTrue($result); + $this->assertNotEmpty($result); } /** @@ -116,14 +116,14 @@ public function testFloatFormatterWithScale() { // Test that the content attribute is not created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__items") and @content]'); - $this->assertFalse($result); + $this->assertEmpty($result); } /** * Tests the float formatter with a scale. Scale is exercised. */ public function testFloatFormatterWithScaleExercised() { - \Drupal::service('theme_handler')->install(['classy']); + \Drupal::service('theme_installer')->install(['classy']); $this->config('system.theme')->set('default', 'classy')->save(); $this->fieldType = 'float'; $formatter = [ @@ -139,7 +139,7 @@ public function testFloatFormatterWithScaleExercised() { // Test that the content attribute is created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__item") and @content=:testValue]', [':testValue' => $testValue]); - $this->assertTrue($result); + $this->assertNotEmpty($result); } /** @@ -154,14 +154,14 @@ public function testDecimalFormatter() { // Test that the content attribute is not created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__items") and @content]'); - $this->assertFalse($result); + $this->assertEmpty($result); } /** * Tests the decimal formatter with settings. */ public function testDecimalFormatterWithSettings() { - \Drupal::service('theme_handler')->install(['classy']); + \Drupal::service('theme_installer')->install(['classy']); $this->config('system.theme')->set('default', 'classy')->save(); $this->fieldType = 'decimal'; $formatter = [ @@ -183,7 +183,7 @@ public function testDecimalFormatterWithSettings() { // Test that the content attribute is created. $result = $this->xpathContent($this->getRawContent(), '//div[contains(@class, "field__item") and @content=:testValue]', [':testValue' => $testValue]); - $this->assertTrue($result); + $this->assertNotEmpty($result); } /** diff --git a/core/modules/rdf/tests/src/Unit/RdfMappingConfigEntityUnitTest.php b/core/modules/rdf/tests/src/Unit/RdfMappingConfigEntityUnitTest.php index 620dbcbb6..d8ba6f018 100644 --- a/core/modules/rdf/tests/src/Unit/RdfMappingConfigEntityUnitTest.php +++ b/core/modules/rdf/tests/src/Unit/RdfMappingConfigEntityUnitTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\rdf\Unit; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Entity\EntityManager; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Tests\UnitTestCase; use Drupal\rdf\Entity\RdfMapping; @@ -17,21 +16,14 @@ class RdfMappingConfigEntityUnitTest extends UnitTestCase { /** * The entity type used for testing. * - * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityType; - /** - * The entity manager used for testing. - * - * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $entityManager; - /** * The entity type manager used for testing. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeManager; @@ -45,7 +37,7 @@ class RdfMappingConfigEntityUnitTest extends UnitTestCase { /** * The UUID generator used for testing. * - * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $uuid; @@ -55,21 +47,18 @@ class RdfMappingConfigEntityUnitTest extends UnitTestCase { protected function setUp() { $this->entityTypeId = $this->randomMachineName(); - $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $this->entityType = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); $this->entityType->expects($this->any()) ->method('getProvider') ->will($this->returnValue('entity')); - $this->entityManager = new EntityManager(); - $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class); + $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); - $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface'); + $this->uuid = $this->createMock('\Drupal\Component\Uuid\UuidInterface'); $container = new ContainerBuilder(); - $container->set('entity.manager', $this->entityManager); $container->set('entity_type.manager', $this->entityTypeManager); $container->set('uuid', $this->uuid); - $this->entityManager->setContainer($container); \Drupal::setContainer($container); } @@ -80,7 +69,7 @@ protected function setUp() { public function testCalculateDependencies() { $target_entity_type_id = $this->randomMachineName(16); - $target_entity_type = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $target_entity_type = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); $target_entity_type->expects($this->any()) ->method('getProvider') ->will($this->returnValue('test_module')); @@ -109,7 +98,7 @@ public function testCalculateDependencies() { */ public function testCalculateDependenciesWithEntityBundle() { $target_entity_type_id = $this->randomMachineName(16); - $target_entity_type = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $target_entity_type = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); $target_entity_type->expects($this->any()) ->method('getProvider') ->will($this->returnValue('test_module')); diff --git a/core/modules/responsive_image/responsive_image.info.yml b/core/modules/responsive_image/responsive_image.info.yml index f2199ac46..8ddf2c72e 100644 --- a/core/modules/responsive_image/responsive_image.info.yml +++ b/core/modules/responsive_image/responsive_image.info.yml @@ -2,15 +2,9 @@ name: Responsive Image type: module description: 'Provides an image formatter and breakpoint mappings to output responsive images using the HTML5 picture tag.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:breakpoint - drupal:image configure: entity.responsive_image_style.collection - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index e1a271b0c..09582a033 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -18,7 +18,7 @@ use Drupal\breakpoint\BreakpointInterface; /** * The machine name for the empty image breakpoint image style option. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * Drupal\responsive_image\ResponsiveImageStyleInterface::EMPTY_IMAGE * instead. * @@ -29,7 +29,7 @@ const RESPONSIVE_IMAGE_EMPTY_IMAGE = '_empty image_'; /** * The machine name for the original image breakpoint image style option. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\responsive_image\ResponsiveImageStyleInterface::ORIGINAL_IMAGE * instead. * @@ -256,7 +256,7 @@ function template_preprocess_responsive_image(&$variables) { * @return \Drupal\Core\Template\Attribute[] * An array of attributes for the source tag. * - * @deprecated in Drupal 8.3.x and will be removed before 9.0.0. + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. */ function responsive_image_build_source_attributes(ImageInterface $image, array $variables, BreakpointInterface $breakpoint, array $multipliers) { return _responsive_image_build_source_attributes($variables, $breakpoint, $multipliers); diff --git a/core/modules/responsive_image/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php b/core/modules/responsive_image/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php index e7e3f753a..396ec1057 100644 --- a/core/modules/responsive_image/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php +++ b/core/modules/responsive_image/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php @@ -117,7 +117,7 @@ public function testResponsiveImageFormatterUi() { 'image_mapping' => 'large', ]) ->save(); - \Drupal::entityManager()->clearCachedFieldDefinitions(); + \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); // Refresh the page. $this->drupalGet($manage_display); $assert_session->responseContains("Select a responsive image style."); diff --git a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php index 362d7623d..dcd059cf7 100644 --- a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php +++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php @@ -105,8 +105,8 @@ public static function create(ContainerInterface $container, array $configuratio $configuration['label'], $configuration['view_mode'], $configuration['third_party_settings'], - $container->get('entity.manager')->getStorage('responsive_image_style'), - $container->get('entity.manager')->getStorage('image_style'), + $container->get('entity_type.manager')->getStorage('responsive_image_style'), + $container->get('entity_type.manager')->getStorage('image_style'), $container->get('link_generator'), $container->get('current_user') ); diff --git a/core/modules/responsive_image/src/ResponsiveImageStyleForm.php b/core/modules/responsive_image/src/ResponsiveImageStyleForm.php index ede47ad2c..08227bfbf 100644 --- a/core/modules/responsive_image/src/ResponsiveImageStyleForm.php +++ b/core/modules/responsive_image/src/ResponsiveImageStyleForm.php @@ -157,7 +157,7 @@ public function form(array $form, FormStateInterface $form_state) { ], ]; $form['keyed_styles'][$breakpoint_id][$multiplier]['sizes'] = [ - '#type' => 'textfield', + '#type' => 'textarea', '#title' => $this->t('Sizes'), '#default_value' => isset($image_style_mapping['image_mapping']['sizes']) ? $image_style_mapping['image_mapping']['sizes'] : '100vw', '#description' => $this->t('Enter the value for the sizes attribute, for example: %example_sizes.', ['%example_sizes' => '(min-width:700px) 700px, 100vw']), diff --git a/core/modules/responsive_image/tests/modules/responsive_image_test_module/responsive_image_test_module.info.yml b/core/modules/responsive_image/tests/modules/responsive_image_test_module/responsive_image_test_module.info.yml index 228349f10..694e2dd9d 100644 --- a/core/modules/responsive_image/tests/modules/responsive_image_test_module/responsive_image_test_module.info.yml +++ b/core/modules/responsive_image/tests/modules/responsive_image_test_module/responsive_image_test_module.info.yml @@ -2,11 +2,5 @@ name: 'Responsive image test theme' type: module description: 'Test theme for responsive image.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonAnonTest.php b/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonAnonTest.php index 50dc1970d..cff45272e 100644 --- a/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonAnonTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonAnonTest.php @@ -17,6 +17,11 @@ class ResponsiveImageStyleHalJsonAnonTest extends ResponsiveImageStyleResourceTe */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonBasicAuthTest.php b/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonBasicAuthTest.php index 1f45a2d74..17e896ee2 100644 --- a/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonBasicAuthTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonBasicAuthTest.php @@ -22,6 +22,11 @@ class ResponsiveImageStyleHalJsonBasicAuthTest extends ResponsiveImageStyleResou */ protected static $format = 'hal_json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonCookieTest.php b/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonCookieTest.php index 85d95431f..3d32819db 100644 --- a/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonCookieTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Hal/ResponsiveImageStyleHalJsonCookieTest.php @@ -17,6 +17,11 @@ class ResponsiveImageStyleHalJsonCookieTest extends ResponsiveImageStyleResource */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageAdminUITest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageAdminUITest.php index 8dc5e24d3..e34b425e0 100644 --- a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageAdminUITest.php +++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageAdminUITest.php @@ -19,6 +19,11 @@ class ResponsiveImageAdminUITest extends BrowserTestBase { */ public static $modules = ['responsive_image', 'responsive_image_test_module']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Drupal\simpletest\WebTestBase\setUp(). */ @@ -87,7 +92,7 @@ public function testResponsiveImageAdmin() { foreach ($image_styles as $image_style_name) { // Check if the image styles are available in the dropdowns. - $this->assertTrue($this->xpath( + $this->assertNotEmpty($this->xpath( '//select[@name=:name]//option[@value=:style]', [ ':name' => 'keyed_styles[responsive_image_test_module.' . $case[0] . '][' . $case[1] . '][image_style]', @@ -107,7 +112,8 @@ public function testResponsiveImageAdmin() { 'keyed_styles[responsive_image_test_module.mobile][1x][image_mapping_type]' => 'image_style', 'keyed_styles[responsive_image_test_module.mobile][1x][image_style]' => 'thumbnail', 'keyed_styles[responsive_image_test_module.narrow][1x][image_mapping_type]' => 'sizes', - 'keyed_styles[responsive_image_test_module.narrow][1x][sizes]' => '(min-width: 700px) 700px, 100vw', + // Ensure the Sizes field allows long values. + 'keyed_styles[responsive_image_test_module.narrow][1x][sizes]' => '(min-resolution: 192dpi) and (min-width: 170px) 386px, (min-width: 170px) 193px, (min-width: 768px) 18vw, (min-width: 480px) 30vw, 48vw', 'keyed_styles[responsive_image_test_module.narrow][1x][sizes_image_styles][large]' => 'large', 'keyed_styles[responsive_image_test_module.narrow][1x][sizes_image_styles][medium]' => 'medium', 'keyed_styles[responsive_image_test_module.wide][1x][image_mapping_type]' => 'image_style', @@ -123,7 +129,7 @@ public function testResponsiveImageAdmin() { // Check the mapping for multipliers 1x and 2x for the narrow breakpoint. $this->assertFieldByName('keyed_styles[responsive_image_test_module.narrow][1x][image_mapping_type]', 'sizes'); - $this->assertFieldByName('keyed_styles[responsive_image_test_module.narrow][1x][sizes]', '(min-width: 700px) 700px, 100vw'); + $this->assertFieldByName('keyed_styles[responsive_image_test_module.narrow][1x][sizes]', '(min-resolution: 192dpi) and (min-width: 170px) 386px, (min-width: 170px) 193px, (min-width: 768px) 18vw, (min-width: 480px) 30vw, 48vw'); $this->assertFieldChecked('edit-keyed-styles-responsive-image-test-modulenarrow-1x-sizes-image-styles-large'); $this->assertFieldChecked('edit-keyed-styles-responsive-image-test-modulenarrow-1x-sizes-image-styles-medium'); $this->assertNoFieldChecked('edit-keyed-styles-responsive-image-test-modulenarrow-1x-sizes-image-styles-thumbnail'); diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php index ac3a0f007..9b805d6e6 100644 --- a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php +++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php @@ -21,6 +21,11 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { use TestFileCreationTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected $dumpHeaders = TRUE; /** @@ -171,7 +176,7 @@ protected function addTestImageStyleMappings($empty_styles = FALSE) { protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = FALSE) { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $field_name = mb_strtolower($this->randomMachineName()); $this->createImageField($field_name, 'article', ['uri_scheme' => $scheme]); // Create a new node with an image attached. Make sure we use a large image @@ -202,7 +207,7 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = 'type' => 'responsive_image_test', 'settings' => ResponsiveImageFormatter::defaultSettings(), ]; - $display = $this->container->get('entity.manager') + $display = $this->container->get('entity_type.manager') ->getStorage('entity_view_display') ->load('node.article.default'); if (!$display) { @@ -212,7 +217,7 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = 'mode' => 'default', 'status' => TRUE, ]; - $display = $this->container->get('entity.manager')->getStorage('entity_view_display')->create($values); + $display = $this->container->get('entity_type.manager')->getStorage('entity_view_display')->create($values); } $display->setComponent($field_name, $display_options)->save(); @@ -226,7 +231,9 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = 'responsive_image_style' => 'style_one', ], ]; - $display = entity_get_display('node', 'article', 'default'); + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $display = $display_repository->getViewDisplay('node', 'article'); $display->setComponent($field_name, $display_options) ->save(); @@ -240,7 +247,7 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = 'responsive_image_style' => 'style_one', ], ]; - $display = entity_get_display('node', 'article', 'default'); + $display = $display_repository->getViewDisplay('node', 'article'); $display->setComponent($field_name, $display_options) ->save(); @@ -367,7 +374,7 @@ public function testResponsiveImageFieldFormattersEmptyMediaQuery() { 'image_mapping' => 'thumbnail', ]) ->save(); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $field_name = mb_strtolower($this->randomMachineName()); $this->createImageField($field_name, 'article', ['uri_scheme' => 'public']); // Create a new node with an image attached. @@ -383,7 +390,8 @@ public function testResponsiveImageFieldFormattersEmptyMediaQuery() { 'responsive_image_style' => 'style_one', ], ]; - $display = entity_get_display('node', 'article', 'default'); + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article'); $display->setComponent($field_name, $display_options) ->save(); @@ -415,7 +423,7 @@ public function testResponsiveImageFieldFormattersOneSource() { 'image_mapping' => 'large', ]) ->save(); - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); $field_name = mb_strtolower($this->randomMachineName()); $this->createImageField($field_name, 'article', ['uri_scheme' => 'public']); // Create a new node with an image attached. @@ -431,7 +439,8 @@ public function testResponsiveImageFieldFormattersOneSource() { 'responsive_image_style' => 'style_one', ], ]; - $display = entity_get_display('node', 'article', 'default'); + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article'); $display->setComponent($field_name, $display_options) ->save(); @@ -459,6 +468,9 @@ private function assertResponsiveImageFieldFormattersLink($link_type) { // Create a new node with an image attached. $test_image = current($this->getTestFiles('image')); + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + // Test the image linked to file formatter. $display_options = [ 'type' => 'responsive_image', @@ -467,7 +479,7 @@ private function assertResponsiveImageFieldFormattersLink($link_type) { 'responsive_image_style' => 'style_one', ], ]; - entity_get_display('node', 'article', 'default') + $display_repository->getViewDisplay('node', 'article') ->setComponent($field_name, $display_options) ->save(); // Ensure that preview works. @@ -477,7 +489,7 @@ private function assertResponsiveImageFieldFormattersLink($link_type) { $this->assertPattern('/picture/'); $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); - $this->container->get('entity.manager')->getStorage('node')->resetCache([$nid]); + $this->container->get('entity_type.manager')->getStorage('node')->resetCache([$nid]); $node = Node::load($nid); // Use the responsive image formatter linked to file formatter. @@ -488,7 +500,7 @@ private function assertResponsiveImageFieldFormattersLink($link_type) { 'responsive_image_style' => 'style_one', ], ]; - entity_get_display('node', 'article', 'default') + $display_repository->getViewDisplay('node', 'article') ->setComponent($field_name, $display_options) ->save(); diff --git a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonAnonTest.php b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonAnonTest.php index 42b3098dd..2b35fd2b5 100644 --- a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonAnonTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonAnonTest.php @@ -21,4 +21,9 @@ class ResponsiveImageStyleJsonAnonTest extends ResponsiveImageStyleResourceTestB */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonBasicAuthTest.php b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonBasicAuthTest.php index b3c9127da..76eadc341 100644 --- a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonBasicAuthTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ResponsiveImageStyleJsonBasicAuthTest extends ResponsiveImageStyleResource */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonCookieTest.php b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonCookieTest.php index e89122eab..4e78c8aa3 100644 --- a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonCookieTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleJsonCookieTest.php @@ -26,4 +26,9 @@ class ResponsiveImageStyleJsonCookieTest extends ResponsiveImageStyleResourceTes */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlAnonTest.php b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlAnonTest.php index 8493eddab..b07b2b06b 100644 --- a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlAnonTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlAnonTest.php @@ -23,4 +23,9 @@ class ResponsiveImageStyleXmlAnonTest extends ResponsiveImageStyleResourceTestBa */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlBasicAuthTest.php b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlBasicAuthTest.php index e850e99d5..75b6ead6b 100644 --- a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlBasicAuthTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ResponsiveImageStyleXmlBasicAuthTest extends ResponsiveImageStyleResourceT */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php index f63f91cd5..f71632b75 100644 --- a/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php +++ b/core/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php @@ -28,4 +28,9 @@ class ResponsiveImageStyleXmlCookieTest extends ResponsiveImageStyleResourceTest */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/responsive_image/tests/src/Functional/ViewsIntegrationTest.php b/core/modules/responsive_image/tests/src/Functional/ViewsIntegrationTest.php index 33f19a5be..4a926dffd 100644 --- a/core/modules/responsive_image/tests/src/Functional/ViewsIntegrationTest.php +++ b/core/modules/responsive_image/tests/src/Functional/ViewsIntegrationTest.php @@ -34,6 +34,11 @@ class ViewsIntegrationTest extends ViewTestBase { 'responsive_image_test_module', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The test views to enable. */ diff --git a/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php b/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php index f935a4e2d..b77edd8cc 100644 --- a/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php +++ b/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php @@ -64,7 +64,7 @@ public function testEntityViewDisplayDependency() { // Check that the 'foo' field is on the display. $this->assertNotNull($display = EntityViewDisplay::load('entity_test.entity_test.default')); - $this->assertTrue($display->getComponent('bar')); + $this->assertNotEmpty($display->getComponent('bar')); $this->assertArrayNotHasKey('bar', $display->get('hidden')); // Delete the responsive image style. diff --git a/core/modules/responsive_image/tests/src/Unit/ResponsiveImageStyleConfigEntityUnitTest.php b/core/modules/responsive_image/tests/src/Unit/ResponsiveImageStyleConfigEntityUnitTest.php index f45c927e3..8091ad32f 100644 --- a/core/modules/responsive_image/tests/src/Unit/ResponsiveImageStyleConfigEntityUnitTest.php +++ b/core/modules/responsive_image/tests/src/Unit/ResponsiveImageStyleConfigEntityUnitTest.php @@ -17,21 +17,21 @@ class ResponsiveImageStyleConfigEntityUnitTest extends UnitTestCase { /** * The entity type used for testing. * - * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityType; /** * The entity type manager used for testing. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeManager; /** * The breakpoint manager used for testing. * - * @var \Drupal\breakpoint\BreakpointManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\breakpoint\BreakpointManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $breakpointManager; @@ -39,18 +39,18 @@ class ResponsiveImageStyleConfigEntityUnitTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $this->entityType = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); $this->entityType->expects($this->any()) ->method('getProvider') ->will($this->returnValue('responsive_image')); - $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class); + $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); $this->entityTypeManager->expects($this->any()) ->method('getDefinition') ->with('responsive_image_style') ->will($this->returnValue($this->entityType)); - $this->breakpointManager = $this->getMock('\Drupal\breakpoint\BreakpointManagerInterface'); + $this->breakpointManager = $this->createMock('\Drupal\breakpoint\BreakpointManagerInterface'); $container = new ContainerBuilder(); $container->set('entity_type.manager', $this->entityTypeManager); @@ -65,13 +65,13 @@ public function testCalculateDependencies() { // Set up image style loading mock. $styles = []; foreach (['fallback', 'small', 'medium', 'large'] as $style) { - $mock = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityInterface'); + $mock = $this->createMock('Drupal\Core\Config\Entity\ConfigEntityInterface'); $mock->expects($this->any()) ->method('getConfigDependencyName') ->willReturn('image.style.' . $style); $styles[$style] = $mock; } - $storage = $this->getMock('\Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); + $storage = $this->createMock('\Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); $storage->expects($this->any()) ->method('loadMultiple') ->with(array_keys($styles)) diff --git a/core/modules/rest/rest.api.php b/core/modules/rest/rest.api.php index 0890e3bbf..65dc8fa30 100644 --- a/core/modules/rest/rest.api.php +++ b/core/modules/rest/rest.api.php @@ -31,7 +31,7 @@ function hook_rest_resource_alter(&$definitions) { /** * Alter the REST type URI. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * hook_serialization_type_uri_alter() instead. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 @@ -59,7 +59,7 @@ function hook_rest_type_uri_alter(&$uri, $context = []) { /** * Alter the REST relation URI. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * hook_serialization_relation_uri_alter() instead. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/rest.info.yml b/core/modules/rest/rest.info.yml index 48602660e..8a4572ab3 100644 --- a/core/modules/rest/rest.info.yml +++ b/core/modules/rest/rest.info.yml @@ -2,13 +2,7 @@ name: 'RESTful Web Services' type: module description: 'Exposes entities and other resources as RESTful web API' package: Web services -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:serialization - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/rest/rest.install b/core/modules/rest/rest.install index 754c27f46..5fdb5cc9e 100644 --- a/core/modules/rest/rest.install +++ b/core/modules/rest/rest.install @@ -8,23 +8,6 @@ use Drupal\Core\Config\Entity\ConfigEntityType; use Drupal\Core\StringTranslation\TranslatableMarkup; -/** - * Implements hook_requirements(). - */ -function rest_requirements($phase) { - $requirements = []; - - if ($phase == 'runtime' && PHP_SAPI !== 'cli' && version_compare(PHP_VERSION, '5.6.0', '>=') && version_compare(PHP_VERSION, '7', '<') && ini_get('always_populate_raw_post_data') != -1) { - $requirements['always_populate_raw_post_data'] = [ - 'title' => t('always_populate_raw_post_data PHP setting'), - 'value' => t('Not set to -1.'), - 'severity' => REQUIREMENT_ERROR, - 'description' => t('The always_populate_raw_post_data PHP setting should be set to -1 in PHP version 5.6. Please check the PHP manual for information on how to correct this.'), - ]; - } - return $requirements; -} - /** * Install the REST config entity type and fix old settings-based config. * diff --git a/core/modules/rest/src/EventSubscriber/EntityResourcePostRouteSubscriber.php b/core/modules/rest/src/EventSubscriber/EntityResourcePostRouteSubscriber.php index 36a3f7311..80d29dc3f 100644 --- a/core/modules/rest/src/EventSubscriber/EntityResourcePostRouteSubscriber.php +++ b/core/modules/rest/src/EventSubscriber/EntityResourcePostRouteSubscriber.php @@ -15,7 +15,7 @@ class EntityResourcePostRouteSubscriber implements EventSubscriberInterface { /** * The REST resource config storage. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $resourceConfigStorage; diff --git a/core/modules/rest/src/LinkManager/ConfigurableLinkManagerInterface.php b/core/modules/rest/src/LinkManager/ConfigurableLinkManagerInterface.php index e52145919..483c01931 100644 --- a/core/modules/rest/src/LinkManager/ConfigurableLinkManagerInterface.php +++ b/core/modules/rest/src/LinkManager/ConfigurableLinkManagerInterface.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\ConfigurableLinkManagerInterface as MovedConfigurableLinkManagerInterface; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/LinkManager/LinkManager.php b/core/modules/rest/src/LinkManager/LinkManager.php index 4586c62ec..46c4c88af 100644 --- a/core/modules/rest/src/LinkManager/LinkManager.php +++ b/core/modules/rest/src/LinkManager/LinkManager.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\LinkManager as MovedLinkManager; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/LinkManager/LinkManagerBase.php b/core/modules/rest/src/LinkManager/LinkManagerBase.php index 85eb7b70a..334a6f472 100644 --- a/core/modules/rest/src/LinkManager/LinkManagerBase.php +++ b/core/modules/rest/src/LinkManager/LinkManagerBase.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\LinkManagerBase as MovedLinkManagerBase; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/LinkManager/LinkManagerInterface.php b/core/modules/rest/src/LinkManager/LinkManagerInterface.php index 9f5d2f2d2..14678d598 100644 --- a/core/modules/rest/src/LinkManager/LinkManagerInterface.php +++ b/core/modules/rest/src/LinkManager/LinkManagerInterface.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\LinkManagerInterface as MovedLinkManagerInterface; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/LinkManager/RelationLinkManager.php b/core/modules/rest/src/LinkManager/RelationLinkManager.php index 6690e2100..ac1cc1b43 100644 --- a/core/modules/rest/src/LinkManager/RelationLinkManager.php +++ b/core/modules/rest/src/LinkManager/RelationLinkManager.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\RelationLinkManager as MovedLinkRelationManager; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/LinkManager/RelationLinkManagerInterface.php b/core/modules/rest/src/LinkManager/RelationLinkManagerInterface.php index c2eec3461..969c4120a 100644 --- a/core/modules/rest/src/LinkManager/RelationLinkManagerInterface.php +++ b/core/modules/rest/src/LinkManager/RelationLinkManagerInterface.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\RelationLinkManagerInterface as MovedRelationLinkManagerInterface; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/LinkManager/TypeLinkManager.php b/core/modules/rest/src/LinkManager/TypeLinkManager.php index 6d7ba03ec..f0c6e13be 100644 --- a/core/modules/rest/src/LinkManager/TypeLinkManager.php +++ b/core/modules/rest/src/LinkManager/TypeLinkManager.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\TypeLinkManager as MovedTypeLinkManager; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/LinkManager/TypeLinkManagerInterface.php b/core/modules/rest/src/LinkManager/TypeLinkManagerInterface.php index 9a37049e2..fbb4d5239 100644 --- a/core/modules/rest/src/LinkManager/TypeLinkManagerInterface.php +++ b/core/modules/rest/src/LinkManager/TypeLinkManagerInterface.php @@ -5,7 +5,7 @@ use Drupal\hal\LinkManager\TypeLinkManagerInterface as MovedTypeLinkManagerInterface; /** - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. This has + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. This has * been moved to the hal module. This exists solely for BC. * * @see https://www.drupal.org/node/2830467 diff --git a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php index e631f5da3..919e1220d 100644 --- a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php +++ b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php @@ -2,7 +2,8 @@ namespace Drupal\rest\Plugin\Deriver; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -12,6 +13,12 @@ * @see \Drupal\rest\Plugin\rest\resource\EntityResource */ class EntityDeriver implements ContainerDeriverInterface { + use DeprecatedServicePropertyTrait; + + /** + * {@inheritdoc} + */ + protected $deprecatedProperties = ['entityManager' => 'entity.manager']; /** * List of derivative definitions. @@ -21,20 +28,20 @@ class EntityDeriver implements ContainerDeriverInterface { protected $derivatives; /** - * The entity manager. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * Constructs an EntityDeriver object. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. */ - public function __construct(EntityManagerInterface $entity_manager) { - $this->entityManager = $entity_manager; + public function __construct(EntityTypeManagerInterface $entity_type_manager) { + $this->entityTypeManager = $entity_type_manager; } /** @@ -42,7 +49,7 @@ public function __construct(EntityManagerInterface $entity_manager) { */ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( - $container->get('entity.manager') + $container->get('entity_type.manager') ); } @@ -64,7 +71,7 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition) public function getDerivativeDefinitions($base_plugin_definition) { if (!isset($this->derivatives)) { // Add in the default plugin configuration and the resource type. - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->isInternal()) { continue; } diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 988b6415a..0203835aa 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -115,6 +115,8 @@ public static function create(ContainerInterface $container, array $configuratio * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity object. + * @param \Symfony\Component\HttpFoundation\Request $request + * The incoming request. * * @return \Drupal\rest\ResourceResponse * The response containing the entity with its accessible fields. diff --git a/core/modules/rest/src/Plugin/views/display/RestExport.php b/core/modules/rest/src/Plugin/views/display/RestExport.php index 93605b8e9..51c080b93 100644 --- a/core/modules/rest/src/Plugin/views/display/RestExport.php +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php @@ -102,7 +102,7 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac * ['cookie' => 'user', 'basic_auth' => 'basic_auth'] * @endcode * - * @deprecated as of 8.4.x, will be removed in before Drupal 9.0.0, see + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. see * https://www.drupal.org/node/2825204. * * @var string[] diff --git a/core/modules/rest/src/Plugin/views/row/DataFieldRow.php b/core/modules/rest/src/Plugin/views/row/DataFieldRow.php index c511877c7..9b9e28573 100644 --- a/core/modules/rest/src/Plugin/views/row/DataFieldRow.php +++ b/core/modules/rest/src/Plugin/views/row/DataFieldRow.php @@ -141,9 +141,13 @@ public function render($row) { if (!empty($this->rawOutputOptions[$id])) { $value = $field->getValue($row); } - // Otherwise, pass this through the field advancedRender() method. + // Otherwise, get rendered field. else { - $value = $field->advancedRender($row); + // Advanced render for token replacement. + $markup = $field->advancedRender($row); + // Post render to support uncacheable fields. + $field->postRender($row, $markup); + $value = $field->last_render; } // Omit excluded fields from the rendered output. diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index 8180b70cb..7d2299a19 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -317,7 +317,7 @@ protected function createArgumentResolver(RouteMatchInterface $route_match, $uns * @param \Symfony\Component\HttpFoundation\Request $request * The request. * - * @deprecated in Drupal 8.4.0, will be removed before Drupal 9.0.0. Use the + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use the * argument resolver method instead, see ::createArgumentResolver(). * * @see https://www.drupal.org/node/2894819 diff --git a/core/modules/rest/src/RestPermissions.php b/core/modules/rest/src/RestPermissions.php index 473c1924e..7255d3f48 100644 --- a/core/modules/rest/src/RestPermissions.php +++ b/core/modules/rest/src/RestPermissions.php @@ -22,7 +22,7 @@ class RestPermissions implements ContainerInjectionInterface { /** * The REST resource config storage. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $resourceConfigStorage; diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index a763f9dfc..b97ae37fb 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -15,7 +15,7 @@ /** * Test helper class that provides a REST client method to send HTTP requests. * - * @deprecated in Drupal 8.3.x-dev and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\rest\Functional\ResourceTestBase and * \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase * instead. Only retained for contributed module tests that may be using this diff --git a/core/modules/rest/tests/modules/config_test_rest/config_test_rest.info.yml b/core/modules/rest/tests/modules/config_test_rest/config_test_rest.info.yml index 0d700c105..b3af0b663 100644 --- a/core/modules/rest/tests/modules/config_test_rest/config_test_rest.info.yml +++ b/core/modules/rest/tests/modules/config_test_rest/config_test_rest.info.yml @@ -1,13 +1,7 @@ name: 'Configuration test REST' type: module package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:config_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/rest/tests/modules/rest_test/rest_test.info.yml b/core/modules/rest/tests/modules/rest_test/rest_test.info.yml index f571ed744..82282ee53 100644 --- a/core/modules/rest/tests/modules/rest_test/rest_test.info.yml +++ b/core/modules/rest/tests/modules/rest_test/rest_test.info.yml @@ -2,11 +2,5 @@ name: 'REST test' type: module description: 'Provides test hooks and resources for REST module.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/rest/tests/modules/rest_test_views/rest_test_views.info.yml b/core/modules/rest/tests/modules/rest_test_views/rest_test_views.info.yml index 13c082ffa..005af649e 100644 --- a/core/modules/rest/tests/modules/rest_test_views/rest_test_views.info.yml +++ b/core/modules/rest/tests/modules/rest_test_views/rest_test_views.info.yml @@ -2,14 +2,8 @@ name: 'REST test views' type: module description: 'Provides default views for views REST tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:rest - drupal:views - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_excluded_field_token_display.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_excluded_field_token_display.yml index e95f95337..ba3d5457c 100644 --- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_excluded_field_token_display.yml +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_excluded_field_token_display.yml @@ -14,7 +14,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_field_counter_display.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_field_counter_display.yml new file mode 100644 index 000000000..092e00aa6 --- /dev/null +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_field_counter_display.yml @@ -0,0 +1,210 @@ +langcode: en +status: true +dependencies: + config: + - node.type.article + module: + - node + - rest + - serialization + - user +id: test_field_counter_display +label: 'Test Field Counter Display' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: serializer + options: + formats: + json: json + row: + type: data_field + options: + field_options: + title: + alias: '' + raw_output: false + fields: + counter: + id: counter + table: views + field: counter + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + counter_start: 1 + plugin_id: counter + filters: + status: + value: '1' + table: node_field_data + field: status + plugin_id: boolean + entity_type: node + entity_field: status + id: status + expose: + operator: '' + operator_limit_selection: false + operator_list: { } + group: 1 + type: + id: type + table: node_field_data + field: type + value: + article: article + entity_type: node + entity_field: type + plugin_id: bundle + expose: + operator_limit_selection: false + operator_list: { } + sorts: + nid: + id: nid + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + entity_type: node + entity_field: nid + plugin_id: standard + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - request_format + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } + rest_export_1: + display_plugin: rest_export + id: rest_export_1 + display_title: 'REST export' + position: 1 + display_options: + display_extenders: { } + path: rest/test/field-counter + pager: + type: some + options: + items_per_page: 10 + offset: 0 + defaults: + style: true + row: true + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - request_format + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml index 2a5818e80..19c090c49 100644 --- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: entity_test base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity_translated.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity_translated.yml index 55cc7bb02..3f6e6b7ea 100644 --- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity_translated.yml +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity_translated.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: entity_test_mul_property_data base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml index 2b981f0bf..0a551aada 100644 --- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: views_test_data base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_display_field.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_display_field.yml index 4a09bbd85..b3d74a499 100644 --- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_display_field.yml +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_display_field.yml @@ -16,7 +16,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_exposed_filter.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_exposed_filter.yml index 6165641ad..6a93a0918 100644 --- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_exposed_filter.yml +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_node_exposed_filter.yml @@ -16,7 +16,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_shared_path.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_shared_path.yml index bc4f6ef98..84f34f1a4 100644 --- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_shared_path.yml +++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_shared_path.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: entity_test base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php index 722319a6e..612b7f6e0 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Action/ActionResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\system\Functional\Rest\ActionResourceTestBase as ActionResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\system\Functional\Rest\ActionResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php index f3f71db92..bbec7e0a1 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/BaseFieldOverride/BaseFieldOverrideResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\FunctionalTests\Rest\BaseFieldOverrideResourceTestBase as BaseFieldOverrideResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\FunctionalTests\Rest\BaseFieldOverrideResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php index ef14fd94d..d9c5fdc57 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\block\Functional\Rest\BlockResourceTestBase as BlockResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\block\Functional\Rest\BlockResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/BlockContent/BlockContentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/BlockContent/BlockContentResourceTestBase.php index de452a87e..7c703431f 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/BlockContent/BlockContentResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/BlockContent/BlockContentResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\block_content\Functional\Rest\BlockContentResourceTestBase as BlockContentResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\block_content\Functional\Rest\BlockContentResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/BlockContentType/BlockContentTypeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/BlockContentType/BlockContentTypeResourceTestBase.php index 033221dc3..1b8f62d5f 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/BlockContentType/BlockContentTypeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/BlockContentType/BlockContentTypeResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\block_content\Functional\Rest\BlockContentTypeResourceTestBase as BlockContentTypeResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\block_content\Functional\Rest\BlockContentTypeResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php index 4b449025d..10e4bdb3e 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\comment\Functional\Rest\CommentResourceTestBase as CommentResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\comment\Functional\Rest\CommentResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/CommentType/CommentTypeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/CommentType/CommentTypeResourceTestBase.php index 11cccc144..1091d5e89 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/CommentType/CommentTypeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/CommentType/CommentTypeResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\comment\Functional\Rest\CommentTypeResourceTestBase as CommentTypeResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\comment\Functional\Rest\CommentTypeResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php index 87ca8b645..cbfc89dab 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\config_test\Functional\Rest\ConfigTestResourceTestBase as ConfigTestResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\config_test\Functional\Rest\ConfigTestResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ConfigurableLanguage/ConfigurableLanguageResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ConfigurableLanguage/ConfigurableLanguageResourceTestBase.php index da2a79ea4..5b62ba55c 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ConfigurableLanguage/ConfigurableLanguageResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ConfigurableLanguage/ConfigurableLanguageResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\language\Functional\Rest\ConfigurableLanguageResourceTestBase as ConfigurableLanguageResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\language\Functional\Rest\ConfigurableLanguageResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ContactForm/ContactFormResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ContactForm/ContactFormResourceTestBase.php index e9a1010fd..47fcedda4 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ContactForm/ContactFormResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ContactForm/ContactFormResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\contact\Functional\Rest\ContactFormResourceTestBase as ContactFormResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\contact\Functional\Rest\ContactFormResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ContentLanguageSettings/ContentLanguageSettingsResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ContentLanguageSettings/ContentLanguageSettingsResourceTestBase.php index dfe5494ba..b0199474e 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ContentLanguageSettings/ContentLanguageSettingsResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ContentLanguageSettings/ContentLanguageSettingsResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\language\Functional\Rest\ContentLanguageSettingsResourceTestBase as ContentLanguageSettingsResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\language\Functional\Rest\ContentLanguageSettingsResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/DateFormat/DateFormatResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/DateFormat/DateFormatResourceTestBase.php index 1c0522a21..f3a3df312 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/DateFormat/DateFormatResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/DateFormat/DateFormatResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\FunctionalTests\Rest\DateFormatResourceTestBase as DateFormatResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\FunctionalTests\Rest\DateFormatResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Editor/EditorResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Editor/EditorResourceTestBase.php index 9cdeab0e7..c7ca81e90 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Editor/EditorResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Editor/EditorResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase as EditorResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\editor\Functional\Rest\EditorResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php index 1862084a7..ddb5cdf01 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityFormDisplay/EntityFormDisplayResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\FunctionalTests\Rest\EntityFormDisplayResourceTestBase as EntityFormDisplayResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\FunctionalTests\Rest\EntityFormDisplayResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityFormMode/EntityFormModeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityFormMode/EntityFormModeResourceTestBase.php index 3131f6285..ab53dfa03 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityFormMode/EntityFormModeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityFormMode/EntityFormModeResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\FunctionalTests\Rest\EntityFormModeResourceTestBase as EntityFormModeResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\FunctionalTests\Rest\EntityFormModeResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceRestTestCoverageTest.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceRestTestCoverageTest.php index 0ef640bbb..8be771f31 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceRestTestCoverageTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceRestTestCoverageTest.php @@ -23,13 +23,18 @@ class EntityResourceRestTestCoverageTest extends BrowserTestBase { */ protected $definitions; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - $all_modules = system_rebuild_module_data(); + $all_modules = $this->container->get('extension.list.module')->getList(); $stable_core_modules = array_filter($all_modules, function ($module) { // Filter out contrib, hidden, testing, and experimental modules. We also // don't need to enable modules that are already enabled. diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 15a450154..da39d3b2c 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -234,7 +234,7 @@ public function setUp() { $this->entity = $reloaded_entity; // Set a default value on the fields. - $this->entity->set('field_rest_test', ['value' => 'All the faith he had had had had no effect on the outcome of his life.']); + $this->entity->set('field_rest_test', ['value' => 'All the faith they had had had had no effect on the outcome of their life.']); $this->entity->set('field_rest_test_multivalue', [['value' => 'One'], ['value' => 'Two']]); $this->entity->set('rest_test_validation', ['value' => 'allowed value']); $this->entity->save(); @@ -901,9 +901,10 @@ public function testPost() { // DX: 422 when invalid entity: multiple values sent for single-value field. $response = $this->request('POST', $url, $request_options); - $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; - $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); - $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + if ($label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName) { + $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + } $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; @@ -988,7 +989,9 @@ public function testPost() { // 500 when creating an entity with a duplicate UUID. $normalized_entity = $this->getModifiedEntityForPostTesting(); $normalized_entity[$created_entity->getEntityType()->getKey('uuid')] = [['value' => $created_entity->uuid()]]; - $normalized_entity[$label_field] = [['value' => $this->randomMachineName()]]; + if ($label_field) { + $normalized_entity[$label_field] = [['value' => $this->randomMachineName()]]; + } $request_options[RequestOptions::BODY] = $this->serializer->encode($normalized_entity, static::$format); $response = $this->request('POST', $url, $request_options); @@ -999,7 +1002,9 @@ public function testPost() { $normalized_entity = $this->getModifiedEntityForPostTesting(); $new_uuid = \Drupal::service('uuid')->generate(); $normalized_entity[$created_entity->getEntityType()->getKey('uuid')] = [['value' => $new_uuid]]; - $normalized_entity[$label_field] = [['value' => $this->randomMachineName()]]; + if ($label_field) { + $normalized_entity[$label_field] = [['value' => $this->randomMachineName()]]; + } $request_options[RequestOptions::BODY] = $this->serializer->encode($normalized_entity, static::$format); $response = $this->request('POST', $url, $request_options); @@ -1130,9 +1135,10 @@ public function testPatch() { // DX: 422 when invalid entity: multiple values sent for single-value field. $response = $this->request('PATCH', $url, $request_options); - $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; - $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); - $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + if ($label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName) { + $label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel(); + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); + } $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; @@ -1241,7 +1247,7 @@ public function testPatch() { // Ensure that fields do not get deleted if they're not present in the PATCH // request. Test this using the configurable field that we added, but which // is not sent in the PATCH request. - $this->assertSame('All the faith he had had had had no effect on the outcome of his life.', $updated_entity->get('field_rest_test')->value); + $this->assertSame('All the faith they had had had had no effect on the outcome of their life.', $updated_entity->get('field_rest_test')->value); // Multi-value field: remove item 0. Then item 1 becomes item 0. $normalization_multi_value_tests = $this->getNormalizedPatchEntity(); @@ -1499,8 +1505,9 @@ protected function makeNormalizationInvalid(array $normalization, $entity_key) { switch ($entity_key) { case 'label': // Add a second label to this entity to make it invalid. - $label_field = $entity_type->hasKey('label') ? $entity_type->getKey('label') : static::$labelFieldName; - $normalization[$label_field][1]['value'] = 'Second Title'; + if ($label_field = $entity_type->hasKey('label') ? $entity_type->getKey('label') : static::$labelFieldName) { + $normalization[$label_field][1]['value'] = 'Second Title'; + } break; case 'id': $normalization[$entity_type->getKey('id')][0]['value'] = $this->anotherEntity->id(); @@ -1538,8 +1545,13 @@ protected function assert406Response(ResponseInterface $response) { else { // This is the desired response. $this->assertSame(406, $response->getStatusCode()); - $this->stringContains('?_format=' . static::$format . '>; rel="alternate"; type="' . static::$mimeType . '"', $response->getHeader('Link')); - $this->stringContains('?_format=foobar>; rel="alternate"', $response->getHeader('Link')); + $actual_link_header = $response->getHeader('Link'); + if ($actual_link_header) { + $this->assertTrue(is_array($actual_link_header)); + $expected_type = explode(';', static::$mimeType)[0]; + $this->assertContains('?_format=' . static::$format . '>; rel="alternate"; type="' . $expected_type . '"', $actual_link_header[0]); + $this->assertContains('?_format=foobar>; rel="alternate"', $actual_link_header[0]); + } } } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php index c9dc9b915..3fcdd43f5 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\entity_test\Functional\Rest\EntityTestResourceTestBase as EntityTestResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\entity_test\Functional\Rest\EntityTestResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTestBundle/EntityTestBundleResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTestBundle/EntityTestBundleResourceTestBase.php index 12fab901c..fb19c7fbd 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTestBundle/EntityTestBundleResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTestBundle/EntityTestBundleResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\entity_test\Functional\Rest\EntityTestBundleResourceTestBase as EntityTestBundleResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\entity_test\Functional\Rest\EntityTestBundleResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTestLabel/EntityTestLabelResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTestLabel/EntityTestLabelResourceTestBase.php index 75a095081..91cd1da13 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTestLabel/EntityTestLabelResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTestLabel/EntityTestLabelResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\entity_test\Functional\Rest\EntityTestLabelResourceTestBase as EntityTestLabelResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\entity_test\Functional\Rest\EntityTestLabelResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php index 83b6be5f9..f0282fbbe 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityViewDisplay/EntityViewDisplayResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\FunctionalTests\Rest\EntityViewDisplayResourceTestBase as EntityViewDisplayResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\FunctionalTests\Rest\EntityViewDisplayResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityViewMode/EntityViewModeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityViewMode/EntityViewModeResourceTestBase.php index bdbd58bfa..6c6474117 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityViewMode/EntityViewModeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityViewMode/EntityViewModeResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\FunctionalTests\Rest\EntityViewModeResourceTestBase as EntityViewModeResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\FunctionalTests\Rest\EntityViewModeResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Feed/FeedResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Feed/FeedResourceTestBase.php index 2805b390f..540aed1bd 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Feed/FeedResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Feed/FeedResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\aggregator\Functional\Rest\FeedResourceTestBase as FeedResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\aggregator\Functional\Rest\FeedResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php index dbb398a7b..ae6a8da73 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/FieldConfig/FieldConfigResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\field\Functional\Rest\FieldConfigResourceTestBase as FieldConfigResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\field\Functional\Rest\FieldConfigResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php index 594e275e0..066da920f 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/FieldStorageConfig/FieldStorageConfigResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\field\Functional\Rest\FieldStorageConfigResourceTestBase as FieldStorageConfigResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\field\Functional\Rest\FieldStorageConfigResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/File/FileResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/File/FileResourceTestBase.php index b9c63a292..9e6872058 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/File/FileResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/File/FileResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\file\Functional\Rest\FileResourceTestBase as FileResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\file\Functional\Rest\FileResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/FilterFormat/FilterFormatResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/FilterFormat/FilterFormatResourceTestBase.php index 1ba0edcd0..4a1c5668e 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/FilterFormat/FilterFormatResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/FilterFormat/FilterFormatResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\filter\Functional\Rest\FilterFormatResourceTestBase as FilterFormatResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\filter\Functional\Rest\FilterFormatResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/FormatSpecificGetBcRouteTestTrait.php b/core/modules/rest/tests/src/Functional/EntityResource/FormatSpecificGetBcRouteTestTrait.php index 54bf816c2..9313a159b 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/FormatSpecificGetBcRouteTestTrait.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/FormatSpecificGetBcRouteTestTrait.php @@ -35,7 +35,7 @@ public function testFormatSpecificGetBcRoute() { * @see \Drupal\rest\Plugin\ResourceBase::routes */ public function testNoFormatSpecificGetBcRouteForOtherFormats() { - $this->setExpectedException(RouteNotFoundException::class); + $this->expectException(RouteNotFoundException::class); $this->provisionEntityResource(); $url = $this->getEntityResourceUrl(); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ImageStyle/ImageStyleResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ImageStyle/ImageStyleResourceTestBase.php index 07ec79bec..c96a290ce 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ImageStyle/ImageStyleResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ImageStyle/ImageStyleResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\image\Functional\Rest\ImageStyleResourceTestBase as ImageStyleResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\image\Functional\Rest\ImageStyleResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Item/ItemResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Item/ItemResourceTestBase.php index 30060562a..a1eebaca5 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Item/ItemResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Item/ItemResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\aggregator\Functional\Rest\ItemResourceTestBase as ItemResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\aggregator\Functional\Rest\ItemResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php index aadc4d612..eb8156508 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\media\Functional\Rest\MediaResourceTestBase as MediaResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\media\Functional\Rest\MediaResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/MediaType/MediaTypeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/MediaType/MediaTypeResourceTestBase.php index b542fea09..7336aa0db 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/MediaType/MediaTypeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/MediaType/MediaTypeResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\media\Functional\Rest\MediaTypeResourceTestBase as MediaTypeResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\media\Functional\Rest\MediaTypeResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Menu/MenuResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Menu/MenuResourceTestBase.php index d27866bab..62ef5e082 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Menu/MenuResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Menu/MenuResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\system\Functional\Rest\MenuResourceTestBase as MenuResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\system\Functional\Rest\MenuResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php index 327f5e524..297042949 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\menu_link_content\Functional\Rest\MenuLinkContentResourceTestBase as MenuLinkContentResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\menu_link_content\Functional\Rest\MenuLinkContentResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Message/MessageResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Message/MessageResourceTestBase.php index 2cd00a7cb..fd0db8edc 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Message/MessageResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Message/MessageResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\contact\Functional\Rest\MessageResourceTestBase as MessageResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\contact\Functional\Rest\MessageResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonAnonTest.php b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonAnonTest.php index f28788ac8..c5b18daa9 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonAnonTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonAnonTest.php @@ -21,4 +21,9 @@ class ModeratedNodeJsonAnonTest extends ModeratedNodeResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonBasicAuthTest.php b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonBasicAuthTest.php index b47a86f65..e1051e941 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonBasicAuthTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ModeratedNodeJsonBasicAuthTest extends ModeratedNodeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonCookieTest.php b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonCookieTest.php index 08fc4b5f4..ffcb5e094 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonCookieTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeJsonCookieTest.php @@ -26,4 +26,9 @@ class ModeratedNodeJsonCookieTest extends ModeratedNodeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlAnonTest.php b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlAnonTest.php index 4b91d766c..3cc3853df 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlAnonTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlAnonTest.php @@ -23,6 +23,11 @@ class ModeratedNodeXmlAnonTest extends ModeratedNodeResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlBasicAuthTest.php b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlBasicAuthTest.php index a321e03cb..9977177b7 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlBasicAuthTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ModeratedNodeXmlBasicAuthTest extends ModeratedNodeResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlCookieTest.php b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlCookieTest.php index 2014a56ff..8a2193fb9 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlCookieTest.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ModeratedNode/ModeratedNodeXmlCookieTest.php @@ -28,6 +28,11 @@ class ModeratedNodeXmlCookieTest extends ModeratedNodeResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php index f74935381..b74c3c067 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\node\Functional\Rest\NodeResourceTestBase as NodeResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\node\Functional\Rest\NodeResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/NodeType/NodeTypeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/NodeType/NodeTypeResourceTestBase.php index f68be26f2..a0a99e037 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/NodeType/NodeTypeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/NodeType/NodeTypeResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\node\Functional\Rest\NodeTypeResourceTestBase as NodeTypeResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\node\Functional\Rest\NodeTypeResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/RdfMapping/RdfMappingResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/RdfMapping/RdfMappingResourceTestBase.php index 7c2dd6d53..4ab1dae3b 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/RdfMapping/RdfMappingResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/RdfMapping/RdfMappingResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\rdf\Functional\Rest\RdfMappingResourceTestBase as RdfMappingResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\rdf\Functional\Rest\RdfMappingResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ResponsiveImageStyle/ResponsiveImageStyleResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ResponsiveImageStyle/ResponsiveImageStyleResourceTestBase.php index af1628bcf..af403212b 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ResponsiveImageStyle/ResponsiveImageStyleResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ResponsiveImageStyle/ResponsiveImageStyleResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\responsive_image\Functional\Rest\ResponsiveImageStyleResourceTestBase as ResponsiveImageStyleResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\responsive_image\Functional\Rest\ResponsiveImageStyleResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/RestResourceConfig/RestResourceConfigResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/RestResourceConfig/RestResourceConfigResourceTestBase.php index 188386fd1..c29cafc23 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/RestResourceConfig/RestResourceConfigResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/RestResourceConfig/RestResourceConfigResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\rest\Functional\Rest\RestResourceConfigResourceTestBase as RestResourceConfigResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\rest\Functional\Rest\RestResourceConfigResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php index ea708e612..38cbb942f 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Role/RoleResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\user\Functional\Rest\RoleResourceTestBase as RoleResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\user\Functional\Rest\RoleResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/SearchPage/SearchPageResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/SearchPage/SearchPageResourceTestBase.php index d862bf2e9..d01029241 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/SearchPage/SearchPageResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/SearchPage/SearchPageResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\search\Functional\Rest\SearchPageResourceTestBase as SearchPageResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\search\Functional\Rest\SearchPageResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Shortcut/ShortcutResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Shortcut/ShortcutResourceTestBase.php index 9c852fb90..18f92ecb7 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Shortcut/ShortcutResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Shortcut/ShortcutResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\shortcut\Functional\Rest\ShortcutResourceTestBase as ShortcutResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\shortcut\Functional\Rest\ShortcutResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php index 5ade571bf..3d6ae3439 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\shortcut\Functional\Rest\ShortcutSetResourceTestBase as ShortcutSetResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\shortcut\Functional\Rest\ShortcutSetResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php index 7fd07e5ec..6cbe7d9db 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\taxonomy\Functional\Rest\TermResourceTestBase as TermResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\taxonomy\Functional\Rest\TermResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Tour/TourResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Tour/TourResourceTestBase.php index 86842a9ad..61951de91 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Tour/TourResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Tour/TourResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\tour\Functional\Rest\TourResourceTestBase as TourResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\tour\Functional\Rest\TourResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php index 67ce0c1ab..6a56112ad 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\user\Functional\Rest\UserResourceTestBase as UserResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\user\Functional\Rest\UserResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php index 6b3b731f0..10632d464 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/View/ViewResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\views\Functional\Rest\ViewResourceTestBase as ViewResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\views\Functional\Rest\ViewResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php index 9571cbec2..2a0beff28 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php @@ -7,7 +7,7 @@ use Drupal\Tests\taxonomy\Functional\Rest\VocabularyResourceTestBase as VocabularyResourceTestBaseReal; /** - * @deprecated in Drupal 8.6.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\taxonomy\Functional\Rest\VocabularyResourceTestBase instead. * * @see https://www.drupal.org/node/2971931 diff --git a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php index 994a4c3cc..a09262110 100644 --- a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php @@ -310,6 +310,37 @@ public function testPostFileUploadDuplicateFile() { $this->assertSame($this->testFileData, file_get_contents('public://foobar/example_0.txt')); } + /** + * Tests using the file upload POST route twice, simulating a race condition. + * + * A validation error should occur when the filenames are not unique. + */ + public function testPostFileUploadDuplicateFileRaceCondition() { + $this->initAuthentication(); + + $this->provisionResource([static::$format], static::$auth ? [static::$auth] : [], ['POST']); + + $this->setUpAuthorization('POST'); + + $uri = Url::fromUri('base:' . static::$postUri); + + // This request will have the default 'application/octet-stream' content + // type header. + $response = $this->fileRequest($uri, $this->testFileData); + + $this->assertSame(201, $response->getStatusCode()); + + // Simulate a race condition where two files are uploaded at almost the same + // time, by removing the first uploaded file from disk (leaving the entry in + // the file_managed table) before trying to upload another file with the + // same name. + unlink(\Drupal::service('file_system')->realpath('public://foobar/example.txt')); + + // Make the same request again. The upload should fail validation. + $response = $this->fileRequest($uri, $this->testFileData); + $this->assertResourceErrorResponse(422, PlainTextOutput::renderFromHtml("Unprocessable Entity: validation failed.\nuri: The file public://foobar/example.txt already exists. Enter a unique file URI.\n"), $response); + } + /** * Tests using the file upload route with any path prefixes being stripped. * diff --git a/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonAnonTest.php b/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonAnonTest.php index 72ccd45e9..7731048a8 100644 --- a/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonAnonTest.php +++ b/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonAnonTest.php @@ -17,6 +17,11 @@ class RestResourceConfigHalJsonAnonTest extends RestResourceConfigResourceTestBa */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonBasicAuthTest.php b/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonBasicAuthTest.php index e5e9a7efb..e220693e9 100644 --- a/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonBasicAuthTest.php +++ b/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class RestResourceConfigHalJsonBasicAuthTest extends RestResourceConfigResourceT */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonCookieTest.php b/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonCookieTest.php index 606d3fd64..d224369f6 100644 --- a/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonCookieTest.php +++ b/core/modules/rest/tests/src/Functional/Hal/RestResourceConfigHalJsonCookieTest.php @@ -17,6 +17,11 @@ class RestResourceConfigHalJsonCookieTest extends RestResourceConfigResourceTest */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/ResourceTest.php b/core/modules/rest/tests/src/Functional/ResourceTest.php index 5a5691069..57a6840bf 100644 --- a/core/modules/rest/tests/src/Functional/ResourceTest.php +++ b/core/modules/rest/tests/src/Functional/ResourceTest.php @@ -25,6 +25,11 @@ class ResourceTest extends BrowserTestBase { */ public static $modules = ['hal', 'rest', 'entity_test', 'rest_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The entity. * diff --git a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonAnonTest.php b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonAnonTest.php index ce355909d..0caad8c81 100644 --- a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonAnonTest.php +++ b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonAnonTest.php @@ -21,4 +21,9 @@ class RestResourceConfigJsonAnonTest extends RestResourceConfigResourceTestBase */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonBasicAuthTest.php b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonBasicAuthTest.php index 8ac84636e..7a1f9c0a5 100644 --- a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonBasicAuthTest.php +++ b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class RestResourceConfigJsonBasicAuthTest extends RestResourceConfigResourceTest */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonCookieTest.php b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonCookieTest.php index 4971f59a4..0fee7a1c4 100644 --- a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonCookieTest.php +++ b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigJsonCookieTest.php @@ -26,4 +26,9 @@ class RestResourceConfigJsonCookieTest extends RestResourceConfigResourceTestBas */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlAnonTest.php b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlAnonTest.php index a508b5540..5c0c24e47 100644 --- a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlAnonTest.php +++ b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlAnonTest.php @@ -23,4 +23,9 @@ class RestResourceConfigXmlAnonTest extends RestResourceConfigResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlBasicAuthTest.php b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlBasicAuthTest.php index c5034b4ee..df9e46e25 100644 --- a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlBasicAuthTest.php +++ b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class RestResourceConfigXmlBasicAuthTest extends RestResourceConfigResourceTestB */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlCookieTest.php b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlCookieTest.php index 377a4497e..796d5424f 100644 --- a/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlCookieTest.php +++ b/core/modules/rest/tests/src/Functional/Rest/RestResourceConfigXmlCookieTest.php @@ -28,4 +28,9 @@ class RestResourceConfigXmlCookieTest extends RestResourceConfigResourceTestBase */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php b/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php index 739089a98..e2b322ac5 100644 --- a/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php +++ b/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\rest\Functional\Update; use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\rest\RestPermissions; /** * Tests that existing sites continue to use permissions for EntityResource. @@ -33,16 +34,17 @@ public function setDatabaseDumpFiles() { * Tests rest_update_8203(). */ public function testBcEntityResourcePermissionSettingAdded() { - $permission_handler = $this->container->get('user.permissions'); - - $is_rest_resource_permission = function ($permission) { - return $permission['provider'] === 'rest' && (string) $permission['title'] !== 'Administer REST resource configuration'; - }; - // Make sure we have the expected values before the update. $rest_settings = $this->config('rest.settings'); $this->assertFalse(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData())); - $this->assertEqual([], array_filter($permission_handler->getPermissions(), $is_rest_resource_permission)); + + // We can not use the 'user.permissions' service here because some + // permissions include generated URLs inside their description, thus + // requiring the path alias system, which is not guaranteed to be working + // before running the database updates. + $rest_permissions_callback = \Drupal::service('controller_resolver')->getControllerFromDefinition(RestPermissions::class . '::permissions'); + $rest_permissions = array_keys(call_user_func($rest_permissions_callback)); + $this->assertEquals([], $rest_permissions); $this->runUpdates(); @@ -50,8 +52,10 @@ public function testBcEntityResourcePermissionSettingAdded() { $rest_settings = $this->config('rest.settings'); $this->assertTrue(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData())); $this->assertTrue($rest_settings->get('bc_entity_resource_permissions')); - $rest_permissions = array_keys(array_filter($permission_handler->getPermissions(), $is_rest_resource_permission)); - $this->assertEqual(['restful delete entity:node', 'restful get entity:node', 'restful patch entity:node', 'restful post entity:node'], $rest_permissions); + + $rest_permissions_callback = \Drupal::service('controller_resolver')->getControllerFromDefinition(RestPermissions::class . '::permissions'); + $rest_permissions = array_keys(call_user_func($rest_permissions_callback)); + $this->assertEquals(['restful get entity:node', 'restful post entity:node', 'restful delete entity:node', 'restful patch entity:node'], $rest_permissions); } } diff --git a/core/modules/rest/tests/src/Functional/Views/ExcludedFieldTokenTest.php b/core/modules/rest/tests/src/Functional/Views/ExcludedFieldTokenTest.php index cf9913d85..22d583fde 100644 --- a/core/modules/rest/tests/src/Functional/Views/ExcludedFieldTokenTest.php +++ b/core/modules/rest/tests/src/Functional/Views/ExcludedFieldTokenTest.php @@ -28,6 +28,11 @@ class ExcludedFieldTokenTest extends ViewTestBase { */ public static $testViews = ['test_excluded_field_token_display']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The modules that need to be installed for this test. * diff --git a/core/modules/rest/tests/src/Functional/Views/FieldCounterTest.php b/core/modules/rest/tests/src/Functional/Views/FieldCounterTest.php new file mode 100644 index 000000000..ef8d0a034 --- /dev/null +++ b/core/modules/rest/tests/src/Functional/Views/FieldCounterTest.php @@ -0,0 +1,92 @@ + 'article', + 'title' => 'Article test ' . $i, + ])->save(); + } + + $this->enableViewsTestModule(); + + $this->view = Views::getView('test_field_counter_display'); + $this->view->setDisplay('rest_export_1'); + } + + /** + * Tests the display of an excluded title field when used as a token. + */ + public function testExcludedTitleTokenDisplay() { + $actual_json = $this->drupalGet($this->view->getPath(), ['query' => ['_format' => 'json']]); + $this->assertResponse(200); + + $expected = [ + ['counter' => '1'], + ['counter' => '2'], + ['counter' => '3'], + ['counter' => '4'], + ['counter' => '5'], + ['counter' => '6'], + ['counter' => '7'], + ['counter' => '8'], + ['counter' => '9'], + ['counter' => '10'], + ]; + $this->assertIdentical($actual_json, json_encode($expected)); + } + +} diff --git a/core/modules/rest/tests/src/Functional/Views/RestExportAuthTest.php b/core/modules/rest/tests/src/Functional/Views/RestExportAuthTest.php index 24546089c..4b6922601 100644 --- a/core/modules/rest/tests/src/Functional/Views/RestExportAuthTest.php +++ b/core/modules/rest/tests/src/Functional/Views/RestExportAuthTest.php @@ -17,6 +17,11 @@ class RestExportAuthTest extends ViewTestBase { */ public static $modules = ['rest', 'views_ui', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php b/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php index 5a039eb0a..094d8797a 100644 --- a/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php +++ b/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php @@ -38,6 +38,11 @@ class StyleSerializerTest extends ViewTestBase { */ public static $modules = ['views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Views used by this test. * @@ -50,6 +55,13 @@ class StyleSerializerTest extends ViewTestBase { */ protected $adminUser; + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + protected function setUp($import_test_views = TRUE) { parent::setUp($import_test_views); @@ -63,6 +75,7 @@ protected function setUp($import_test_views = TRUE) { } $this->enableViewsTestModule(); + $this->renderer = \Drupal::service('renderer'); } /** @@ -113,7 +126,7 @@ public function testSerializerResponses() { // propagation of cache max-age. // Test the http Content-type. - $headers = $this->drupalGetHeaders(); + $headers = $this->getSession()->getResponseHeaders(); $this->assertSame(['application/json'], $headers['Content-Type']); $expected = []; @@ -133,7 +146,7 @@ public function testSerializerResponses() { // Mock the request content type by setting it on the display handler. $view->display_handler->setContentType('json'); $output = $view->preview(); - $this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.'); + $this->assertIdentical($actual_json, (string) $this->renderer->renderRoot($output), 'The expected JSON preview output was found.'); // Test a 403 callback. $this->drupalGet('test/serialize/denied', ['query' => ['_format' => 'json']]); @@ -314,14 +327,14 @@ public function testRestRenderCaching() { $this->assertHeader('content-type', 'application/json'); $this->assertCacheContexts($cache_contexts); $this->assertCacheTags($cache_tags); - $this->assertTrue($render_cache->get($original)); + $this->assertNotEmpty($render_cache->get($original)); $result_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]); $this->addRequestWithFormat('xml'); $this->assertHeader('content-type', 'text/xml; charset=UTF-8'); $this->assertCacheContexts($cache_contexts); $this->assertCacheTags($cache_tags); - $this->assertTrue($render_cache->get($original)); + $this->assertNotEmpty($render_cache->get($original)); // Ensure that the XML output is different from the JSON one. $this->assertNotEqual($result1, $result_xml); @@ -333,7 +346,7 @@ public function testRestRenderCaching() { $this->assertEqual($result2, $result1); $this->assertCacheContexts($cache_contexts); $this->assertCacheTags($cache_tags); - $this->assertTrue($render_cache->get($original)); + $this->assertNotEmpty($render_cache->get($original)); // Create a new entity and ensure that the cache tags are taken over. EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save(); @@ -349,7 +362,7 @@ public function testRestRenderCaching() { $this->assertCacheContexts($cache_contexts); $this->assertCacheTags($cache_tags); - $this->assertTrue($render_cache->get($original)); + $this->assertNotEmpty($render_cache->get($original)); } /** @@ -694,8 +707,6 @@ public function testFieldapiField() { * Tests the "Grouped rows" functionality. */ public function testGroupRows() { - /** @var \Drupal\Core\Render\RendererInterface $renderer */ - $renderer = $this->container->get('renderer'); $this->drupalCreateContentType(['type' => 'page']); // Create a text field with cardinality set to unlimited. $field_name = 'field_group_rows'; @@ -738,7 +749,7 @@ public function testGroupRows() { // Check if the field_group_rows field is grouped. $expected = []; $expected[] = [$field_name => implode(', ', $grouped_field_values)]; - $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build)); + $this->assertEqual($serializer->serialize($expected, 'json'), (string) $this->renderer->renderRoot($build)); // Set the group rows setting to false. $view = Views::getView('test_serializer_node_display_field'); $view->setDisplay('rest_export_1'); @@ -750,7 +761,7 @@ public function testGroupRows() { foreach ($grouped_field_values as $grouped_field_value) { $expected[] = [$field_name => $grouped_field_value]; } - $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build)); + $this->assertEqual($serializer->serialize($expected, 'json'), (string) $this->renderer->renderRoot($build)); } /** diff --git a/core/modules/rest/tests/src/Kernel/Views/RestExportTest.php b/core/modules/rest/tests/src/Kernel/Views/RestExportTest.php index f3a8f04f5..5246e484a 100644 --- a/core/modules/rest/tests/src/Kernel/Views/RestExportTest.php +++ b/core/modules/rest/tests/src/Kernel/Views/RestExportTest.php @@ -51,7 +51,7 @@ public function testBuildResponse() { // No custom header should be set yet. $response = RestExport::buildResponse('test_serializer_display_entity', 'rest_export_1', []); - $this->assertFalse($response->headers->get('Custom-Header')); + $this->assertEmpty($response->headers->get('Custom-Header')); // Clear render cache. /** @var \Drupal\Core\Cache\MemoryBackend $render_cache */ diff --git a/core/modules/rest/tests/src/Unit/CollectRoutesTest.php b/core/modules/rest/tests/src/Unit/CollectRoutesTest.php index e171e047c..7e456130c 100644 --- a/core/modules/rest/tests/src/Unit/CollectRoutesTest.php +++ b/core/modules/rest/tests/src/Unit/CollectRoutesTest.php @@ -39,12 +39,15 @@ protected function setUp() { ->disableOriginalConstructor() ->getMock(); - $this->view = $this->getMock('\Drupal\views\Entity\View', ['initHandlers'], [ - ['id' => 'test_view'], - 'view', - ]); + $this->view = $this->getMockBuilder('\Drupal\views\Entity\View') + ->setMethods(['initHandlers']) + ->setConstructorArgs([['id' => 'test_view'], 'view']) + ->getMock(); - $view_executable = $this->getMock('\Drupal\views\ViewExecutable', ['initHandlers', 'getTitle'], [], '', FALSE); + $view_executable = $this->getMockBuilder('\Drupal\views\ViewExecutable') + ->setMethods(['initHandlers', 'getTitle']) + ->disableOriginalConstructor() + ->getMock(); $view_executable->expects($this->any()) ->method('getTitle') ->willReturn('View title'); @@ -69,16 +72,16 @@ protected function setUp() { $container->setParameter('authentication_providers', ['basic_auth' => 'basic_auth']); - $state = $this->getMock('\Drupal\Core\State\StateInterface'); + $state = $this->createMock('\Drupal\Core\State\StateInterface'); $container->set('state', $state); $style_manager = $this->getMockBuilder('\Drupal\views\Plugin\ViewsPluginManager') ->disableOriginalConstructor() ->getMock(); $container->set('plugin.manager.views.style', $style_manager); - $container->set('renderer', $this->getMock('Drupal\Core\Render\RendererInterface')); + $container->set('renderer', $this->createMock('Drupal\Core\Render\RendererInterface')); - $authentication_collector = $this->getMock('\Drupal\Core\Authentication\AuthenticationCollectorInterface'); + $authentication_collector = $this->createMock('\Drupal\Core\Authentication\AuthenticationCollectorInterface'); $container->set('authentication_collector', $authentication_collector); $authentication_collector->expects($this->any()) ->method('getSortedProviders') @@ -112,7 +115,10 @@ protected function setUp() { ->method('createInstance') ->will($this->returnValue($none)); - $style_plugin = $this->getMock('\Drupal\rest\Plugin\views\style\Serializer', ['getFormats', 'init'], [], '', FALSE); + $style_plugin = $this->getMockBuilder('\Drupal\rest\Plugin\views\style\Serializer') + ->setMethods(['getFormats', 'init']) + ->disableOriginalConstructor() + ->getMock(); $style_plugin->expects($this->once()) ->method('getFormats') diff --git a/core/modules/rest/tests/src/Unit/EntityResourceValidationTraitTest.php b/core/modules/rest/tests/src/Unit/EntityResourceValidationTraitTest.php index 8c9758e8a..972b00c39 100644 --- a/core/modules/rest/tests/src/Unit/EntityResourceValidationTraitTest.php +++ b/core/modules/rest/tests/src/Unit/EntityResourceValidationTraitTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\EntityConstraintViolationList; use Drupal\node\Entity\Node; +use Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait; use Drupal\Tests\UnitTestCase; use Drupal\user\Entity\User; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; @@ -19,7 +20,7 @@ class EntityResourceValidationTraitTest extends UnitTestCase { * @covers ::validate */ public function testValidate() { - $trait = $this->getMockForTrait('Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait'); + $trait = new EntityResourceValidationTraitTestClass(); $method = new \ReflectionMethod($trait, 'validate'); $method->setAccessible(TRUE); @@ -59,14 +60,26 @@ public function testFailedValidate() { $entity->validate()->willReturn($violations); - $trait = $this->getMockForTrait('Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait'); + $trait = new EntityResourceValidationTraitTestClass(); $method = new \ReflectionMethod($trait, 'validate'); $method->setAccessible(TRUE); - $this->setExpectedException(UnprocessableEntityHttpException::class); + $this->expectException(UnprocessableEntityHttpException::class); $method->invoke($trait, $entity->reveal()); } } + +/** + * A test class to use to test EntityResourceValidationTrait. + * + * Using ->getMockForTrait is problematic, as this trait is marked internal. + * Because the mock doesn't use the \Drupal namespace, the Symfony 4+ class + * loader will throw a deprecation error. + */ +class EntityResourceValidationTraitTestClass { + use EntityResourceValidationTrait; + +} diff --git a/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php b/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php index f9753b734..2670b2ab8 100644 --- a/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php +++ b/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php @@ -18,14 +18,14 @@ class SerializerTest extends UnitTestCase { /** * The View instance. * - * @var \Drupal\views\ViewExecutable|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\views\ViewExecutable|\PHPUnit\Framework\MockObject\MockObject */ protected $view; /** * The RestExport display handler. * - * @var \Drupal\rest\Plugin\views\display\RestExport|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\rest\Plugin\views\display\RestExport|\PHPUnit\Framework\MockObject\MockObject */ protected $displayHandler; diff --git a/core/modules/search/config/schema/search.schema.yml b/core/modules/search/config/schema/search.schema.yml index 2ed4c09ba..64d75b8db 100644 --- a/core/modules/search/config/schema/search.schema.yml +++ b/core/modules/search/config/schema/search.schema.yml @@ -88,3 +88,11 @@ search.page.*: label: 'Plugin' configuration: type: search.plugin.[%parent.plugin] + +block.settings.search_form_block: + type: block_settings + label: 'Search block' + mapping: + page_id: + type: string + label: 'Search page' diff --git a/core/modules/search/migrations/state/search.migrate_drupal.yml b/core/modules/search/migrations/state/search.migrate_drupal.yml new file mode 100644 index 000000000..6011126e1 --- /dev/null +++ b/core/modules/search/migrations/state/search.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + search: search + 7: + search: search diff --git a/core/modules/search/search.info.yml b/core/modules/search/search.info.yml index 5c7c84805..97b7d3fe7 100644 --- a/core/modules/search/search.info.yml +++ b/core/modules/search/search.info.yml @@ -2,12 +2,6 @@ name: Search type: module description: 'Enables site-wide keyword searching.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: entity.search_page.collection - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/search/search.module b/core/modules/search/search.module index dc8add6a1..3afc24bd2 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -5,13 +5,11 @@ * Enables site-wide keyword searching. */ -use Drupal\Core\Url; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; -use Drupal\Core\Cache\Cache; -use Drupal\Core\Database\Query\Condition; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Url; /** * Matches all 'N' Unicode character classes (numbers) @@ -133,41 +131,22 @@ function search_preprocess_block(&$variables) { * (optional) The plugin ID or other machine-readable type for the items to * remove from the search index. If omitted, $sid and $langcode are ignored * and the entire search index is cleared. - * @param string|null $sid - * (optional) The ID of the items to remove from the search index. If - * omitted, all items matching $type are cleared, and $langcode is ignored. + * @param int|array|null $sid + * (optional) The ID or array of IDs of the items to remove from the search + * index. If omitted, all items matching $type are cleared, and $langcode + * is ignored. * @param string|null $langcode * (optional) Language code of the item to remove from the search index. If * omitted, all items matching $sid and $type are cleared. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\search\SearchIndex::clear() instead. + * + * @see https://www.drupal.org/node/3075696 */ function search_index_clear($type = NULL, $sid = NULL, $langcode = NULL) { - $connection = \Drupal::database(); - $query_index = $connection->delete('search_index'); - $query_dataset = $connection->delete('search_dataset'); - if ($type) { - $query_index->condition('type', $type); - $query_dataset->condition('type', $type); - if ($sid) { - $query_index->condition('sid', $sid); - $query_dataset->condition('sid', $sid); - if ($langcode) { - $query_index->condition('langcode', $langcode); - $query_dataset->condition('langcode', $langcode); - } - } - } - - $query_index->execute(); - $query_dataset->execute(); - - if ($type) { - // Invalidate all render cache items that contain data from this index. - Cache::invalidateTags(['search_index:' . $type]); - } - else { - // Invalidate all render cache items that contain data from any index. - Cache::invalidateTags(['search_index']); - } + @trigger_error("search_index_clear() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::clear() instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED); + \Drupal::service('search.index')->clear($type, $sid, $langcode); } /** @@ -175,14 +154,17 @@ function search_index_clear($type = NULL, $sid = NULL, $langcode = NULL) { * * This is used during indexing (cron). Words that are dirty have outdated * total counts in the search_total table, and need to be recounted. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use custom + * implementation of \Drupal\search\SearchIndexInterface instead. + * + * @see https://www.drupal.org/node/3075696 */ function search_dirty($word = NULL) { - $dirty = &drupal_static(__FUNCTION__, []); - if ($word !== NULL) { - $dirty[$word] = TRUE; - } - else { - return $dirty; + @trigger_error("search_dirty() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED); + // Keep return result type for backward compatibility. + if ($word === NULL) { + return []; } } @@ -191,14 +173,8 @@ function search_dirty($word = NULL) { * * Fires updateIndex() in the plugins for all indexable active search pages, * and cleans up dirty words. - * - * @see search_dirty() */ function search_cron() { - // We register a shutdown function to ensure that search_total is always up - // to date. - drupal_register_shutdown_function('search_update_totals'); - /** @var $search_page_repository \Drupal\search\SearchPageRepositoryInterface */ $search_page_repository = \Drupal::service('search.search_page_repository'); foreach ($search_page_repository->getIndexableSearchPages() as $entity) { @@ -211,33 +187,14 @@ function search_cron() { * * This function is called on shutdown to ensure that {search_total} is always * up to date (even if cron times out or otherwise fails). + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use custom + * implementation of \Drupal\search\SearchIndexInterface instead. + * + * @see https://www.drupal.org/node/3075696 */ function search_update_totals() { - $connection = \Drupal::database(); - // Update word IDF (Inverse Document Frequency) counts for new/changed words. - foreach (search_dirty() as $word => $dummy) { - // Get total count - $total = db_query("SELECT SUM(score) FROM {search_index} WHERE word = :word", [':word' => $word], ['target' => 'replica'])->fetchField(); - // Apply Zipf's law to equalize the probability distribution. - $total = log10(1 + 1 / (max(1, $total))); - $connection->merge('search_total') - ->key('word', $word) - ->fields(['count' => $total]) - ->execute(); - } - // Find words that were deleted from search_index, but are still in - // search_total. We use a LEFT JOIN between the two tables and keep only the - // rows which fail to join. - $result = db_query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL", [], ['target' => 'replica']); - $or = new Condition('OR'); - foreach ($result as $word) { - $or->condition('word', $word->realword); - } - if (count($or) > 0) { - $connection->delete('search_total') - ->condition($or) - ->execute(); - } + @trigger_error("search_update_totals() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED); } /** @@ -437,137 +394,15 @@ function search_invoke_preprocess(&$text, $langcode = NULL) { * The content of this item. Must be a piece of HTML or plain text. * * @ingroup search + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\search\SearchIndex::index() instead. + * + * @see https://www.drupal.org/node/3075696 */ function search_index($type, $sid, $langcode, $text) { - $minimum_word_size = \Drupal::config('search.settings')->get('index.minimum_word_size'); - - // Multipliers for scores of words inside certain HTML tags. The weights are - // stored in config so that modules can overwrite the default weights. - // Note: 'a' must be included for link ranking to work. - $tags = \Drupal::config('search.settings')->get('index.tag_weights'); - - // Strip off all ignored tags to speed up processing, but insert space before - // and after them to keep word boundaries. - $text = str_replace(['<', '>'], [' <', '> '], $text); - $text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>'); - - // Split HTML tags from plain text. - $split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); - // Note: PHP ensures the array consists of alternating delimiters and literals - // and begins and ends with a literal (inserting $null as required). - - // Odd/even counter. Tag or no tag. - $tag = FALSE; - // Starting score per word. - $score = 1; - // Accumulator for cleaned up data. - $accum = ' '; - // Stack with open tags. - $tagstack = []; - // Counter for consecutive words. - $tagwords = 0; - // Focus state. - $focus = 1; - - // Accumulator for words for index. - $scored_words = []; - - foreach ($split as $value) { - if ($tag) { - // Increase or decrease score per word based on tag - list($tagname) = explode(' ', $value, 2); - $tagname = mb_strtolower($tagname); - // Closing or opening tag? - if ($tagname[0] == '/') { - $tagname = substr($tagname, 1); - // If we encounter unexpected tags, reset score to avoid incorrect boosting. - if (!count($tagstack) || $tagstack[0] != $tagname) { - $tagstack = []; - $score = 1; - } - else { - // Remove from tag stack and decrement score - $score = max(1, $score - $tags[array_shift($tagstack)]); - } - } - else { - if (isset($tagstack[0]) && $tagstack[0] == $tagname) { - // None of the tags we look for make sense when nested identically. - // If they are, it's probably broken HTML. - $tagstack = []; - $score = 1; - } - else { - // Add to open tag stack and increment score - array_unshift($tagstack, $tagname); - $score += $tags[$tagname]; - } - } - // A tag change occurred, reset counter. - $tagwords = 0; - } - else { - // Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty values - if ($value != '') { - $words = search_index_split($value, $langcode); - foreach ($words as $word) { - // Add word to accumulator - $accum .= $word . ' '; - // Check word length. - if (is_numeric($word) || mb_strlen($word) >= $minimum_word_size) { - if (!isset($scored_words[$word])) { - $scored_words[$word] = 0; - } - $scored_words[$word] += $score * $focus; - // Focus is a decaying value in terms of the amount of unique words up to this point. - // From 100 words and more, it decays, to e.g. 0.5 at 500 words and 0.3 at 1000 words. - $focus = min(1, .01 + 3.5 / (2 + count($scored_words) * .015)); - } - $tagwords++; - // Too many words inside a single tag probably mean a tag was accidentally left open. - if (count($tagstack) && $tagwords >= 15) { - $tagstack = []; - $score = 1; - } - } - } - } - $tag = !$tag; - } - - // Remove the item $sid from the search index, and invalidate the relevant - // cache tags. - search_index_clear($type, $sid, $langcode); - - $connection = \Drupal::database(); - // Insert cleaned up data into dataset - $connection->insert('search_dataset') - ->fields([ - 'sid' => $sid, - 'langcode' => $langcode, - 'type' => $type, - 'data' => $accum, - 'reindex' => 0, - ]) - ->execute(); - - // Insert results into search index - foreach ($scored_words as $word => $score) { - // If a word already exists in the database, its score gets increased - // appropriately. If not, we create a new record with the appropriate - // starting score. - $connection->merge('search_index') - ->keys([ - 'word' => $word, - 'sid' => $sid, - 'langcode' => $langcode, - 'type' => $type, - ]) - ->fields(['score' => $score]) - ->expression('score', 'score + :score', [':score' => $score]) - ->execute(); - search_dirty($word); - } + @trigger_error("search_index() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::index() instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED); + \Drupal::service('search.index')->index($type, $sid, $langcode, $text); } /** @@ -587,25 +422,15 @@ function search_index($type, $sid, $langcode, $text) { * @param string $langcode * (optional) The language code to clear. If omitted, everything matching * $type and $sid is marked. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\search\SearchIndex::markForReindex() instead. + * + * @see https://www.drupal.org/node/3075696 */ function search_mark_for_reindex($type = NULL, $sid = NULL, $langcode = NULL) { - $query = \Drupal::database()->update('search_dataset') - ->fields(['reindex' => REQUEST_TIME]) - // Only mark items that were not previously marked for reindex, so that - // marked items maintain their priority by request time. - ->condition('reindex', 0); - - if ($type) { - $query->condition('type', $type); - if ($sid) { - $query->condition('sid', $sid); - if ($langcode) { - $query->condition('langcode', $langcode); - } - } - } - - $query->execute(); + @trigger_error("search_mark_for_reindex() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::markForReindex() instead. See https://www.drupal.org/node/3075696", E_USER_DEPRECATED); + \Drupal::service('search.index')->markForReindex($type, $sid, $langcode); } /** diff --git a/core/modules/search/search.post_update.php b/core/modules/search/search.post_update.php new file mode 100644 index 000000000..a6b1fd968 --- /dev/null +++ b/core/modules/search/search.post_update.php @@ -0,0 +1,24 @@ +moduleExists('block')) { + // Early exit when block module disabled. + return; + } + \Drupal::classResolver(ConfigEntityUpdater::class) + ->update($sandbox, 'block', function (BlockInterface $block) { + // Save search block to set default search page from plugin. + return $block->getPluginId() === 'search_form_block'; + }); +} diff --git a/core/modules/search/search.services.yml b/core/modules/search/search.services.yml index 80dfd8fce..49ec2acb7 100644 --- a/core/modules/search/search.services.yml +++ b/core/modules/search/search.services.yml @@ -6,3 +6,7 @@ services: search.search_page_repository: class: Drupal\search\SearchPageRepository arguments: ['@config.factory', '@entity_type.manager'] + + search.index: + class: Drupal\search\SearchIndex + arguments: ['@config.factory', '@database','@database.replica', '@cache_tags.invalidator'] diff --git a/core/modules/search/src/Exception/SearchIndexException.php b/core/modules/search/src/Exception/SearchIndexException.php new file mode 100644 index 000000000..35d9ae9bf --- /dev/null +++ b/core/modules/search/src/Exception/SearchIndexException.php @@ -0,0 +1,8 @@ +searchPageRepository->getDefaultSearchPage(); - - // SearchPageRepository::getDefaultSearchPage() depends on search.settings. - // The dependency needs to be added before the conditional return, otherwise - // the block would get cached without the necessary cacheablity metadata in - // case there is no default search page and would not be invalidated if that - // changes. - $this->renderer->addCacheableDependency($form, $this->configFactory->get('search.settings')); + if (!$entity_id) { + $entity_id = $this->searchPageRepository->getDefaultSearchPage(); + // SearchPageRepository::getDefaultSearchPage() depends on + // search.settings. The dependency needs to be added before the + // conditional return, otherwise the block would get cached without the + // necessary cacheability metadata in case there is no default search page + // and would not be invalidated if that changes. + $this->renderer->addCacheableDependency($form, $this->configFactory->get('search.settings')); + } if (!$entity_id) { $form['message'] = [ @@ -93,7 +95,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { } $route = 'search.view_' . $entity_id; - $form['#action'] = $this->url($route); + $form['#action'] = Url::fromRoute($route)->toString(); $form['#method'] = 'get'; $form['keys'] = [ diff --git a/core/modules/search/src/Plugin/Block/SearchBlock.php b/core/modules/search/src/Plugin/Block/SearchBlock.php index d956d7ae3..d1a1f249f 100644 --- a/core/modules/search/src/Plugin/Block/SearchBlock.php +++ b/core/modules/search/src/Plugin/Block/SearchBlock.php @@ -3,8 +3,14 @@ namespace Drupal\search\Plugin\Block; use Drupal\Core\Access\AccessResult; -use Drupal\Core\Session\AccountInterface; use Drupal\Core\Block\BlockBase; +use Drupal\Core\Form\FormBuilderInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\search\Form\SearchBlockForm; +use Drupal\search\SearchPageRepositoryInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a 'Search form' block. @@ -15,7 +21,51 @@ * category = @Translation("Forms") * ) */ -class SearchBlock extends BlockBase { +class SearchBlock extends BlockBase implements ContainerFactoryPluginInterface { + + /** + * The form builder. + * + * @var \Drupal\Core\Form\FormBuilderInterface + */ + protected $formBuilder; + + /** + * The search page repository. + * + * @var \Drupal\search\SearchPageRepositoryInterface + */ + protected $searchPageRepository; + + /** + * Constructs a new SearchLocalTask. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Form\FormBuilderInterface $form_builder + * The form builder. + * @param \Drupal\search\SearchPageRepositoryInterface $search_page_repository + * The search page repository. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, FormBuilderInterface $form_builder, SearchPageRepositoryInterface $search_page_repository) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->formBuilder = $form_builder; + $this->searchPageRepository = $search_page_repository; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, + $container->get('form_builder'), + $container->get('search.search_page_repository') + ); + } /** * {@inheritdoc} @@ -28,7 +78,49 @@ protected function blockAccess(AccountInterface $account) { * {@inheritdoc} */ public function build() { - return \Drupal::formBuilder()->getForm('Drupal\search\Form\SearchBlockForm'); + $page = $this->configuration['page_id'] ?? NULL; + return $this->formBuilder->getForm(SearchBlockForm::class, $page); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'page_id' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + // The configuration for this block is which search page to connect the + // form to. Options are all configured/active search pages. + $options = []; + $active_search_pages = $this->searchPageRepository->getActiveSearchPages(); + foreach ($this->searchPageRepository->sortSearchPages($active_search_pages) as $entity_id => $entity) { + $options[$entity_id] = $entity->label(); + } + + $form['page_id'] = [ + '#type' => 'select', + '#title' => $this->t('Search page'), + '#description' => $this->t('The search page that the form submits to, or Default for the default search page.'), + '#default_value' => $this->configuration['page_id'], + '#options' => $options, + '#empty_option' => $this->t('Default'), + '#empty_value' => '', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + $this->configuration['page_id'] = $form_state->getValue('page_id'); } } diff --git a/core/modules/search/src/Plugin/SearchIndexingInterface.php b/core/modules/search/src/Plugin/SearchIndexingInterface.php index b35bdbf02..7b92f39c7 100644 --- a/core/modules/search/src/Plugin/SearchIndexingInterface.php +++ b/core/modules/search/src/Plugin/SearchIndexingInterface.php @@ -30,8 +30,8 @@ interface SearchIndexingInterface { * This method is called every cron run if the plugin has been set as * an active search module on the Search settings page * (admin/config/search/pages). It allows your module to add items to the - * built-in search index using search_index(), or to add them to your module's - * own indexing mechanism. + * built-in search index by calling the index() method on the search.index + * service class, or to add them to your module's own indexing mechanism. * * When implementing this method, your module should index content items that * were modified or added since the last run. There is a time limit for cron, @@ -49,10 +49,10 @@ public function updateIndex(); * * When a request is made to clear all items from the search index related to * this plugin, this method will be called. If this plugin uses the default - * search index, this method can call search_index_clear($type) to remove - * indexed items from the search database. + * search index, this method can call clear($type) method on the search.index + * service class to remove indexed items from the search database. * - * @see search_index_clear() + * @see \Drupal\search\SearchIndexInterface::clear() */ public function indexClear(); @@ -61,11 +61,11 @@ public function indexClear(); * * When a request is made to mark all items from the search index related to * this plugin for reindexing, this method will be called. If this plugin uses - * the default search index, this method can call - * search_mark_for_reindex($type) to mark the items in the search database for - * reindexing. + * the default search index, this method can call markForReindex($type) method + * on the search.index service class to mark the items in the search database + * for reindexing. * - * @see search_mark_for_reindex() + * @see \Drupal\search\SearchIndexInterface::markForReindex() */ public function markForReindex(); diff --git a/core/modules/search/src/Plugin/SearchInterface.php b/core/modules/search/src/Plugin/SearchInterface.php index 8f95ce824..09d1fff41 100644 --- a/core/modules/search/src/Plugin/SearchInterface.php +++ b/core/modules/search/src/Plugin/SearchInterface.php @@ -21,7 +21,7 @@ interface SearchInterface extends PluginInspectionInterface { * @param array $attributes * Array of attributes, usually from the current request object. * - * @return \Drupal\search\Plugin\SearchInterface + * @return $this * A search plugin object for chaining. */ public function setSearch($keywords, array $parameters, array $attributes); @@ -66,8 +66,8 @@ public function isSearchExecutable(); * The type used by this search plugin in the search index, or NULL if this * plugin does not use the search index. * - * @see search_index() - * @see search_index_clear() + * @see \Drupal\search\SearchIndexInterface::index() + * @see \Drupal\search\SearchIndexInterface::clear() */ public function getType(); diff --git a/core/modules/search/src/Plugin/migrate/destination/EntitySearchPage.php b/core/modules/search/src/Plugin/migrate/destination/EntitySearchPage.php index cc95ae069..75d74ac31 100644 --- a/core/modules/search/src/Plugin/migrate/destination/EntitySearchPage.php +++ b/core/modules/search/src/Plugin/migrate/destination/EntitySearchPage.php @@ -68,7 +68,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('entity.manager')->getStorage($entity_type_id), + $container->get('entity_type.manager')->getStorage($entity_type_id), array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id)), $container->get('language_manager'), $container->get('config.factory'), diff --git a/core/modules/search/src/Plugin/migrate/process/d6/SearchConfigurationRankings.php b/core/modules/search/src/Plugin/migrate/process/d6/SearchConfigurationRankings.php index 402d74f98..b2f98e7a8 100644 --- a/core/modules/search/src/Plugin/migrate/process/d6/SearchConfigurationRankings.php +++ b/core/modules/search/src/Plugin/migrate/process/d6/SearchConfigurationRankings.php @@ -7,7 +7,7 @@ /** * Generate configuration rankings. * - * @deprecated in Drupal 8.7.x and will be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Use * \Drupal\search\Plugin\migrate\process\SearchConfigurationRankings instead. * * @MigrateProcessPlugin( diff --git a/core/modules/search/src/SearchIndex.php b/core/modules/search/src/SearchIndex.php new file mode 100644 index 000000000..e5655d497 --- /dev/null +++ b/core/modules/search/src/SearchIndex.php @@ -0,0 +1,317 @@ +configFactory = $config_factory; + $this->connection = $connection; + $this->replica = $replica; + $this->cacheTagsInvalidator = $cache_tags_invalidator; + } + + /** + * {@inheritdoc} + */ + public function index($type, $sid, $langcode, $text, $update_weights = TRUE) { + $settings = $this->configFactory->get('search.settings'); + $minimum_word_size = $settings->get('index.minimum_word_size'); + + // Keep track of the words that need to have their weights updated. + $current_words = []; + + // Multipliers for scores of words inside certain HTML tags. The weights are + // stored in config so that modules can overwrite the default weights. + // Note: 'a' must be included for link ranking to work. + $tags = $settings->get('index.tag_weights'); + + // Strip off all ignored tags to speed up processing, but insert space + // before and after them to keep word boundaries. + $text = str_replace(['<', '>'], [' <', '> '], $text); + $text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>'); + + // Split HTML tags from plain text. + $split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + // Note: PHP ensures the array consists of alternating delimiters and + // literals and begins and ends with a literal (inserting $null as + // required). + // Odd/even counter. Tag or no tag. + $tag = FALSE; + // Starting score per word. + $score = 1; + // Accumulator for cleaned up data. + $accum = ' '; + // Stack with open tags. + $tagstack = []; + // Counter for consecutive words. + $tagwords = 0; + // Focus state. + $focus = 1; + + // Accumulator for words for index. + $scored_words = []; + + foreach ($split as $value) { + if ($tag) { + // Increase or decrease score per word based on tag. + list($tagname) = explode(' ', $value, 2); + $tagname = mb_strtolower($tagname); + // Closing or opening tag? + if ($tagname[0] == '/') { + $tagname = substr($tagname, 1); + // If we encounter unexpected tags, reset score to avoid incorrect + // boosting. + if (!count($tagstack) || $tagstack[0] != $tagname) { + $tagstack = []; + $score = 1; + } + else { + // Remove from tag stack and decrement score. + $score = max(1, $score - $tags[array_shift($tagstack)]); + } + } + else { + if (isset($tagstack[0]) && $tagstack[0] == $tagname) { + // None of the tags we look for make sense when nested identically. + // If they are, it's probably broken HTML. + $tagstack = []; + $score = 1; + } + else { + // Add to open tag stack and increment score. + array_unshift($tagstack, $tagname); + $score += $tags[$tagname]; + } + } + // A tag change occurred, reset counter. + $tagwords = 0; + } + else { + // Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty + // values. + if ($value != '') { + $words = search_index_split($value, $langcode); + foreach ($words as $word) { + // Add word to accumulator. + $accum .= $word . ' '; + // Check word length. + if (is_numeric($word) || mb_strlen($word) >= $minimum_word_size) { + if (!isset($scored_words[$word])) { + $scored_words[$word] = 0; + } + $scored_words[$word] += $score * $focus; + // Focus is a decaying value in terms of the amount of unique + // words up to this point. From 100 words and more, it decays, to + // e.g. 0.5 at 500 words and 0.3 at 1000 words. + $focus = min(1, .01 + 3.5 / (2 + count($scored_words) * .015)); + } + $tagwords++; + // Too many words inside a single tag probably mean a tag was + // accidentally left open. + if (count($tagstack) && $tagwords >= 15) { + $tagstack = []; + $score = 1; + } + } + } + } + $tag = !$tag; + } + + // Remove the item $sid from the search index, and invalidate the relevant + // cache tags. + $this->clear($type, $sid, $langcode); + + try { + // Insert cleaned up data into dataset. + $this->connection->insert('search_dataset') + ->fields([ + 'sid' => $sid, + 'langcode' => $langcode, + 'type' => $type, + 'data' => $accum, + 'reindex' => 0, + ]) + ->execute(); + + // Insert results into search index. + foreach ($scored_words as $word => $score) { + // If a word already exists in the database, its score gets increased + // appropriately. If not, we create a new record with the appropriate + // starting score. + $this->connection->merge('search_index') + ->keys([ + 'word' => $word, + 'sid' => $sid, + 'langcode' => $langcode, + 'type' => $type, + ]) + ->fields(['score' => $score]) + ->expression('score', 'score + :score', [':score' => $score]) + ->execute(); + $current_words[$word] = TRUE; + } + } + catch (\Exception $e) { + throw new SearchIndexException("Failed to insert dataset in index for type '$type', sid '$sid' and langcode '$langcode'", 0, $e); + } + finally { + if ($update_weights) { + $this->updateWordWeights($current_words); + } + } + return $current_words; + } + + /** + * {@inheritdoc} + */ + public function clear($type = NULL, $sid = NULL, $langcode = NULL) { + + try { + $query_index = $this->connection->delete('search_index'); + $query_dataset = $this->connection->delete('search_dataset'); + if ($type) { + $query_index->condition('type', $type); + $query_dataset->condition('type', $type); + if ($sid) { + $query_index->condition('sid', $sid); + $query_dataset->condition('sid', $sid); + if ($langcode) { + $query_index->condition('langcode', $langcode); + $query_dataset->condition('langcode', $langcode); + } + } + } + $query_index->execute(); + $query_dataset->execute(); + } + catch (\Exception $e) { + throw new SearchIndexException("Failed to clear index for type '$type', sid '$sid' and langcode '$langcode'", 0, $e); + } + if ($type) { + // Invalidate all render cache items that contain data from this index. + $this->cacheTagsInvalidator->invalidateTags(['search_index:' . $type]); + } + else { + // Invalidate all render cache items that contain data from any index. + $this->cacheTagsInvalidator->invalidateTags(['search_index']); + } + } + + /** + * {@inheritdoc} + */ + public function markForReindex($type = NULL, $sid = NULL, $langcode = NULL) { + + try { + $query = $this->connection->update('search_dataset') + ->fields(['reindex' => REQUEST_TIME]) + // Only mark items that were not previously marked for reindex, so that + // marked items maintain their priority by request time. + ->condition('reindex', 0); + if ($type) { + $query->condition('type', $type); + if ($sid) { + $query->condition('sid', $sid); + if ($langcode) { + $query->condition('langcode', $langcode); + } + } + } + $query->execute(); + } + catch (\Exception $e) { + throw new SearchIndexException("Failed to mark index for re-indexing for type '$type', sid '$sid' and langcode '$langcode'", 0, $e); + } + } + + /** + * {@inheritdoc} + */ + public function updateWordWeights(array $words) { + try { + // Update word IDF (Inverse Document Frequency) counts for new/changed + // words. + $words = array_keys($words); + foreach ($words as $word) { + // Get total count. + $total = $this->replica->query("SELECT SUM(score) FROM {search_index} WHERE word = :word", [':word' => $word]) + ->fetchField(); + // Apply Zipf's law to equalize the probability distribution. + $total = log10(1 + 1 / (max(1, $total))); + $this->connection->merge('search_total') + ->key('word', $word) + ->fields(['count' => $total]) + ->execute(); + } + // Find words that were deleted from search_index, but are still in + // search_total. We use a LEFT JOIN between the two tables and keep only + // the rows which fail to join. + $result = $this->replica->query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL"); + $or = new Condition('OR'); + foreach ($result as $word) { + $or->condition('word', $word->realword); + } + if (count($or) > 0) { + $this->connection->delete('search_total') + ->condition($or) + ->execute(); + } + } + catch (\Exception $e) { + throw new SearchIndexException("Failed to update totals for index words.", 0, $e); + } + } + +} diff --git a/core/modules/search/src/SearchIndexInterface.php b/core/modules/search/src/SearchIndexInterface.php new file mode 100644 index 000000000..3216d5b9b --- /dev/null +++ b/core/modules/search/src/SearchIndexInterface.php @@ -0,0 +1,99 @@ +configFactory = $config_factory; $this->searchManager = $search_manager; $this->messenger = $messenger; + if (!$search_index) { + @trigger_error('Calling SearchPageListBuilder::__construct() without the $search_index argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/3075696', E_USER_DEPRECATED); + $search_index = \Drupal::service('search.index'); + } + $this->searchIndex = $search_index; } /** @@ -80,7 +94,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('entity_type.manager')->getStorage($entity_type->id()), $container->get('plugin.manager.search'), $container->get('config.factory'), - $container->get('messenger') + $container->get('messenger'), + $container->get('search.index') ); } @@ -344,9 +359,9 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $search_settings->set('index.minimum_word_size', $form_state->getValue('minimum_word_size')); $search_settings->set('index.overlap_cjk', $form_state->getValue('overlap_cjk')); // Specifically mark items in the default index for reindexing, since - // these settings are used in the search_index() function. + // these settings are used in the SearchIndex::index() function. $this->messenger->addStatus($this->t('The default search index will be rebuilt.')); - search_mark_for_reindex(); + $this->searchIndex->markForReindex(); } $search_settings diff --git a/core/modules/search/src/SearchPageRepositoryInterface.php b/core/modules/search/src/SearchPageRepositoryInterface.php index 6169fbcfe..7ed5d43a8 100644 --- a/core/modules/search/src/SearchPageRepositoryInterface.php +++ b/core/modules/search/src/SearchPageRepositoryInterface.php @@ -34,8 +34,8 @@ public function getIndexableSearchPages(); /** * Returns the default search page. * - * @return \Drupal\search\SearchPageInterface|bool - * The search page entity, or FALSE if no pages are active. + * @return string|false + * The default search page entity ID, or FALSE if no pages are active. */ public function getDefaultSearchPage(); diff --git a/core/modules/search/src/Tests/SearchTestBase.php b/core/modules/search/src/Tests/SearchTestBase.php index 61a5a91e3..75758d82c 100644 --- a/core/modules/search/src/Tests/SearchTestBase.php +++ b/core/modules/search/src/Tests/SearchTestBase.php @@ -10,7 +10,7 @@ /** * Defines the common search test code. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\search\Functional\SearchTestBase instead. * * @see https://www.drupal.org/node/2999939 @@ -91,8 +91,8 @@ protected function submitGetForm($path, $edit, $submit, $form_html_id = NULL) { foreach ($edit as $name => $value) { $this->fail(new FormattableMarkup('Failed to set field @name to @value', ['@name' => $name, '@value' => $value])); } - $this->assertTrue($submit_matches, format_string('Found the @submit button', ['@submit' => $submit])); - $this->fail(format_string('Found the requested form fields at @path', ['@path' => $path])); + $this->assertTrue($submit_matches, new FormattableMarkup('Found the @submit button', ['@submit' => $submit])); + $this->fail(new FormattableMarkup('Found the requested form fields at @path', ['@path' => $path])); } } diff --git a/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml b/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml index a818519a9..5b573478b 100644 --- a/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml +++ b/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml @@ -2,11 +2,5 @@ name: 'Search Date Query Alter' type: module description: 'Test module that adds date conditions to node searches.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.info.yml b/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.info.yml index 7cb889f16..0df41aa09 100644 --- a/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.info.yml +++ b/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.info.yml @@ -2,11 +2,5 @@ name: 'Search Embedded Form' type: module description: 'Support module for Search module testing of embedded forms.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/search/tests/modules/search_extra_type/search_extra_type.info.yml b/core/modules/search/tests/modules/search_extra_type/search_extra_type.info.yml index 6bc8f393a..8f4c8a4c9 100644 --- a/core/modules/search/tests/modules/search_extra_type/search_extra_type.info.yml +++ b/core/modules/search/tests/modules/search_extra_type/search_extra_type.info.yml @@ -2,13 +2,7 @@ name: 'Test Search Type' type: module description: 'Support module for Search module testing.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:test_page_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.info.yml b/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.info.yml index 830be0cea..da34c5fa1 100644 --- a/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.info.yml +++ b/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.info.yml @@ -2,11 +2,5 @@ name: 'Test search entity langcode' type: module description: 'Support module for search module testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/search/tests/modules/search_query_alter/search_query_alter.info.yml b/core/modules/search/tests/modules/search_query_alter/search_query_alter.info.yml index 2176d9c14..8c6a3d824 100644 --- a/core/modules/search/tests/modules/search_query_alter/search_query_alter.info.yml +++ b/core/modules/search/tests/modules/search_query_alter/search_query_alter.info.yml @@ -2,11 +2,5 @@ name: 'Test Search Query Alter' type: module description: 'Support module for Search module testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonAnonTest.php b/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonAnonTest.php index cd0079803..833c59bee 100644 --- a/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonAnonTest.php +++ b/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonAnonTest.php @@ -17,6 +17,11 @@ class SearchPageHalJsonAnonTest extends SearchPageResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonBasicAuthTest.php b/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonBasicAuthTest.php index d1ff293ba..d7e8a619b 100644 --- a/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonBasicAuthTest.php +++ b/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class SearchPageHalJsonBasicAuthTest extends SearchPageResourceTestBase { */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonCookieTest.php b/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonCookieTest.php index a09fa5f9b..47ab4dd46 100644 --- a/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonCookieTest.php +++ b/core/modules/search/tests/src/Functional/Hal/SearchPageHalJsonCookieTest.php @@ -17,6 +17,11 @@ class SearchPageHalJsonCookieTest extends SearchPageResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/search/tests/src/Functional/Rest/SearchPageJsonAnonTest.php b/core/modules/search/tests/src/Functional/Rest/SearchPageJsonAnonTest.php index 970819264..b58490208 100644 --- a/core/modules/search/tests/src/Functional/Rest/SearchPageJsonAnonTest.php +++ b/core/modules/search/tests/src/Functional/Rest/SearchPageJsonAnonTest.php @@ -21,4 +21,9 @@ class SearchPageJsonAnonTest extends SearchPageResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/search/tests/src/Functional/Rest/SearchPageJsonBasicAuthTest.php b/core/modules/search/tests/src/Functional/Rest/SearchPageJsonBasicAuthTest.php index f311ce9b4..615393be6 100644 --- a/core/modules/search/tests/src/Functional/Rest/SearchPageJsonBasicAuthTest.php +++ b/core/modules/search/tests/src/Functional/Rest/SearchPageJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class SearchPageJsonBasicAuthTest extends SearchPageResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/search/tests/src/Functional/Rest/SearchPageJsonCookieTest.php b/core/modules/search/tests/src/Functional/Rest/SearchPageJsonCookieTest.php index 8e7c8899d..ca6c6a386 100644 --- a/core/modules/search/tests/src/Functional/Rest/SearchPageJsonCookieTest.php +++ b/core/modules/search/tests/src/Functional/Rest/SearchPageJsonCookieTest.php @@ -26,4 +26,9 @@ class SearchPageJsonCookieTest extends SearchPageResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/search/tests/src/Functional/Rest/SearchPageXmlAnonTest.php b/core/modules/search/tests/src/Functional/Rest/SearchPageXmlAnonTest.php index 5f4698ec1..3ed7990aa 100644 --- a/core/modules/search/tests/src/Functional/Rest/SearchPageXmlAnonTest.php +++ b/core/modules/search/tests/src/Functional/Rest/SearchPageXmlAnonTest.php @@ -23,4 +23,9 @@ class SearchPageXmlAnonTest extends SearchPageResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/search/tests/src/Functional/Rest/SearchPageXmlBasicAuthTest.php b/core/modules/search/tests/src/Functional/Rest/SearchPageXmlBasicAuthTest.php index 7a2c3c880..5381043be 100644 --- a/core/modules/search/tests/src/Functional/Rest/SearchPageXmlBasicAuthTest.php +++ b/core/modules/search/tests/src/Functional/Rest/SearchPageXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class SearchPageXmlBasicAuthTest extends SearchPageResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/search/tests/src/Functional/Rest/SearchPageXmlCookieTest.php b/core/modules/search/tests/src/Functional/Rest/SearchPageXmlCookieTest.php index 3ad901d6e..860981f09 100644 --- a/core/modules/search/tests/src/Functional/Rest/SearchPageXmlCookieTest.php +++ b/core/modules/search/tests/src/Functional/Rest/SearchPageXmlCookieTest.php @@ -28,4 +28,9 @@ class SearchPageXmlCookieTest extends SearchPageResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/search/tests/src/Functional/SearchAdvancedSearchFormTest.php b/core/modules/search/tests/src/Functional/SearchAdvancedSearchFormTest.php index 03f0ff75c..099663ed5 100644 --- a/core/modules/search/tests/src/Functional/SearchAdvancedSearchFormTest.php +++ b/core/modules/search/tests/src/Functional/SearchAdvancedSearchFormTest.php @@ -16,6 +16,11 @@ class SearchAdvancedSearchFormTest extends BrowserTestBase { */ protected static $modules = ['node', 'search', 'dblog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A node to use for testing. * @@ -38,11 +43,6 @@ protected function setUp() { // First update the index. This does the initial processing. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - - // Then, run the shutdown function. Testing is a unique case where indexing - // and searching has to happen in the same request, so running the shutdown - // function manually is needed to finish the indexing process. - search_update_totals(); } /** diff --git a/core/modules/search/tests/src/Functional/SearchBlockTest.php b/core/modules/search/tests/src/Functional/SearchBlockTest.php index eedb1a624..07791269c 100644 --- a/core/modules/search/tests/src/Functional/SearchBlockTest.php +++ b/core/modules/search/tests/src/Functional/SearchBlockTest.php @@ -15,14 +15,34 @@ class SearchBlockTest extends BrowserTestBase { /** * {@inheritdoc} */ - protected static $modules = ['block', 'node', 'search', 'dblog']; + protected static $modules = ['block', 'node', 'search', 'dblog', 'user']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + + /** + * The administrative user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $adminUser; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); // Create and log in user. - $admin_user = $this->drupalCreateUser(['administer blocks', 'search content']); - $this->drupalLogin($admin_user); + $this->adminUser = $this->drupalCreateUser([ + 'administer blocks', + 'search content', + 'access user profiles', + 'access content', + ]); + $this->drupalLogin($this->adminUser); } /** @@ -105,6 +125,16 @@ public function testSearchFormBlock() { $this->drupalPostForm(NULL, ['keys' => $this->randomMachineName()], t('Search'), [], 'search-form'); $this->assertNoText('You must include at least one keyword to match in the content', 'Keyword message is not displayed when searching for long word after short word search'); + // Edit the block configuration so that it searches users instead of nodes, + // and test. + $this->drupalPostForm('admin/structure/block/manage/' . $block->id(), + [ + 'settings[page_id]' => 'user_search', + ], 'Save block'); + $name = $this->adminUser->getAccountName(); + $email = $this->adminUser->getEmail(); + $this->drupalPostForm('node', ['keys' => $name], t('Search')); + $this->assertLink($name); } } diff --git a/core/modules/search/tests/src/Functional/SearchCommentCountToggleTest.php b/core/modules/search/tests/src/Functional/SearchCommentCountToggleTest.php index 2d7811576..03520359a 100644 --- a/core/modules/search/tests/src/Functional/SearchCommentCountToggleTest.php +++ b/core/modules/search/tests/src/Functional/SearchCommentCountToggleTest.php @@ -27,6 +27,11 @@ class SearchCommentCountToggleTest extends BrowserTestBase { */ protected static $modules = ['node', 'comment', 'search', 'dblog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to search and post comments. * @@ -70,11 +75,6 @@ protected function setUp() { // First update the index. This does the initial processing. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - - // Then, run the shutdown function. Testing is a unique case where indexing - // and searching has to happen in the same request, so running the shutdown - // function manually is needed to finish the indexing process. - search_update_totals(); } /** diff --git a/core/modules/search/tests/src/Functional/SearchCommentTest.php b/core/modules/search/tests/src/Functional/SearchCommentTest.php index a77d73574..c513be8d5 100644 --- a/core/modules/search/tests/src/Functional/SearchCommentTest.php +++ b/core/modules/search/tests/src/Functional/SearchCommentTest.php @@ -26,6 +26,11 @@ class SearchCommentTest extends BrowserTestBase { */ protected static $modules = ['filter', 'node', 'comment', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test subject for comments. * @@ -89,7 +94,7 @@ protected function setUp() { * Verify that comments are rendered using proper format in search results. */ public function testSearchResultsComment() { - $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); // Create basic_html format that escapes all HTML. $basic_html_format = FilterFormat::create([ 'format' => 'basic_html', @@ -294,7 +299,7 @@ public function setRolePermissions($rid, $access_comments = FALSE, $search_conte */ public function assertCommentAccess($assume_access, $message) { // Invoke search index update. - search_mark_for_reindex('node_search', $this->node->id()); + \Drupal::service('search.index')->markForReindex('node_search', $this->node->id()); $this->cronRun(); // Search for the comment subject. diff --git a/core/modules/search/tests/src/Functional/SearchConfigSettingsFormTest.php b/core/modules/search/tests/src/Functional/SearchConfigSettingsFormTest.php index 8dccf3e26..e00eab9f8 100644 --- a/core/modules/search/tests/src/Functional/SearchConfigSettingsFormTest.php +++ b/core/modules/search/tests/src/Functional/SearchConfigSettingsFormTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\search\Functional; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\search\Entity\SearchPage; use Drupal\Tests\BrowserTestBase; @@ -18,6 +20,11 @@ class SearchConfigSettingsFormTest extends BrowserTestBase { */ protected static $modules = ['block', 'dblog', 'node', 'search', 'search_extra_type', 'test_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * User who can search and administer search. * @@ -47,11 +54,10 @@ protected function setUp() { // Link the node to itself to test that it's only indexed once. The content // also needs the word "pizza" so we can use it as the search keyword. $body_key = 'body[0][value]'; - $edit[$body_key] = \Drupal::l($node->label(), $node->toUrl()) . ' pizza sandwich'; + $edit[$body_key] = Link::fromTextAndUrl($node->label(), $node->toUrl())->toString() . ' pizza sandwich'; $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); // Enable the search block. $this->drupalPlaceBlock('search_form_block'); @@ -111,7 +117,7 @@ public function testSearchModuleSettingsPage() { $this->clickLink(t('Edit'), 1); // Ensure that the default setting was picked up from the default config - $this->assertTrue($this->xpath('//select[@id="edit-extra-type-settings-boost"]//option[@value="bi" and @selected="selected"]'), 'Module specific settings are picked up from the default config'); + $this->assertSession()->elementExists('xpath', '//select[@id="edit-extra-type-settings-boost"]//option[@value="bi" and @selected="selected"]'); // Change extra type setting and also modify a common search setting. $edit = [ @@ -122,7 +128,7 @@ public function testSearchModuleSettingsPage() { // Ensure that the modifications took effect. $this->assertRaw(t('The %label search page has been updated.', ['%label' => 'Dummy search type'])); $this->drupalGet('admin/config/search/pages/manage/dummy_search_type'); - $this->assertTrue($this->xpath('//select[@id="edit-extra-type-settings-boost"]//option[@value="ii" and @selected="selected"]'), 'Module specific settings can be changed'); + $this->assertSession()->elementExists('xpath', '//select[@id="edit-extra-type-settings-boost"]//option[@value="ii" and @selected="selected"]'); } /** @@ -207,7 +213,7 @@ public function testSearchModuleDisabling() { $this->drupalGet($item['path'], $item['options']); foreach ($plugins as $entity_id) { $label = $entities[$entity_id]->label(); - $this->assertText($label, format_string('%label search tab is shown', ['%label' => $label])); + $this->assertText($label, new FormattableMarkup('%label search tab is shown', ['%label' => $label])); } } } @@ -228,7 +234,7 @@ public function testDefaultSearchPageOrdering() { */ public function testMultipleSearchPages() { $this->assertDefaultSearch('node_search', 'The default page is set to the installer default.'); - $search_storage = \Drupal::entityManager()->getStorage('search_page'); + $search_storage = \Drupal::entityTypeManager()->getStorage('search_page'); $entities = $search_storage->loadMultiple(); $search_storage->delete($entities); $this->assertDefaultSearch(FALSE); diff --git a/core/modules/search/tests/src/Functional/SearchDateIntervalTest.php b/core/modules/search/tests/src/Functional/SearchDateIntervalTest.php index 46edc3bb0..22c19b9fd 100644 --- a/core/modules/search/tests/src/Functional/SearchDateIntervalTest.php +++ b/core/modules/search/tests/src/Functional/SearchDateIntervalTest.php @@ -17,6 +17,11 @@ class SearchDateIntervalTest extends BrowserTestBase { */ protected static $modules = ['language', 'search_date_query_alter', 'node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -56,7 +61,6 @@ protected function setUp() { // Update the index. $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); $plugin->updateIndex(); - search_update_totals(); } /** diff --git a/core/modules/search/tests/src/Functional/SearchEmbedFormTest.php b/core/modules/search/tests/src/Functional/SearchEmbedFormTest.php index af0d9b7b6..80033b4de 100644 --- a/core/modules/search/tests/src/Functional/SearchEmbedFormTest.php +++ b/core/modules/search/tests/src/Functional/SearchEmbedFormTest.php @@ -16,6 +16,11 @@ class SearchEmbedFormTest extends BrowserTestBase { */ protected static $modules = ['node', 'search', 'search_embedded_form']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Node used for testing. * @@ -42,7 +47,6 @@ protected function setUp() { $this->node = $this->drupalCreateNode(); $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); // Set up a dummy initial count of times the form has been submitted. $this->submitCount = \Drupal::state()->get('search_embedded_form.submit_count'); diff --git a/core/modules/search/tests/src/Functional/SearchExactTest.php b/core/modules/search/tests/src/Functional/SearchExactTest.php index 94de349eb..1b77e703c 100644 --- a/core/modules/search/tests/src/Functional/SearchExactTest.php +++ b/core/modules/search/tests/src/Functional/SearchExactTest.php @@ -16,6 +16,11 @@ class SearchExactTest extends BrowserTestBase { */ protected static $modules = ['node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that the correct number of pager links are found for both keywords and phrases. */ @@ -46,7 +51,6 @@ public function testExactQuery() { // Update the search index. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); // Refresh variables after the treatment. $this->refreshVariables(); diff --git a/core/modules/search/tests/src/Functional/SearchKeywordsConditionsTest.php b/core/modules/search/tests/src/Functional/SearchKeywordsConditionsTest.php index 4c946894d..38faa34e5 100644 --- a/core/modules/search/tests/src/Functional/SearchKeywordsConditionsTest.php +++ b/core/modules/search/tests/src/Functional/SearchKeywordsConditionsTest.php @@ -21,6 +21,11 @@ class SearchKeywordsConditionsTest extends BrowserTestBase { */ protected static $modules = ['comment', 'search', 'search_extra_type', 'test_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to search and post comments. * diff --git a/core/modules/search/tests/src/Functional/SearchLanguageTest.php b/core/modules/search/tests/src/Functional/SearchLanguageTest.php index c874df7f6..539fac67f 100644 --- a/core/modules/search/tests/src/Functional/SearchLanguageTest.php +++ b/core/modules/search/tests/src/Functional/SearchLanguageTest.php @@ -19,6 +19,11 @@ class SearchLanguageTest extends BrowserTestBase { */ protected static $modules = ['language', 'node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Array of nodes available to search. * @@ -85,7 +90,6 @@ protected function setUp() { // Update the index and then run the shutdown method. $plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); $plugin->updateIndex(); - search_update_totals(); } public function testLanguages() { diff --git a/core/modules/search/tests/src/Functional/SearchMultilingualEntityTest.php b/core/modules/search/tests/src/Functional/SearchMultilingualEntityTest.php index 17b3f06af..4377ef0b7 100644 --- a/core/modules/search/tests/src/Functional/SearchMultilingualEntityTest.php +++ b/core/modules/search/tests/src/Functional/SearchMultilingualEntityTest.php @@ -5,6 +5,7 @@ use Drupal\Core\Database\Database; use Drupal\field\Entity\FieldStorageConfig; use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\search\SearchIndexInterface; use Drupal\Tests\BrowserTestBase; /** @@ -33,6 +34,11 @@ class SearchMultilingualEntityTest extends BrowserTestBase { */ protected static $modules = ['language', 'locale', 'comment', 'node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -128,7 +134,8 @@ public function testMultilingualSearch() { // Run the shutdown function. Testing is a unique case where indexing // and searching has to happen in the same request, so running the shutdown // function manually is needed to finish the indexing process. - search_update_totals(); + $search_index = \Drupal::service('search.index'); + assert($search_index instanceof SearchIndexInterface); $this->assertIndexCounts(6, 8, 'after updating partially'); $this->assertDatabaseCounts(2, 0, 'after updating partially'); @@ -140,7 +147,6 @@ public function testMultilingualSearch() { $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); $this->plugin->updateIndex(); - search_update_totals(); $this->assertIndexCounts(0, 8, 'after updating fully'); $this->assertDatabaseCounts(8, 0, 'after updating fully'); @@ -150,7 +156,6 @@ public function testMultilingualSearch() { $this->assertIndexCounts(8, 8, 'after reindex'); $this->assertDatabaseCounts(8, 0, 'after reindex'); $this->plugin->updateIndex(); - search_update_totals(); // Test search results. @@ -190,13 +195,12 @@ public function testMultilingualSearch() { // Mark one of the nodes for reindexing, using the API function, and // verify indexing status. - search_mark_for_reindex('node_search', $this->searchableNodes[0]->id()); + $search_index->markForReindex('node_search', $this->searchableNodes[0]->id()); $this->assertIndexCounts(1, 8, 'after marking one node to reindex via API function'); // Update the index and verify the totals again. $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); $this->plugin->updateIndex(); - search_update_totals(); $this->assertIndexCounts(0, 8, 'after indexing again'); // Mark one node for reindexing by saving it, and verify indexing status. @@ -227,32 +231,32 @@ public function testMultilingualSearch() { // Add a bogus entry to the search index table using a different search // type. This will not appear in the index status, because it is not // managed by a plugin. - search_index('foo', $this->searchableNodes[0]->id(), 'en', 'some text'); + $search_index->index('foo', $this->searchableNodes[0]->id(), 'en', 'some text'); $this->assertIndexCounts(1, 8, 'after adding a different index item'); // Mark just this "foo" index for reindexing. - search_mark_for_reindex('foo'); + $search_index->markForReindex('foo'); $this->assertIndexCounts(1, 8, 'after reindexing the other search type'); // Mark everything for reindexing. - search_mark_for_reindex(); + $search_index->markForReindex(); $this->assertIndexCounts(8, 8, 'after reindexing everything'); // Clear one item from the index, but with wrong language. $this->assertDatabaseCounts(8, 1, 'before clear'); - search_index_clear('node_search', $this->searchableNodes[0]->id(), 'hu'); + $search_index->clear('node_search', $this->searchableNodes[0]->id(), 'hu'); $this->assertDatabaseCounts(8, 1, 'after clear with wrong language'); // Clear using correct language. - search_index_clear('node_search', $this->searchableNodes[0]->id(), 'en'); + $search_index->clear('node_search', $this->searchableNodes[0]->id(), 'en'); $this->assertDatabaseCounts(7, 1, 'after clear with right language'); // Don't specify language. - search_index_clear('node_search', $this->searchableNodes[1]->id()); + $search_index->clear('node_search', $this->searchableNodes[1]->id()); $this->assertDatabaseCounts(6, 1, 'unspecified language clear'); // Clear everything in 'foo'. - search_index_clear('foo'); + $search_index->clear('foo'); $this->assertDatabaseCounts(6, 0, 'other index clear'); // Clear everything. - search_index_clear(); + $search_index->clear(); $this->assertDatabaseCounts(0, 0, 'complete clear'); } diff --git a/core/modules/search/tests/src/Functional/SearchNodeDiacriticsTest.php b/core/modules/search/tests/src/Functional/SearchNodeDiacriticsTest.php index a529cea9b..2e7150d8d 100644 --- a/core/modules/search/tests/src/Functional/SearchNodeDiacriticsTest.php +++ b/core/modules/search/tests/src/Functional/SearchNodeDiacriticsTest.php @@ -16,6 +16,11 @@ class SearchNodeDiacriticsTest extends BrowserTestBase { */ protected static $modules = ['node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to use advanced search. * @@ -45,7 +50,6 @@ public function testPhraseSearchPunctuation() { // Update the search index. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); // Refresh variables after the treatment. $this->refreshVariables(); diff --git a/core/modules/search/tests/src/Functional/SearchNodePunctuationTest.php b/core/modules/search/tests/src/Functional/SearchNodePunctuationTest.php index 0c8571492..02945562a 100644 --- a/core/modules/search/tests/src/Functional/SearchNodePunctuationTest.php +++ b/core/modules/search/tests/src/Functional/SearchNodePunctuationTest.php @@ -16,6 +16,11 @@ class SearchNodePunctuationTest extends BrowserTestBase { */ protected static $modules = ['node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to use advanced search. * @@ -43,7 +48,6 @@ public function testPhraseSearchPunctuation() { // Update the search index. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); // Refresh variables after the treatment. $this->refreshVariables(); diff --git a/core/modules/search/tests/src/Functional/SearchNodeUpdateAndDeletionTest.php b/core/modules/search/tests/src/Functional/SearchNodeUpdateAndDeletionTest.php index 9615cfb3a..6f2a4941c 100644 --- a/core/modules/search/tests/src/Functional/SearchNodeUpdateAndDeletionTest.php +++ b/core/modules/search/tests/src/Functional/SearchNodeUpdateAndDeletionTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\search\Functional; +use Drupal\Core\Database\Database; +use Drupal\search\SearchIndexInterface; use Drupal\Tests\BrowserTestBase; /** @@ -16,6 +18,11 @@ class SearchNodeUpdateAndDeletionTest extends BrowserTestBase { */ protected static $modules = ['node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to access and search content. * @@ -47,7 +54,8 @@ public function testSearchIndexUpdateOnNodeChange() { $node_search_plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); // Update the search index. $node_search_plugin->updateIndex(); - search_update_totals(); + $search_index = \Drupal::service('search.index'); + assert($search_index instanceof SearchIndexInterface); // Search the node to verify it appears in search results $edit = ['keys' => 'knights']; @@ -60,7 +68,6 @@ public function testSearchIndexUpdateOnNodeChange() { // Run indexer again $node_search_plugin->updateIndex(); - search_update_totals(); // Search again to verify the new text appears in test results. $edit = ['keys' => 'shrubbery']; @@ -82,7 +89,6 @@ public function testSearchIndexUpdateOnNodeDeletion() { $node_search_plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); // Update the search index. $node_search_plugin->updateIndex(); - search_update_totals(); // Search the node to verify it appears in search results $edit = ['keys' => 'dragons']; @@ -90,7 +96,8 @@ public function testSearchIndexUpdateOnNodeDeletion() { $this->assertText($node->label()); // Get the node info from the search index tables. - $search_index_dataset = db_query("SELECT sid FROM {search_index} WHERE type = 'node_search' AND word = :word", [':word' => 'dragons']) + $connection = Database::getConnection(); + $search_index_dataset = $connection->query("SELECT sid FROM {search_index} WHERE type = 'node_search' AND word = :word", [':word' => 'dragons']) ->fetchField(); $this->assertNotEqual($search_index_dataset, FALSE, t('Node info found on the search_index')); @@ -98,7 +105,7 @@ public function testSearchIndexUpdateOnNodeDeletion() { $node->delete(); // Check if the node info is gone from the search table. - $search_index_dataset = db_query("SELECT sid FROM {search_index} WHERE type = 'node_search' AND word = :word", [':word' => 'dragons']) + $search_index_dataset = $connection->query("SELECT sid FROM {search_index} WHERE type = 'node_search' AND word = :word", [':word' => 'dragons']) ->fetchField(); $this->assertFalse($search_index_dataset, t('Node info successfully removed from search_index')); diff --git a/core/modules/search/tests/src/Functional/SearchNumberMatchingTest.php b/core/modules/search/tests/src/Functional/SearchNumberMatchingTest.php index adc2c436a..8888b4ddc 100644 --- a/core/modules/search/tests/src/Functional/SearchNumberMatchingTest.php +++ b/core/modules/search/tests/src/Functional/SearchNumberMatchingTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\search\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Language\LanguageInterface; use Drupal\Tests\BrowserTestBase; use Drupal\Tests\Traits\Core\CronRunTrait; @@ -20,6 +21,11 @@ class SearchNumberMatchingTest extends BrowserTestBase { */ protected static $modules = ['dblog', 'node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to administer nodes. * @@ -89,7 +95,7 @@ public function testNumberSearching() { $this->drupalPostForm('search/node', ['keys' => 'foo'], t('Search')); - $this->assertNoText($node->label(), format_string('%number: node title not shown in dummy search', ['%number' => $i])); + $this->assertNoText($node->label(), new FormattableMarkup('%number: node title not shown in dummy search', ['%number' => $i])); // Now verify that we can find node i by searching for any of the // numbers. @@ -102,7 +108,7 @@ public function testNumberSearching() { $this->drupalPostForm('search/node', ['keys' => $number], t('Search')); - $this->assertText($node->label(), format_string('%i: node title shown (search found the node) in search for number %number', ['%i' => $i, '%number' => $number])); + $this->assertText($node->label(), new FormattableMarkup('%i: node title shown (search found the node) in search for number %number', ['%i' => $i, '%number' => $number])); } } diff --git a/core/modules/search/tests/src/Functional/SearchNumbersTest.php b/core/modules/search/tests/src/Functional/SearchNumbersTest.php index 0901a270e..43dd788d1 100644 --- a/core/modules/search/tests/src/Functional/SearchNumbersTest.php +++ b/core/modules/search/tests/src/Functional/SearchNumbersTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\search\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Language\LanguageInterface; use Drupal\Tests\BrowserTestBase; use Drupal\Tests\Traits\Core\CronRunTrait; @@ -20,6 +21,11 @@ class SearchNumbersTest extends BrowserTestBase { */ protected static $modules = ['dblog', 'node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to administer nodes. * @@ -109,7 +115,7 @@ public function testNumberSearching() { $this->drupalPostForm('search/node', ['keys' => $number], t('Search')); - $this->assertText($node->label(), format_string('%type: node title shown (search found the node) in search for number %number.', ['%type' => $type, '%number' => $number])); + $this->assertText($node->label(), new FormattableMarkup('%type: node title shown (search found the node) in search for number %number.', ['%type' => $type, '%number' => $number])); } } diff --git a/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php b/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php index 2b36a33c4..daa55efae 100644 --- a/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php +++ b/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php @@ -20,6 +20,11 @@ class SearchPageCacheTagsTest extends BrowserTestBase { */ protected static $modules = ['node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -55,7 +60,6 @@ protected function setUp() { $this->node->setOwner($this->searchingUser); $this->node->save(); $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); } /** @@ -174,7 +178,6 @@ public function testSearchTagsBubbling() { // Refresh the search index. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); // Log in with searching user again. $this->drupalLogin($this->searchingUser); diff --git a/core/modules/search/tests/src/Functional/SearchPageOverrideTest.php b/core/modules/search/tests/src/Functional/SearchPageOverrideTest.php index 5a614e303..2faac41a5 100644 --- a/core/modules/search/tests/src/Functional/SearchPageOverrideTest.php +++ b/core/modules/search/tests/src/Functional/SearchPageOverrideTest.php @@ -19,6 +19,11 @@ class SearchPageOverrideTest extends BrowserTestBase { */ protected static $modules = ['search', 'search_extra_type']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to administer search. * diff --git a/core/modules/search/tests/src/Functional/SearchPageTextTest.php b/core/modules/search/tests/src/Functional/SearchPageTextTest.php index d126bf927..148b4372c 100644 --- a/core/modules/search/tests/src/Functional/SearchPageTextTest.php +++ b/core/modules/search/tests/src/Functional/SearchPageTextTest.php @@ -18,6 +18,11 @@ class SearchPageTextTest extends BrowserTestBase { */ protected static $modules = ['block', 'node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to use advanced search. * diff --git a/core/modules/search/tests/src/Functional/SearchPreprocessLangcodeTest.php b/core/modules/search/tests/src/Functional/SearchPreprocessLangcodeTest.php index 459c1bacc..cdbfb2f65 100644 --- a/core/modules/search/tests/src/Functional/SearchPreprocessLangcodeTest.php +++ b/core/modules/search/tests/src/Functional/SearchPreprocessLangcodeTest.php @@ -16,6 +16,11 @@ class SearchPreprocessLangcodeTest extends BrowserTestBase { */ protected static $modules = ['node', 'search', 'search_langcode_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test node for searching. * @@ -47,11 +52,6 @@ public function testPreprocessLangcode() { // First update the index. This does the initial processing. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - // Then, run the shutdown function. Testing is a unique case where indexing - // and searching has to happen in the same request, so running the shutdown - // function manually is needed to finish the indexing process. - search_update_totals(); - // Search for the additional text that is added by the preprocess // function. If you search for text that is in the node, preprocess is // not invoked on the node during the search excerpt generation. @@ -76,11 +76,6 @@ public function testPreprocessStemming() { // First update the index. This does the initial processing. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - // Then, run the shutdown function. Testing is a unique case where indexing - // and searching has to happen in the same request, so running the shutdown - // function manually is needed to finish the indexing process. - search_update_totals(); - // Search for the title of the node with a POST query. $edit = ['or' => 'testing']; $this->drupalPostForm('search/node', $edit, 'edit-submit--2'); diff --git a/core/modules/search/tests/src/Functional/SearchQueryAlterTest.php b/core/modules/search/tests/src/Functional/SearchQueryAlterTest.php index 12cb1d7d8..bcfd7e269 100644 --- a/core/modules/search/tests/src/Functional/SearchQueryAlterTest.php +++ b/core/modules/search/tests/src/Functional/SearchQueryAlterTest.php @@ -16,6 +16,11 @@ class SearchQueryAlterTest extends BrowserTestBase { */ protected static $modules = ['node', 'search', 'search_query_alter']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that the query alter works. */ @@ -41,7 +46,6 @@ public function testQueryAlter() { // Update the search index. $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex(); - search_update_totals(); // Search for the body keyword 'pizza'. $this->drupalPostForm('search/node', ['keys' => 'pizza'], t('Search')); diff --git a/core/modules/search/tests/src/Functional/SearchRankingTest.php b/core/modules/search/tests/src/Functional/SearchRankingTest.php index 49bfc4c41..884359b3d 100644 --- a/core/modules/search/tests/src/Functional/SearchRankingTest.php +++ b/core/modules/search/tests/src/Functional/SearchRankingTest.php @@ -5,9 +5,11 @@ use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; use Drupal\comment\Tests\CommentTestTrait; use Drupal\Core\Database\Database; +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\filter\Entity\FilterFormat; use Drupal\search\Entity\SearchPage; +use Drupal\search\SearchIndexInterface; use Drupal\Tests\BrowserTestBase; use Drupal\Tests\Traits\Core\CronRunTrait; @@ -33,6 +35,11 @@ class SearchRankingTest extends BrowserTestBase { */ protected static $modules = ['node', 'search', 'statistics', 'comment']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -118,7 +125,7 @@ public function testRankings() { // Check that all rankings are visible and set to 0. foreach ($node_ranks as $node_rank) { - $this->assertTrue($this->xpath('//select[@id="edit-rankings-' . $node_rank . '-value"]//option[@value="0"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 0.'); + $this->assertNotEmpty($this->xpath('//select[@id="edit-rankings-' . $node_rank . '-value"]//option[@value="0"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 0.'); } // Test each of the possible rankings. @@ -128,7 +135,7 @@ public function testRankings() { $edit['rankings[' . $node_rank . '][value]'] = 10; $this->drupalPostForm('admin/config/search/pages/manage/node_search', $edit, t('Save search page')); $this->drupalGet('admin/config/search/pages/manage/node_search'); - $this->assertTrue($this->xpath('//select[@id="edit-rankings-' . $node_rank . '-value"]//option[@value="10"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 10.'); + $this->assertNotEmpty($this->xpath('//select[@id="edit-rankings-' . $node_rank . '-value"]//option[@value="10"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 10.'); // Reload the plugin to get the up-to-date values. $this->nodeSearch = SearchPage::load('node_search'); @@ -146,7 +153,7 @@ public function testRankings() { $this->drupalPostForm('admin/config/search/pages/manage/node_search', $edit, t('Save search page')); $this->drupalGet('admin/config/search/pages/manage/node_search'); foreach ($node_ranks as $node_rank) { - $this->assertTrue($this->xpath('//select[@id="edit-rankings-' . $node_rank . '-value"]//option[@value="0"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 0.'); + $this->assertNotEmpty($this->xpath('//select[@id="edit-rankings-' . $node_rank . '-value"]//option[@value="0"]'), 'Select list to prioritize ' . $node_rank . ' for node ranks is visible and set to 0.'); } // Try with sticky, then promoted. This is a test for issue @@ -224,7 +231,7 @@ public function testHTMLRankings() { foreach ($shuffled_tags as $tag) { switch ($tag) { case 'a': - $settings['body'] = [['value' => \Drupal::l('Drupal Rocks', new Url('')), 'format' => 'full_html']]; + $settings['body'] = [['value' => Link::fromTextAndUrl('Drupal Rocks', Url::fromRoute(''))->toString(), 'format' => 'full_html']]; break; case 'notag': $settings['body'] = [['value' => 'Drupal Rocks']]; @@ -238,7 +245,8 @@ public function testHTMLRankings() { // Update the search index. $this->nodeSearch->getPlugin()->updateIndex(); - search_update_totals(); + $search_index = \Drupal::service('search.index'); + assert($search_index instanceof SearchIndexInterface); $this->nodeSearch->getPlugin()->setSearch('rocks', [], []); // Do the search and assert the results. @@ -263,7 +271,6 @@ public function testHTMLRankings() { // Update the search index. $this->nodeSearch->getPlugin()->updateIndex(); - search_update_totals(); $this->nodeSearch->getPlugin()->setSearch('rocks', [], []); // Do the search and assert the results. diff --git a/core/modules/search/tests/src/Functional/SearchSetLocaleTest.php b/core/modules/search/tests/src/Functional/SearchSetLocaleTest.php index bca342116..c0faabcce 100644 --- a/core/modules/search/tests/src/Functional/SearchSetLocaleTest.php +++ b/core/modules/search/tests/src/Functional/SearchSetLocaleTest.php @@ -16,6 +16,11 @@ class SearchSetLocaleTest extends BrowserTestBase { */ protected static $modules = ['comment', 'node', 'search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A node search plugin instance. * @@ -34,7 +39,6 @@ protected function setUp() { $this->drupalCreateNode(['body' => [['value' => 'tapir']]]); // Update the search index. $this->nodeSearchPlugin->updateIndex(); - search_update_totals(); } /** diff --git a/core/modules/search/tests/src/Functional/SearchSimplifyTest.php b/core/modules/search/tests/src/Functional/SearchSimplifyTest.php index a451063e0..127539ef7 100644 --- a/core/modules/search/tests/src/Functional/SearchSimplifyTest.php +++ b/core/modules/search/tests/src/Functional/SearchSimplifyTest.php @@ -16,6 +16,11 @@ class SearchSimplifyTest extends BrowserTestBase { */ protected static $modules = ['search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that all Unicode characters simplify correctly. */ diff --git a/core/modules/search/tests/src/Functional/SearchTestBase.php b/core/modules/search/tests/src/Functional/SearchTestBase.php index fe6ac7132..fb6973903 100644 --- a/core/modules/search/tests/src/Functional/SearchTestBase.php +++ b/core/modules/search/tests/src/Functional/SearchTestBase.php @@ -9,7 +9,7 @@ /** * Defines the common search test code. * - * @deprecated in Drupal 8.6.0 and will be removed in Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\BrowserTestBase instead. * * @see https://www.drupal.org/node/2979950 @@ -48,7 +48,7 @@ protected function setUp() { * @param string $form_html_id * (optional) HTML ID of the form, to disambiguate. * - * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.x. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\BrowserTestBase::drupalPostForm() instead. * * @see https://www.drupal.org/node/2979950 diff --git a/core/modules/search/tests/src/Functional/SearchTokenizerTest.php b/core/modules/search/tests/src/Functional/SearchTokenizerTest.php index 2e608fc05..8b906a724 100644 --- a/core/modules/search/tests/src/Functional/SearchTokenizerTest.php +++ b/core/modules/search/tests/src/Functional/SearchTokenizerTest.php @@ -16,6 +16,11 @@ class SearchTokenizerTest extends BrowserTestBase { */ protected static $modules = ['search']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Verifies that strings of CJK characters are tokenized. * diff --git a/core/modules/search/tests/src/Functional/Update/BlockPageSettingTest.php b/core/modules/search/tests/src/Functional/Update/BlockPageSettingTest.php new file mode 100644 index 000000000..d11ef0bde --- /dev/null +++ b/core/modules/search/tests/src/Functional/Update/BlockPageSettingTest.php @@ -0,0 +1,39 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Tests existing search block settings upgrade. + * + * @see search_post_update_block_page() + */ + public function testUpdateActionPlugins() { + $config = \Drupal::configFactory()->get('block.block.bartik_search'); + $this->assertArrayNotHasKey('page_id', $config->get('settings')); + + $this->runUpdates(); + + $config = \Drupal::configFactory()->get('block.block.bartik_search'); + $this->assertSame('', $config->get('settings')['page_id']); + } + +} diff --git a/core/modules/search/tests/src/Kernel/SearchDeprecationTest.php b/core/modules/search/tests/src/Kernel/SearchDeprecationTest.php new file mode 100644 index 000000000..a34633c39 --- /dev/null +++ b/core/modules/search/tests/src/Kernel/SearchDeprecationTest.php @@ -0,0 +1,70 @@ +installSchema('search', [ + 'search_index', + 'search_dataset', + 'search_total', + ]); + $this->installConfig(['search']); + } + + /** + * @expectedDeprecation search_index() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::index() instead. See https://www.drupal.org/node/3075696 + */ + public function testIndex() { + $this->assertNull(search_index('_test_', 1, LanguageInterface::LANGCODE_NOT_SPECIFIED, "foo")); + } + + /** + * @expectedDeprecation search_index_clear() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::clear() instead. See https://www.drupal.org/node/3075696 + */ + public function testClear() { + $this->assertNull(search_index_clear()); + } + + /** + * @expectedDeprecation search_dirty() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696 + */ + public function testDirty() { + $this->assertNull(search_dirty("foo")); + $this->assertEqual([], search_dirty()); + } + + /** + * @expectedDeprecation search_update_totals() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use custom implementation of \Drupal\search\SearchIndexInterface instead. See https://www.drupal.org/node/3075696 + */ + public function testUpdateTotals() { + $this->assertNull(search_update_totals()); + } + + /** + * @expectedDeprecation search_mark_for_reindex() is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\search\SearchIndex::markForReindex() instead. See https://www.drupal.org/node/3075696 + */ + public function testMarkForReindex() { + $this->assertNull(search_mark_for_reindex('_test_', 1, LanguageInterface::LANGCODE_NOT_SPECIFIED)); + } + +} diff --git a/core/modules/search/tests/src/Kernel/SearchMatchTest.php b/core/modules/search/tests/src/Kernel/SearchMatchTest.php index 577028bd4..f0de1ec8c 100644 --- a/core/modules/search/tests/src/Kernel/SearchMatchTest.php +++ b/core/modules/search/tests/src/Kernel/SearchMatchTest.php @@ -5,6 +5,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Language\LanguageInterface; use Drupal\KernelTests\KernelTestBase; +use Drupal\search\SearchIndexInterface; /** * Indexes content and queries it. @@ -49,11 +50,13 @@ public function testMatching() { public function _setup() { $this->config('search.settings')->set('index.minimum_word_size', 3)->save(); + $search_index = \Drupal::service('search.index'); + assert($search_index instanceof SearchIndexInterface); for ($i = 1; $i <= 7; ++$i) { - search_index(static::SEARCH_TYPE, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText($i)); + $search_index->index(static::SEARCH_TYPE, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText($i)); } for ($i = 1; $i <= 5; ++$i) { - search_index(static::SEARCH_TYPE_2, $i + 7, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText2($i)); + $search_index->index(static::SEARCH_TYPE_2, $i + 7, LanguageInterface::LANGCODE_NOT_SPECIFIED, $this->getText2($i)); } // No getText builder function for Japanese text; just a simple array. foreach ([ @@ -61,9 +64,8 @@ public function _setup() { 14 => 'ドルーパルが大好きよ!', 15 => 'コーヒーとケーキ', ] as $i => $jpn) { - search_index(static::SEARCH_TYPE_JPN, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $jpn); + $search_index->index(static::SEARCH_TYPE_JPN, $i, LanguageInterface::LANGCODE_NOT_SPECIFIED, $jpn); } - search_update_totals(); } /** diff --git a/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php b/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php index 543dd9bd5..be8cbfc29 100644 --- a/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php +++ b/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php @@ -28,21 +28,21 @@ class SearchPageRepositoryTest extends UnitTestCase { /** * The entity query object. * - * @var \Drupal\Core\Entity\Query\QueryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\Query\QueryInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $query; /** * The search page storage. * - * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $storage; /** * The config factory. * - * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $configFactory; @@ -50,20 +50,20 @@ class SearchPageRepositoryTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface'); + $this->query = $this->createMock('Drupal\Core\Entity\Query\QueryInterface'); - $this->storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); + $this->storage = $this->createMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); $this->storage->expects($this->any()) ->method('getQuery') ->will($this->returnValue($this->query)); - /** @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject $entity_type_manager */ + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject $entity_type_manager */ $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class); $entity_type_manager->expects($this->any()) ->method('getStorage') ->will($this->returnValue($this->storage)); - $this->configFactory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface'); + $this->configFactory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface'); $this->searchPageRepository = new SearchPageRepository($this->configFactory, $entity_type_manager); } @@ -80,8 +80,8 @@ public function testGetActiveSearchPages() { ->will($this->returnValue(['test' => 'test', 'other_test' => 'other_test'])); $entities = []; - $entities['test'] = $this->getMock('Drupal\search\SearchPageInterface'); - $entities['other_test'] = $this->getMock('Drupal\search\SearchPageInterface'); + $entities['test'] = $this->createMock('Drupal\search\SearchPageInterface'); + $entities['other_test'] = $this->createMock('Drupal\search\SearchPageInterface'); $this->storage->expects($this->once()) ->method('loadMultiple') ->with(['test' => 'test', 'other_test' => 'other_test']) @@ -123,11 +123,11 @@ public function testGetIndexableSearchPages() { ->will($this->returnValue(['test' => 'test', 'other_test' => 'other_test'])); $entities = []; - $entities['test'] = $this->getMock('Drupal\search\SearchPageInterface'); + $entities['test'] = $this->createMock('Drupal\search\SearchPageInterface'); $entities['test']->expects($this->once()) ->method('isIndexable') ->will($this->returnValue(TRUE)); - $entities['other_test'] = $this->getMock('Drupal\search\SearchPageInterface'); + $entities['other_test'] = $this->createMock('Drupal\search\SearchPageInterface'); $entities['other_test']->expects($this->once()) ->method('isIndexable') ->will($this->returnValue(FALSE)); @@ -233,7 +233,7 @@ public function testSetDefaultSearchPage() { ->with('search.settings') ->will($this->returnValue($config)); - $search_page = $this->getMock('Drupal\search\SearchPageInterface'); + $search_page = $this->createMock('Drupal\search\SearchPageInterface'); $search_page->expects($this->once()) ->method('id') ->will($this->returnValue($id)); @@ -250,7 +250,7 @@ public function testSetDefaultSearchPage() { * Tests the sortSearchPages() method. */ public function testSortSearchPages() { - $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface'); $entity_type->expects($this->any()) ->method('getClass') ->will($this->returnValue('Drupal\Tests\search\Unit\TestSearchPage')); diff --git a/core/modules/search/tests/src/Unit/SearchPluginCollectionTest.php b/core/modules/search/tests/src/Unit/SearchPluginCollectionTest.php index 25ab8735a..a96d89d0a 100644 --- a/core/modules/search/tests/src/Unit/SearchPluginCollectionTest.php +++ b/core/modules/search/tests/src/Unit/SearchPluginCollectionTest.php @@ -14,7 +14,7 @@ class SearchPluginCollectionTest extends UnitTestCase { /** * The mocked plugin manager. * - * @var \Drupal\Component\Plugin\PluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Component\Plugin\PluginManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $pluginManager; @@ -36,7 +36,7 @@ class SearchPluginCollectionTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->pluginManager = $this->getMock('Drupal\Component\Plugin\PluginManagerInterface'); + $this->pluginManager = $this->createMock('Drupal\Component\Plugin\PluginManagerInterface'); $this->searchPluginCollection = new SearchPluginCollection($this->pluginManager, 'banana', ['id' => 'banana', 'color' => 'yellow'], 'fruit_stand'); } @@ -44,7 +44,7 @@ protected function setUp() { * Tests the get() method. */ public function testGet() { - $plugin = $this->getMock('Drupal\search\Plugin\SearchInterface'); + $plugin = $this->createMock('Drupal\search\Plugin\SearchInterface'); $this->pluginManager->expects($this->once()) ->method('createInstance') ->will($this->returnValue($plugin)); @@ -55,7 +55,7 @@ public function testGet() { * Tests the get() method with a configurable plugin. */ public function testGetWithConfigurablePlugin() { - $plugin = $this->getMock('Drupal\search\Plugin\ConfigurableSearchPluginInterface'); + $plugin = $this->createMock('Drupal\search\Plugin\ConfigurableSearchPluginInterface'); $plugin->expects($this->once()) ->method('setSearchPageId') ->with('fruit_stand') diff --git a/core/modules/serialization/serialization.info.yml b/core/modules/serialization/serialization.info.yml index f03d1d002..be8d3404c 100644 --- a/core/modules/serialization/serialization.info.yml +++ b/core/modules/serialization/serialization.info.yml @@ -1,12 +1,6 @@ name: Serialization type: module -description: Provides a service for (de)serializing data to/from formats such as JSON and XML +description: 'Provides a service for (de)serializing data to/from formats such as JSON and XML.' package: Web services -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/serialization/src/Tests/NormalizerTestBase.php b/core/modules/serialization/src/Tests/NormalizerTestBase.php index 8cb2b3e73..5fa8675c3 100644 --- a/core/modules/serialization/src/Tests/NormalizerTestBase.php +++ b/core/modules/serialization/src/Tests/NormalizerTestBase.php @@ -9,7 +9,7 @@ /** * Helper base class to set up some test fields for serialization testing. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\serialization\Kernel\NormalizerTestBase instead. */ abstract class NormalizerTestBase extends SerializationNormalizerTestBase {} diff --git a/core/modules/serialization/tests/modules/entity_serialization_test/entity_serialization_test.info.yml b/core/modules/serialization/tests/modules/entity_serialization_test/entity_serialization_test.info.yml index b951e4b1e..451a3748e 100644 --- a/core/modules/serialization/tests/modules/entity_serialization_test/entity_serialization_test.info.yml +++ b/core/modules/serialization/tests/modules/entity_serialization_test/entity_serialization_test.info.yml @@ -2,11 +2,5 @@ name: 'Entity serialization test support' type: module description: 'Provides test support for entity serialization tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.info.yml b/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.info.yml index 1ccffd4b7..4ba215ecb 100644 --- a/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.info.yml +++ b/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.info.yml @@ -2,11 +2,5 @@ name: 'FieldItem normalization test support' type: module description: 'Provides test support for fieldItem normalization test support.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/serialization/tests/modules/test_datatype_boolean_emoji_normalizer/test_datatype_boolean_emoji_normalizer.info.yml b/core/modules/serialization/tests/modules/test_datatype_boolean_emoji_normalizer/test_datatype_boolean_emoji_normalizer.info.yml index 94c579607..0dabae378 100644 --- a/core/modules/serialization/tests/modules/test_datatype_boolean_emoji_normalizer/test_datatype_boolean_emoji_normalizer.info.yml +++ b/core/modules/serialization/tests/modules/test_datatype_boolean_emoji_normalizer/test_datatype_boolean_emoji_normalizer.info.yml @@ -2,11 +2,5 @@ name: 'Test @DataType normalizer' type: module description: 'Provides test support for @DataType-level normalization.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/serialization/tests/modules/test_fieldtype_boolean_emoji_normalizer/test_fieldtype_boolean_emoji_normalizer.info.yml b/core/modules/serialization/tests/modules/test_fieldtype_boolean_emoji_normalizer/test_fieldtype_boolean_emoji_normalizer.info.yml index cbb5a076d..e6fa99918 100644 --- a/core/modules/serialization/tests/modules/test_fieldtype_boolean_emoji_normalizer/test_fieldtype_boolean_emoji_normalizer.info.yml +++ b/core/modules/serialization/tests/modules/test_fieldtype_boolean_emoji_normalizer/test_fieldtype_boolean_emoji_normalizer.info.yml @@ -2,11 +2,5 @@ name: 'Test @FieldType normalizer' type: module description: 'Provides test support for @FieldType-level normalization.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/serialization/tests/serialization_test/serialization_test.info.yml b/core/modules/serialization/tests/serialization_test/serialization_test.info.yml index b345e289c..d6c01304c 100644 --- a/core/modules/serialization/tests/serialization_test/serialization_test.info.yml +++ b/core/modules/serialization/tests/serialization_test/serialization_test.info.yml @@ -2,11 +2,5 @@ name: Serialization test module type: module description: "Support module for serialization tests." package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php index eff749805..fd5702e3e 100644 --- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php +++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php @@ -88,7 +88,7 @@ protected function setUp() { ])->save(); // Create a test user to use as the entity owner. - $this->user = \Drupal::entityManager()->getStorage('user')->create([ + $this->user = \Drupal::entityTypeManager()->getStorage('user')->create([ 'name' => 'serialization_test_user', 'mail' => 'foo@example.com', 'pass' => '123456', @@ -265,7 +265,8 @@ public function testDenormalize() { * Tests denormalizing serialized columns. */ public function testDenormalizeSerializedItem() { - $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedItem).'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedItem).'); $this->serializer->denormalize([ 'serialized' => [ [ @@ -283,7 +284,8 @@ public function testDenormalizeCustomSerializedItem() { $entity = EntitySerializedField::create(['serialized_text' => serialize(['Hello world!'])]); $normalized = $this->serializer->normalize($entity); $this->assertEquals($normalized['serialized_text'][0]['value'], ['Hello world!']); - $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_text" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedPropertyItem).'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_text" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedPropertyItem).'); $this->serializer->denormalize([ 'serialized_text' => [ [ @@ -301,7 +303,8 @@ public function testDenormalizeInvalidCustomSerializedField() { $entity = EntitySerializedField::create(['serialized_long' => serialize(['Hello world!'])]); $normalized = $this->serializer->normalize($entity); $this->assertEquals($normalized['serialized_long'][0]['value'], ['Hello world!']); - $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); $this->serializer->denormalize([ 'serialized_long' => [ [ @@ -342,7 +345,8 @@ public function testDenormalizeValidCustomSerializedField() { * Tests normalizing/denormalizing using string values. */ public function testDenormalizeStringValue() { - $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); $this->serializer->denormalize([ 'serialized_long' => ['boo'], 'type' => 'entity_test_serialized_field', diff --git a/core/modules/serialization/tests/src/Kernel/FieldItemSerializationTest.php b/core/modules/serialization/tests/src/Kernel/FieldItemSerializationTest.php index 9b9888d78..0933bb442 100644 --- a/core/modules/serialization/tests/src/Kernel/FieldItemSerializationTest.php +++ b/core/modules/serialization/tests/src/Kernel/FieldItemSerializationTest.php @@ -138,7 +138,8 @@ public function testFieldNormalizeDenormalize() { * Tests denormalizing using a scalar field value. */ public function testFieldDenormalizeWithScalarValue() { - $this->setExpectedException(UnexpectedValueException::class, 'Field values for "uuid" must use an array structure'); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Field values for "uuid" must use an array structure'); $normalized = $this->serializer->normalize($this->entity, 'json'); diff --git a/core/modules/serialization/tests/src/Unit/Encoder/XmlEncoderTest.php b/core/modules/serialization/tests/src/Unit/Encoder/XmlEncoderTest.php index cbff14a9b..170366046 100644 --- a/core/modules/serialization/tests/src/Unit/Encoder/XmlEncoderTest.php +++ b/core/modules/serialization/tests/src/Unit/Encoder/XmlEncoderTest.php @@ -22,7 +22,7 @@ class XmlEncoderTest extends UnitTestCase { protected $encoder; /** - * @var \Symfony\Component\Serializer\Encoder\XmlEncoder|\PHPUnit_Framework_MockObject_MockObject + * @var \Symfony\Component\Serializer\Encoder\XmlEncoder|\PHPUnit\Framework\MockObject\MockObject */ protected $baseEncoder; @@ -34,7 +34,7 @@ class XmlEncoderTest extends UnitTestCase { protected $testArray = ['test' => 'test']; protected function setUp() { - $this->baseEncoder = $this->getMock(BaseXmlEncoder::class); + $this->baseEncoder = $this->createMock(BaseXmlEncoder::class); $this->encoder = new XmlEncoder(); $this->encoder->setBaseEncoder($this->baseEncoder); } diff --git a/core/modules/serialization/tests/src/Unit/EntityResolver/ChainEntityResolverTest.php b/core/modules/serialization/tests/src/Unit/EntityResolver/ChainEntityResolverTest.php index 64e73b693..3dbe73ebc 100644 --- a/core/modules/serialization/tests/src/Unit/EntityResolver/ChainEntityResolverTest.php +++ b/core/modules/serialization/tests/src/Unit/EntityResolver/ChainEntityResolverTest.php @@ -14,7 +14,7 @@ class ChainEntityResolverTest extends UnitTestCase { /** * A mocked normalizer. * - * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $testNormalizer; @@ -36,7 +36,7 @@ class ChainEntityResolverTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->testNormalizer = $this->getMock('Symfony\Component\Serializer\Normalizer\NormalizerInterface'); + $this->testNormalizer = $this->createMock('Symfony\Component\Serializer\Normalizer\NormalizerInterface'); $this->testData = new \stdClass(); } @@ -130,11 +130,11 @@ public function testResolverWithResolvedToZero() { * @param bool $called * Whether or not the resolve method is expected to be called. * - * @return \Drupal\serialization\EntityResolver\EntityResolverInterface|\PHPUnit_Framework_MockObject_MockObject + * @return \Drupal\serialization\EntityResolver\EntityResolverInterface|\PHPUnit\Framework\MockObject\MockObject * The mocked entity resolver. */ protected function createEntityResolverMock($return = NULL, $called = TRUE) { - $mock = $this->getMock('Drupal\serialization\EntityResolver\EntityResolverInterface'); + $mock = $this->createMock('Drupal\serialization\EntityResolver\EntityResolverInterface'); if ($called) { $mock->expects($this->once()) diff --git a/core/modules/serialization/tests/src/Unit/EntityResolver/UuidResolverTest.php b/core/modules/serialization/tests/src/Unit/EntityResolver/UuidResolverTest.php index 90856ff7a..a9cc71f98 100644 --- a/core/modules/serialization/tests/src/Unit/EntityResolver/UuidResolverTest.php +++ b/core/modules/serialization/tests/src/Unit/EntityResolver/UuidResolverTest.php @@ -22,7 +22,7 @@ class UuidResolverTest extends UnitTestCase { /** * The mock entity repository service. * - * @var \Drupal\Core\Entity\EntityRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityRepository; @@ -42,7 +42,7 @@ public function testResolveNotInInterface() { $this->entityRepository->expects($this->never()) ->method('loadEntityByUuid'); - $normalizer = $this->getMock('Symfony\Component\Serializer\Normalizer\NormalizerInterface'); + $normalizer = $this->createMock('Symfony\Component\Serializer\Normalizer\NormalizerInterface'); $this->assertNull($this->resolver->resolve($normalizer, [], 'test_type')); } @@ -53,7 +53,7 @@ public function testResolveNoUuid() { $this->entityRepository->expects($this->never()) ->method('loadEntityByUuid'); - $normalizer = $this->getMock('Drupal\serialization\EntityResolver\UuidReferenceInterface'); + $normalizer = $this->createMock('Drupal\serialization\EntityResolver\UuidReferenceInterface'); $normalizer->expects($this->once()) ->method('getUuid') ->with([]) @@ -72,7 +72,7 @@ public function testResolveNoEntity() { ->with('test_type') ->will($this->returnValue(NULL)); - $normalizer = $this->getMock('Drupal\serialization\EntityResolver\UuidReferenceInterface'); + $normalizer = $this->createMock('Drupal\serialization\EntityResolver\UuidReferenceInterface'); $normalizer->expects($this->once()) ->method('getUuid') ->with([]) @@ -87,7 +87,7 @@ public function testResolveNoEntity() { public function testResolveWithEntity() { $uuid = '392eab92-35c2-4625-872d-a9dab4da008e'; - $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity = $this->createMock('Drupal\Core\Entity\EntityInterface'); $entity->expects($this->once()) ->method('id') ->will($this->returnValue(1)); @@ -97,7 +97,7 @@ public function testResolveWithEntity() { ->with('test_type', $uuid) ->will($this->returnValue($entity)); - $normalizer = $this->getMock('Drupal\serialization\EntityResolver\UuidReferenceInterface'); + $normalizer = $this->createMock('Drupal\serialization\EntityResolver\UuidReferenceInterface'); $normalizer->expects($this->once()) ->method('getUuid') ->with([]) diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/ConfigEntityNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/ConfigEntityNormalizerTest.php index 98e818a3d..36c970b57 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/ConfigEntityNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/ConfigEntityNormalizerTest.php @@ -39,7 +39,7 @@ public function testNormalize() { $entity_field_manager ); - $config_entity = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityInterface'); + $config_entity = $this->createMock('Drupal\Core\Config\Entity\ConfigEntityInterface'); $config_entity->expects($this->once()) ->method('toArray') ->will($this->returnValue($test_export_properties)); diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/ContentEntityNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/ContentEntityNormalizerTest.php index 6a58f6deb..f11971e73 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/ContentEntityNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/ContentEntityNormalizerTest.php @@ -20,7 +20,7 @@ class ContentEntityNormalizerTest extends UnitTestCase { /** * The mock serializer. * - * @var \Symfony\Component\Serializer\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Symfony\Component\Serializer\SerializerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $serializer; @@ -52,8 +52,8 @@ protected function setUp() { * @covers ::supportsNormalization */ public function testSupportsNormalization() { - $content_mock = $this->getMock('Drupal\Core\Entity\ContentEntityInterface'); - $config_mock = $this->getMock('Drupal\Core\Entity\ConfigEntityInterface'); + $content_mock = $this->createMock('Drupal\Core\Entity\ContentEntityInterface'); + $config_mock = $this->createMock('Drupal\Core\Config\Entity\ConfigEntityInterface'); $this->assertTrue($this->contentEntityNormalizer->supportsNormalization($content_mock)); $this->assertFalse($this->contentEntityNormalizer->supportsNormalization($config_mock)); } @@ -92,7 +92,7 @@ public function testNormalize() { * @covers ::normalize */ public function testNormalizeWithAccountContext() { - $mock_account = $this->getMock('Drupal\Core\Session\AccountInterface'); + $mock_account = $this->createMock('Drupal\Core\Session\AccountInterface'); $context = [ 'account' => $mock_account, @@ -123,7 +123,7 @@ public function testNormalizeWithAccountContext() { * * @param $definitions * - * @return \PHPUnit_Framework_MockObject_MockObject + * @return \PHPUnit\Framework\MockObject\MockObject */ public function createMockForContentEntity($definitions) { $content_entity_mock = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase') @@ -148,11 +148,11 @@ public function createMockForContentEntity($definitions) { * @param bool $internal * @param \Drupal\Core\Session\AccountInterface $user_context * - * @return \Drupal\Core\Field\FieldItemListInterface|\PHPUnit_Framework_MockObject_MockObject + * @return \Drupal\Core\Field\FieldItemListInterface|\PHPUnit\Framework\MockObject\MockObject */ protected function createMockFieldListItem($access, $internal, AccountInterface $user_context = NULL) { $data_definition = $this->prophesize(DataDefinitionInterface::class); - $mock = $this->getMock('Drupal\Core\Field\FieldItemListInterface'); + $mock = $this->createMock('Drupal\Core\Field\FieldItemListInterface'); $mock->expects($this->once()) ->method('getDataDefinition') ->will($this->returnValue($data_definition->reveal())); diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeIso8601NormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeIso8601NormalizerTest.php index 188d0b54c..bcb6a3490 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeIso8601NormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeIso8601NormalizerTest.php @@ -231,7 +231,8 @@ public function testDenormalizeDateAndTimeDeprecatedFormat() { * @covers ::denormalize */ public function testDenormalizeDateOnlyException() { - $this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06" is not in an accepted format: "Y-m-d" (date-only).'); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('The specified date "2016/11/06" is not in an accepted format: "Y-m-d" (date-only).'); $normalized = '2016/11/06'; @@ -246,7 +247,8 @@ public function testDenormalizeDateOnlyException() { * @covers ::denormalize */ public function testDenormalizeDateAndTimeException() { - $this->setExpectedException(UnexpectedValueException::class, 'The specified date "on a rainy day" is not in an accepted format: "Y-m-d\TH:i:sP" (RFC 3339), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:s" (backward compatibility — deprecated).'); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('The specified date "on a rainy day" is not in an accepted format: "Y-m-d\TH:i:sP" (RFC 3339), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:s" (backward compatibility — deprecated).'); $normalized = 'on a rainy day'; @@ -261,7 +263,8 @@ public function testDenormalizeDateAndTimeException() { * @covers ::denormalize */ public function testDenormalizeNoTargetInstanceOrFieldDefinitionException() { - $this->setExpectedException(InvalidArgumentException::class, '$context[\'target_instance\'] or $context[\'field_definition\'] must be set to denormalize with the DateTimeIso8601Normalizer'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$context[\'target_instance\'] or $context[\'field_definition\'] must be set to denormalize with the DateTimeIso8601Normalizer'); $this->normalizer->denormalize('', DateTimeIso8601::class, NULL, []); } diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeNormalizerTest.php index 8914186e2..e6b6c788e 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeNormalizerTest.php @@ -166,7 +166,8 @@ public function providerTestDenormalizeUserFormats() { * @covers ::denormalize */ public function testDenormalizeException() { - $this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "Y-m-d\TH:i:sP" (RFC 3339), "Y-m-d\TH:i:sO" (ISO 8601).'); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "Y-m-d\TH:i:sP" (RFC 3339), "Y-m-d\TH:i:sO" (ISO 8601).'); $normalized = '2016/11/06 09:02am GMT'; diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php index 1188e4489..9d760fb79 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php @@ -20,28 +20,28 @@ class EntityNormalizerTest extends UnitTestCase { /** * The mock entity field manager. * - * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityFieldManager; /** * The mock entity type manager. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeManager; /** * The mock entity type repository. * - * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $entityTypeRepository; /** * The mock serializer. * - * @var \Symfony\Component\Serializer\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Symfony\Component\Serializer\SerializerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $serializer; @@ -73,8 +73,8 @@ protected function setUp() { * @covers ::normalize */ public function testNormalize() { - $list_item_1 = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); - $list_item_2 = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); + $list_item_1 = $this->createMock('Drupal\Core\TypedData\TypedDataInterface'); + $list_item_2 = $this->createMock('Drupal\Core\TypedData\TypedDataInterface'); $definitions = [ 'field_1' => $list_item_1, @@ -111,7 +111,7 @@ public function testNormalize() { * @covers ::denormalize */ public function testDenormalizeWithNoEntityType() { - $this->setExpectedException(UnexpectedValueException::class); + $this->expectException(UnexpectedValueException::class); $this->entityNormalizer->denormalize([], 'Drupal\Core\Entity\ContentEntityBase'); } @@ -129,7 +129,7 @@ public function testDenormalizeWithValidBundle() { ], ]; - $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface'); $entity_type->expects($this->once()) ->method('id') @@ -151,12 +151,12 @@ public function testDenormalizeWithValidBundle() { ->method('getBundleEntityType') ->will($this->returnValue('test_bundle')); - $entity_type_storage_definition = $this->getmock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $entity_type_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); $entity_type_storage_definition->expects($this->once()) ->method('getMainPropertyName') ->will($this->returnValue('name')); - $entity_type_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $entity_type_definition = $this->createMock('Drupal\Core\Field\FieldDefinitionInterface'); $entity_type_definition->expects($this->once()) ->method('getFieldStorageDefinition') ->will($this->returnValue($entity_type_storage_definition)); @@ -174,12 +174,12 @@ public function testDenormalizeWithValidBundle() { ->with('test') ->will($this->returnValue($base_definitions)); - $entity_query_mock = $this->getMock('Drupal\Core\Entity\Query\QueryInterface'); + $entity_query_mock = $this->createMock('Drupal\Core\Entity\Query\QueryInterface'); $entity_query_mock->expects($this->once()) ->method('execute') ->will($this->returnValue(['test_bundle' => 'test_bundle'])); - $entity_type_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $entity_type_storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $entity_type_storage->expects($this->once()) ->method('getQuery') ->will($this->returnValue($entity_query_mock)); @@ -189,10 +189,10 @@ public function testDenormalizeWithValidBundle() { ->with('test_bundle') ->will($this->returnValue($entity_type_storage)); - $key_1 = $this->getMock(FieldItemListInterface::class); - $key_2 = $this->getMock(FieldItemListInterface::class); + $key_1 = $this->createMock(FieldItemListInterface::class); + $key_2 = $this->createMock(FieldItemListInterface::class); - $entity = $this->getMock(FieldableEntityInterface::class); + $entity = $this->createMock(FieldableEntityInterface::class); $entity->expects($this->at(0)) ->method('get') ->with('key_1') @@ -202,7 +202,7 @@ public function testDenormalizeWithValidBundle() { ->with('key_2') ->willReturn($key_2); - $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); // Create should only be called with the bundle property at first. $expected_test_data = [ 'test_type' => 'test_bundle', @@ -250,7 +250,7 @@ public function testDenormalizeWithInvalidBundle() { ], ]; - $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface'); $entity_type->expects($this->once()) ->method('id') @@ -272,12 +272,12 @@ public function testDenormalizeWithInvalidBundle() { ->method('getBundleEntityType') ->will($this->returnValue('test_bundle')); - $entity_type_storage_definition = $this->getmock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $entity_type_storage_definition = $this->createMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); $entity_type_storage_definition->expects($this->once()) ->method('getMainPropertyName') ->will($this->returnValue('name')); - $entity_type_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $entity_type_definition = $this->createMock('Drupal\Core\Field\FieldDefinitionInterface'); $entity_type_definition->expects($this->once()) ->method('getFieldStorageDefinition') ->will($this->returnValue($entity_type_storage_definition)); @@ -295,12 +295,12 @@ public function testDenormalizeWithInvalidBundle() { ->with('test') ->will($this->returnValue($base_definitions)); - $entity_query_mock = $this->getMock('Drupal\Core\Entity\Query\QueryInterface'); + $entity_query_mock = $this->createMock('Drupal\Core\Entity\Query\QueryInterface'); $entity_query_mock->expects($this->once()) ->method('execute') ->will($this->returnValue(['test_bundle_other' => 'test_bundle_other'])); - $entity_type_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $entity_type_storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $entity_type_storage->expects($this->once()) ->method('getQuery') ->will($this->returnValue($entity_query_mock)); @@ -310,7 +310,7 @@ public function testDenormalizeWithInvalidBundle() { ->with('test_bundle') ->will($this->returnValue($entity_type_storage)); - $this->setExpectedException(UnexpectedValueException::class); + $this->expectException(UnexpectedValueException::class); $this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']); } @@ -325,7 +325,7 @@ public function testDenormalizeWithNoBundle() { 'key_2' => 'value_2', ]; - $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface'); $entity_type->expects($this->once()) ->method('entityClassImplements') ->with(FieldableEntityInterface::class) @@ -342,10 +342,10 @@ public function testDenormalizeWithNoBundle() { ->with('test') ->will($this->returnValue($entity_type)); - $key_1 = $this->getMock(FieldItemListInterface::class); - $key_2 = $this->getMock(FieldItemListInterface::class); + $key_1 = $this->createMock(FieldItemListInterface::class); + $key_2 = $this->createMock(FieldItemListInterface::class); - $entity = $this->getMock(FieldableEntityInterface::class); + $entity = $this->createMock(FieldableEntityInterface::class); $entity->expects($this->at(0)) ->method('get') ->with('key_1') @@ -355,7 +355,7 @@ public function testDenormalizeWithNoBundle() { ->with('key_2') ->willReturn($key_2); - $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $storage->expects($this->once()) ->method('create') ->with([]) @@ -398,7 +398,7 @@ public function testDenormalizeWithNoFieldableEntityType() { 'key_2' => 'value_2', ]; - $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface'); $entity_type->expects($this->once()) ->method('entityClassImplements') ->with(FieldableEntityInterface::class) @@ -412,11 +412,11 @@ public function testDenormalizeWithNoFieldableEntityType() { ->with('test') ->will($this->returnValue($entity_type)); - $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $storage->expects($this->once()) ->method('create') ->with($test_data) - ->will($this->returnValue($this->getMock('Drupal\Core\Entity\EntityInterface'))); + ->will($this->returnValue($this->createMock('Drupal\Core\Entity\EntityInterface'))); $this->entityTypeManager->expects($this->once()) ->method('getStorage') diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php index 581f43181..04a9c5cbf 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php @@ -349,7 +349,8 @@ public function testDenormalizeWithUuidWithoutType() { * @covers ::denormalize */ public function testDenormalizeWithUuidWithIncorrectType() { - $this->setExpectedException(UnexpectedValueException::class, 'The field "field_reference" property "target_type" must be set to "test_type" or omitted.'); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('The field "field_reference" property "target_type" must be set to "test_type" or omitted.'); $data = [ 'target_id' => 'test', @@ -369,7 +370,8 @@ public function testDenormalizeWithUuidWithIncorrectType() { * @covers ::denormalize */ public function testDenormalizeWithTypeWithIncorrectUuid() { - $this->setExpectedException(InvalidArgumentException::class, 'No "test_type" entity found with UUID "unique-but-none-non-existent" for field "field_reference"'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('No "test_type" entity found with UUID "unique-but-none-non-existent" for field "field_reference"'); $data = [ 'target_id' => 'test', @@ -392,7 +394,8 @@ public function testDenormalizeWithTypeWithIncorrectUuid() { * @covers ::denormalize */ public function testDenormalizeWithEmtpyUuid() { - $this->setExpectedException(InvalidArgumentException::class, 'If provided "target_uuid" cannot be empty for field "test_type".'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('If provided "target_uuid" cannot be empty for field "test_type".'); $data = [ 'target_id' => 'test', diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php index fccc6dbaa..e74ffba9b 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php @@ -25,7 +25,7 @@ class ListNormalizerTest extends UnitTestCase { /** * The mock list instance. * - * @var \Drupal\Core\TypedData\ListInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\TypedData\ListInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $list; @@ -39,14 +39,14 @@ class ListNormalizerTest extends UnitTestCase { /** * The mocked typed data. * - * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\TypedData\TypedDataInterface + * @var \PHPUnit\Framework\MockObject\MockObject|\Drupal\Core\TypedData\TypedDataInterface */ protected $typedData; protected function setUp() { // Mock the TypedDataManager to return a TypedDataInterface mock. - $this->typedData = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); - $typed_data_manager = $this->getMock(TypedDataManagerInterface::class); + $this->typedData = $this->createMock('Drupal\Core\TypedData\TypedDataInterface'); + $typed_data_manager = $this->createMock(TypedDataManagerInterface::class); $typed_data_manager->expects($this->any()) ->method('getPropertyInstance') ->will($this->returnValue($this->typedData)); diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/NullNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/NullNormalizerTest.php index 34a28b208..ff3b9861b 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/NullNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/NullNormalizerTest.php @@ -37,7 +37,7 @@ protected function setUp() { * @covers ::supportsNormalization */ public function testSupportsNormalization() { - $mock = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); + $mock = $this->createMock('Drupal\Core\TypedData\TypedDataInterface'); $this->assertTrue($this->normalizer->supportsNormalization($mock)); // Also test that an object not implementing TypedDataInterface fails. $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); @@ -47,7 +47,7 @@ public function testSupportsNormalization() { * @covers ::normalize */ public function testNormalize() { - $mock = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); + $mock = $this->createMock('Drupal\Core\TypedData\TypedDataInterface'); $this->assertNull($this->normalizer->normalize($mock)); } diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampNormalizerTest.php index e7ddb8656..81c456d67 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampNormalizerTest.php @@ -122,7 +122,8 @@ public function providerTestDenormalizeValidFormats() { * @covers ::denormalize */ public function testDenormalizeException() { - $this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "U" (UNIX timestamp), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:sP" (RFC 3339).'); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "U" (UNIX timestamp), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:sP" (RFC 3339).'); $normalized = '2016/11/06 09:02am GMT'; diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/TypedDataNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/TypedDataNormalizerTest.php index 6a31e2ea7..0a899616b 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/TypedDataNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/TypedDataNormalizerTest.php @@ -21,13 +21,13 @@ class TypedDataNormalizerTest extends UnitTestCase { /** * The mock typed data instance. * - * @var \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $typedData; protected function setUp() { $this->normalizer = new TypedDataNormalizer(); - $this->typedData = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); + $this->typedData = $this->createMock('Drupal\Core\TypedData\TypedDataInterface'); } /** diff --git a/core/modules/settings_tray/settings_tray.info.yml b/core/modules/settings_tray/settings_tray.info.yml index e3e80ef42..8747d17af 100644 --- a/core/modules/settings_tray/settings_tray.info.yml +++ b/core/modules/settings_tray/settings_tray.info.yml @@ -2,15 +2,9 @@ name: 'Settings Tray' type: module description: 'Allows users to directly edit the configuration of blocks on the current page.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:block - drupal:toolbar - drupal:contextual - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/settings_tray/tests/modules/settings_tray_override_test/settings_tray_override_test.info.yml b/core/modules/settings_tray/tests/modules/settings_tray_override_test/settings_tray_override_test.info.yml index 4604507f6..551451800 100644 --- a/core/modules/settings_tray/tests/modules/settings_tray_override_test/settings_tray_override_test.info.yml +++ b/core/modules/settings_tray/tests/modules/settings_tray_override_test/settings_tray_override_test.info.yml @@ -1,13 +1,7 @@ name: 'Configuration override test for Settings Tray' type: module package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:settings_tray - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/settings_tray/tests/modules/settings_tray_test/settings_tray_test.info.yml b/core/modules/settings_tray/tests/modules/settings_tray_test/settings_tray_test.info.yml index 84e638488..e8818867e 100644 --- a/core/modules/settings_tray/tests/modules/settings_tray_test/settings_tray_test.info.yml +++ b/core/modules/settings_tray/tests/modules/settings_tray_test/settings_tray_test.info.yml @@ -2,14 +2,8 @@ name: 'Settings Tray Test' type: module description: 'Provides Settings Tray test functionality.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:block - drupal:settings_tray - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/settings_tray/tests/modules/settings_tray_test_css/css/css_fix.theme.css b/core/modules/settings_tray/tests/modules/settings_tray_test_css/css/css_fix.theme.css index e07c95194..58ef85ec6 100644 --- a/core/modules/settings_tray/tests/modules/settings_tray_test_css/css/css_fix.theme.css +++ b/core/modules/settings_tray/tests/modules/settings_tray_test_css/css/css_fix.theme.css @@ -2,26 +2,3 @@ .dialog-off-canvas-main-canvas.js-settings-tray-edit-mode input { pointer-events: inherit !important; } -/** - * Remove all transitions for testing. - */ -* { - /* CSS transitions. */ - -o-transition-property: none !important; - -moz-transition-property: none !important; - -ms-transition-property: none !important; - -webkit-transition-property: none !important; - transition-property: none !important; - /* CSS transforms. */ - -o-transform: none !important; - -moz-transform: none !important; - -ms-transform: none !important; - -webkit-transform: none !important; - transform: none !important; - /* CSS animations. */ - -webkit-animation: none !important; - -moz-animation: none !important; - -o-animation: none !important; - -ms-animation: none !important; - animation: none !important; -} diff --git a/core/modules/settings_tray/tests/modules/settings_tray_test_css/settings_tray_test_css.info.yml b/core/modules/settings_tray/tests/modules/settings_tray_test_css/settings_tray_test_css.info.yml index 7a53e8947..edc5a9dde 100644 --- a/core/modules/settings_tray/tests/modules/settings_tray_test_css/settings_tray_test_css.info.yml +++ b/core/modules/settings_tray/tests/modules/settings_tray_test_css/settings_tray_test_css.info.yml @@ -2,13 +2,7 @@ name: 'CSS Test fix' type: module description: 'Provides CSS fixes for tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:settings_tray - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/settings_tray/tests/src/Functional/SettingsTrayTest.php b/core/modules/settings_tray/tests/src/Functional/SettingsTrayTest.php index aec0392de..2aa98363c 100644 --- a/core/modules/settings_tray/tests/src/Functional/SettingsTrayTest.php +++ b/core/modules/settings_tray/tests/src/Functional/SettingsTrayTest.php @@ -20,6 +20,11 @@ class SettingsTrayTest extends BrowserTestBase { 'settings_tray_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Gets the block CSS selector. * diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/ConfigAccessTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/ConfigAccessTest.php index 6b15be942..c3e604a29 100644 --- a/core/modules/settings_tray/tests/src/FunctionalJavascript/ConfigAccessTest.php +++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/ConfigAccessTest.php @@ -20,6 +20,11 @@ class ConfigAccessTest extends SettingsTrayTestBase { 'menu_ui', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/OverriddenConfigurationTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/OverriddenConfigurationTest.php index 97cd4db18..ec8afac59 100644 --- a/core/modules/settings_tray/tests/src/FunctionalJavascript/OverriddenConfigurationTest.php +++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/OverriddenConfigurationTest.php @@ -22,6 +22,11 @@ class OverriddenConfigurationTest extends SettingsTrayTestBase { 'menu_link_content', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php index a543baf12..6ee2e51fd 100644 --- a/core/modules/settings_tray/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php +++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/QuickEditIntegrationTest.php @@ -22,6 +22,11 @@ class QuickEditIntegrationTest extends SettingsTrayTestBase { 'quickedit', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php index a5db109f3..bad7a75d6 100644 --- a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php +++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php @@ -23,6 +23,11 @@ class SettingsTrayBlockFormTest extends SettingsTrayTestBase { 'settings_tray_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -79,14 +84,14 @@ protected function doTestBlocks($theme, $block_plugin, $new_page_text, $element_ if ($element = $page->find('css', "#toolbar-administration a.is-active")) { // If a tray was open from page load close it. $element->click(); - $this->waitForNoElement("#toolbar-administration a.is-active"); + $web_assert->assertNoElementAfterWait('css', "#toolbar-administration a.is-active"); } $page->find('css', $toolbar_item)->click(); $this->assertElementVisibleAfterWait('css', "{$toolbar_item}.is-active"); } $this->enableEditMode(); if (isset($toolbar_item)) { - $this->waitForNoElement("{$toolbar_item}.is-active"); + $web_assert->assertNoElementAfterWait('css', "{$toolbar_item}.is-active"); } $this->openBlockForm($block_selector); switch ($block_plugin) { @@ -144,7 +149,7 @@ protected function doTestBlocks($theme, $block_plugin, $new_page_text, $element_ $this->getSession()->getPage()->find('css', static::TOOLBAR_EDIT_LINK_SELECTOR)->mouseOver(); $this->assertEditModeDisabled(); $this->assertNotEmpty($web_assert->waitForElement('css', '#drupal-live-announce:contains(Exited edit mode)')); - $this->waitForNoElement('.contextual-toolbar-tab button:contains(Editing)'); + $web_assert->assertNoElementAfterWait('css', '.contextual-toolbar-tab button:contains(Editing)'); $web_assert->elementAttributeNotContains('css', '.dialog-off-canvas-main-canvas', 'class', 'js-settings-tray-edit-mode'); // Clean up test data so each test does not impact the next. diff --git a/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php b/core/modules/settings_tray/tests/src/Kernel/BcRoutesTest.php similarity index 75% rename from core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php rename to core/modules/settings_tray/tests/src/Kernel/BcRoutesTest.php index 6750b1c6c..7bc4b5374 100644 --- a/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php +++ b/core/modules/settings_tray/tests/src/Kernel/BcRoutesTest.php @@ -1,9 +1,10 @@ getStorage('user_role')->loadByProperties(['is_admin' => TRUE]); + $roles = \Drupal::entityTypeManager()->getStorage('user_role')->loadByProperties(['is_admin' => TRUE]); $user_admin_roles = array_intersect(array_keys($roles), $account->getRoles()); if ($user_admin_roles) { return 'admin-shortcuts'; diff --git a/core/modules/shortcut/shortcut.info.yml b/core/modules/shortcut/shortcut.info.yml index 521057ce9..8546317e1 100644 --- a/core/modules/shortcut/shortcut.info.yml +++ b/core/modules/shortcut/shortcut.info.yml @@ -2,14 +2,8 @@ name: Shortcut type: module description: 'Allows users to manage customizable lists of shortcut links.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: entity.shortcut_set.collection dependencies: - drupal:link - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/shortcut/shortcut.install b/core/modules/shortcut/shortcut.install index 21ebf5194..ba1c70055 100644 --- a/core/modules/shortcut/shortcut.install +++ b/core/modules/shortcut/shortcut.install @@ -52,8 +52,13 @@ function shortcut_schema() { function shortcut_install() { // Theme settings are not configuration entities and cannot depend on modules // so to set a module-specific setting, we need to set it with logic. - if (\Drupal::service('theme_handler')->themeExists('seven')) { - \Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save(TRUE); + foreach (['seven', 'claro'] as $theme) { + if (\Drupal::service('theme_handler')->themeExists($theme)) { + \Drupal::configFactory() + ->getEditable("$theme.settings") + ->set('third_party_settings.shortcut.module_link', TRUE) + ->save(TRUE); + } } } @@ -63,7 +68,12 @@ function shortcut_install() { function shortcut_uninstall() { // Theme settings are not configuration entities and cannot depend on modules // so to unset a module-specific setting, we need to unset it with logic. - if (\Drupal::service('theme_handler')->themeExists('seven')) { - \Drupal::configFactory()->getEditable('seven.settings')->clear('third_party_settings.shortcut')->save(TRUE); + foreach (['seven', 'claro'] as $theme) { + if (\Drupal::service('theme_handler')->themeExists($theme)) { + \Drupal::configFactory() + ->getEditable("$theme.settings") + ->clear('third_party_settings.shortcut') + ->save(TRUE); + } } } diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 3941eb362..f6da17897 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -122,11 +122,11 @@ function shortcut_set_switch_access($account = NULL) { * @param $account * A user account that will be assigned to use the set. * - * @deprecated in Drupal 8.x, will be removed before Drupal 9.0. - * Use \Drupal::entityManager()->getStorage('shortcut_set')->assignUser(). + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. + * Use \Drupal::entityTypeManager()->getStorage('shortcut_set')->assignUser(). */ function shortcut_set_assign_user($shortcut_set, $account) { - \Drupal::entityManager() + \Drupal::entityTypeManager() ->getStorage('shortcut_set') ->assignUser($shortcut_set, $account); } @@ -144,11 +144,11 @@ function shortcut_set_assign_user($shortcut_set, $account) { * successfully removed from it. FALSE if the user was already not assigned * to any set. * - * @deprecated in Drupal 8.x, will be removed before Drupal 9.0. - * Use \Drupal::entityManager()->getStorage('shortcut_set')->unassignUser(). + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. + * Use \Drupal::entityTypeManager()->getStorage('shortcut_set')->unassignUser(). */ function shortcut_set_unassign_user($account) { - return (bool) \Drupal::entityManager() + return (bool) \Drupal::entityTypeManager() ->getStorage('shortcut_set') ->unassignUser($account); } @@ -177,7 +177,7 @@ function shortcut_current_displayed_set($account = NULL) { } // If none was found, try to find a shortcut set that is explicitly assigned // to this user. - $shortcut_set_name = \Drupal::entityManager() + $shortcut_set_name = \Drupal::entityTypeManager() ->getStorage('shortcut_set') ->getAssignedToUser($account); if ($shortcut_set_name) { @@ -233,7 +233,7 @@ function shortcut_default_set($account = NULL) { * @return * TRUE if a shortcut set with that title exists; FALSE otherwise. * - * @deprecated in Drupal 8.x, will be removed before Drupal 9.0. + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. */ function shortcut_set_title_exists($title) { $sets = ShortcutSet::loadMultiple(); @@ -325,7 +325,7 @@ function shortcut_preprocess_page_title(&$variables) { $shortcut_set = shortcut_current_displayed_set(); // Check if $link is already a shortcut and set $link_mode accordingly. - $shortcuts = \Drupal::entityManager()->getStorage('shortcut')->loadByProperties(['shortcut_set' => $shortcut_set->id()]); + $shortcuts = \Drupal::entityTypeManager()->getStorage('shortcut')->loadByProperties(['shortcut_set' => $shortcut_set->id()]); /** @var \Drupal\shortcut\ShortcutInterface $shortcut */ foreach ($shortcuts as $shortcut) { if (($shortcut_url = $shortcut->getUrl()) && $shortcut_url->isRouted() && $shortcut_url->getRouteName() == $route_match->getRouteName() && $shortcut_url->getRouteParameters() == $route_match->getRawParameters()->all()) { @@ -435,11 +435,13 @@ function shortcut_toolbar() { * Implements hook_themes_installed(). */ function shortcut_themes_installed($theme_list) { - if (in_array('seven', $theme_list)) { - // Theme settings are not configuration entities and cannot depend on modules - // so to set a module-specific setting, we need to set it with logic. - if (\Drupal::moduleHandler()->moduleExists('shortcut')) { - \Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save(TRUE); + // Theme settings are not configuration entities and cannot depend on modules + // so to set a module-specific setting, we need to set it with logic. + foreach (['seven', 'claro'] as $theme) { + if (in_array($theme, $theme_list, TRUE)) { + \Drupal::configFactory()->getEditable("$theme.settings") + ->set('third_party_settings.shortcut.module_link', TRUE) + ->save(TRUE); } } } diff --git a/core/modules/shortcut/shortcut.routing.yml b/core/modules/shortcut/shortcut.routing.yml index 113335756..0bf0a3bd2 100644 --- a/core/modules/shortcut/shortcut.routing.yml +++ b/core/modules/shortcut/shortcut.routing.yml @@ -33,7 +33,7 @@ entity.shortcut_set.edit_form: shortcut.link_add_inline: path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}/add-link-inline' defaults: - _controller: 'Drupal\shortcut\Controller\ShortcutSetController::addShortcutLinkInline' + _controller: '\Drupal\shortcut\Controller\ShortcutSetController::addShortcutLinkInline' requirements: _entity_access: 'shortcut_set.update' _csrf_token: 'TRUE' @@ -75,7 +75,7 @@ entity.shortcut.edit_form: entity.shortcut.link_delete_inline: path: '/admin/config/user-interface/shortcut/link/{shortcut}/delete-inline' defaults: - _controller: 'Drupal\shortcut\Controller\ShortcutController::deleteShortcutLinkInline' + _controller: '\Drupal\shortcut\Controller\ShortcutController::deleteShortcutLinkInline' requirements: _entity_access: 'shortcut.delete' _csrf_token: 'TRUE' @@ -93,10 +93,10 @@ entity.shortcut.delete_form: shortcut.set_switch: path: '/user/{user}/shortcuts' defaults: - _form: 'Drupal\shortcut\Form\SwitchShortcutSet' + _form: '\Drupal\shortcut\Form\SwitchShortcutSet' _title: 'Shortcuts' requirements: - _custom_access: 'Drupal\shortcut\Form\SwitchShortcutSet::checkAccess' + _custom_access: '\Drupal\shortcut\Form\SwitchShortcutSet::checkAccess' options: _admin_route: TRUE user: \d+ diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php index 92e59fb53..0cca92e2d 100644 --- a/core/modules/shortcut/src/Entity/Shortcut.php +++ b/core/modules/shortcut/src/Entity/Shortcut.php @@ -34,7 +34,6 @@ * "edit" = "Drupal\shortcut\ShortcutForm", * "delete" = "Drupal\shortcut\Form\ShortcutDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler" * }, * base_table = "shortcut", * data_table = "shortcut_field_data", diff --git a/core/modules/shortcut/src/Entity/ShortcutSet.php b/core/modules/shortcut/src/Entity/ShortcutSet.php index 819f6475d..fba28ac41 100644 --- a/core/modules/shortcut/src/Entity/ShortcutSet.php +++ b/core/modules/shortcut/src/Entity/ShortcutSet.php @@ -100,7 +100,7 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie ->condition('shortcut_set', $entity->id(), '=') ->execute(); - $controller = \Drupal::entityManager()->getStorage('shortcut'); + $controller = \Drupal::entityTypeManager()->getStorage('shortcut'); $entities = $controller->loadMultiple($shortcut_ids); $controller->delete($entities); } @@ -123,7 +123,7 @@ public function resetLinkWeights() { * {@inheritdoc} */ public function getShortcuts() { - $shortcuts = \Drupal::entityManager()->getStorage('shortcut')->loadByProperties(['shortcut_set' => $this->id()]); + $shortcuts = \Drupal::entityTypeManager()->getStorage('shortcut')->loadByProperties(['shortcut_set' => $this->id()]); uasort($shortcuts, ['\Drupal\shortcut\Entity\Shortcut', 'sort']); return $shortcuts; } diff --git a/core/modules/shortcut/src/Form/SetCustomize.php b/core/modules/shortcut/src/Form/SetCustomize.php index f69a9c92b..6f86a0e2a 100644 --- a/core/modules/shortcut/src/Form/SetCustomize.php +++ b/core/modules/shortcut/src/Form/SetCustomize.php @@ -5,6 +5,7 @@ use Drupal\Core\Entity\EntityForm; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Url; /** * Builds the shortcut set customize form. @@ -33,7 +34,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['shortcuts']['links'] = [ '#type' => 'table', '#header' => [t('Name'), t('Weight'), t('Operations')], - '#empty' => $this->t('No shortcuts available. Add a shortcut', [':link' => $this->url('shortcut.link_add', ['shortcut_set' => $this->entity->id()])]), + '#empty' => $this->t('No shortcuts available. Add a shortcut', [':link' => Url::fromRoute('shortcut.link_add', ['shortcut_set' => $this->entity->id()])->toString()]), '#attributes' => ['id' => 'shortcuts'], '#tabledrag' => [ [ diff --git a/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php b/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php index b14e4e386..05e6684f2 100644 --- a/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php +++ b/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php @@ -43,7 +43,7 @@ public function __construct(Connection $database, ShortcutSetStorageInterface $s public static function create(ContainerInterface $container) { return new static( $container->get('database'), - $container->get('entity.manager')->getStorage('shortcut_set') + $container->get('entity_type.manager')->getStorage('shortcut_set') ); } diff --git a/core/modules/shortcut/src/Form/SwitchShortcutSet.php b/core/modules/shortcut/src/Form/SwitchShortcutSet.php index a71aba28e..35ccd86cf 100644 --- a/core/modules/shortcut/src/Form/SwitchShortcutSet.php +++ b/core/modules/shortcut/src/Form/SwitchShortcutSet.php @@ -4,6 +4,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\shortcut\Entity\ShortcutSet; use Drupal\shortcut\ShortcutSetStorageInterface; use Drupal\user\UserInterface; @@ -45,7 +46,7 @@ public function __construct(ShortcutSetStorageInterface $shortcut_set_storage) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager')->getStorage('shortcut_set') + $container->get('entity_type.manager')->getStorage('shortcut_set') ); } @@ -183,7 +184,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $replacements = [ '%user' => $this->user->label(), '%set_name' => $set->label(), - ':switch-url' => $this->url(''), + ':switch-url' => Url::fromRoute('')->toString(), ]; if ($account_is_user) { // Only administrators can create new shortcut sets, so we know they have diff --git a/core/modules/shortcut/src/Plugin/migrate/destination/ShortcutSetUsers.php b/core/modules/shortcut/src/Plugin/migrate/destination/ShortcutSetUsers.php index 0cb843be7..a2c956cae 100644 --- a/core/modules/shortcut/src/Plugin/migrate/destination/ShortcutSetUsers.php +++ b/core/modules/shortcut/src/Plugin/migrate/destination/ShortcutSetUsers.php @@ -52,7 +52,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('entity.manager')->getStorage('shortcut_set') + $container->get('entity_type.manager')->getStorage('shortcut_set') ); } diff --git a/core/modules/shortcut/src/ShortcutAccessControlHandler.php b/core/modules/shortcut/src/ShortcutAccessControlHandler.php index 3fe2734fb..dbfd3a688 100644 --- a/core/modules/shortcut/src/ShortcutAccessControlHandler.php +++ b/core/modules/shortcut/src/ShortcutAccessControlHandler.php @@ -43,7 +43,7 @@ public function __construct(EntityTypeInterface $entity_type, ShortcutSetStorage public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { return new static( $entity_type, - $container->get('entity.manager')->getStorage('shortcut_set') + $container->get('entity_type.manager')->getStorage('shortcut_set') ); } diff --git a/core/modules/shortcut/src/ShortcutForm.php b/core/modules/shortcut/src/ShortcutForm.php index f41ba2531..e9d37eba9 100644 --- a/core/modules/shortcut/src/ShortcutForm.php +++ b/core/modules/shortcut/src/ShortcutForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Link; /** * Form handler for the shortcut entity forms. @@ -40,7 +41,7 @@ public function save(array $form, FormStateInterface $form_state) { // 'link to any content', but has no right to access the linked page. So we // check the access before showing the link. if ($url->access()) { - $view_link = \Drupal::l($entity->getTitle(), $url); + $view_link = Link::fromTextAndUrl($entity->getTitle(), $url)->toString(); } else { $view_link = $entity->getTitle(); diff --git a/core/modules/shortcut/src/ShortcutInterface.php b/core/modules/shortcut/src/ShortcutInterface.php index e434266ee..d095bfc7f 100644 --- a/core/modules/shortcut/src/ShortcutInterface.php +++ b/core/modules/shortcut/src/ShortcutInterface.php @@ -23,7 +23,7 @@ public function getTitle(); * @param string $title * The title of this shortcut. * - * @return \Drupal\shortcut\ShortcutInterface + * @return $this * The called shortcut entity. */ public function setTitle($title); @@ -42,7 +42,7 @@ public function getWeight(); * @param int $weight * The shortcut weight. * - * @return \Drupal\shortcut\ShortcutInterface + * @return $this * The called shortcut entity. */ public function setWeight($weight); diff --git a/core/modules/shortcut/src/ShortcutSetInterface.php b/core/modules/shortcut/src/ShortcutSetInterface.php index 3ad5e696c..8b8cf45ea 100644 --- a/core/modules/shortcut/src/ShortcutSetInterface.php +++ b/core/modules/shortcut/src/ShortcutSetInterface.php @@ -16,7 +16,7 @@ interface ShortcutSetInterface extends ConfigEntityInterface { * to the set. If the link is added to the end of the array and this function * is called, it will force that link to display at the end of the list. * - * @return \Drupal\shortcut\ShortcutSetInterface + * @return $this * The shortcut set. */ public function resetLinkWeights(); diff --git a/core/modules/shortcut/src/ShortcutSetStorage.php b/core/modules/shortcut/src/ShortcutSetStorage.php index 3fdeb388c..3318f6cb7 100644 --- a/core/modules/shortcut/src/ShortcutSetStorage.php +++ b/core/modules/shortcut/src/ShortcutSetStorage.php @@ -7,6 +7,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\Entity\ConfigEntityStorage; use Drupal\Core\Database\Connection; +use Drupal\Core\Database\Database; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -118,7 +119,7 @@ public function getAssignedToUser($account) { * {@inheritdoc} */ public function countAssignedUsers(ShortcutSetInterface $shortcut_set) { - return db_query('SELECT COUNT(*) FROM {shortcut_set_users} WHERE set_name = :name', [':name' => $shortcut_set->id()])->fetchField(); + return Database::getConnection()->query('SELECT COUNT(*) FROM {shortcut_set_users} WHERE set_name = :name', [':name' => $shortcut_set->id()])->fetchField(); } /** diff --git a/core/modules/shortcut/src/Tests/ShortcutTestBase.php b/core/modules/shortcut/src/Tests/ShortcutTestBase.php index 584a6154c..57bff7053 100644 --- a/core/modules/shortcut/src/Tests/ShortcutTestBase.php +++ b/core/modules/shortcut/src/Tests/ShortcutTestBase.php @@ -12,7 +12,7 @@ /** * Defines base class for shortcut test cases. * - * @deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. * Use \Drupal\Tests\shortcut\Functional\ShortcutTestBase. * * @see https://www.drupal.org/node/2906736 @@ -94,7 +94,7 @@ protected function setUp() { // Log in as admin and grab the default shortcut set. $this->drupalLogin($this->adminUser); $this->set = ShortcutSet::load('default'); - \Drupal::entityManager()->getStorage('shortcut_set')->assignUser($this->set, $this->adminUser); + \Drupal::entityTypeManager()->getStorage('shortcut_set')->assignUser($this->set, $this->adminUser); } /** @@ -125,7 +125,7 @@ public function generateShortcutSet($label = '', $id = NULL) { */ public function getShortcutInformation(ShortcutSetInterface $set, $key) { $info = []; - \Drupal::entityManager()->getStorage('shortcut')->resetCache(); + \Drupal::entityTypeManager()->getStorage('shortcut')->resetCache(); foreach ($set->getShortcuts() as $shortcut) { if ($key == 'link') { $info[] = $shortcut->link->uri; diff --git a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonAnonTest.php b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonAnonTest.php index ba6c8a798..6dd633b7e 100644 --- a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonAnonTest.php +++ b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonAnonTest.php @@ -20,6 +20,11 @@ class ShortcutHalJsonAnonTest extends ShortcutResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonBasicAuthTest.php b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonBasicAuthTest.php index 784c3ebc4..65923fdee 100644 --- a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonBasicAuthTest.php +++ b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ShortcutHalJsonBasicAuthTest extends ShortcutHalJsonAnonTest { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonCookieTest.php b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonCookieTest.php index 7b3b8348d..01f152dd4 100644 --- a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonCookieTest.php +++ b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutHalJsonCookieTest.php @@ -15,4 +15,9 @@ class ShortcutHalJsonCookieTest extends ShortcutHalJsonAnonTest { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonAnonTest.php b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonAnonTest.php index b3faae7fb..4f1948059 100644 --- a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonAnonTest.php +++ b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonAnonTest.php @@ -17,6 +17,11 @@ class ShortcutSetHalJsonAnonTest extends ShortcutSetResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonBasicAuthTest.php b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonBasicAuthTest.php index d3a705741..5d7b70148 100644 --- a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonBasicAuthTest.php +++ b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ShortcutSetHalJsonBasicAuthTest extends ShortcutSetHalJsonAnonTest { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonCookieTest.php b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonCookieTest.php index 786936ef0..9c8189965 100644 --- a/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonCookieTest.php +++ b/core/modules/shortcut/tests/src/Functional/Hal/ShortcutSetHalJsonCookieTest.php @@ -16,4 +16,9 @@ class ShortcutSetHalJsonCookieTest extends ShortcutSetHalJsonAnonTest { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonAnonTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonAnonTest.php index 04456fac2..114dc60d1 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonAnonTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonAnonTest.php @@ -21,4 +21,9 @@ class ShortcutJsonAnonTest extends ShortcutResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonBasicAuthTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonBasicAuthTest.php index e0859e3aa..c36716e58 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonBasicAuthTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ShortcutJsonBasicAuthTest extends ShortcutResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonCookieTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonCookieTest.php index 7bf210225..fed029af8 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonCookieTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutJsonCookieTest.php @@ -26,4 +26,9 @@ class ShortcutJsonCookieTest extends ShortcutResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonAnonTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonAnonTest.php index 96750b57b..ce5f270a2 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonAnonTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonAnonTest.php @@ -21,4 +21,9 @@ class ShortcutSetJsonAnonTest extends ShortcutSetResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonBasicAuthTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonBasicAuthTest.php index 6ddaf54b0..620f1f722 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonBasicAuthTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ShortcutSetJsonBasicAuthTest extends ShortcutSetResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonCookieTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonCookieTest.php index 60fe84060..11676fe19 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonCookieTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetJsonCookieTest.php @@ -26,4 +26,9 @@ class ShortcutSetJsonCookieTest extends ShortcutSetResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlAnonTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlAnonTest.php index 75ad14efd..5dc659538 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlAnonTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlAnonTest.php @@ -23,4 +23,9 @@ class ShortcutSetXmlAnonTest extends ShortcutSetResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlBasicAuthTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlBasicAuthTest.php index 50c6e71c2..67b32991d 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlBasicAuthTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ShortcutSetXmlBasicAuthTest extends ShortcutSetResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlCookieTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlCookieTest.php index 71dccd16c..969fefbaa 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlCookieTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutSetXmlCookieTest.php @@ -28,4 +28,9 @@ class ShortcutSetXmlCookieTest extends ShortcutSetResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlAnonTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlAnonTest.php index 56c5c681a..ca58e8bfa 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlAnonTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlAnonTest.php @@ -23,4 +23,9 @@ class ShortcutXmlAnonTest extends ShortcutResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlBasicAuthTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlBasicAuthTest.php index 9645aedae..209de30ae 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlBasicAuthTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ShortcutXmlBasicAuthTest extends ShortcutResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlCookieTest.php b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlCookieTest.php index 899b51d88..d2c4af202 100644 --- a/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlCookieTest.php +++ b/core/modules/shortcut/tests/src/Functional/Rest/ShortcutXmlCookieTest.php @@ -28,4 +28,9 @@ class ShortcutXmlCookieTest extends ShortcutResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php b/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php index a2282b1dd..71720d444 100644 --- a/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php +++ b/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php @@ -20,6 +20,11 @@ class ShortcutCacheTagsTest extends EntityCacheTagsTestBase { */ public static $modules = ['shortcut']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php b/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php index 238ee4fa9..8f7bf0aa1 100644 --- a/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php +++ b/core/modules/shortcut/tests/src/Functional/ShortcutLinksTest.php @@ -8,6 +8,7 @@ use Drupal\shortcut\Entity\Shortcut; use Drupal\shortcut\Entity\ShortcutSet; use Drupal\Tests\block\Functional\AssertBlockAppearsTrait; +use Drupal\Tests\Traits\Core\PathAliasTestTrait; use Drupal\views\Entity\View; /** @@ -18,6 +19,7 @@ class ShortcutLinksTest extends ShortcutTestBase { use AssertBlockAppearsTrait; + use PathAliasTestTrait; /** * Modules to enable. @@ -26,6 +28,11 @@ class ShortcutLinksTest extends ShortcutTestBase { */ public static $modules = ['router_test', 'views', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -42,11 +49,7 @@ public function testShortcutLinkAdd() { $set = $this->set; // Create an alias for the node so we can test aliases. - $path = [ - 'source' => '/node/' . $this->node->id(), - 'alias' => '/' . $this->randomMachineName(8), - ]; - $this->container->get('path.alias_storage')->save($path['source'], $path['alias']); + $path_alias = $this->createPathAlias('/node/' . $this->node->id(), '/' . $this->randomMachineName(8)); // Create some paths to test. $test_cases = [ @@ -54,7 +57,7 @@ public function testShortcutLinkAdd() { '/admin', '/admin/config/system/site-information', '/node/' . $this->node->id() . '/edit', - $path['alias'], + $path_alias->getAlias(), '/router_test/test2', '/router_test/test3/value', ]; @@ -140,7 +143,7 @@ public function testShortcutLinkAdd() { * Tests that the "add to shortcut" and "remove from shortcut" links work. */ public function testShortcutQuickLink() { - \Drupal::service('theme_handler')->install(['seven']); + \Drupal::service('theme_installer')->install(['seven']); $this->config('system.theme')->set('admin', 'seven')->save(); $this->config('node.settings')->set('use_admin_theme', '1')->save(); $this->container->get('router.builder')->rebuild(); @@ -290,7 +293,8 @@ public function testShortcutLinkDelete() { $this->assertFalse(in_array($shortcut->id(), $ids), 'Successfully deleted a shortcut.'); // Delete all the remaining shortcut links. - entity_delete_multiple('shortcut', array_filter($ids)); + $storage = \Drupal::entityTypeManager()->getStorage('shortcut'); + $storage->delete($storage->loadMultiple(array_filter($ids))); // Get the front page to check that no exceptions occur. $this->drupalGet(''); @@ -304,7 +308,7 @@ public function testShortcutLinkDelete() { */ public function testNoShortcutLink() { // Change to a theme that displays shortcuts. - \Drupal::service('theme_handler')->install(['seven']); + \Drupal::service('theme_installer')->install(['seven']); $this->config('system.theme') ->set('default', 'seven') ->save(); @@ -335,7 +339,7 @@ public function testNoShortcutLink() { */ public function testAccessShortcutsPermission() { // Change to a theme that displays shortcuts. - \Drupal::service('theme_handler')->install(['seven']); + \Drupal::service('theme_installer')->install(['seven']); $this->config('system.theme') ->set('default', 'seven') ->save(); @@ -410,7 +414,7 @@ private function verifyAccessShortcutsPermissionForEditPages() { foreach ($edit_paths as $path) { $this->drupalGet($path); - $message = format_string('Access is denied on %s', ['%s' => $path]); + $message = new FormattableMarkup('Access is denied on %s', ['%s' => $path]); $this->assertResponse(403, $message); } } @@ -449,7 +453,7 @@ public function testShortcutBlockAccess() { * Link position counting from zero. * @param string $message * (optional) A message to display with the assertion. Do not translate - * messages: use format_string() to embed variables in the message text, not + * messages: use new FormattableMarkup() to embed variables in the message text, not * t(). If left blank, a default message will be displayed. * @param string $group * (optional) The group this message is in, which is displayed in a column diff --git a/core/modules/shortcut/tests/src/Functional/ShortcutSetsTest.php b/core/modules/shortcut/tests/src/Functional/ShortcutSetsTest.php index b897df752..b7bf48588 100644 --- a/core/modules/shortcut/tests/src/Functional/ShortcutSetsTest.php +++ b/core/modules/shortcut/tests/src/Functional/ShortcutSetsTest.php @@ -18,6 +18,11 @@ class ShortcutSetsTest extends ShortcutTestBase { */ public static $modules = ['block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -38,7 +43,7 @@ public function testShortcutSetAdd() { 'id' => strtolower($this->randomMachineName()), ]; $this->drupalPostForm(NULL, $edit, t('Save')); - $new_set = $this->container->get('entity.manager')->getStorage('shortcut_set')->load($edit['id']); + $new_set = $this->container->get('entity_type.manager')->getStorage('shortcut_set')->load($edit['id']); $this->assertIdentical($new_set->id(), $edit['id'], 'Successfully created a shortcut set.'); $this->drupalGet('user/' . $this->adminUser->id() . '/shortcuts'); $this->assertText($new_set->label(), 'Generated shortcut set was listed as a choice on the user account page.'); @@ -59,7 +64,7 @@ public function testShortcutSetEdit() { // Test for the table. $element = $this->xpath('//div[@class="layout-content"]//table'); - $this->assertTrue($element, 'Shortcut entity list table found.'); + $this->assertNotEmpty($element, 'Shortcut entity list table found.'); // Test the table header. $elements = $this->xpath('//div[@class="layout-content"]//table/thead/tr/th'); @@ -91,7 +96,7 @@ public function testShortcutSetEdit() { $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertRaw(t('The shortcut set has been updated.')); - \Drupal::entityManager()->getStorage('shortcut')->resetCache(); + \Drupal::entityTypeManager()->getStorage('shortcut')->resetCache(); // Check to ensure that the shortcut weights have changed and that // ShortcutSet::.getShortcuts() returns shortcuts in the new order. $this->assertIdentical(array_reverse(array_keys($shortcuts)), array_keys($set->getShortcuts())); @@ -117,7 +122,7 @@ public function testShortcutSetSwitchOwn() { public function testShortcutSetAssign() { $new_set = $this->generateShortcutSet($this->randomMachineName()); - \Drupal::entityManager()->getStorage('shortcut_set')->assignUser($new_set, $this->shortcutUser); + \Drupal::entityTypeManager()->getStorage('shortcut_set')->assignUser($new_set, $this->shortcutUser); $current_set = shortcut_current_displayed_set($this->shortcutUser); $this->assertTrue($new_set->id() == $current_set->id(), "Successfully switched another user's shortcut set."); } @@ -169,7 +174,7 @@ public function testShortcutSetRename() { public function testShortcutSetUnassign() { $new_set = $this->generateShortcutSet($this->randomMachineName()); - $shortcut_set_storage = \Drupal::entityManager()->getStorage('shortcut_set'); + $shortcut_set_storage = \Drupal::entityTypeManager()->getStorage('shortcut_set'); $shortcut_set_storage->assignUser($new_set, $this->shortcutUser); $shortcut_set_storage->unassignUser($this->shortcutUser); $current_set = shortcut_current_displayed_set($this->shortcutUser); diff --git a/core/modules/shortcut/tests/src/Functional/ShortcutTestBase.php b/core/modules/shortcut/tests/src/Functional/ShortcutTestBase.php index 3f9cc87be..c11c418ae 100644 --- a/core/modules/shortcut/tests/src/Functional/ShortcutTestBase.php +++ b/core/modules/shortcut/tests/src/Functional/ShortcutTestBase.php @@ -87,7 +87,7 @@ protected function setUp() { // Log in as admin and grab the default shortcut set. $this->drupalLogin($this->adminUser); $this->set = ShortcutSet::load('default'); - \Drupal::entityManager()->getStorage('shortcut_set')->assignUser($this->set, $this->adminUser); + \Drupal::entityTypeManager()->getStorage('shortcut_set')->assignUser($this->set, $this->adminUser); } /** @@ -118,7 +118,7 @@ public function generateShortcutSet($label = '', $id = NULL) { */ public function getShortcutInformation(ShortcutSetInterface $set, $key) { $info = []; - \Drupal::entityManager()->getStorage('shortcut')->resetCache(); + \Drupal::entityTypeManager()->getStorage('shortcut')->resetCache(); foreach ($set->getShortcuts() as $shortcut) { if ($key == 'link') { $info[] = $shortcut->link->uri; diff --git a/core/modules/shortcut/tests/src/Functional/ShortcutTranslationUITest.php b/core/modules/shortcut/tests/src/Functional/ShortcutTranslationUITest.php index 6701ee933..252d2d022 100644 --- a/core/modules/shortcut/tests/src/Functional/ShortcutTranslationUITest.php +++ b/core/modules/shortcut/tests/src/Functional/ShortcutTranslationUITest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\shortcut\Functional; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase; use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Language\Language; @@ -31,6 +32,11 @@ class ShortcutTranslationUITest extends ContentTranslationUITestBase { 'toolbar', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ @@ -78,7 +84,7 @@ protected function doTestBasicTranslation() { $expected_path = \Drupal::urlGenerator()->generateFromRoute('user.page', [], ['language' => $language]); $label = $entity->getTranslation($langcode)->label(); $elements = $this->xpath('//nav[contains(@class, "toolbar-lining")]/ul[@class="toolbar-menu"]/li/a[contains(@href, :href) and normalize-space(text())=:label]', [':href' => $expected_path, ':label' => $label]); - $this->assertTrue(!empty($elements), format_string('Translated @language shortcut link @label found.', ['@label' => $label, '@language' => $language->getName()])); + $this->assertTrue(!empty($elements), new FormattableMarkup('Translated @language shortcut link @label found.', ['@label' => $label, '@language' => $language->getName()])); } } } @@ -120,7 +126,7 @@ protected function doTestTranslationChanged() { $this->assertFalse( $entity instanceof EntityChangedInterface, - format_string('%entity is not implementing EntityChangedInterface.', ['%entity' => $this->entityTypeId]) + new FormattableMarkup('%entity is not implementing EntityChangedInterface.', ['%entity' => $this->entityTypeId]) ); } diff --git a/core/modules/simpletest/migrations/state/simpletest.migrate_drupal.yml b/core/modules/simpletest/migrations/state/simpletest.migrate_drupal.yml new file mode 100644 index 000000000..f195ad82f --- /dev/null +++ b/core/modules/simpletest/migrations/state/simpletest.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + simpletest: simpletest + 7: + simpletest: simpletest diff --git a/core/modules/simpletest/simpletest.api.php b/core/modules/simpletest/simpletest.api.php index d8f820946..35802eaf5 100644 --- a/core/modules/simpletest/simpletest.api.php +++ b/core/modules/simpletest/simpletest.api.php @@ -20,7 +20,7 @@ * name of the test class, and the value is in associative array containing * 'name', 'description', 'group', and 'requires' keys. * - * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Convert + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Convert * your test to a PHPUnit-based one and implement test listeners. * * @see https://www.drupal.org/node/2939892 @@ -36,6 +36,14 @@ function hook_simpletest_alter(&$groups) { * A test group has started. * * This hook is called just once at the beginning of a test group. + * + * This hook is only invoked by the Simpletest UI form runner. It will not be + * invoked by run-tests.sh or the phpunit tool. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Convert your + * test to a PHPUnit-based one and implement test listeners. + * + * @see https://www.drupal.org/node/2934242 */ function hook_test_group_started() { } @@ -44,6 +52,14 @@ function hook_test_group_started() { * A test group has finished. * * This hook is called just once at the end of a test group. + * + * This hook is only invoked by the Simpletest UI form runner. It will not be + * invoked by run-tests.sh or the phpunit tool. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Convert your + * test to a PHPUnit-based one and implement test listeners. + * + * @see https://www.drupal.org/node/2934242 */ function hook_test_group_finished() { } @@ -53,11 +69,18 @@ function hook_test_group_finished() { * * This hook is called when an individual test has finished. * + * This hook is only invoked by the Simpletest UI form runner. It will not be + * invoked by run-tests.sh or the phpunit tool. + * * @param * $results The results of the test as gathered by * \Drupal\simpletest\WebTestBase. * - * @see \Drupal\simpletest\WebTestBase::results() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Convert your + * test to a PHPUnit-based one and implement test listeners. + * + * @see https://www.drupal.org/node/2934242 + * @see _simpletest_batch_operation() */ function hook_test_finished($results) { } diff --git a/core/modules/simpletest/simpletest.es6.js b/core/modules/simpletest/simpletest.es6.js index 7e65c12f4..1961f0931 100644 --- a/core/modules/simpletest/simpletest.es6.js +++ b/core/modules/simpletest/simpletest.es6.js @@ -47,10 +47,9 @@ .each(function() { const $group = $(this); const $cell = $group.find('.simpletest-group-select-all'); - const $groupCheckbox = $( - ``, + const $groupCheckbox = $(Drupal.theme('checkbox')).attr( + 'id', + `${$cell.attr('id')}-group-select-all`, ); const $testCheckboxes = $group .nextUntil('.simpletest-group') diff --git a/core/modules/simpletest/simpletest.info.yml b/core/modules/simpletest/simpletest.info.yml index d35de44de..232680e7c 100644 --- a/core/modules/simpletest/simpletest.info.yml +++ b/core/modules/simpletest/simpletest.info.yml @@ -2,12 +2,6 @@ name: Testing type: module description: 'Provides a framework for unit and functional testing.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: simpletest.settings - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/simpletest/simpletest.install b/core/modules/simpletest/simpletest.install index 3ecc6046b..373876807 100644 --- a/core/modules/simpletest/simpletest.install +++ b/core/modules/simpletest/simpletest.install @@ -5,8 +5,10 @@ * Install, update and uninstall functions for the simpletest module. */ +use Drupal\Component\FileSecurity\FileSecurity; use Drupal\Component\Utility\Environment; use Drupal\Core\File\Exception\FileException; +use Drupal\Core\Test\TestDatabase; use PHPUnit\Framework\TestCase; /** @@ -20,9 +22,14 @@ const SIMPLETEST_MINIMUM_PHP_MEMORY_LIMIT = '128M'; function simpletest_requirements($phase) { $requirements = []; + $requirements['deprecation'] = [ + 'title' => t('Testing (SimpleTest)'), + 'value' => t('The Testing (SimpleTest) module is deprecated for removal in Drupal 9. It should not be enabled on production sites. Read The Drupal core SimpleTest module is deprecated for alternative ways to run tests during development.'), + 'severity' => $phase === 'runtime' ? REQUIREMENT_WARNING : REQUIREMENT_INFO, + ]; + $has_phpunit = class_exists(TestCase::class); $has_curl = function_exists('curl_init'); - $open_basedir = ini_get('open_basedir'); $requirements['phpunit'] = [ 'title' => t('PHPUnit dependency'), @@ -42,18 +49,6 @@ function simpletest_requirements($phase) { $requirements['curl']['description'] = t('The testing framework requires the PHP cURL library. For more information, see the online information on installing the PHP cURL extension.'); } - // SimpleTest currently needs 2 cURL options which are incompatible with - // having PHP's open_basedir restriction set. - // See https://www.drupal.org/node/674304. - $requirements['php_open_basedir'] = [ - 'title' => t('PHP open_basedir restriction'), - 'value' => $open_basedir ? t('Enabled') : t('Disabled'), - ]; - if ($open_basedir) { - $requirements['php_open_basedir']['severity'] = REQUIREMENT_ERROR; - $requirements['php_open_basedir']['description'] = t('The testing framework requires the PHP open_basedir restriction to be disabled. Check your webserver configuration or contact your web host.'); - } - // Check the current memory limit. If it is set too low, SimpleTest will fail // to load all tests and throw a fatal error. $memory_limit = ini_get('memory_limit'); @@ -73,7 +68,7 @@ function simpletest_requirements($phase) { ]), ]; } - elseif (!file_save_htaccess(\Drupal::root() . '/' . $site_directory, FALSE)) { + elseif (!FileSecurity::writeHtaccess(\Drupal::root() . '/' . $site_directory, FALSE)) { $requirements['simpletest_site_directory'] = [ 'title' => t('Simpletest site directory'), 'value' => t('Not protected'), @@ -91,92 +86,7 @@ function simpletest_requirements($phase) { * Implements hook_schema(). */ function simpletest_schema() { - $schema['simpletest'] = [ - 'description' => 'Stores simpletest messages', - 'fields' => [ - 'message_id' => [ - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique simpletest message ID.', - ], - 'test_id' => [ - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'Test ID, messages belonging to the same ID are reported together', - ], - 'test_class' => [ - 'type' => 'varchar_ascii', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The name of the class that created this message.', - ], - 'status' => [ - 'type' => 'varchar', - 'length' => 9, - 'not null' => TRUE, - 'default' => '', - 'description' => 'Message status. Core understands pass, fail, exception.', - ], - 'message' => [ - 'type' => 'text', - 'not null' => TRUE, - 'description' => 'The message itself.', - ], - 'message_group' => [ - 'type' => 'varchar_ascii', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The message group this message belongs to. For example: warning, browser, user.', - ], - 'function' => [ - 'type' => 'varchar_ascii', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'Name of the assertion function or method that created this message.', - ], - 'line' => [ - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'Line number on which the function is called.', - ], - 'file' => [ - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'Name of the file where the function is called.', - ], - ], - 'primary key' => ['message_id'], - 'indexes' => [ - 'reporter' => ['test_class', 'message_id'], - ], - ]; - $schema['simpletest_test_id'] = [ - 'description' => 'Stores simpletest test IDs, used to auto-increment the test ID so that a fresh test ID is used.', - 'fields' => [ - 'test_id' => [ - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique simpletest ID used to group test results together. Each time a set of tests - are run a new test ID is used.', - ], - 'last_prefix' => [ - 'type' => 'varchar', - 'length' => 60, - 'not null' => FALSE, - 'default' => '', - 'description' => 'The last database prefix used during testing.', - ], - ], - 'primary key' => ['test_id'], - ]; - return $schema; + return TestDatabase::testingSchema(); } /** @@ -187,7 +97,7 @@ function simpletest_uninstall() { // in a (recursive) test for itself, since simpletest_clean_environment() // would also delete the test site of the parent test process. if (!drupal_valid_test_ua()) { - simpletest_clean_environment(); + \Drupal::service('environment_cleaner')->cleanEnvironment(); } // Delete verbose test output and any other testing framework files. try { diff --git a/core/modules/simpletest/simpletest.js b/core/modules/simpletest/simpletest.js index af94504db..9cfd09574 100644 --- a/core/modules/simpletest/simpletest.js +++ b/core/modules/simpletest/simpletest.js @@ -27,7 +27,7 @@ $(context).find('.simpletest-group').once('simpletest-group-select-all').each(function () { var $group = $(this); var $cell = $group.find('.simpletest-group-select-all'); - var $groupCheckbox = $(''); + var $groupCheckbox = $(Drupal.theme('checkbox')).attr('id', $cell.attr('id') + '-group-select-all'); var $testCheckboxes = $group.nextUntil('.simpletest-group').find('input[type=checkbox]'); $cell.append($groupCheckbox); diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index dbd2a9aaf..a53857550 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -5,19 +5,22 @@ * Provides testing functionality. */ -use Drupal\Core\Url; +use Drupal\Component\Uuid\Php; +use Drupal\Core\Asset\AttachedAssets; use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Database\Database; use Drupal\Core\File\Exception\FileException; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\simpletest\TestBase; +use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\Core\Test\JUnitConverter; +use Drupal\Core\Test\PhpUnitTestRunner; use Drupal\Core\Test\TestDatabase; +use Drupal\Core\Url; +use Drupal\simpletest\Form\SimpletestResultsForm; use Drupal\simpletest\TestDiscovery; -use Drupal\Tests\Listeners\SimpletestUiPrinter; use PHPUnit\Framework\TestCase; -use Symfony\Component\Process\PhpExecutableFinder; -use Drupal\Core\Test\TestStatus; /** * Implements hook_help(). @@ -120,8 +123,9 @@ function _simpletest_format_summary_line($summary) { /** * Runs tests. * - * @param $test_list - * List of tests to run. + * @param array[] $test_list + * List of tests to run. The top level is keyed by type of test, either + * 'simpletest' or 'phpunit'. Under that is an array of class names to run. * * @return string * The test ID. @@ -168,7 +172,7 @@ function simpletest_run_tests($test_list) { ]; batch_set($batch); - \Drupal::moduleHandler()->invokeAll('test_group_started'); + \Drupal::moduleHandler()->invokeAllDeprecated('Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242', 'test_group_started'); return $test_id; } @@ -188,28 +192,16 @@ function simpletest_run_tests($test_list) { * @return array * The parsed results of PHPUnit's JUnit XML output, in the format of * {simpletest}'s schema. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\PhpUnitTestRunner::runTests() instead. + * + * @see https://www.drupal.org/node/2948547 */ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames, &$status = NULL) { - $phpunit_file = simpletest_phpunit_xml_filepath($test_id); - simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file, $status, $output); - - $rows = []; - if ($status == TestStatus::PASS) { - $rows = simpletest_phpunit_xml_to_rows($test_id, $phpunit_file); - } - else { - $rows[] = [ - 'test_id' => $test_id, - 'test_class' => implode(",", $unescaped_test_classnames), - 'status' => TestStatus::label($status), - 'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output), - 'message_group' => 'Other', - 'function' => implode(",", $unescaped_test_classnames), - 'line' => '0', - 'file' => $phpunit_file, - ]; - } - return $rows; + $runner = PhpUnitTestRunner::create(\Drupal::getContainer()); + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::runTests() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return $runner->runTests($test_id, $unescaped_test_classnames, $status); } /** @@ -217,19 +209,15 @@ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames * * @param array[] $phpunit_results * An array of test results returned from simpletest_phpunit_xml_to_rows(). + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\TestDatabase::processPhpUnitResults() instead. + * + * @see https://www.drupal.org/node/3075252 */ function simpletest_process_phpunit_results($phpunit_results) { - // Insert the results of the PHPUnit test run into the database so the results - // are displayed along with Simpletest's results. - if (!empty($phpunit_results)) { - $query = TestDatabase::getConnection() - ->insert('simpletest') - ->fields(array_keys($phpunit_results[0])); - foreach ($phpunit_results as $result) { - $query->values($result); - } - $query->execute(); - } + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::processPhpUnitResults() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED); + TestDatabase::processPhpUnitResults($phpunit_results); } /** @@ -240,38 +228,16 @@ function simpletest_process_phpunit_results($phpunit_results) { * * @return array * The test result summary. A row per test class. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\PhpUnitTestRunner::summarizeResults() instead. + * + * @see https://www.drupal.org/node/2948547 */ function simpletest_summarize_phpunit_result($results) { - $summaries = []; - foreach ($results as $result) { - if (!isset($summaries[$result['test_class']])) { - $summaries[$result['test_class']] = [ - '#pass' => 0, - '#fail' => 0, - '#exception' => 0, - '#debug' => 0, - ]; - } - - switch ($result['status']) { - case 'pass': - $summaries[$result['test_class']]['#pass']++; - break; - - case 'fail': - $summaries[$result['test_class']]['#fail']++; - break; - - case 'exception': - $summaries[$result['test_class']]['#exception']++; - break; - - case 'debug': - $summaries[$result['test_class']]['#debug']++; - break; - } - } - return $summaries; + $runner = PhpUnitTestRunner::create(\Drupal::getContainer()); + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::summarizeResults() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return $runner->summarizeResults($results); } /** @@ -282,9 +248,16 @@ function simpletest_summarize_phpunit_result($results) { * * @return string * Path to the PHPUnit XML file to use for the current $test_id. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\PhpUnitTestRunner::xmlLogFilepath() instead. + * + * @see https://www.drupal.org/node/2948547 */ function simpletest_phpunit_xml_filepath($test_id) { - return \Drupal::service('file_system')->realpath('public://simpletest') . '/phpunit-' . $test_id . '.xml'; + $runner = PhpUnitTestRunner::create(\Drupal::getContainer()); + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::xmlLogFilepath() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return $runner->xmlLogFilePath($test_id); } /** @@ -293,7 +266,7 @@ function simpletest_phpunit_xml_filepath($test_id) { * @return string * The path to core's phpunit.xml.dist configuration file. * - * @deprecated in Drupal 8.4.x for removal before Drupal 9.0.0. PHPUnit test + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. PHPUnit test * runners should change directory into core/ and then run the phpunit tool. * See simpletest_phpunit_run_command() for an example. * @@ -320,65 +293,16 @@ function simpletest_phpunit_configuration_filepath() { * * @return string * The results as returned by exec(). + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\PhpUnitTestRunner::runCommand() instead. + * + * @see https://www.drupal.org/node/2948547 */ function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpunit_file, &$status = NULL, &$output = NULL) { - global $base_url; - // Setup an environment variable containing the database connection so that - // functional tests can connect to the database. - putenv('SIMPLETEST_DB=' . Database::getConnectionInfoAsUrl()); - - // Setup an environment variable containing the base URL, if it is available. - // This allows functional tests to browse the site under test. When running - // tests via CLI, core/phpunit.xml.dist or core/scripts/run-tests.sh can set - // this variable. - if ($base_url) { - putenv('SIMPLETEST_BASE_URL=' . $base_url); - putenv('BROWSERTEST_OUTPUT_DIRECTORY=' . \Drupal::service('file_system')->realpath('public://simpletest')); - } - $phpunit_bin = simpletest_phpunit_command(); - - $command = [ - $phpunit_bin, - '--log-junit', - escapeshellarg($phpunit_file), - '--printer', - escapeshellarg(SimpletestUiPrinter::class), - ]; - - // Optimized for running a single test. - if (count($unescaped_test_classnames) == 1) { - $class = new \ReflectionClass($unescaped_test_classnames[0]); - $command[] = escapeshellarg($class->getFileName()); - } - else { - // Double escape namespaces so they'll work in a regexp. - $escaped_test_classnames = array_map(function ($class) { - return addslashes($class); - }, $unescaped_test_classnames); - - $filter_string = implode("|", $escaped_test_classnames); - $command = array_merge($command, [ - '--filter', - escapeshellarg($filter_string), - ]); - } - - // Need to change directories before running the command so that we can use - // relative paths in the configuration file's exclusions. - $old_cwd = getcwd(); - chdir(\Drupal::root() . "/core"); - - // exec in a subshell so that the environment is isolated when running tests - // via the simpletest UI. - $ret = exec(implode(" ", $command), $output, $status); - - chdir($old_cwd); - putenv('SIMPLETEST_DB='); - if ($base_url) { - putenv('SIMPLETEST_BASE_URL='); - putenv('BROWSERTEST_OUTPUT_DIRECTORY='); - } - return $ret; + $runner = PhpUnitTestRunner::create(\Drupal::getContainer()); + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::runCommand() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return $runner->runCommand($unescaped_test_classnames, $phpunit_file, $status, $output); } /** @@ -386,24 +310,16 @@ function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpun * * @return string * The command that can be run through exec(). + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\PhpUnitTestRunner::phpUnitCommand() instead. + * + * @see https://www.drupal.org/node/2948547 */ function simpletest_phpunit_command() { - // Load the actual autoloader being used and determine its filename using - // reflection. We can determine the vendor directory based on that filename. - $autoloader = require \Drupal::root() . '/autoload.php'; - $reflector = new ReflectionClass($autoloader); - $vendor_dir = dirname(dirname($reflector->getFileName())); - - // The file in Composer's bin dir is a *nix link, which does not work when - // extracted from a tarball and generally not on Windows. - $command = escapeshellarg($vendor_dir . '/phpunit/phpunit/phpunit'); - if (substr(PHP_OS, 0, 3) == 'WIN') { - // On Windows it is necessary to run the script using the PHP executable. - $php_executable_finder = new PhpExecutableFinder(); - $php = $php_executable_finder->find(); - $command = $php . ' -f ' . $command . ' --'; - } - return $command; + $runner = PhpUnitTestRunner::create(\Drupal::getContainer()); + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::phpUnitCommand() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return $runner->phpUnitCommand(); } /** @@ -428,14 +344,15 @@ function _simpletest_batch_operation($test_list_init, $test_id, &$context) { // Perform the next test. $test_class = array_shift($test_list); if (is_subclass_of($test_class, TestCase::class)) { - $phpunit_results = simpletest_run_phpunit_tests($test_id, [$test_class]); - simpletest_process_phpunit_results($phpunit_results); + $runner = PhpUnitTestRunner::create(\Drupal::getContainer()); + $phpunit_results = $runner->runTests($test_id, [$test_class]); + TestDatabase::processPhpUnitResults($phpunit_results); $test_results[$test_class] = simpletest_summarize_phpunit_result($phpunit_results)[$test_class]; } else { $test = new $test_class($test_id); $test->run(); - \Drupal::moduleHandler()->invokeAll('test_finished', [$test->results]); + \Drupal::moduleHandler()->invokeAllDeprecated('Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242', 'test_finished', [$test->results]); $test_results[$test_class] = $test->results; } $size = count($test_list); @@ -491,13 +408,13 @@ function _simpletest_batch_finished($success, $results, $operations, $elapsed) { // Retrieve the last database prefix used for testing and the last test // class that was run from. Use the information to read the lgo file // in case any fatal errors caused the test to crash. - list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id); - simpletest_log_read($test_id, $last_prefix, $last_test_class); + $last_test = TestDatabase::lastTestGet($test_id); + (new TestDatabase($last_test['last_prefix']))->logRead($test_id, $last_test['test_class']); \Drupal::messenger()->addError(t('The test run did not successfully finish.')); \Drupal::messenger()->addWarning(t('Use the Clean environment button to clean-up temporary files and tables.')); } - \Drupal::moduleHandler()->invokeAll('test_group_finished'); + \Drupal::moduleHandler()->invokeAllDeprecated('Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242', 'test_group_finished'); } /** @@ -508,19 +425,15 @@ function _simpletest_batch_finished($success, $results, $operations, $elapsed) { * @return array * Array containing the last database prefix used and the last test class * that ran. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\TestDatabase::lastTestGet() instead. + * + * @see https://www.drupal.org/node/3075252 */ function simpletest_last_test_get($test_id) { - $last_prefix = TestDatabase::getConnection() - ->queryRange('SELECT last_prefix FROM {simpletest_test_id} WHERE test_id = :test_id', 0, 1, [ - ':test_id' => $test_id, - ]) - ->fetchField(); - $last_test_class = TestDatabase::getConnection() - ->queryRange('SELECT test_class FROM {simpletest} WHERE test_id = :test_id ORDER BY message_id DESC', 0, 1, [ - ':test_id' => $test_id, - ]) - ->fetchField(); - return [$last_prefix, $last_test_class]; + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::lastTestGet() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED); + return array_values(TestDatabase::lastTestGet($test_id)); } /** @@ -538,30 +451,53 @@ function simpletest_last_test_get($test_id) { * * @return bool * Whether any fatal errors were found. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\TestDatabase::logRead() instead. + * + * @see https://www.drupal.org/node/3075252 */ function simpletest_log_read($test_id, $database_prefix, $test_class) { + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::logRead() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED); $test_db = new TestDatabase($database_prefix); - $log = DRUPAL_ROOT . '/' . $test_db->getTestSitePath() . '/error.log'; - $found = FALSE; - if (file_exists($log)) { - foreach (file($log) as $line) { - if (preg_match('/\[.*?\] (.*?): (.*?) in (.*) on line (\d+)/', $line, $match)) { - // Parse PHP fatal errors for example: PHP Fatal error: Call to - // undefined function break_me() in /path/to/file.php on line 17 - $caller = [ - 'line' => $match[4], - 'file' => $match[3], - ]; - TestBase::insertAssert($test_id, $test_class, FALSE, $match[2], $match[1], $caller); - } - else { - // Unknown format, place the entire message in the log. - TestBase::insertAssert($test_id, $test_class, FALSE, $line, 'Fatal error'); - } - $found = TRUE; - } - } - return $found; + return $test_db->logRead($test_id, $test_class); +} + +/** + * Store an assertion from outside the testing context. + * + * This is useful for inserting assertions that can only be recorded after + * the test case has been destroyed, such as PHP fatal errors. The caller + * information is not automatically gathered since the caller is most likely + * inserting the assertion on behalf of other code. In all other respects + * the method behaves just like \Drupal\simpletest\TestBase::assert() in terms + * of storing the assertion. + * + * @param string $test_id + * The test ID to which the assertion relates. + * @param string $test_class + * The test class to store an assertion for. + * @param bool|string $status + * A boolean or a string of 'pass' or 'fail'. TRUE means 'pass'. + * @param string $message + * The assertion message. + * @param string $group + * The assertion message group. + * @param array $caller + * The an array containing the keys 'file' and 'line' that represent the file + * and line number of that file that is responsible for the assertion. + * + * @return + * Message ID of the stored assertion. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\TestDatabase::insertAssert() instead. + * + * @see https://www.drupal.org/node/3075252 + */ +function simpletest_insert_assert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = []) { + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::insertAssert() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED); + TestDatabase::insertAssert($test_id, $test_class, $status, $message, $group, $caller); } /** @@ -589,7 +525,7 @@ function simpletest_log_read($test_id, $database_prefix, $test_class) { * ); * @endcode * - * @deprecated in Drupal 8.3.x, for removal before 9.0.0 release. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal::service('test_discovery')->getTestClasses($extension, $types) * instead. */ @@ -601,7 +537,7 @@ function simpletest_test_get_all($extension = NULL, array $types = []) { /** * Registers test namespaces of all extensions and core test classes. * - * @deprecated in Drupal 8.3.x for removal before 9.0.0 release. Use + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use * \Drupal::service('test_discovery')->registerTestNamespaces() instead. */ function simpletest_classloader_register() { @@ -630,8 +566,14 @@ function simpletest_classloader_register() { * * @return string * The name of the file, including the path. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Tests\TestFileCreationTrait::generateFile() instead. + * + * @see https://www.drupal.org/node/3077768 */ function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') { + @trigger_error(__FUNCTION__ . '() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Tests\TestFileCreationTrait::generateFile() instead. See https://www.drupal.org/node/3077768', E_USER_DEPRECATED); $text = ''; for ($i = 0; $i < $lines; $i++) { // Generate $width - 1 characters to leave space for the "\n" character. @@ -659,72 +601,51 @@ function simpletest_generate_file($filename, $width, $lines, $type = 'binary-tex /** * Removes all temporary database tables and directories. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the + * environment_cleaner service and call its cleanEnvironment() method, or use + * \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead. + * + * @see https://www.drupal.org/node/3076634 */ function simpletest_clean_environment() { - simpletest_clean_database(); - simpletest_clean_temporary_directories(); - if (\Drupal::config('simpletest.settings')->get('clear_results')) { - $count = simpletest_clean_results_table(); - \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural($count, 'Removed 1 test result.', 'Removed @count test results.')); - } - else { - \Drupal::messenger()->addWarning(t('Clear results is disabled and the test results table will not be cleared.'), 'warning'); - } + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanEnvironment() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead.. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED); + /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */ + $cleaner = \Drupal::service('environment_cleaner'); + $cleaner->cleanEnvironment(); } /** * Removes prefixed tables from the database from crashed tests. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the + * environment_cleaner service and call its cleanDatabase() method, or use + * \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead. + * + * @see https://www.drupal.org/node/3076634 */ function simpletest_clean_database() { - $schema = Database::getConnection()->schema(); - $tables = $schema->findTables('test%'); - $count = 0; - foreach ($tables as $table) { - // Only drop tables which begin wih 'test' followed by digits, for example, - // {test12345678node__body}. - if (preg_match('/^test\d+.*/', $table, $matches)) { - $schema->dropTable($matches[0]); - $count++; - } - } - - if ($count > 0) { - \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural($count, 'Removed 1 leftover table.', 'Removed @count leftover tables.')); - } - else { - \Drupal::messenger()->addStatus(t('No leftover tables to remove.')); - } + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanDatabase() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED); + /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */ + $cleaner = \Drupal::service('environment_cleaner'); + $cleaner->cleanDatabase(); } /** * Finds all leftover temporary directories and removes them. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the + * environment_cleaner service and call its cleanTemporaryDirectories() + * method, or use + * \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead. + * + * @see https://www.drupal.org/node/3076634 */ function simpletest_clean_temporary_directories() { - $count = 0; - if (is_dir(DRUPAL_ROOT . '/sites/simpletest')) { - $files = scandir(DRUPAL_ROOT . '/sites/simpletest'); - foreach ($files as $file) { - if ($file[0] != '.') { - $path = DRUPAL_ROOT . '/sites/simpletest/' . $file; - try { - \Drupal::service('file_system')->deleteRecursive($path, function ($any_path) { - @chmod($any_path, 0700); - }); - } - catch (FileException $e) { - // Ignore failed deletes. - } - $count++; - } - } - } - - if ($count > 0) { - \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural($count, 'Removed 1 temporary directory.', 'Removed @count temporary directories.')); - } - else { - \Drupal::messenger()->addStatus(t('No temporary directories to remove.')); - } + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanTemporaryDirectories() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED); + /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */ + $cleaner = \Drupal::service('environment_cleaner'); + $cleaner->cleanTemporaryDirectories(); } /** @@ -735,44 +656,22 @@ function simpletest_clean_temporary_directories() { * * @return int * The number of results that were removed. - */ -function simpletest_clean_results_table($test_id = NULL) { - if (\Drupal::config('simpletest.settings')->get('clear_results')) { - $connection = TestDatabase::getConnection(); - if ($test_id) { - $count = $connection->query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = :test_id', [':test_id' => $test_id])->fetchField(); - - $connection->delete('simpletest') - ->condition('test_id', $test_id) - ->execute(); - $connection->delete('simpletest_test_id') - ->condition('test_id', $test_id) - ->execute(); - } - else { - $count = $connection->query('SELECT COUNT(test_id) FROM {simpletest_test_id}')->fetchField(); - - // Clear test results. - $connection->delete('simpletest')->execute(); - $connection->delete('simpletest_test_id')->execute(); - } - - return $count; - } - return 0; -} - -/** - * Implements hook_mail_alter(). * - * Aborts sending of messages with ID 'simpletest_cancel_test'. + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the + * environment_cleaner service and call its cleanResultsTable() method, or use + * \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead. * - * @see MailTestCase::testCancelMessage() + * @see https://www.drupal.org/node/3076634 */ -function simpletest_mail_alter(&$message) { - if ($message['id'] == 'simpletest_cancel_test') { - $message['send'] = FALSE; +function simpletest_clean_results_table($test_id = NULL) { + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanResultsTable() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED); + $count = 0; + if (\Drupal::config('simpletest.settings')->get('clear_results')) { + /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */ + $cleaner = \Drupal::service('environment_cleaner'); + $count = $cleaner->cleanResultsTable($test_id); } + return $count; } /** @@ -787,18 +686,15 @@ function simpletest_mail_alter(&$message) { * The results as array of rows in a format that can be inserted into * {simpletest}. If the phpunit_xml_file does not have any contents then the * function will return NULL. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\JUnitConverter::xmlToRows() instead. + * + * @see https://www.drupal.org/node/2948547 */ function simpletest_phpunit_xml_to_rows($test_id, $phpunit_xml_file) { - $contents = @file_get_contents($phpunit_xml_file); - if (!$contents) { - return; - } - $records = []; - $testcases = simpletest_phpunit_find_testcases(new SimpleXMLElement($contents)); - foreach ($testcases as $testcase) { - $records[] = simpletest_phpunit_testcase_to_row($test_id, $testcase); - } - return $records; + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::xmlToRows() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return JUnitConverter::xmlToRows($test_id, $phpunit_xml_file) ?: NULL; } /** @@ -811,34 +707,15 @@ function simpletest_phpunit_xml_to_rows($test_id, $phpunit_xml_file) { * * @return array * A list of all test cases. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\JUnitConverter::findTestCases() instead. + * + * @see https://www.drupal.org/node/2948547 */ function simpletest_phpunit_find_testcases(\SimpleXMLElement $element, \SimpleXMLElement $parent = NULL) { - $testcases = []; - - if (!isset($parent)) { - $parent = $element; - } - - if ($element->getName() === 'testcase' && (int) $parent->attributes()->tests > 0) { - // Add the class attribute if the testcase does not have one. This is the - // case for tests using a data provider. The name of the parent testsuite - // will be in the format class::method. - if (!$element->attributes()->class) { - $name = explode('::', $parent->attributes()->name, 2); - $element->addAttribute('class', $name[0]); - } - $testcases[] = $element; - } - else { - foreach ($element as $child) { - $file = (string) $parent->attributes()->file; - if ($file && !$child->attributes()->file) { - $child->addAttribute('file', $file); - } - $testcases = array_merge($testcases, simpletest_phpunit_find_testcases($child, $element)); - } - } - return $testcases; + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::findTestCases() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return JUnitConverter::findTestCases($element, $parent); } /** @@ -846,40 +723,106 @@ function simpletest_phpunit_find_testcases(\SimpleXMLElement $element, \SimpleXM * * @param int $test_id * The current test ID. - * @param \SimpleXMLElement $testcase + * @param \SimpleXMLElement $test_case * The PHPUnit test case represented as XML element. * * @return array * An array containing the {simpletest} result row. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\JUnitConverter::convertTestCaseToSimpletestRow() instead. + * + * @see https://www.drupal.org/node/2948547 */ -function simpletest_phpunit_testcase_to_row($test_id, \SimpleXMLElement $testcase) { - $message = ''; - $pass = TRUE; - if ($testcase->failure) { - $lines = explode("\n", $testcase->failure); - $message = $lines[2]; - $pass = FALSE; +function simpletest_phpunit_testcase_to_row($test_id, \SimpleXMLElement $test_case) { + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::convertTestCaseToSimpletestRow() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED); + return JUnitConverter::convertTestCaseToSimpletestRow($test_id, $test_case); +} + +/** + * Display test results from run-tests.sh in a browser. + * + * @internal + * This function is only used by run-tests.sh + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. This function + * supports the --browser option in this script. Use the --verbose option + * instead. + * + * @see https://www.drupal.org/node/3083549 + */ +function _simpletest_run_tests_script_open_browser() { + global $test_ids; + + try { + $connection = Database::getConnection('default', 'test-runner'); + $results = $connection->select('simpletest') + ->fields('simpletest') + ->condition('test_id', $test_ids, 'IN') + ->orderBy('test_class') + ->orderBy('message_id') + ->execute() + ->fetchAll(); + } + catch (Exception $e) { + echo (string) $e; + exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION); + } + + // Get the results form. + $form = []; + SimpletestResultsForm::addResultForm($form, $results); + + // Get the assets to make the details element collapsible and theme the result + // form. + $assets = new AttachedAssets(); + $assets->setLibraries([ + 'core/drupal.collapse', + 'system/admin', + 'simpletest/drupal.simpletest', + ]); + $resolver = \Drupal::service('asset.resolver'); + list($js_assets_header, $js_assets_footer) = $resolver->getJsAssets($assets, FALSE); + $js_collection_renderer = \Drupal::service('asset.js.collection_renderer'); + $js_assets_header = $js_collection_renderer->render($js_assets_header); + $js_assets_footer = $js_collection_renderer->render($js_assets_footer); + $css_assets = \Drupal::service('asset.css.collection_renderer')->render($resolver->getCssAssets($assets, FALSE)); + + // Make the html page to write to disk. + $render_service = \Drupal::service('renderer'); + $html = '' . $render_service->renderPlain($js_assets_header) . $render_service->renderPlain($css_assets) . '' . $render_service->renderPlain($form) . $render_service->renderPlain($js_assets_footer) . ''; + + // Ensure we have assets verbose directory - tests with no verbose output will + // not have created one. + $directory = PublicStream::basePath() . '/simpletest/verbose'; + \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + $php = new Php(); + $uuid = $php->generate(); + $filename = $directory . '/results-' . $uuid . '.html'; + $base_url = getenv('SIMPLETEST_BASE_URL'); + if (empty($base_url)) { + simpletest_script_print_error("--browser needs argument --url."); + } + $url = $base_url . '/' . PublicStream::basePath() . '/simpletest/verbose/results-' . $uuid . '.html'; + file_put_contents($filename, $html); + + // See if we can find an OS helper to open URLs in default browser. + $browser = FALSE; + if (shell_exec('which xdg-open')) { + $browser = 'xdg-open'; + } + elseif (shell_exec('which open')) { + $browser = 'open'; + } + elseif (substr(PHP_OS, 0, 3) == 'WIN') { + $browser = 'start'; + } + + if ($browser) { + shell_exec($browser . ' ' . escapeshellarg($url)); } - if ($testcase->error) { - $message = $testcase->error; - $pass = FALSE; + else { + // Can't find assets valid browser. + print 'Open file://' . realpath($filename) . ' in your browser to see the verbose output.'; } - - $attributes = $testcase->attributes(); - - $function = $attributes->class . '->' . $attributes->name . '()'; - $record = [ - 'test_id' => $test_id, - 'test_class' => (string) $attributes->class, - 'status' => $pass ? 'pass' : 'fail', - 'message' => $message, - // @todo: Check on the proper values for this. - 'message_group' => 'Other', - 'function' => $function, - 'line' => $attributes->line ?: 0, - // There are situations when the file will not be present because a PHPUnit - // @requires has caused a test to be skipped. - 'file' => $attributes->file ?: $function, - ]; - return $record; } diff --git a/core/modules/simpletest/simpletest.routing.yml b/core/modules/simpletest/simpletest.routing.yml index 43016b20e..b4e81e7a4 100644 --- a/core/modules/simpletest/simpletest.routing.yml +++ b/core/modules/simpletest/simpletest.routing.yml @@ -1,7 +1,7 @@ simpletest.settings: path: '/admin/config/development/testing/settings' defaults: - _form: 'Drupal\simpletest\Form\SimpletestSettingsForm' + _form: '\Drupal\simpletest\Form\SimpletestSettingsForm' _title: 'Test settings' requirements: _permission: 'administer unit tests' @@ -17,7 +17,7 @@ simpletest.test_form: simpletest.result_form: path: '/admin/config/development/testing/results/{test_id}' defaults: - _form: 'Drupal\simpletest\Form\SimpletestResultsForm' + _form: '\Drupal\simpletest\Form\SimpletestResultsForm' _title: 'Test result' requirements: _permission: 'administer unit tests' diff --git a/core/modules/simpletest/simpletest.services.yml b/core/modules/simpletest/simpletest.services.yml index 326cf38f2..afae7c435 100644 --- a/core/modules/simpletest/simpletest.services.yml +++ b/core/modules/simpletest/simpletest.services.yml @@ -2,6 +2,12 @@ services: test_discovery: class: Drupal\simpletest\TestDiscovery arguments: ['@app.root', '@class_loader', '@module_handler'] + environment_cleaner_factory: + class: Drupal\simpletest\EnvironmentCleanerFactory + arguments: ['@service_container'] + environment_cleaner: + class: Drupal\simpletest\EnvironmentCleanerService + factory: 'environment_cleaner_factory:createCleaner' cache_context.test_discovery: class: Drupal\simpletest\Cache\Context\TestDiscoveryCacheContext arguments: ['@test_discovery', '@private_key'] diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php index 32f59b485..54a9c1811 100644 --- a/core/modules/simpletest/src/AssertContentTrait.php +++ b/core/modules/simpletest/src/AssertContentTrait.php @@ -4,10 +4,12 @@ use Drupal\KernelTests\AssertContentTrait as CoreAssertContentTrait; +@trigger_error('\Drupal\simpletest\AssertContentTrait is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\KernelTests\AssertContentTrait.', E_USER_DEPRECATED); + /** * Provides test methods to assert content. * - * @deprecated in Drupal 8.6.0, to be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\KernelTests\AssertContentTrait instead. * * @see https://www.drupal.org/node/2943146 diff --git a/core/modules/simpletest/src/AssertHelperTrait.php b/core/modules/simpletest/src/AssertHelperTrait.php index 95c175dea..70262bef1 100644 --- a/core/modules/simpletest/src/AssertHelperTrait.php +++ b/core/modules/simpletest/src/AssertHelperTrait.php @@ -9,7 +9,7 @@ /** * Provides helper methods for assertions. * - * @deprecated in Drupal 8.4.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\AssertHelperTrait instead. * * @see https://www.drupal.org/node/2884454 diff --git a/core/modules/simpletest/src/BlockCreationTrait.php b/core/modules/simpletest/src/BlockCreationTrait.php index 1697914c0..3b5043dda 100644 --- a/core/modules/simpletest/src/BlockCreationTrait.php +++ b/core/modules/simpletest/src/BlockCreationTrait.php @@ -11,7 +11,7 @@ * * This trait is meant to be used only by test classes. * - * @deprecated in Drupal 8.4.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\block\Traits\BlockCreationTrait instead. * * @see https://www.drupal.org/node/2884454 diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php index 8819d4fc8..cfed1dd42 100644 --- a/core/modules/simpletest/src/BrowserTestBase.php +++ b/core/modules/simpletest/src/BrowserTestBase.php @@ -18,7 +18,7 @@ * @see \Drupal\simpletest\WebTestBase * @see \Drupal\Tests\BrowserTestBase * - * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0. + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. * Use Drupal\Tests\BrowserTestBase instead. */ abstract class BrowserTestBase extends BaseBrowserTestBase { diff --git a/core/modules/simpletest/src/ContentTypeCreationTrait.php b/core/modules/simpletest/src/ContentTypeCreationTrait.php index 7222561a8..f5f8396c1 100644 --- a/core/modules/simpletest/src/ContentTypeCreationTrait.php +++ b/core/modules/simpletest/src/ContentTypeCreationTrait.php @@ -11,7 +11,7 @@ * * This trait is meant to be used only by test classes. * - * @deprecated in Drupal 8.4.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\node\Traits\ContentTypeCreationTrait instead. * * @see https://www.drupal.org/node/2884454 diff --git a/core/modules/simpletest/src/EnvironmentCleanerFactory.php b/core/modules/simpletest/src/EnvironmentCleanerFactory.php new file mode 100644 index 000000000..dc48b50a2 --- /dev/null +++ b/core/modules/simpletest/src/EnvironmentCleanerFactory.php @@ -0,0 +1,55 @@ +container = $container; + } + + /** + * Factory method to create the environment cleaner service. + * + * @return \Drupal\simpletest\EnvironmentCleanerService + * The environment cleaner service. + */ + public function createCleaner() { + $cleaner = new EnvironmentCleanerService( + $this->container->get('app.root'), + Database::getConnection(), + TestDatabase::getConnection(), + $this->container->get('messenger'), + $this->container->get('string_translation'), + $this->container->get('config.factory'), + $this->container->get('cache.default'), + $this->container->get('file_system') + ); + + return $cleaner; + } + +} diff --git a/core/modules/simpletest/src/EnvironmentCleanerService.php b/core/modules/simpletest/src/EnvironmentCleanerService.php new file mode 100644 index 000000000..b4de5789e --- /dev/null +++ b/core/modules/simpletest/src/EnvironmentCleanerService.php @@ -0,0 +1,124 @@ +root = $root; + $this->testDatabase = $test_database; + $this->resultsDatabase = $results_database; + $this->messenger = $messenger; + $this->translation = $translation; + $this->configFactory = $config; + $this->cacheDefault = $cache_default; + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE) { + $results_removed = 0; + $clear_results = $this->configFactory->get('simpletest.settings')->get('clear_results'); + + if ($clear_database) { + $this->cleanDatabase(); + } + if ($clear_temp_directories) { + $this->cleanTemporaryDirectories(); + } + if ($clear_results) { + $results_removed = $this->cleanResultsTable(); + } + $this->cacheDefault->delete('simpletest'); + $this->cacheDefault->delete('simpletest_phpunit'); + + if ($clear_results) { + $this->messenger->addMessage($this->translation->formatPlural($results_removed, 'Removed 1 test result.', 'Removed @count test results.')); + } + else { + $this->messenger->addMessage($this->translation->translate('Clear results is disabled and the test results table will not be cleared.'), 'warning'); + } + } + + /** + * {@inheritdoc} + */ + public function cleanDatabase() { + $tables_removed = $this->doCleanDatabase(); + if ($tables_removed > 0) { + $this->messenger->addMessage($this->translation->formatPlural($tables_removed, 'Removed 1 leftover table.', 'Removed @count leftover tables.')); + } + else { + $this->messenger->addMessage($this->translation->translate('No leftover tables to remove.')); + } + } + + /** + * {@inheritdoc} + */ + public function cleanTemporaryDirectories() { + $directories_removed = $this->doCleanTemporaryDirectories(); + if ($directories_removed > 0) { + $this->messenger->addMessage($this->translation->formatPlural($directories_removed, 'Removed 1 temporary directory.', 'Removed @count temporary directories.')); + } + else { + $this->messenger->addMessage($this->translation->translate('No temporary directories to remove.')); + } + } + +} diff --git a/core/modules/simpletest/src/Exception/MissingGroupException.php b/core/modules/simpletest/src/Exception/MissingGroupException.php index 85a0696ca..c320304c9 100644 --- a/core/modules/simpletest/src/Exception/MissingGroupException.php +++ b/core/modules/simpletest/src/Exception/MissingGroupException.php @@ -2,8 +2,17 @@ namespace Drupal\simpletest\Exception; +@trigger_error(__NAMESPACE__ . '\\MissingGroupException is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\Exception\MissingGroupException instead. See https://www.drupal.org/node/2949692', E_USER_DEPRECATED); + +use Drupal\Core\Test\Exception\MissingGroupException as CoreMissingGroupException; + /** * Exception thrown when a simpletest class is missing an @group annotation. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\Exception\MissingGroupException instead. + * + * @see https://www.drupal.org/node/2949692 */ -class MissingGroupException extends \LogicException { +class MissingGroupException extends CoreMissingGroupException { } diff --git a/core/modules/simpletest/src/Form/SimpletestResultsForm.php b/core/modules/simpletest/src/Form/SimpletestResultsForm.php index a2241571e..adc1d4a5b 100644 --- a/core/modules/simpletest/src/Form/SimpletestResultsForm.php +++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php @@ -6,10 +6,10 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Test\EnvironmentCleanerInterface; use Drupal\Core\Url; use Drupal\simpletest\TestDiscovery; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; /** * Test results form for $test_id. @@ -38,12 +38,20 @@ class SimpletestResultsForm extends FormBase { */ protected $database; + /** + * The environment cleaner service. + * + * @var \Drupal\Core\Test\EnvironmentCleanerInterface + */ + protected $cleaner; + /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( - $container->get('database') + $container->get('database'), + $container->get('environment_cleaner') ); } @@ -53,8 +61,9 @@ public static function create(ContainerInterface $container) { * @param \Drupal\Core\Database\Connection $database * The database connection service. */ - public function __construct(Connection $database) { + public function __construct(Connection $database, EnvironmentCleanerInterface $cleaner) { $this->database = $database; + $this->cleaner = $cleaner; } /** @@ -113,7 +122,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $test_id $results = []; if (is_numeric($test_id) && !$results = $this->getResults($test_id)) { $this->messenger()->addError($this->t('No test results to display.')); - return new RedirectResponse($this->url('simpletest.test_form', [], ['absolute' => TRUE])); + return $this->redirect('simpletest.test_form'); } // Load all classes and include CSS. @@ -122,7 +131,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $test_id $filter = static::addResultForm($form, $results, $this->getStringTranslation()); // Actions. - $form['#action'] = $this->url('simpletest.result_form', ['test_id' => 're-run']); + $form['#action'] = Url::fromRoute('simpletest.result_form', ['test_id' => 're-run'])->toString(); $form['action'] = [ '#type' => 'fieldset', '#title' => $this->t('Actions'), @@ -163,7 +172,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $test_id ]; if (is_numeric($test_id)) { - simpletest_clean_results_table($test_id); + $this->cleaner->cleanResultsTable($test_id); } return $form; diff --git a/core/modules/simpletest/src/InstallerTestBase.php b/core/modules/simpletest/src/InstallerTestBase.php index 0f4af3a3a..747810e11 100644 --- a/core/modules/simpletest/src/InstallerTestBase.php +++ b/core/modules/simpletest/src/InstallerTestBase.php @@ -17,7 +17,7 @@ /** * Base class for testing the interactive installer. * - * @deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. * Use \Drupal\FunctionalTests\Installer\InstallerTestBase. See * https://www.drupal.org/node/2988752 */ @@ -148,9 +148,7 @@ protected function setUp() { $request = Request::createFromGlobals(); $class_loader = require $this->container->get('app.root') . '/autoload.php'; Settings::initialize($this->container->get('app.root'), DrupalKernel::findSitePath($request), $class_loader); - foreach ($GLOBALS['config_directories'] as $type => $path) { - $this->configDirectories[$type] = $path; - } + $this->configDirectories['sync'] = Settings::get('config_sync_directory'); // After writing settings.php, the installer removes write permissions // from the site directory. To allow drupal_generate_test_ua() to write @@ -160,8 +158,13 @@ protected function setUp() { // Not using File API; a potential error must trigger a PHP warning. chmod($this->container->get('app.root') . '/' . $this->siteDirectory, 0777); $this->kernel = DrupalKernel::createFromRequest($request, $class_loader, 'prod', FALSE); - $this->kernel->prepareLegacyRequest($request); + $this->kernel->boot(); + $this->kernel->preHandle($request); $this->container = $this->kernel->getContainer(); + // Ensure our request includes the session if appropriate. + if (PHP_SAPI !== 'cli') { + $request->setSession($this->container->get('session')); + } // Manually configure the test mail collector implementation to prevent // tests from sending out emails and collect them in state instead. diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index 80c8eb0b7..dc06c8708 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -62,7 +62,7 @@ * @see \Drupal\Tests\KernelTestBase::installSchema() * @see \Drupal\Tests\BrowserTestBase * - * @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal\KernelTests\KernelTestBase instead. * * @ingroup testing @@ -138,23 +138,26 @@ protected function beforePrepareEnvironment() { /** * Create and set new configuration directories. * - * @see config_get_config_directory() + * @see \Drupal\Core\Site\Settings::getConfigDirectory() * * @throws \RuntimeException - * Thrown when CONFIG_SYNC_DIRECTORY cannot be created or made writable. + * Thrown when the configuration sync directory cannot be created or made + * writable. + * + * @return string + * The config sync directory path. */ protected function prepareConfigDirectories() { $this->configDirectories = []; - include_once DRUPAL_ROOT . '/core/includes/install.inc'; // Assign the relative path to the global variable. $path = $this->siteDirectory . '/config_' . CONFIG_SYNC_DIRECTORY; - $GLOBALS['config_directories'][CONFIG_SYNC_DIRECTORY] = $path; // Ensure the directory can be created and is writeable. if (!\Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) { throw new \RuntimeException("Failed to create '" . CONFIG_SYNC_DIRECTORY . "' config directory $path"); } // Provide the already resolved path for tests. $this->configDirectories[CONFIG_SYNC_DIRECTORY] = $path; + return $path; } /** @@ -236,14 +239,14 @@ protected function setUp() { // @see \Drupal\Core\Extension\ExtensionDiscovery::scan() $settings['test_parent_site'] = $this->originalSite; + // Create and set new configuration directories. + $settings['config_sync_directory'] = $this->prepareConfigDirectories(); + // Restore and merge settings. // DrupalKernel::boot() initializes new Settings, and the containerBuild() // method sets additional settings. new Settings($settings + Settings::getAll()); - // Create and set new configuration directories. - $this->prepareConfigDirectories(); - // Set the request scope. $this->container = $this->kernel->getContainer(); $this->container->get('request_stack')->push($request); @@ -348,7 +351,7 @@ public function containerBuild(ContainerBuilder $container) { if ($this->strictConfigSchema) { $container - ->register('simpletest.config_schema_checker', ConfigSchemaChecker::class) + ->register('testing.config_schema_checker', ConfigSchemaChecker::class) ->addArgument(new Reference('config.typed')) ->addArgument($this->getConfigSchemaExclusions()) ->addTag('event_subscriber'); @@ -382,12 +385,12 @@ public function containerBuild(ContainerBuilder $container) { ->addArgument(new Reference('keyvalue')); } - if ($container->hasDefinition('path_processor_alias')) { - // Prevent the alias-based path processor, which requires a url_alias db - // table, from being registered to the path processor manager. We do this - // by removing the tags that the compiler pass looks for. This means the - // url generator can safely be used within tests. - $definition = $container->getDefinition('path_processor_alias'); + if ($container->hasDefinition('path_alias.path_processor')) { + // The alias-based processor requires the path_alias entity schema to be + // installed, so we prevent it from being registered to the path processor + // manager. We do this by removing the tags that the compiler pass looks + // for. This means that the URL generator can safely be used within tests. + $definition = $container->getDefinition('path_alias.path_processor'); $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound'); } @@ -431,7 +434,7 @@ protected function installConfig(array $modules) { } \Drupal::service('config.installer')->installDefaultConfig('module', $module); } - $this->pass(format_string('Installed default config: %modules.', [ + $this->pass(new FormattableMarkup('Installed default config: %modules.', [ '%modules' => implode(', ', $modules), ])); } @@ -473,7 +476,7 @@ protected function installSchema($module, $tables) { } $this->container->get('database')->schema()->createTable($table, $schema); } - $this->pass(format_string('Installed %module tables: %tables.', [ + $this->pass(new FormattableMarkup('Installed %module tables: %tables.', [ '%tables' => '{' . implode('}, {', $tables) . '}', '%module' => $module, ])); @@ -563,7 +566,7 @@ protected function enableModules(array $modules) { // Note that the kernel has rebuilt the container; this $module_handler is // no longer the $module_handler instance from above. $this->container->get('module_handler')->reload(); - $this->pass(format_string('Enabled modules: %modules.', [ + $this->pass(new FormattableMarkup('Enabled modules: %modules.', [ '%modules' => implode(', ', $modules), ])); } @@ -598,7 +601,7 @@ protected function disableModules(array $modules) { // no longer the $module_handler instance from above. $module_handler = $this->container->get('module_handler'); $module_handler->reload(); - $this->pass(format_string('Disabled modules: %modules.', [ + $this->pass(new FormattableMarkup('Disabled modules: %modules.', [ '%modules' => implode(', ', $modules), ])); } diff --git a/core/modules/simpletest/src/NodeCreationTrait.php b/core/modules/simpletest/src/NodeCreationTrait.php index c4cffa7b2..aadbd5f7b 100644 --- a/core/modules/simpletest/src/NodeCreationTrait.php +++ b/core/modules/simpletest/src/NodeCreationTrait.php @@ -11,7 +11,7 @@ * * This trait is meant to be used only by test classes. * - * @deprecated in Drupal 8.4.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * \Drupal\Tests\node\Traits\NodeCreationTrait instead. * * @see https://www.drupal.org/node/2884454 diff --git a/core/modules/simpletest/src/RandomGeneratorTrait.php b/core/modules/simpletest/src/RandomGeneratorTrait.php index a371520c8..a3c874012 100644 --- a/core/modules/simpletest/src/RandomGeneratorTrait.php +++ b/core/modules/simpletest/src/RandomGeneratorTrait.php @@ -9,7 +9,7 @@ /** * Provides random generator utility methods. * - * @deprecated in Drupal 8.1.1, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.1.1 and is removed from drupal:9.0.0. Use * \Drupal\Tests\RandomGeneratorTrait instead. * * @see \Drupal\Tests diff --git a/core/modules/simpletest/src/RouteProvider.php b/core/modules/simpletest/src/RouteProvider.php index 063c9567c..c733cb417 100644 --- a/core/modules/simpletest/src/RouteProvider.php +++ b/core/modules/simpletest/src/RouteProvider.php @@ -7,7 +7,7 @@ /** * Rebuilds the router when the provider is instantiated. * - * @deprecated in 8.6.0 for removal before 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\KernelTests\RouteProvider instead. * * @see https://www.drupal.org/node/2943146 diff --git a/core/modules/simpletest/src/SessionTestTrait.php b/core/modules/simpletest/src/SessionTestTrait.php index 280140f3d..cef07d90f 100644 --- a/core/modules/simpletest/src/SessionTestTrait.php +++ b/core/modules/simpletest/src/SessionTestTrait.php @@ -9,7 +9,7 @@ /** * Provides methods to generate and get session name in tests. * - * @deprecated in Drupal 8.1.1 will be removed before 9.0.0. Use + * @deprecated in drupal:8.1.1 and is removed from drupal:9.0.0. Use * \Drupal\Tests\SessionTestTrait instead. * * @see \Drupal\Tests diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index 9af30b70e..969e22704 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -12,6 +12,7 @@ use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\PublicStream; use Drupal\Core\Test\TestDatabase; +use Drupal\Core\Test\TestDiscovery; use Drupal\Core\Test\TestSetupTrait; use Drupal\Core\Utility\Error; use Drupal\Tests\AssertHelperTrait as BaseAssertHelperTrait; @@ -23,6 +24,10 @@ * Base class for Drupal tests. * * Do not extend this class directly; use \Drupal\simpletest\WebTestBase. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, + * use one of the phpunit base test classes like + * Drupal\Tests\BrowserTestBase. See https://www.drupal.org/node/3030340. */ abstract class TestBase { @@ -380,6 +385,10 @@ protected function assert($status, $message = '', $group = 'Other', array $calle * @return * Message ID of the stored assertion. * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * simpletest_insert_assert() instead. + * + * @see https://www.drupal.org/node/3030340 * @see \Drupal\simpletest\TestBase::assert() * @see \Drupal\simpletest\TestBase::deleteAssert() */ @@ -919,8 +928,7 @@ public function run(array $methods = []) { $this->httpAuthCredentials = $username . ':' . $password; } - // Force assertion failures to be thrown as AssertionError for PHP 5 & 7 - // compatibility. + // Force assertion failures to be thrown as exceptions. Handle::register(); set_error_handler([$this, 'errorHandler']); @@ -1096,13 +1104,11 @@ private function prepareEnvironment() { // Backup statics and globals. $this->originalContainer = \Drupal::getContainer(); $this->originalLanguage = $language_interface; - $this->originalConfigDirectories = $GLOBALS['config_directories']; // Save further contextual information. // Use the original files directory to avoid nesting it within an existing // simpletest directory if a test is executed within a test. $this->originalFileDirectory = Settings::get('file_public_path', $site_path . '/files'); - $this->originalProfile = drupal_get_profile(); $this->originalUser = isset($user) ? clone $user : NULL; // Prevent that session data is leaked into the UI test runner by closing @@ -1245,7 +1251,7 @@ private function restoreEnvironment() { // In case a fatal error occurred that was not in the test process read the // log to pick up any fatal errors. - simpletest_log_read($this->testId, $this->databasePrefix, get_class($this)); + (new TestDatabase($this->databasePrefix))->logRead($this->testId, get_class($this)); // Restore original dependency injection container. $this->container = $this->originalContainer; @@ -1268,9 +1274,6 @@ private function restoreEnvironment() { $GLOBALS['conf'] = $this->originalConf; new Settings($this->originalSettings); - // Restore original statics and globals. - $GLOBALS['config_directories'] = $this->originalConfigDirectories; - // Re-initialize original stream wrappers of the parent site. // This must happen after static variables have been reset and the original // container and $config_directories are restored, as simpletest_log_read() diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php index a9cbe15db..8ce8b7de4 100644 --- a/core/modules/simpletest/src/TestDiscovery.php +++ b/core/modules/simpletest/src/TestDiscovery.php @@ -2,53 +2,27 @@ namespace Drupal\simpletest; +@trigger_error(__NAMESPACE__ . '\\TestDiscovery is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDiscovery instead. See https://www.drupal.org/node/2949692', E_USER_DEPRECATED); + use Doctrine\Common\Reflection\StaticReflectionParser; use Drupal\Component\Annotation\Reflection\MockFileFinder; use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\simpletest\Exception\MissingGroupException; -use PHPUnit_Util_Test; +use Drupal\Core\Test\Exception\MissingGroupException; +use Drupal\Core\Test\TestDiscovery as CoreTestDiscovery; /** * Discovers available tests. + * + * This class provides backwards compatibility for code which uses the legacy + * \Drupal\simpletest\TestDiscovery. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Test\TestDiscovery instead. + * + * @see https://www.drupal.org/node/2949692 */ -class TestDiscovery { - - /** - * The class loader. - * - * @var \Composer\Autoload\ClassLoader - */ - protected $classLoader; - - /** - * Statically cached list of test classes. - * - * @var array - */ - protected $testClasses; - - /** - * Cached map of all test namespaces to respective directories. - * - * @var array - */ - protected $testNamespaces; - - /** - * Cached list of all available extension names, keyed by extension type. - * - * @var array - */ - protected $availableExtensions; - - /** - * The app root. - * - * @var string - */ - protected $root; +class TestDiscovery extends CoreTestDiscovery { /** * The module handler. @@ -70,66 +44,17 @@ class TestDiscovery { * The module handler. */ public function __construct($root, $class_loader, ModuleHandlerInterface $module_handler) { - $this->root = $root; - $this->classLoader = $class_loader; + parent::__construct($root, $class_loader); $this->moduleHandler = $module_handler; } - /** - * Registers test namespaces of all extensions and core test classes. - * - * @return array - * An associative array whose keys are PSR-4 namespace prefixes and whose - * values are directory names. - */ - public function registerTestNamespaces() { - if (isset($this->testNamespaces)) { - return $this->testNamespaces; - } - $this->testNamespaces = []; - - $existing = $this->classLoader->getPrefixesPsr4(); - - // Add PHPUnit test namespaces of Drupal core. - $this->testNamespaces['Drupal\\Tests\\'] = [$this->root . '/core/tests/Drupal/Tests']; - $this->testNamespaces['Drupal\\KernelTests\\'] = [$this->root . '/core/tests/Drupal/KernelTests']; - $this->testNamespaces['Drupal\\FunctionalTests\\'] = [$this->root . '/core/tests/Drupal/FunctionalTests']; - $this->testNamespaces['Drupal\\FunctionalJavascriptTests\\'] = [$this->root . '/core/tests/Drupal/FunctionalJavascriptTests']; - - $this->availableExtensions = []; - foreach ($this->getExtensions() as $name => $extension) { - $this->availableExtensions[$extension->getType()][$name] = $name; - - $base_path = $this->root . '/' . $extension->getPath(); - - // Add namespace of disabled/uninstalled extensions. - if (!isset($existing["Drupal\\$name\\"])) { - $this->classLoader->addPsr4("Drupal\\$name\\", "$base_path/src"); - } - // Add Simpletest test namespace. - $this->testNamespaces["Drupal\\$name\\Tests\\"][] = "$base_path/src/Tests"; - - // Add PHPUnit test namespaces. - $this->testNamespaces["Drupal\\Tests\\$name\\Unit\\"][] = "$base_path/tests/src/Unit"; - $this->testNamespaces["Drupal\\Tests\\$name\\Kernel\\"][] = "$base_path/tests/src/Kernel"; - $this->testNamespaces["Drupal\\Tests\\$name\\Functional\\"][] = "$base_path/tests/src/Functional"; - $this->testNamespaces["Drupal\\Tests\\$name\\FunctionalJavascript\\"][] = "$base_path/tests/src/FunctionalJavascript"; - - // Add discovery for traits which are shared between different test - // suites. - $this->testNamespaces["Drupal\\Tests\\$name\\Traits\\"][] = "$base_path/tests/src/Traits"; - } - - foreach ($this->testNamespaces as $prefix => $paths) { - $this->classLoader->addPsr4($prefix, $paths); - } - - return $this->testNamespaces; - } - /** * Discovers all available tests in all extensions. * + * This method is a near-duplicate of + * \Drupal\Core\Tests\TestDiscovery::getTestClasses(). It exists so that we + * can provide a BC invocation of hook_simpletest_alter(). + * * @param string $extension * (optional) The name of an extension to limit discovery to; e.g., 'node'. * @param string[] $types @@ -149,9 +74,6 @@ public function registerTestNamespaces() { * ), * ); * @endcode - * - * @todo Remove singular grouping; retain list of groups in 'group' key. - * @see https://www.drupal.org/node/2296615 */ public function getTestClasses($extension = NULL, array $types = []) { if (!isset($extension) && empty($types)) { @@ -225,279 +147,4 @@ public function getTestClasses($extension = NULL, array $types = []) { return $list; } - /** - * Discovers all class files in all available extensions. - * - * @param string $extension - * (optional) The name of an extension to limit discovery to; e.g., 'node'. - * - * @return array - * A classmap containing all discovered class files; i.e., a map of - * fully-qualified classnames to pathnames. - */ - public function findAllClassFiles($extension = NULL) { - $classmap = []; - $namespaces = $this->registerTestNamespaces(); - if (isset($extension)) { - // Include tests in the \Drupal\Tests\{$extension} namespace. - $pattern = "/Drupal\\\(Tests\\\)?$extension\\\/"; - $namespaces = array_intersect_key($namespaces, array_flip(preg_grep($pattern, array_keys($namespaces)))); - } - foreach ($namespaces as $namespace => $paths) { - foreach ($paths as $path) { - if (!is_dir($path)) { - continue; - } - $classmap += static::scanDirectory($namespace, $path); - } - } - return $classmap; - } - - /** - * Scans a given directory for class files. - * - * @param string $namespace_prefix - * The namespace prefix to use for discovered classes. Must contain a - * trailing namespace separator (backslash). - * For example: 'Drupal\\node\\Tests\\' - * @param string $path - * The directory path to scan. - * For example: '/path/to/drupal/core/modules/node/tests/src' - * - * @return array - * An associative array whose keys are fully-qualified class names and whose - * values are corresponding filesystem pathnames. - * - * @throws \InvalidArgumentException - * If $namespace_prefix does not end in a namespace separator (backslash). - * - * @todo Limit to '*Test.php' files (~10% less files to reflect/introspect). - * @see https://www.drupal.org/node/2296635 - */ - public static function scanDirectory($namespace_prefix, $path) { - if (substr($namespace_prefix, -1) !== '\\') { - throw new \InvalidArgumentException("Namespace prefix for $path must contain a trailing namespace separator."); - } - $flags = \FilesystemIterator::UNIX_PATHS; - $flags |= \FilesystemIterator::SKIP_DOTS; - $flags |= \FilesystemIterator::FOLLOW_SYMLINKS; - $flags |= \FilesystemIterator::CURRENT_AS_SELF; - $flags |= \FilesystemIterator::KEY_AS_FILENAME; - - $iterator = new \RecursiveDirectoryIterator($path, $flags); - $filter = new \RecursiveCallbackFilterIterator($iterator, function ($current, $file_name, $iterator) { - if ($iterator->hasChildren()) { - return TRUE; - } - // We don't want to discover abstract TestBase classes, traits or - // interfaces. They can be deprecated and will call @trigger_error() - // during discovery. - return substr($file_name, -4) === '.php' && - substr($file_name, -12) !== 'TestBase.php' && - substr($file_name, -9) !== 'Trait.php' && - substr($file_name, -13) !== 'Interface.php'; - }); - $files = new \RecursiveIteratorIterator($filter); - $classes = []; - foreach ($files as $fileinfo) { - $class = $namespace_prefix; - if ('' !== $subpath = $fileinfo->getSubPath()) { - $class .= strtr($subpath, '/', '\\') . '\\'; - } - $class .= $fileinfo->getBasename('.php'); - $classes[$class] = $fileinfo->getPathname(); - } - return $classes; - } - - /** - * Retrieves information about a test class for UI purposes. - * - * @param string $classname - * The test classname. - * @param string $doc_comment - * (optional) The class PHPDoc comment. If not passed in reflection will be - * used but this is very expensive when parsing all the test classes. - * - * @return array - * An associative array containing: - * - name: The test class name. - * - description: The test (PHPDoc) summary. - * - group: The test's first @group (parsed from PHPDoc annotations). - * - groups: All of the test's @group annotations, as an array (parsed from - * PHPDoc annotations). - * - requires: An associative array containing test requirements parsed from - * PHPDoc annotations: - * - module: List of Drupal module extension names the test depends on. - * - * @throws \Drupal\simpletest\Exception\MissingGroupException - * If the class does not have a @group annotation. - */ - public static function getTestInfo($classname, $doc_comment = NULL) { - if ($doc_comment === NULL) { - $reflection = new \ReflectionClass($classname); - $doc_comment = $reflection->getDocComment(); - } - $info = [ - 'name' => $classname, - ]; - $annotations = []; - // Look for annotations, allow an arbitrary amount of spaces before the - // * but nothing else. - preg_match_all('/^[ ]*\* \@([^\s]*) (.*$)/m', $doc_comment, $matches); - if (isset($matches[1])) { - foreach ($matches[1] as $key => $annotation) { - // For historical reasons, there is a single-value 'group' result key - // and a 'groups' key as an array. - if ($annotation === 'group') { - $annotations['groups'][] = $matches[2][$key]; - } - if (!empty($annotations[$annotation])) { - // Only @group is allowed to have more than one annotation, in the - // 'groups' key. Other annotations only have one value per key. - continue; - } - $annotations[$annotation] = $matches[2][$key]; - } - } - - if (empty($annotations['group'])) { - // Concrete tests must have a group. - throw new MissingGroupException(sprintf('Missing @group annotation in %s', $classname)); - } - $info['group'] = $annotations['group']; - $info['groups'] = $annotations['groups']; - - // Sort out PHPUnit-runnable tests by type. - if ($testsuite = static::getPhpunitTestSuite($classname)) { - $info['type'] = 'PHPUnit-' . $testsuite; - } - else { - $info['type'] = 'Simpletest'; - } - - if (!empty($annotations['coversDefaultClass'])) { - $info['description'] = 'Tests ' . $annotations['coversDefaultClass'] . '.'; - } - else { - $info['description'] = static::parseTestClassSummary($doc_comment); - } - if (isset($annotations['dependencies'])) { - $info['requires']['module'] = array_map('trim', explode(',', $annotations['dependencies'])); - } - - return $info; - } - - /** - * Parses the phpDoc summary line of a test class. - * - * @param string $doc_comment - * - * @return string - * The parsed phpDoc summary line. An empty string is returned if no summary - * line can be parsed. - */ - public static function parseTestClassSummary($doc_comment) { - // Normalize line endings. - $doc_comment = preg_replace('/\r\n|\r/', '\n', $doc_comment); - // Strip leading and trailing doc block lines. - $doc_comment = substr($doc_comment, 4, -4); - - $lines = explode("\n", $doc_comment); - $summary = []; - // Add every line to the summary until the first empty line or annotation - // is found. - foreach ($lines as $line) { - if (preg_match('/^[ ]*\*$/', $line) || preg_match('/^[ ]*\* \@/', $line)) { - break; - } - $summary[] = trim($line, ' *'); - } - return implode(' ', $summary); - } - - /** - * Parses annotations in the phpDoc of a test class. - * - * @param \ReflectionClass $class - * The reflected test class. - * - * @return array - * An associative array that contains all annotations on the test class; - * typically including: - * - group: A list of @group values. - * - requires: An associative array of @requires values; e.g.: - * - module: A list of Drupal module dependencies that are required to - * exist. - * - * @see PHPUnit_Util_Test::parseTestMethodAnnotations() - * @see http://phpunit.de/manual/current/en/incomplete-and-skipped-tests.html#incomplete-and-skipped-tests.skipping-tests-using-requires - */ - public static function parseTestClassAnnotations(\ReflectionClass $class) { - $annotations = PHPUnit_Util_Test::parseTestMethodAnnotations($class->getName())['class']; - - // @todo Enhance PHPUnit upstream to allow for custom @requires identifiers. - // @see PHPUnit_Util_Test::getRequirements() - // @todo Add support for 'PHP', 'OS', 'function', 'extension'. - // @see https://www.drupal.org/node/1273478 - if (isset($annotations['requires'])) { - foreach ($annotations['requires'] as $i => $value) { - list($type, $value) = explode(' ', $value, 2); - if ($type === 'module') { - $annotations['requires']['module'][$value] = $value; - unset($annotations['requires'][$i]); - } - } - } - return $annotations; - } - - /** - * Determines the phpunit testsuite for a given classname. - * - * @param string $classname - * The test classname. - * - * @return string|false - * The testsuite name or FALSE if its not a phpunit test. - */ - public static function getPhpunitTestSuite($classname) { - if (preg_match('/Drupal\\\\Tests\\\\Core\\\\(\w+)/', $classname, $matches)) { - return 'Unit'; - } - if (preg_match('/Drupal\\\\Tests\\\\Component\\\\(\w+)/', $classname, $matches)) { - return 'Unit'; - } - // Module tests. - if (preg_match('/Drupal\\\\Tests\\\\(\w+)\\\\(\w+)/', $classname, $matches)) { - return $matches[2]; - } - // Core tests. - elseif (preg_match('/Drupal\\\\(\w*)Tests\\\\/', $classname, $matches)) { - if ($matches[1] == '') { - return 'Unit'; - } - return $matches[1]; - } - return FALSE; - } - - /** - * Returns all available extensions. - * - * @return \Drupal\Core\Extension\Extension[] - * An array of Extension objects, keyed by extension name. - */ - protected function getExtensions() { - $listing = new ExtensionDiscovery($this->root); - // Ensure that tests in all profiles are discovered. - $listing->setProfileDirectories([]); - $extensions = $listing->scan('module', TRUE); - $extensions += $listing->scan('profile', TRUE); - $extensions += $listing->scan('theme', TRUE); - return $extensions; - } - } diff --git a/core/modules/simpletest/src/TestServiceProvider.php b/core/modules/simpletest/src/TestServiceProvider.php index 151260c59..79fc2847a 100644 --- a/core/modules/simpletest/src/TestServiceProvider.php +++ b/core/modules/simpletest/src/TestServiceProvider.php @@ -7,7 +7,7 @@ /** * Provides special routing services for tests. * - * @deprecated in 8.6.0 for removal before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * Drupal\KernelTests\TestServiceProvider instead. * * @see https://www.drupal.org/node/2943146 diff --git a/core/modules/simpletest/src/Tests/BrokenSetUpTest.php b/core/modules/simpletest/src/Tests/BrokenSetUpTest.php index 28fd31beb..fab44d00c 100644 --- a/core/modules/simpletest/src/Tests/BrokenSetUpTest.php +++ b/core/modules/simpletest/src/Tests/BrokenSetUpTest.php @@ -13,6 +13,8 @@ * properly are skipped. * * @group WebTestBase + * @group legacy + * * @see \Drupal\simpletest\WebTestBase */ class BrokenSetUpTest extends WebTestBase { diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php index c92381fab..d34ff91e6 100644 --- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php +++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php @@ -240,7 +240,7 @@ public function testInstallConfig() { public function testEnableModulesFixedList() { // Install system module. $this->container->get('module_installer')->install(['system', 'user', 'menu_link_content']); - $entity_manager = \Drupal::entityManager(); + $entity_manager = \Drupal::entityTypeManager(); // entity_test is loaded via $modules; its entity type should exist. $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE); diff --git a/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php b/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php index ae9d6f729..4bf7c7b6c 100644 --- a/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php +++ b/core/modules/simpletest/src/Tests/MissingCheckedRequirementsTest.php @@ -9,6 +9,7 @@ * * @group simpletest * @group WebTestBase + * @group legacy */ class MissingCheckedRequirementsTest extends WebTestBase { diff --git a/core/modules/simpletest/src/Tests/SimpleTestErrorCollectorTest.php b/core/modules/simpletest/src/Tests/SimpleTestErrorCollectorTest.php index 0c9fc2d41..59456fa1b 100644 --- a/core/modules/simpletest/src/Tests/SimpleTestErrorCollectorTest.php +++ b/core/modules/simpletest/src/Tests/SimpleTestErrorCollectorTest.php @@ -2,6 +2,7 @@ namespace Drupal\simpletest\Tests; +use Drupal\Component\Render\FormattableMarkup; use Drupal\simpletest\WebTestBase; /** @@ -83,11 +84,11 @@ protected function error($message = '', $group = 'Other', array $caller = NULL) * Asserts that a collected error matches what we are expecting. */ public function assertError($error, $group, $function, $file, $message = NULL) { - $this->assertEqual($error['group'], $group, format_string("Group was %group", ['%group' => $group])); - $this->assertEqual($error['caller']['function'], $function, format_string("Function was %function", ['%function' => $function])); - $this->assertEqual(\Drupal::service('file_system')->basename($error['caller']['file']), $file, format_string("File was %file", ['%file' => $file])); + $this->assertEqual($error['group'], $group, new FormattableMarkup("Group was %group", ['%group' => $group])); + $this->assertEqual($error['caller']['function'], $function, new FormattableMarkup("Function was %function", ['%function' => $function])); + $this->assertEqual(\Drupal::service('file_system')->basename($error['caller']['file']), $file, new FormattableMarkup("File was %file", ['%file' => $file])); if (isset($message)) { - $this->assertEqual($error['message'], $message, format_string("Message was %message", ['%message' => $message])); + $this->assertEqual($error['message'], $message, new FormattableMarkup("Message was %message", ['%message' => $message])); } } diff --git a/core/modules/simpletest/src/Tests/SimpleTestTest.php b/core/modules/simpletest/src/Tests/SimpleTestTest.php index 942902c98..ea10bcef7 100644 --- a/core/modules/simpletest/src/Tests/SimpleTestTest.php +++ b/core/modules/simpletest/src/Tests/SimpleTestTest.php @@ -2,6 +2,7 @@ namespace Drupal\simpletest\Tests; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\Crypt; use Drupal\Core\Test\TestDatabase; use Drupal\simpletest\WebTestBase; @@ -13,6 +14,7 @@ * * @group simpletest * @group WebTestBase + * @group legacy */ class SimpleTestTest extends WebTestBase { @@ -205,7 +207,7 @@ public function stubTest() { // This causes the eleventh of the sixteen passes asserted in // confirmStubResults(). - $this->pass(t('Test ID is @id.', ['@id' => $this->testId])); + $this->pass('Test ID is ' . $this->testId . '.'); // These cause the twelfth to fifteenth of the sixteen passes asserted in // confirmStubResults(). @@ -234,7 +236,7 @@ public function stubTest() { * Assert nothing. */ public function assertNothing() { - $this->pass("This is nothing."); + $this->pass('This is nothing.'); } /** @@ -313,7 +315,7 @@ public function assertAssertion($message, $type, $status, $file, $function) { break; } } - return $this->assertTrue($found, format_string('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', ['@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function])); + return $this->assertTrue($found, new FormattableMarkup('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', ['@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function])); } /** diff --git a/core/modules/simpletest/src/Tests/UiPhpUnitOutputTest.php b/core/modules/simpletest/src/Tests/UiPhpUnitOutputTest.php index c2ebfc92c..cdbae0603 100644 --- a/core/modules/simpletest/src/Tests/UiPhpUnitOutputTest.php +++ b/core/modules/simpletest/src/Tests/UiPhpUnitOutputTest.php @@ -9,6 +9,7 @@ * Test PHPUnit output for the Simpletest UI. * * @group simpletest + * @group legacy * * @see \Drupal\Tests\Listeners\SimpletestUiPrinter */ diff --git a/core/modules/simpletest/src/UserCreationTrait.php b/core/modules/simpletest/src/UserCreationTrait.php index a08c3735f..52a0aaf8e 100644 --- a/core/modules/simpletest/src/UserCreationTrait.php +++ b/core/modules/simpletest/src/UserCreationTrait.php @@ -13,7 +13,7 @@ * This trait is meant to be used only by test classes extending * \Drupal\simpletest\TestBase. * - * @deprecated in Drupal 8.4.x. Will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use * Drupal\Tests\user\Traits\UserCreationTrait instead. * * @see https://www.drupal.org/node/2884454 diff --git a/core/modules/simpletest/src/WebAssert.php b/core/modules/simpletest/src/WebAssert.php index 04b519203..97a4ee8e6 100644 --- a/core/modules/simpletest/src/WebAssert.php +++ b/core/modules/simpletest/src/WebAssert.php @@ -7,7 +7,7 @@ /** * Defines a class with methods for asserting presence of elements during tests. * - * @deprecated in Drupal 8.1.1 will be removed before 9.0.0. + * @deprecated in drupal:8.1.1 and is removed from drupal:9.0.0. * This was moved to another namespace. * * @see \Drupal\Tests diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 54d64e785..b23004d57 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -14,7 +14,9 @@ use Drupal\Core\Session\AnonymousUserSession; use Drupal\Core\Test\AssertMailTrait; use Drupal\Core\Test\FunctionalTestSetupTrait; +use Drupal\Core\Test\TestDiscovery; use Drupal\Core\Url; +use Drupal\KernelTests\AssertContentTrait as CoreAssertContentTrait; use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; use Drupal\Tests\EntityViewTrait; use Drupal\Tests\block\Traits\BlockCreationTrait as BaseBlockCreationTrait; @@ -31,11 +33,14 @@ * Test case for typical Drupal tests. * * @ingroup testing + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Instead, + * use \Drupal\Tests\BrowserTestBase. See https://www.drupal.org/node/3030340. */ abstract class WebTestBase extends TestBase { use FunctionalTestSetupTrait; - use AssertContentTrait; + use CoreAssertContentTrait; use TestFileCreationTrait { getTestFiles as drupalGetTestFiles; compareFiles as drupalCompareFiles; @@ -176,6 +181,8 @@ abstract class WebTestBase extends TestBase { /** * The number of redirects followed during the handling of a request. + * + * @var int */ protected $redirectCount; @@ -225,7 +232,7 @@ public function __construct($test_id = NULL) { */ protected function assertBlockAppears(Block $block) { $result = $this->findBlockInstance($block); - $this->assertTrue(!empty($result), format_string('Ensure the block @id appears on the page', ['@id' => $block->id()])); + $this->assertTrue(!empty($result), new FormattableMarkup('Ensure the block @id appears on the page', ['@id' => $block->id()])); } /** @@ -236,7 +243,7 @@ protected function assertBlockAppears(Block $block) { */ protected function assertNoBlockAppears(Block $block) { $result = $this->findBlockInstance($block); - $this->assertFalse(!empty($result), format_string('Ensure the block @id does not appear on the page', ['@id' => $block->id()])); + $this->assertFalse(!empty($result), new FormattableMarkup('Ensure the block @id does not appear on the page', ['@id' => $block->id()])); } /** @@ -294,7 +301,7 @@ protected function drupalLogin(AccountInterface $account) { if (isset($this->sessionId)) { $account->session_id = $this->sessionId; } - $pass = $this->assert($this->drupalUserIsLoggedIn($account), format_string('User %name successfully logged in.', ['%name' => $account->getAccountName()]), 'User login'); + $pass = $this->assert($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', ['%name' => $account->getAccountName()]), 'User login'); if ($pass) { $this->loggedInUser = $account; $this->container->get('current_user')->setAccount($account); @@ -385,6 +392,8 @@ protected function setUp() { // Initialize and override certain configurations. $this->initConfig($container); + $this->installDefaultThemeFromClassProperty($container); + // Collect modules to install. $this->installModulesFromClassProperty($container); @@ -699,7 +708,7 @@ protected function curlHeaderCallback($curlHandler, $header) { if (getenv('SYMFONY_DEPRECATIONS_HELPER') !== 'disabled') { $message = (string) $parameters[0]; $test_info = TestDiscovery::getTestInfo(get_called_class()); - if ($test_info['group'] !== 'legacy' && !in_array($message, DeprecationListenerTrait::getSkippedDeprecations())) { + if (!in_array('legacy', $test_info['groups']) && !in_array($message, DeprecationListenerTrait::getSkippedDeprecations())) { call_user_func_array([&$this, 'error'], $parameters); } } @@ -1057,9 +1066,9 @@ protected function drupalPostForm($path, $edit, $submit, array $options = [], ar $this->fail(new FormattableMarkup('Failed to set field @name to @value', ['@name' => $name, '@value' => $value])); } if (!$ajax && isset($submit)) { - $this->assertTrue($submit_matches, format_string('Found the @submit button', ['@submit' => $submit])); + $this->assertTrue($submit_matches, new FormattableMarkup('Found the @submit button', ['@submit' => $submit])); } - $this->fail(format_string('Found the requested form fields at @path', ['@path' => ($path instanceof Url) ? $path->toString() : $path])); + $this->fail(new FormattableMarkup('Found the requested form fields at @path', ['@path' => ($path instanceof Url) ? $path->toString() : $path])); } } diff --git a/core/modules/simpletest/tests/fixtures/phpunit_error.xml b/core/modules/simpletest/tests/fixtures/phpunit_error.xml index 82386aea7..6a6a1cbc2 100644 --- a/core/modules/simpletest/tests/fixtures/phpunit_error.xml +++ b/core/modules/simpletest/tests/fixtures/phpunit_error.xml @@ -7,7 +7,7 @@ - Drupal\Tests\Core\Extension\ModuleHandlerUnitTest::testloadInclude + Drupal\Tests\Core\Extension\ModuleHandlerUnitTest::testloadInclude Undefined index: foo /home/chx/www/system/core/lib/Drupal/Core/Extension/ModuleHandler.php:219 @@ -20,19 +20,19 @@ Undefined index: foo - Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #0 ('role_test_1', array(Drupal\user\Entity\User, Drupal\user\Entity\User)) + Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #0 ('role_test_1', array(Drupal\user\Entity\User, Drupal\user\Entity\User)) Access granted for user with the roles role_test_1 on path: role_test_1 Failed asserting that false is true. - Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #1 ('role_test_2', array(Drupal\user\Entity\User, Drupal\user\Entity\User)) + Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #1 ('role_test_2', array(Drupal\user\Entity\User, Drupal\user\Entity\User)) Access granted for user with the roles role_test_2 on path: role_test_2 Failed asserting that false is true. - Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #2 ('role_test_3', array(Drupal\user\Entity\User)) + Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #2 ('role_test_3', array(Drupal\user\Entity\User)) Access granted for user with the roles role_test_1, role_test_2 on path: role_test_3 Failed asserting that false is true. diff --git a/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.info.yml b/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.info.yml index e7034e1fe..2cb995bd8 100644 --- a/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.info.yml +++ b/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.info.yml @@ -2,11 +2,5 @@ name: 'Simpletest deprecation test' type: module description: 'Support module for Simpletest deprecation tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.module b/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.module index f61059783..d0f01eb7a 100644 --- a/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.module +++ b/core/modules/simpletest/tests/modules/simpletest_deprecation_test/simpletest_deprecation_test.module @@ -13,3 +13,30 @@ function simpletest_deprecation_test_simpletest_alter(&$groups) { // No-op. } + +/** + * Implements hook_test_group_started(). + * + * This hook is deprecated and should trigger a deprecation error when invoked. + */ +function simpletest_deprecation_test_test_group_started() { + // No-op. +} + +/** + * Implements hook_test_group_finished(). + * + * This hook is deprecated and should trigger a deprecation error when invoked. + */ +function simpletest_deprecation_test_test_group_finished() { + // No-op. +} + +/** + * Implements hook_test_finished(). + * + * This hook is deprecated and should trigger a deprecation error when invoked. + */ +function simpletest_deprecation_test_test_finished($results) { + // No-op. +} diff --git a/core/modules/simpletest/tests/src/Functional/OtherInstallationProfileTestsTest.php b/core/modules/simpletest/tests/src/Functional/OtherInstallationProfileTestsTest.php index 972495f57..8a9ba1828 100644 --- a/core/modules/simpletest/tests/src/Functional/OtherInstallationProfileTestsTest.php +++ b/core/modules/simpletest/tests/src/Functional/OtherInstallationProfileTestsTest.php @@ -3,11 +3,14 @@ namespace Drupal\Tests\simpletest\Functional; use Drupal\Tests\BrowserTestBase; +use Drupal\Core\Url; /** * Verifies that tests in other installation profiles are found. * * @group simpletest + * @group legacy + * * @see \Drupal\simpletest\Tests\InstallationProfileModuleTestsTest */ class OtherInstallationProfileTestsTest extends BrowserTestBase { @@ -51,11 +54,13 @@ protected function setUp() { /** * Tests that tests located in another installation profile appear. + * + * @expectedDeprecation Drupal\simpletest\TestDiscovery is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDiscovery instead. See https://www.drupal.org/node/2949692 */ public function testOtherInstallationProfile() { // Assert the existence of a test in a different installation profile than // the current. - $this->drupalGet('admin/config/development/testing'); + $this->drupalGet(Url::fromRoute('simpletest.test_form')); $this->assertText('Tests Standard installation profile expectations.'); // Assert the existence of a test for a module in a different installation diff --git a/core/modules/simpletest/tests/src/Functional/SimpletestTest.php b/core/modules/simpletest/tests/src/Functional/SimpletestTest.php new file mode 100644 index 000000000..781a1bae3 --- /dev/null +++ b/core/modules/simpletest/tests/src/Functional/SimpletestTest.php @@ -0,0 +1,41 @@ +container->get('module_installer'); + $this->assertTrue($installer->uninstall(['simpletest'])); + } + +} diff --git a/core/modules/simpletest/tests/src/Functional/SimpletestUiTest.php b/core/modules/simpletest/tests/src/Functional/SimpletestUiTest.php index f91cb6d50..9103cb8aa 100644 --- a/core/modules/simpletest/tests/src/Functional/SimpletestUiTest.php +++ b/core/modules/simpletest/tests/src/Functional/SimpletestUiTest.php @@ -12,6 +12,7 @@ * * @group #slow * @group simpletest + * @group legacy */ class SimpletestUiTest extends BrowserTestBase { @@ -30,6 +31,8 @@ protected function setUp() { /** * Tests that unit, kernel, and functional tests work through the UI. + * + * @expectedDeprecation Drupal\simpletest\TestDiscovery is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDiscovery instead. See https://www.drupal.org/node/2949692 */ public function testTestingThroughUI() { $url = Url::fromRoute('simpletest.test_form'); diff --git a/core/modules/simpletest/tests/src/Functional/ThroughUITest.php b/core/modules/simpletest/tests/src/Functional/ThroughUITest.php index 48737433f..1e325e4c5 100644 --- a/core/modules/simpletest/tests/src/Functional/ThroughUITest.php +++ b/core/modules/simpletest/tests/src/Functional/ThroughUITest.php @@ -10,6 +10,7 @@ * @see \Drupal\simpletest\Tests::testTestingThroughUI() * * @group simpletest + * @group legacy */ class ThroughUITest extends BrowserTestBase { diff --git a/core/modules/simpletest/tests/src/Kernel/Cache/Context/TestDiscoveryCacheContextTest.php b/core/modules/simpletest/tests/src/Kernel/Cache/Context/TestDiscoveryCacheContextTest.php index 25b2af92e..00d83581c 100644 --- a/core/modules/simpletest/tests/src/Kernel/Cache/Context/TestDiscoveryCacheContextTest.php +++ b/core/modules/simpletest/tests/src/Kernel/Cache/Context/TestDiscoveryCacheContextTest.php @@ -8,6 +8,7 @@ /** * @group simpletest + * @group legacy */ class TestDiscoveryCacheContextTest extends KernelTestBase { @@ -18,6 +19,8 @@ class TestDiscoveryCacheContextTest extends KernelTestBase { /** * Tests that test context hashes are unique. + * + * @expectedDeprecation Drupal\simpletest\TestDiscovery is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDiscovery instead. See https://www.drupal.org/node/2949692 */ public function testContext() { // Mock test discovery. diff --git a/core/modules/simpletest/tests/src/Kernel/DeprecatedCleanupTest.php b/core/modules/simpletest/tests/src/Kernel/DeprecatedCleanupTest.php new file mode 100644 index 000000000..ec29a7676 --- /dev/null +++ b/core/modules/simpletest/tests/src/Kernel/DeprecatedCleanupTest.php @@ -0,0 +1,65 @@ +setDefinition('environment_cleaner', $cleaner_definition); + } + + /** + * @expectedDeprecation simpletest_clean_environment is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanEnvironment() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead.. See https://www.drupal.org/node/3076634 + * @expectedDeprecation simpletest_clean_database is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanDatabase() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead. See https://www.drupal.org/node/3076634 + * @expectedDeprecation simpletest_clean_temporary_directories is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanTemporaryDirectories() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead. See https://www.drupal.org/node/3076634 + * @expectedDeprecation simpletest_clean_results_table is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanResultsTable() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead. See https://www.drupal.org/node/3076634 + */ + public function testDeprecatedCleanFunctions() { + $this->assertNull(simpletest_clean_environment()); + $this->assertNull(simpletest_clean_database()); + $this->assertNull(simpletest_clean_temporary_directories()); + $this->assertEquals(0, simpletest_clean_results_table()); + } + +} + +/** + * Mock environment_cleaner service that does not perform any cleaning. + */ +class StubEnvironmentCleanerService implements EnvironmentCleanerInterface { + + public function cleanDatabase() { + + } + + public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE) { + + } + + public function cleanResultsTable($test_id = NULL) { + + } + + public function cleanTemporaryDirectories() { + + } + +} diff --git a/core/modules/simpletest/tests/src/Kernel/PhpUnitErrorTest.php b/core/modules/simpletest/tests/src/Kernel/PhpUnitErrorTest.php new file mode 100644 index 000000000..4f01936ee --- /dev/null +++ b/core/modules/simpletest/tests/src/Kernel/PhpUnitErrorTest.php @@ -0,0 +1,46 @@ +assertEquals(count($res), 4, 'All testcases got extracted'); + $this->assertNotEquals($res[0]['status'], 'pass'); + $this->assertEquals($res[0]['status'], 'fail'); + + // Test nested testsuites, which appear when you use @dataProvider. + for ($i = 0; $i < 3; $i++) { + $this->assertNotEquals($res[$i + 1]['status'], 'pass'); + $this->assertEquals($res[$i + 1]['status'], 'fail'); + } + + // Make sure simpletest_phpunit_xml_to_rows() does not balk if the test + // didn't run. + $this->assertNull(simpletest_phpunit_xml_to_rows(1, 'does_not_exist')); + } + +} diff --git a/core/modules/simpletest/tests/src/Kernel/SimpletestDeprecationTest.php b/core/modules/simpletest/tests/src/Kernel/SimpletestDeprecationTest.php index 384f8c1b9..4ef09a903 100644 --- a/core/modules/simpletest/tests/src/Kernel/SimpletestDeprecationTest.php +++ b/core/modules/simpletest/tests/src/Kernel/SimpletestDeprecationTest.php @@ -3,9 +3,10 @@ namespace Drupal\Tests\simpletest\Kernel; use Drupal\KernelTests\KernelTestBase; +use Drupal\simpletest\TestDiscovery; /** - * Verify deprecation of simpletest. + * Verify deprecations within the simpletest module. * * @group simpletest * @group legacy @@ -25,4 +26,79 @@ public function testDeprecatedFunctions() { simpletest_classloader_register(); } + /** + * @expectedDeprecation Drupal\simpletest\TestDiscovery is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDiscovery instead. See https://www.drupal.org/node/2949692 + * @expectedDeprecation The "test_discovery" service relies on the deprecated "Drupal\simpletest\TestDiscovery" class. It should either be deprecated or its implementation upgraded. + */ + public function testDeprecatedServices() { + $this->assertInstanceOf(TestDiscovery::class, $this->container->get('test_discovery')); + } + + /** + * @expectedDeprecation simpletest_phpunit_xml_filepath is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::xmlLogFilepath() instead. See https://www.drupal.org/node/2948547 + * @expectedDeprecation simpletest_phpunit_command is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::phpUnitCommand() instead. See https://www.drupal.org/node/2948547 + * @expectedDeprecation simpletest_phpunit_find_testcases is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::findTestCases() instead. See https://www.drupal.org/node/2948547 + * @expectedDeprecation simpletest_phpunit_testcase_to_row is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::convertTestCaseToSimpletestRow() instead. See https://www.drupal.org/node/2948547 + * @expectedDeprecation simpletest_summarize_phpunit_result is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::summarizeResults() instead. See https://www.drupal.org/node/2948547 + */ + public function testDeprecatedPhpUnitFunctions() { + // We can't test the deprecation errors for the following functions because + // they cannot be mocked, and calling them would change the test results: + // - simpletest_run_phpunit_tests(). + // - simpletest_phpunit_run_command(). + // - simpletest_phpunit_xml_to_rows(). + $this->assertStringEndsWith('/phpunit-23.xml', simpletest_phpunit_xml_filepath(23)); + + $this->assertInternalType('string', simpletest_phpunit_command()); + + $this->assertEquals([], simpletest_phpunit_find_testcases(new \SimpleXMLElement(''))); + + $this->assertEquals([ + 'test_id' => 23, + 'test_class' => '', + 'status' => 'pass', + 'message' => '', + 'message_group' => 'Other', + 'function' => '->()', + 'line' => 0, + 'file' => NULL, + ], simpletest_phpunit_testcase_to_row(23, new \SimpleXMLElement(''))); + + $this->assertEquals( + [ + static::class => [ + '#pass' => 0, + '#fail' => 0, + '#exception' => 0, + '#debug' => 1, + ], + ], + simpletest_summarize_phpunit_result([ + [ + 'test_class' => static::class, + 'status' => 'debug', + ], + ]) + ); + } + + /** + * @expectedDeprecation simpletest_generate_file() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Tests\TestFileCreationTrait::generateFile() instead. See https://www.drupal.org/node/3077768 + */ + public function testDeprecatedSimpletestGenerateFile() { + $file = simpletest_generate_file('foo', 40, 10); + $public_file = 'public://' . $file . '.txt'; + $this->assertFileExists($public_file); + $this->assertTrue(unlink($public_file)); + } + + /** + * @expectedDeprecation simpletest_process_phpunit_results() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::processPhpUnitResults() instead. See https://www.drupal.org/node/3075252 + */ + public function testProcessPhpUnitResults() { + // The only safe way to test this deprecation is to call it with an empty + // result set. This should not touch the results database. + $this->assertNull(simpletest_process_phpunit_results([])); + } + } diff --git a/core/modules/simpletest/tests/src/Kernel/TestDeprecatedTestHooks.php b/core/modules/simpletest/tests/src/Kernel/TestDeprecatedTestHooks.php new file mode 100644 index 000000000..c578de699 --- /dev/null +++ b/core/modules/simpletest/tests/src/Kernel/TestDeprecatedTestHooks.php @@ -0,0 +1,146 @@ +assertNull(_simpletest_batch_finished(TRUE, [], [], 10)); + } + + /** + * @expectedDeprecation The deprecated hook hook_test_group_started() is implemented in these functions: simpletest_deprecation_test_test_group_started(). Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242 + */ + public function testHookTestGroupStarted() { + // Mock a database connection enough for simpletest_run_tests(). + $insert = $this->getMockBuilder(Insert::class) + ->disableOriginalConstructor() + ->setMethods(['execute', 'useDefaults']) + ->getMock(); + $insert->expects($this->any()) + ->method('useDefaults') + ->willReturn($insert); + $insert->expects($this->any()) + ->method('execute') + // Return an arbitrary test ID. + ->willReturn(__METHOD__); + + $connection = $this->getMockBuilder(Connection::class) + ->disableOriginalConstructor() + ->setMethods(['insert']) + ->getMockForAbstractClass(); + $connection->expects($this->once()) + ->method('insert') + ->willReturn($insert); + + // Mock public stream wrapper enough for simpletest_run_tests(). + $public = $this->getMockBuilder(PublicStream::class) + ->disableOriginalConstructor() + // Mock all methods to do nothing and return NULL. + ->setMethods([]) + ->getMock(); + + // Set up the container. + $this->container->set('database', $connection); + $this->container->set('stream_wrapper.public', $public); + + // Make sure our mocked database is in use by expecting a test ID that is + // __METHOD__. + $this->assertEquals(__METHOD__, simpletest_run_tests([static::class])); + } + + /** + * @expectedDeprecation The deprecated hook hook_test_finished() is implemented in these functions: simpletest_deprecation_test_test_finished(). Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242 + */ + public function testHookTestFinished() { + // Mock test_discovery. + $discovery = $this->getMockBuilder(TestDiscovery::class) + ->disableOriginalConstructor() + ->setMethods(['registerTestNamespaces']) + ->getMock(); + $discovery->expects($this->once()) + ->method('registerTestNamespaces') + ->willReturn([]); + + // Mock renderer. + $renderer = $this->getMockBuilder(Renderer::class) + ->disableOriginalConstructor() + ->setMethods(['render']) + ->getMock(); + // We don't care what the rendered batch elements look like. + $renderer->expects($this->any()) + ->method('render') + ->willReturn(''); + + // Set up the container. + $this->container->set('test_discovery', $discovery); + $this->container->set('renderer', $renderer); + + // A mock batch. + $context = []; + + // InnocuousTest is a WebTestBase test class which passes and never touches + // the database. + _simpletest_batch_operation([InnocuousTest::class], __METHOD__, $context); + } + +} + +/** + * A very simple WebTestBase test that never touches the database. + * + * @group WebTestBase + * @group legacy + */ +class InnocuousTest extends WebTestBase { + + /** + * Override to prevent any environmental side-effects. + */ + protected function prepareEnvironment() { + } + + /** + * Override run() since it uses TestBase. + */ + public function run(array $methods = []) { + } + + /** + * Override to prevent any assertions from being stored. + */ + protected function storeAssertion(array $assertion) { + } + + /** + * Override to prevent any assertions from being stored. + */ + public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = []) { + } + +} diff --git a/core/modules/simpletest/tests/src/Kernel/TestDiscoveryDeprecationTest.php b/core/modules/simpletest/tests/src/Kernel/TestDiscoveryDeprecationTest.php index 6d4f44888..c9c781d27 100644 --- a/core/modules/simpletest/tests/src/Kernel/TestDiscoveryDeprecationTest.php +++ b/core/modules/simpletest/tests/src/Kernel/TestDiscoveryDeprecationTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\simpletest\Kernel; use Drupal\KernelTests\KernelTestBase; +use Drupal\simpletest\TestDiscovery; /** * @group simpletest @@ -22,11 +23,13 @@ class TestDiscoveryDeprecationTest extends KernelTestBase { * @covers ::getTestClasses */ public function testHookSimpletestAlter() { + $test_discovery = $this->container->get('test_discovery'); + + $this->assertEquals(TestDiscovery::class, get_class($test_discovery)); + // The simpletest_test module implements hook_simpletest_alter(), which // should trigger a deprecation error during getTestClasses(). - $this->assertNotEmpty( - $this->container->get('test_discovery')->getTestClasses() - ); + $this->assertNotEmpty($test_discovery->getTestClasses()); } } diff --git a/core/modules/simpletest/tests/src/Unit/PhpUnitErrorTest.php b/core/modules/simpletest/tests/src/Unit/PhpUnitErrorTest.php deleted file mode 100644 index 25211fc40..000000000 --- a/core/modules/simpletest/tests/src/Unit/PhpUnitErrorTest.php +++ /dev/null @@ -1,40 +0,0 @@ -assertEquals(count($res), 4, 'All testcases got extracted'); - $this->assertNotEquals($res[0]['status'], 'pass'); - $this->assertEquals($res[0]['status'], 'fail'); - - // Test nested testsuites, which appear when you use @dataProvider. - for ($i = 0; $i < 3; $i++) { - $this->assertNotEquals($res[$i + 1]['status'], 'pass'); - $this->assertEquals($res[$i + 1]['status'], 'fail'); - } - - // Make sure simpletest_phpunit_xml_to_rows() does not balk if the test - // didn't run. - simpletest_phpunit_xml_to_rows(1, 'foobar'); - } - -} diff --git a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php index 30617b956..4f90438d1 100644 --- a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php +++ b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php @@ -5,15 +5,25 @@ use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Test\PhpUnitTestRunner; use PHPUnit\Framework\TestCase; /** * Tests simpletest_run_phpunit_tests() handles PHPunit fatals correctly. * - * We don't extend Drupal\Tests\UnitTestCase here because its $root property is + * We don't extend \Drupal\Tests\UnitTestCase here because its $root property is * not static and we need it to be static here. * + * The file simpletest_phpunit_run_command_test.php contains the test class + * \Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTestWillDie which + * can be made to exit with result code 2. It lives in a file which won't be + * autoloaded, so that it won't fail test runs. + * + * Here, we run SimpletestPhpunitRunCommandTestWillDie, make it die, and see + * what happens. + * * @group simpletest + * @group legacy * * @runTestsInSeparateProcesses * @preserveGlobalState disabled @@ -27,6 +37,13 @@ class SimpletestPhpunitRunCommandTest extends TestCase { */ protected static $root; + /** + * A fixture container. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $fixtureContainer; + /** * {@inheritdoc} */ @@ -56,6 +73,7 @@ protected function setUp() { $file_system->realpath('public://simpletest')->willReturn(sys_get_temp_dir()); $container->set('file_system', $file_system->reveal()); \Drupal::setContainer($container); + $this->fixtureContainer = $container; } /** @@ -82,9 +100,14 @@ public function provideStatusCodes() { /** * Test the round trip for PHPUnit execution status codes. * + * Also tests backwards-compatibility of PhpUnitTestRunner::runTests(). + * * @covers ::simpletest_run_phpunit_tests * * @dataProvider provideStatusCodes + * + * @expectedDeprecation simpletest_run_phpunit_tests is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::runTests() instead. See https://www.drupal.org/node/2948547 + * @expectedDeprecation simpletest_phpunit_xml_filepath is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::xmlLogFilepath() instead. See https://www.drupal.org/node/2948547 */ public function testSimpletestPhpUnitRunCommand($status, $label) { // Add a default database connection in order for @@ -101,8 +124,17 @@ public function testSimpletestPhpUnitRunCommand($status, $label) { ); $test_id = basename(tempnam(sys_get_temp_dir(), 'xxx')); putenv('SimpletestPhpunitRunCommandTestWillDie=' . $status); - $ret = simpletest_run_phpunit_tests($test_id, [SimpletestPhpunitRunCommandTestWillDie::class]); + + // Test against simpletest_run_phpunit_tests(). + $bc_ret = simpletest_run_phpunit_tests($test_id, [SimpletestPhpunitRunCommandTestWillDie::class]); + $this->assertSame($bc_ret[0]['status'], $label); + + // Test against PhpUnitTestRunner::runTests(). + $runner = PhpUnitTestRunner::create($this->fixtureContainer); + $ret = $runner->runTests($test_id, [SimpletestPhpunitRunCommandTestWillDie::class]); $this->assertSame($ret[0]['status'], $label); + + // Unset our environmental variable. putenv('SimpletestPhpunitRunCommandTestWillDie'); unlink(simpletest_phpunit_xml_filepath($test_id)); } diff --git a/core/modules/simpletest/tests/src/Unit/TestDiscoveryTest.php b/core/modules/simpletest/tests/src/Unit/TestDiscoveryTest.php index fa3a93bf6..eb0278950 100644 --- a/core/modules/simpletest/tests/src/Unit/TestDiscoveryTest.php +++ b/core/modules/simpletest/tests/src/Unit/TestDiscoveryTest.php @@ -7,295 +7,17 @@ use Drupal\Core\DrupalKernel; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\simpletest\Exception\MissingGroupException; use Drupal\simpletest\TestDiscovery; use Drupal\Tests\UnitTestCase; use org\bovigo\vfs\vfsStream; /** * @coversDefaultClass \Drupal\simpletest\TestDiscovery - * @group simpletest - */ -class TestDiscoveryTest extends UnitTestCase { - - /** - * @covers ::getTestInfo - * @dataProvider infoParserProvider - */ - public function testTestInfoParser($expected, $classname, $doc_comment = NULL) { - $info = TestDiscovery::getTestInfo($classname, $doc_comment); - $this->assertEquals($expected, $info); - } - - public function infoParserProvider() { - // A module provided unit test. - $tests[] = [ - // Expected result. - [ - 'name' => static::class, - 'group' => 'simpletest', - 'groups' => ['simpletest'], - 'description' => 'Tests \Drupal\simpletest\TestDiscovery.', - 'type' => 'PHPUnit-Unit', - ], - // Classname. - static::class, - ]; - - // A core unit test. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\Tests\Core\DrupalTest', - 'group' => 'DrupalTest', - 'groups' => ['DrupalTest'], - 'description' => 'Tests \Drupal.', - 'type' => 'PHPUnit-Unit', - ], - // Classname. - 'Drupal\Tests\Core\DrupalTest', - ]; - - // Functional PHPUnit test. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\FunctionalTests\BrowserTestBaseTest', - 'group' => 'browsertestbase', - 'groups' => ['browsertestbase'], - 'description' => 'Tests BrowserTestBase functionality.', - 'type' => 'PHPUnit-Functional', - ], - // Classname. - 'Drupal\FunctionalTests\BrowserTestBaseTest', - ]; - - // kernel PHPUnit test. - $tests['phpunit-kernel'] = [ - // Expected result. - [ - 'name' => '\Drupal\Tests\file\Kernel\FileItemValidationTest', - 'group' => 'file', - 'groups' => ['file'], - 'description' => 'Tests that files referenced in file and image fields are always validated.', - 'type' => 'PHPUnit-Kernel', - ], - // Classname. - '\Drupal\Tests\file\Kernel\FileItemValidationTest', - ]; - - // Simpletest classes can not be autoloaded in a PHPUnit test, therefore - // provide a docblock. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'group' => 'simpletest', - 'groups' => ['simpletest'], - 'description' => 'Tests the Simpletest UI internal browser.', - 'type' => 'Simpletest', - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. * * @group simpletest + * @group legacy */ - ", - ]; - - // Test with a different amount of leading spaces. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'group' => 'simpletest', - 'groups' => ['simpletest'], - 'description' => 'Tests the Simpletest UI internal browser.', - 'type' => 'Simpletest', - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. - * - * @group simpletest - */ - */ - ", - ]; - - // Make sure that a "* @" inside a string does not get parsed as an - // annotation. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'group' => 'simpletest', - 'groups' => ['simpletest'], - 'description' => 'Tests the Simpletest UI internal browser. * @', - 'type' => 'Simpletest', - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. * @ - * - * @group simpletest - */ - ", - ]; - - // Multiple @group annotations. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'group' => 'Test', - 'groups' => ['Test', 'simpletest'], - 'description' => 'Tests the Simpletest UI internal browser.', - 'type' => 'Simpletest', - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. - * - * @group Test - * @group simpletest - */ - ", - ]; - - // A great number of @group annotations. - $tests['many-group-annotations'] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'group' => 'Test', - 'groups' => ['Test', 'simpletest', 'another', 'more', 'many', 'enough', 'whoa'], - 'description' => 'Tests the Simpletest UI internal browser.', - 'type' => 'Simpletest', - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. - * - * @group Test - * @group simpletest - * @group another - * @group more - * @group many - * @group enough - * @group whoa - */ - ", - ]; - - // @dependencies annotation. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'description' => 'Tests the Simpletest UI internal browser.', - 'type' => 'Simpletest', - 'requires' => ['module' => ['test']], - 'group' => 'simpletest', - 'groups' => ['simpletest'], - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. - * - * @dependencies test - * @group simpletest - */ - ", - ]; - - // Multiple @dependencies annotation. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'description' => 'Tests the Simpletest UI internal browser.', - 'type' => 'Simpletest', - 'requires' => ['module' => ['test', 'test1', 'test2']], - 'group' => 'simpletest', - 'groups' => ['simpletest'], - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. - * - * @dependencies test, test1, test2 - * @group simpletest - */ - ", - ]; - - // Multi-line summary line. - $tests[] = [ - // Expected result. - [ - 'name' => 'Drupal\simpletest\Tests\ExampleSimpleTest', - 'description' => 'Tests the Simpletest UI internal browser. And the summary line continues an there is no gap to the annotation.', - 'type' => 'Simpletest', - 'group' => 'simpletest', - 'groups' => ['simpletest'], - ], - // Classname. - 'Drupal\simpletest\Tests\ExampleSimpleTest', - // Doc block. - "/** - * Tests the Simpletest UI internal browser. And the summary line continues an - * there is no gap to the annotation. - * - * @group simpletest - */ - ", - ]; - return $tests; - } - - /** - * @covers ::getTestInfo - */ - public function testTestInfoParserMissingGroup() { - $classname = 'Drupal\KernelTests\field\BulkDeleteTest'; - $doc_comment = <<setExpectedException(MissingGroupException::class, 'Missing @group annotation in Drupal\KernelTests\field\BulkDeleteTest'); - TestDiscovery::getTestInfo($classname, $doc_comment); - } - - /** - * @covers ::getTestInfo - */ - public function testTestInfoParserMissingSummary() { - $classname = 'Drupal\KernelTests\field\BulkDeleteTest'; - $doc_comment = <<assertEmpty($info['description']); - } +class TestDiscoveryTest extends UnitTestCase { protected function setupVfsWithTestClasses() { vfsStream::setup('drupal'); @@ -325,6 +47,7 @@ class FunctionalExampleTest {} vfsStream::create([ 'modules' => [ 'test_module' => [ + 'test_module.info.yml' => $test_module_info, 'tests' => [ 'src' => [ 'Functional' => [ @@ -362,19 +85,34 @@ class FunctionalExampleTest {} } /** - * @covers ::getTestClasses + * Mock a TestDiscovery object to return specific extension values. */ - public function testGetTestClasses() { - $this->setupVfsWithTestClasses(); + protected function getTestDiscoveryMock($app_root, $extensions) { $class_loader = $this->prophesize(ClassLoader::class); $module_handler = $this->prophesize(ModuleHandlerInterface::class); - $test_discovery = new TestTestDiscovery('vfs://drupal', $class_loader->reveal(), $module_handler->reveal()); + $test_discovery = $this->getMockBuilder(TestDiscovery::class) + ->setConstructorArgs([$app_root, $class_loader->reveal(), $module_handler->reveal()]) + ->setMethods(['getExtensions']) + ->getMock(); + + $test_discovery->expects($this->any()) + ->method('getExtensions') + ->willReturn($extensions); + + return $test_discovery; + } + /** + * @covers ::getTestClasses + */ + public function testGetTestClasses() { + $this->setupVfsWithTestClasses(); $extensions = [ 'test_module' => new Extension('vfs://drupal', 'module', 'modules/test_module/test_module.info.yml'), ]; - $test_discovery->setExtensions($extensions); + $test_discovery = $this->getTestDiscoveryMock('vfs://drupal', $extensions); + $result = $test_discovery->getTestClasses(); $this->assertCount(3, $result); $this->assertEquals([ @@ -420,16 +158,12 @@ public function testGetTestClasses() { */ public function testGetTestClassesWithSelectedTypes() { $this->setupVfsWithTestClasses(); - $class_loader = $this->prophesize(ClassLoader::class); - $module_handler = $this->prophesize(ModuleHandlerInterface::class); - - $test_discovery = new TestTestDiscovery('vfs://drupal', $class_loader->reveal(), $module_handler->reveal()); - $extensions = [ 'test_module' => new Extension('vfs://drupal', 'module', 'modules/test_module/test_module.info.yml'), 'test_profile_module' => new Extension('vfs://drupal', 'profile', 'profiles/test_profile/modules/test_profile_module/test_profile_module.info.yml'), ]; - $test_discovery->setExtensions($extensions); + $test_discovery = $this->getTestDiscoveryMock('vfs://drupal', $extensions); + $result = $test_discovery->getTestClasses(NULL, ['PHPUnit-Kernel']); $this->assertCount(4, $result); $this->assertEquals([ @@ -479,7 +213,7 @@ public function testGetTestsInProfiles() { $test_discovery = new TestDiscovery('vfs://drupal', $class_loader->reveal(), $module_handler->reveal()); - $result = $test_discovery->getTestClasses(NULL, ['PHPUnit-Kernel']); + $result = $test_discovery->getTestClasses('test_profile_module', ['PHPUnit-Kernel']); $expected = [ 'example3' => [ 'Drupal\Tests\test_profile_module\Kernel\KernelExampleTest4' => [ @@ -494,79 +228,4 @@ public function testGetTestsInProfiles() { $this->assertEquals($expected, $result); } - /** - * @covers ::getPhpunitTestSuite - * @dataProvider providerTestGetPhpunitTestSuite - */ - public function testGetPhpunitTestSuite($classname, $expected) { - $this->assertEquals($expected, TestDiscovery::getPhpunitTestSuite($classname)); - } - - public function providerTestGetPhpunitTestSuite() { - $data = []; - $data['simpletest-webtest'] = ['\Drupal\rest\Tests\NodeTest', FALSE]; - $data['simpletest-kerneltest'] = ['\Drupal\hal\Tests\FileNormalizeTest', FALSE]; - $data['module-unittest'] = [static::class, 'Unit']; - $data['module-kerneltest'] = ['\Drupal\KernelTests\Core\Theme\TwigMarkupInterfaceTest', 'Kernel']; - $data['module-functionaltest'] = ['\Drupal\FunctionalTests\BrowserTestBaseTest', 'Functional']; - $data['module-functionaljavascripttest'] = ['\Drupal\Tests\toolbar\FunctionalJavascript\ToolbarIntegrationTest', 'FunctionalJavascript']; - $data['core-unittest'] = ['\Drupal\Tests\ComposerIntegrationTest', 'Unit']; - $data['core-unittest2'] = ['Drupal\Tests\Core\DrupalTest', 'Unit']; - $data['core-kerneltest'] = ['\Drupal\KernelTests\KernelTestBaseTest', 'Kernel']; - $data['core-functionaltest'] = ['\Drupal\FunctionalTests\ExampleTest', 'Functional']; - $data['core-functionaljavascripttest'] = ['\Drupal\FunctionalJavascriptTests\ExampleTest', 'FunctionalJavascript']; - - return $data; - } - - /** - * Ensure that classes are not reflected when the docblock is empty. - * - * @covers ::getTestInfo - */ - public function testGetTestInfoEmptyDocblock() { - // If getTestInfo() performed reflection, it won't be able to find the - // class we asked it to analyze, so it will throw a ReflectionException. - // We want to make sure it didn't do that, because we already did some - // analysis and already have an empty docblock. getTestInfo() will throw - // MissingGroupException because the annotation is empty. - $this->setExpectedException(MissingGroupException::class); - TestDiscovery::getTestInfo('Drupal\Tests\simpletest\ThisTestDoesNotExistTest', ''); - } - - /** - * Ensure TestDiscovery::scanDirectory() ignores certain abstract file types. - * - * @covers ::scanDirectory - */ - public function testScanDirectoryNoAbstract() { - $this->setupVfsWithTestClasses(); - $files = TestDiscovery::scanDirectory('Drupal\\Tests\\test_module\\Kernel\\', vfsStream::url('drupal/modules/test_module/tests/src/Kernel')); - $this->assertNotEmpty($files); - $this->assertArrayNotHasKey('Drupal\Tests\test_module\Kernel\KernelExampleTestBase', $files); - $this->assertArrayNotHasKey('Drupal\Tests\test_module\Kernel\KernelExampleTrait', $files); - $this->assertArrayNotHasKey('Drupal\Tests\test_module\Kernel\KernelExampleInterface', $files); - $this->assertArrayHasKey('Drupal\Tests\test_module\Kernel\KernelExampleTest3', $files); - } - -} - -class TestTestDiscovery extends TestDiscovery { - - /** - * @var \Drupal\Core\Extension\Extension[] - */ - protected $extensions = []; - - public function setExtensions(array $extensions) { - $this->extensions = $extensions; - } - - /** - * {@inheritdoc} - */ - protected function getExtensions() { - return $this->extensions; - } - } diff --git a/core/modules/statistics/migrations/state/statistics.migrate_drupal.yml b/core/modules/statistics/migrations/state/statistics.migrate_drupal.yml new file mode 100644 index 000000000..6d8189f7a --- /dev/null +++ b/core/modules/statistics/migrations/state/statistics.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + statistics: statistics + 7: + statistics: statistics diff --git a/core/modules/statistics/src/Tests/StatisticsTestBase.php b/core/modules/statistics/src/Tests/StatisticsTestBase.php index aee634e79..d2fced057 100644 --- a/core/modules/statistics/src/Tests/StatisticsTestBase.php +++ b/core/modules/statistics/src/Tests/StatisticsTestBase.php @@ -9,7 +9,7 @@ /** * Defines a base class for testing the Statistics module. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\statistics\Functional\StatisticsTestBase instead. * * @see https://www.drupal.org/node/2999939 diff --git a/core/modules/statistics/statistics.info.yml b/core/modules/statistics/statistics.info.yml index 534562b2f..84a50bec9 100644 --- a/core/modules/statistics/statistics.info.yml +++ b/core/modules/statistics/statistics.info.yml @@ -2,14 +2,8 @@ name: Statistics type: module description: 'Logs content statistics for your site.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: statistics.settings dependencies: - drupal:node - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 222fe9853..30d898359 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -93,7 +93,7 @@ function statistics_cron() { * and the username for the selected node(s), or FALSE if the query could not * be executed correctly. * - * @deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. * Use \Drupal\statistics\NodeStatisticsDatabaseStorage::fetchAll() instead. */ function statistics_title_list($dbfield, $dbrows) { @@ -123,10 +123,13 @@ function statistics_title_list($dbfield, $dbrows) { /** * Retrieves a node's "view statistics". * - * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.0.0. - * Use \Drupal::service('statistics.storage.node')->fetchView($id). + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use + * \Drupal::service('statistics.storage.node')->fetchView($id) instead. + * + * @see https://www.drupal.org/node/2778245 */ function statistics_get($id) { + @trigger_error("statistics_get() is deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use Drupal::service('statistics.storage.node')->fetchView() instead. See https://www.drupal.org/node/2778245", E_USER_DEPRECATED); if ($id > 0) { /** @var \Drupal\statistics\StatisticsViewsResult $statistics */ $statistics = \Drupal::service('statistics.storage.node')->fetchView($id); diff --git a/core/modules/statistics/statistics.routing.yml b/core/modules/statistics/statistics.routing.yml index d5ea04c3f..6edf4ca89 100644 --- a/core/modules/statistics/statistics.routing.yml +++ b/core/modules/statistics/statistics.routing.yml @@ -1,7 +1,7 @@ statistics.settings: path: '/admin/config/system/statistics' defaults: - _form: 'Drupal\statistics\StatisticsSettingsForm' + _form: '\Drupal\statistics\StatisticsSettingsForm' _title: 'Statistics' requirements: _permission: 'administer statistics' diff --git a/core/modules/statistics/statistics.tokens.inc b/core/modules/statistics/statistics.tokens.inc index 056cac45f..467c6b428 100644 --- a/core/modules/statistics/statistics.tokens.inc +++ b/core/modules/statistics/statistics.tokens.inc @@ -40,25 +40,22 @@ function statistics_tokens($type, $tokens, array $data, array $options, Bubbleab if ($type == 'node' & !empty($data['node'])) { $node = $data['node']; - + /** @var \Drupal\statistics\StatisticsStorageInterface $stats_storage */ + $stats_storage = \Drupal::service('statistics.storage.node'); foreach ($tokens as $name => $original) { if ($name == 'total-count') { - $statistics = statistics_get($node->id()); - $replacements[$original] = $statistics['totalcount']; + $replacements[$original] = $stats_storage->fetchView($node->id())->getTotalCount(); } elseif ($name == 'day-count') { - $statistics = statistics_get($node->id()); - $replacements[$original] = $statistics['daycount']; + $replacements[$original] = $stats_storage->fetchView($node->id())->getDayCount(); } elseif ($name == 'last-view') { - $statistics = statistics_get($node->id()); - $replacements[$original] = \Drupal::service('date.formatter')->format($statistics['timestamp']); + $replacements[$original] = \Drupal::service('date.formatter')->format($stats_storage->fetchView($node->id())->getTimestamp()); } } if ($created_tokens = $token_service->findWithPrefix($tokens, 'last-view')) { - $statistics = statistics_get($node->id()); - $replacements += $token_service->generate('date', $created_tokens, ['date' => $statistics['timestamp']], $options, $bubbleable_metadata); + $replacements += $token_service->generate('date', $created_tokens, ['date' => $stats_storage->fetchView($node->id())->getTimestamp()], $options, $bubbleable_metadata); } } diff --git a/core/modules/statistics/tests/modules/statistics_test_views/statistics_test_views.info.yml b/core/modules/statistics/tests/modules/statistics_test_views/statistics_test_views.info.yml index f134b5e25..beaf8e94a 100644 --- a/core/modules/statistics/tests/modules/statistics_test_views/statistics_test_views.info.yml +++ b/core/modules/statistics/tests/modules/statistics_test_views/statistics_test_views.info.yml @@ -2,14 +2,8 @@ name: 'Statistics test views' type: module description: 'Provides default views for views statistics tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:statistics - drupal:views - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml b/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml index 4d2d90ff6..b8708cb3d 100644 --- a/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml +++ b/core/modules/statistics/tests/modules/statistics_test_views/test_views/views.view.test_statistics_integration.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: node_field_data base_field: nid -core: 8.x display: default: display_plugin: default diff --git a/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php b/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php index 5efc804b8..b0f633442 100644 --- a/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php +++ b/core/modules/statistics/tests/src/Functional/StatisticsAdminTest.php @@ -22,6 +22,11 @@ class StatisticsAdminTest extends BrowserTestBase { */ public static $modules = ['node', 'statistics']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user that has permission to administer statistics. * @@ -64,13 +69,13 @@ protected function setUp() { */ public function testStatisticsSettings() { $config = $this->config('statistics.settings'); - $this->assertFalse($config->get('count_content_views'), 'Count content view log is disabled by default.'); + $this->assertEmpty($config->get('count_content_views'), 'Count content view log is disabled by default.'); // Enable counter on content view. $edit['statistics_count_content_views'] = 1; $this->drupalPostForm('admin/config/system/statistics', $edit, t('Save configuration')); $config = $this->config('statistics.settings'); - $this->assertTrue($config->get('count_content_views'), 'Count content view log is enabled.'); + $this->assertNotEmpty($config->get('count_content_views'), 'Count content view log is enabled.'); // Hit the node. $this->drupalGet('node/' . $this->testNode->id()); @@ -169,7 +174,7 @@ public function testExpiredLogs() { ->condition('nid', $this->testNode->id(), '=') ->execute() ->fetchField(); - $this->assertFalse($result, 'Daycounter is zero.'); + $this->assertEmpty($result, 'Daycounter is zero.'); } } diff --git a/core/modules/statistics/tests/src/Functional/StatisticsAttachedTest.php b/core/modules/statistics/tests/src/Functional/StatisticsAttachedTest.php index 90a62093b..9a77f50e0 100644 --- a/core/modules/statistics/tests/src/Functional/StatisticsAttachedTest.php +++ b/core/modules/statistics/tests/src/Functional/StatisticsAttachedTest.php @@ -19,6 +19,11 @@ class StatisticsAttachedTest extends BrowserTestBase { */ public static $modules = ['node', 'statistics']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -29,7 +34,7 @@ protected function setUp() { // Install "statistics_test_attached" and set it as the default theme. $theme = 'statistics_test_attached'; - \Drupal::service('theme_handler')->install([$theme]); + \Drupal::service('theme_installer')->install([$theme]); $this->config('system.theme') ->set('default', $theme) ->save(); diff --git a/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php b/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php index 4421bc7c6..07abf5fdb 100644 --- a/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php +++ b/core/modules/statistics/tests/src/Functional/StatisticsLoggingTest.php @@ -23,6 +23,11 @@ class StatisticsLoggingTest extends BrowserTestBase { */ public static $modules = ['node', 'statistics', 'block', 'locale']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * User with permissions to create and edit pages. * @@ -125,8 +130,8 @@ public function testLogging() { global $base_root; $post = ['nid' => $this->node->id()]; $this->client->post($base_root . $stats_path, ['form_params' => $post]); - $node_counter = statistics_get($this->node->id()); - $this->assertIdentical($node_counter['totalcount'], 1); + $node_counter = \Drupal::service('statistics.storage.node')->fetchView($this->node->id()); + $this->assertIdentical(1, $node_counter->getTotalCount()); // Try fetching statistics for an invalid node ID and verify it returns // FALSE. @@ -136,7 +141,7 @@ public function testLogging() { // This is a test specifically for the deprecated statistics_get() function // and so should remain unconverted until that function is removed. - $result = statistics_get($node_id); + $result = \Drupal::service('statistics.storage.node')->fetchView($node_id); $this->assertIdentical($result, FALSE); } diff --git a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php index 574a27fa6..0b80251d6 100644 --- a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php +++ b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\statistics\Functional; use Drupal\Core\Cache\Cache; +use Drupal\Core\Link; use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait; /** @@ -14,6 +15,11 @@ class StatisticsReportsTest extends StatisticsTestBase { use AssertPageCacheContextsAndTagsTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the "popular content" block. */ @@ -57,7 +63,7 @@ public function testPopularContentBlock() { $this->assertCacheContexts($contexts); // Check if the node link is displayed. - $this->assertRaw(\Drupal::l($node->label(), $node->toUrl('canonical')), 'Found link to visited node.'); + $this->assertRaw(Link::fromTextAndUrl($node->label(), $node->toUrl('canonical'))->toString(), 'Found link to visited node.'); } } diff --git a/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php b/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php index 70f2f4298..6a6709881 100644 --- a/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php +++ b/core/modules/statistics/tests/src/Functional/StatisticsTokenReplaceTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\statistics\Functional; +use Drupal\Component\Render\FormattableMarkup; + /** * Generates text using placeholders for dummy content to check statistics token * replacement. @@ -10,6 +12,11 @@ */ class StatisticsTokenReplaceTest extends StatisticsTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Creates a node, then tests the statistics tokens generated from it. */ @@ -31,7 +38,8 @@ public function testStatisticsTokenReplacement() { $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics') . '/statistics.php'; $client = \Drupal::httpClient(); $client->post($stats_path, ['headers' => $headers, 'body' => $post]); - $statistics = statistics_get($node->id()); + /** @var \Drupal\statistics\StatisticsViewsResult $statistics */ + $statistics = \Drupal::service('statistics.storage.node')->fetchView($node->id()); // Generate and test tokens. $tests = []; @@ -39,15 +47,15 @@ public function testStatisticsTokenReplacement() { $tests['[node:day-count]'] = 1; /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ $date_formatter = $this->container->get('date.formatter'); - $tests['[node:last-view]'] = $date_formatter->format($statistics['timestamp']); - $tests['[node:last-view:short]'] = $date_formatter->format($statistics['timestamp'], 'short'); + $tests['[node:last-view]'] = $date_formatter->format($statistics->getTimestamp()); + $tests['[node:last-view:short]'] = $date_formatter->format($statistics->getTimestamp(), 'short'); // Test to make sure that we generated something for each token. $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); foreach ($tests as $input => $expected) { $output = \Drupal::token()->replace($input, ['node' => $node], ['langcode' => $language_interface->getId()]); - $this->assertEqual($output, $expected, format_string('Statistics token %token replaced.', ['%token' => $input])); + $this->assertEqual($output, $expected, new FormattableMarkup('Statistics token %token replaced.', ['%token' => $input])); } } diff --git a/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php b/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php index 56e18eefa..ebee1ee43 100644 --- a/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php +++ b/core/modules/statistics/tests/src/Functional/Views/IntegrationTest.php @@ -21,6 +21,11 @@ class IntegrationTest extends ViewTestBase { */ public static $modules = ['statistics', 'statistics_test_views', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Stores the user object that accesses the page. * @@ -78,26 +83,20 @@ public function testNodeCounterIntegration() { $client->post($stats_path, ['form_params' => ['nid' => $this->node->id()]]); $this->drupalGet('test_statistics_integration'); - $expected = statistics_get($this->node->id()); - // Convert the timestamp to year, to match the expected output of the date - // handler. - $expected['timestamp'] = date('Y', $expected['timestamp']); - - foreach ($expected as $field => $value) { - $xpath = "//div[contains(@class, views-field-$field)]/span[@class = 'field-content']"; - $this->assertFieldByXpath($xpath, $value, "The $field output matches the expected."); - } + /** @var \Drupal\statistics\StatisticsViewsResult $statistics */ + $statistics = \Drupal::service('statistics.storage.node')->fetchView($this->node->id()); + $this->assertSession()->pageTextContains('Total views: 1'); + $this->assertSession()->pageTextContains('Views today: 1'); + $this->assertSession()->pageTextContains('Most recent view: ' . date('Y', $statistics->getTimestamp())); $this->drupalLogout(); $this->drupalLogin($this->deniedUser); $this->drupalGet('test_statistics_integration'); $this->assertResponse(200); - foreach ($expected as $field => $value) { - $xpath = "//div[contains(@class, views-field-$field)]/span[@class = 'field-content']"; - $this->assertNoFieldByXpath($xpath, $value, "The $field output is not displayed."); - } - + $this->assertSession()->pageTextNotContains('Total views:'); + $this->assertSession()->pageTextNotContains('Views today:'); + $this->assertSession()->pageTextNotContains('Most recent view:'); } } diff --git a/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php b/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php index acd103e9f..c1b7bff29 100644 --- a/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php +++ b/core/modules/statistics/tests/src/FunctionalJavascript/StatisticsLoggingTest.php @@ -19,6 +19,11 @@ class StatisticsLoggingTest extends WebDriverTestBase { */ public static $modules = ['node', 'statistics', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Node for tests. * diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php index 29a35fb33..929398054 100644 --- a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php +++ b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateNodeCounterTest.php @@ -83,10 +83,9 @@ public function testStatisticsSettings() { protected function assertNodeCounter($nid, $total_count, $day_count, $timestamp) { /** @var \Drupal\statistics\StatisticsViewsResult $statistics */ $statistics = $this->container->get('statistics.storage.node')->fetchView($nid); - // @todo Remove casting after https://www.drupal.org/node/2926069 lands. - $this->assertSame($total_count, (int) $statistics->getTotalCount()); - $this->assertSame($day_count, (int) $statistics->getDayCount()); - $this->assertSame($timestamp, (int) $statistics->getTimestamp()); + $this->assertSame($total_count, $statistics->getTotalCount()); + $this->assertSame($day_count, $statistics->getDayCount()); + $this->assertSame($timestamp, $statistics->getTimestamp()); } } diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php index d36224825..708f0a7b4 100644 --- a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php +++ b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateNodeCounterTest.php @@ -74,10 +74,9 @@ public function testStatisticsSettings() { protected function assertNodeCounter($nid, $total_count, $day_count, $timestamp) { /** @var \Drupal\statistics\StatisticsViewsResult $statistics */ $statistics = $this->container->get('statistics.storage.node')->fetchView($nid); - // @todo Remove casting after https://www.drupal.org/node/2926069 lands. - $this->assertSame($total_count, (int) $statistics->getTotalCount()); - $this->assertSame($day_count, (int) $statistics->getDayCount()); - $this->assertSame($timestamp, (int) $statistics->getTimestamp()); + $this->assertSame($total_count, $statistics->getTotalCount()); + $this->assertSame($day_count, $statistics->getDayCount()); + $this->assertSame($timestamp, $statistics->getTimestamp()); } } diff --git a/core/modules/statistics/tests/src/Kernel/StatisticsDeprecationsTest.php b/core/modules/statistics/tests/src/Kernel/StatisticsDeprecationsTest.php new file mode 100644 index 000000000..5a2416e9f --- /dev/null +++ b/core/modules/statistics/tests/src/Kernel/StatisticsDeprecationsTest.php @@ -0,0 +1,36 @@ +fetchView() instead. See https://www.drupal.org/node/2778245 + */ + public function testStatisticsGetDeprecation() { + $this->installSchema('statistics', 'node_counter'); + $this->container->get('statistics.storage.node')->recordView(1); + $expected_timestamp = $this->container->get('datetime.time')->getRequestTime(); + $this->assertSame([ + 'totalcount' => 1, + 'daycount' => 1, + 'timestamp' => $expected_timestamp, + ], statistics_get(1)); + } + +} diff --git a/core/modules/statistics/tests/themes/statistics_test_attached/statistics_test_attached.info.yml b/core/modules/statistics/tests/themes/statistics_test_attached/statistics_test_attached.info.yml index 7af51d31f..c3144edef 100644 --- a/core/modules/statistics/tests/themes/statistics_test_attached/statistics_test_attached.info.yml +++ b/core/modules/statistics/tests/themes/statistics_test_attached/statistics_test_attached.info.yml @@ -1,11 +1,6 @@ name: 'Statistics test attached theme' type: theme +base theme: stable description: 'Theme for testing attached library' -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/syslog/migrations/state/syslog.migrate_drupal.yml b/core/modules/syslog/migrations/state/syslog.migrate_drupal.yml new file mode 100644 index 000000000..7573f7bd4 --- /dev/null +++ b/core/modules/syslog/migrations/state/syslog.migrate_drupal.yml @@ -0,0 +1,5 @@ +finished: + 6: + syslog: syslog + 7: + syslog: syslog diff --git a/core/modules/syslog/syslog.info.yml b/core/modules/syslog/syslog.info.yml index b1f7bb77b..1d8dc8f1b 100644 --- a/core/modules/syslog/syslog.info.yml +++ b/core/modules/syslog/syslog.info.yml @@ -2,12 +2,6 @@ name: Syslog type: module description: 'Logs and records system events to syslog.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: system.logging_settings - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/syslog/syslog.module b/core/modules/syslog/syslog.module index 19c2db967..c40bf33bb 100644 --- a/core/modules/syslog/syslog.module +++ b/core/modules/syslog/syslog.module @@ -5,6 +5,7 @@ * Redirects logging messages to syslog. */ +use Drupal\Core\Link; use Drupal\Core\Url; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; @@ -34,7 +35,7 @@ function syslog_help($route_name, RouteMatchInterface $route_match) { */ function syslog_form_system_logging_settings_alter(&$form, FormStateInterface $form_state) { $config = \Drupal::configFactory()->getEditable('syslog.settings'); - $help = \Drupal::moduleHandler()->moduleExists('help') ? ' ' . \Drupal::l(t('More information'), new Url('help.page', ['name' => 'syslog'])) . '.' : NULL; + $help = \Drupal::moduleHandler()->moduleExists('help') ? ' ' . Link::fromTextAndUrl(t('More information'), Url::fromRoute('help.page', ['name' => 'syslog']))->toString() . '.' : NULL; $form['syslog_identity'] = [ '#type' => 'textfield', '#title' => t('Syslog identity'), diff --git a/core/modules/syslog/tests/modules/syslog_test/syslog_test.info.yml b/core/modules/syslog/tests/modules/syslog_test/syslog_test.info.yml index d6fb0870e..cac4de2a5 100644 --- a/core/modules/syslog/tests/modules/syslog_test/syslog_test.info.yml +++ b/core/modules/syslog/tests/modules/syslog_test/syslog_test.info.yml @@ -2,13 +2,7 @@ name: 'Syslog test' type: module description: 'Provides a test logger for syslog module.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:syslog - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/syslog/tests/src/Functional/SyslogTest.php b/core/modules/syslog/tests/src/Functional/SyslogTest.php index 11b577f2f..8348cb1ee 100644 --- a/core/modules/syslog/tests/src/Functional/SyslogTest.php +++ b/core/modules/syslog/tests/src/Functional/SyslogTest.php @@ -18,6 +18,11 @@ class SyslogTest extends BrowserTestBase { */ public static $modules = ['syslog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the syslog settings page. */ diff --git a/core/modules/system/config/install/system.file.yml b/core/modules/system/config/install/system.file.yml index ec8c0533f..02b376f45 100644 --- a/core/modules/system/config/install/system.file.yml +++ b/core/modules/system/config/install/system.file.yml @@ -1,5 +1,3 @@ allow_insecure_uploads: false default_scheme: 'public' -path: - temporary: '' temporary_maximum_age: 21600 diff --git a/core/modules/system/js/system.date.es6.js b/core/modules/system/js/system.date.es6.js index c011ddd99..46dac9f5a 100644 --- a/core/modules/system/js/system.date.es6.js +++ b/core/modules/system/js/system.date.es6.js @@ -38,12 +38,11 @@ */ function dateFormatHandler(e) { const baseValue = $(e.target).val() || ''; - const dateString = baseValue.replace( - /\\?(.?)/gi, - (key, value) => (dateFormats[key] ? dateFormats[key] : value), + const dateString = baseValue.replace(/\\?(.?)/gi, (key, value) => + dateFormats[key] ? dateFormats[key] : value, ); - $preview.html(dateString); + $preview.text(dateString); $target.toggleClass('js-hide', !dateString.length); } diff --git a/core/modules/system/js/system.date.js b/core/modules/system/js/system.date.js index 1c80ad792..785a5c65f 100644 --- a/core/modules/system/js/system.date.js +++ b/core/modules/system/js/system.date.js @@ -25,7 +25,7 @@ return dateFormats[key] ? dateFormats[key] : value; }); - $preview.html(dateString); + $preview.text(dateString); $target.toggleClass('js-hide', !dateString.length); } diff --git a/core/modules/system/migrations/d6_system_file.yml b/core/modules/system/migrations/d6_system_file.yml index 3f7145533..025fd2804 100644 --- a/core/modules/system/migrations/d6_system_file.yml +++ b/core/modules/system/migrations/d6_system_file.yml @@ -6,11 +6,9 @@ migration_tags: source: plugin: variable variables: - - file_directory_temp - allow_insecure_uploads source_module: system process: - 'path/temporary': file_directory_temp allow_insecure_uploads: plugin: static_map source: allow_insecure_uploads diff --git a/core/modules/system/migrations/d7_system_file.yml b/core/modules/system/migrations/d7_system_file.yml index b9fba89ea..08aa9b27a 100644 --- a/core/modules/system/migrations/d7_system_file.yml +++ b/core/modules/system/migrations/d7_system_file.yml @@ -7,7 +7,6 @@ source: plugin: variable variables: - allow_insecure_uploads - - file_temporary_path source_module: system process: allow_insecure_uploads: @@ -16,7 +15,6 @@ process: map: 0: FALSE 1: TRUE - 'path/temporary': file_temporary_path destination: plugin: config config_name: system.file diff --git a/core/modules/system/migrations/state/system.migrate_drupal.yml b/core/modules/system/migrations/state/system.migrate_drupal.yml new file mode 100644 index 000000000..92e9bd747 --- /dev/null +++ b/core/modules/system/migrations/state/system.migrate_drupal.yml @@ -0,0 +1,15 @@ +finished: + 6: + menu: + - system + - menu_link_content + - menu_ui + system: system +# An upgrade path is not needed for jquery_ui. + jquery_ui: core + 7: + menu: + - system + - menu_link_content + - menu_ui + system: system diff --git a/core/modules/system/src/Access/DbUpdateAccessCheck.php b/core/modules/system/src/Access/DbUpdateAccessCheck.php index 2bcc5fb0a..1d93c27cb 100644 --- a/core/modules/system/src/Access/DbUpdateAccessCheck.php +++ b/core/modules/system/src/Access/DbUpdateAccessCheck.php @@ -18,8 +18,8 @@ class DbUpdateAccessCheck implements AccessInterface { * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. */ public function access(AccountInterface $account) { // Allow the global variable in settings.php to override the access check. diff --git a/core/modules/system/src/Controller/AdminController.php b/core/modules/system/src/Controller/AdminController.php index 3da4b0901..767a8ce22 100644 --- a/core/modules/system/src/Controller/AdminController.php +++ b/core/modules/system/src/Controller/AdminController.php @@ -3,12 +3,45 @@ namespace Drupal\system\Controller; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Extension\ModuleExtensionList; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Controller for admin section. */ class AdminController extends ControllerBase { + /** + * The module extension list. + * + * @var \Drupal\Core\Extension\ModuleExtensionList + */ + protected $moduleExtensionList; + + /** + * AdminController constructor. + * + * @param \Drupal\Core\Extension\ModuleExtensionList|null $extension_list_module + * The module extension list. This is left optional for BC reasons, but the + * optional usage is deprecated and will become required in Drupal 9.0.0. + */ + public function __construct(ModuleExtensionList $extension_list_module = NULL) { + if ($extension_list_module === NULL) { + @trigger_error('Calling AdminController::__construct() with the $module_extension_list argument is supported in drupal:8.8.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2709919.', E_USER_DEPRECATED); + $extension_list_module = \Drupal::service('extension.list.module'); + } + $this->moduleExtensionList = $extension_list_module; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('extension.list.module') + ); + } + /** * Prints a listing of admin tasks, organized by module. * @@ -16,7 +49,7 @@ class AdminController extends ControllerBase { * A render array containing the listing. */ public function index() { - $module_info = system_get_info('module'); + $module_info = $this->moduleExtensionList->getAllInstalledInfo(); foreach ($module_info as $module => $info) { $module_info[$module] = new \stdClass(); $module_info[$module]->info = $info; diff --git a/core/modules/system/src/Controller/EntityAutocompleteController.php b/core/modules/system/src/Controller/EntityAutocompleteController.php index 88c367f28..b326385d5 100644 --- a/core/modules/system/src/Controller/EntityAutocompleteController.php +++ b/core/modules/system/src/Controller/EntityAutocompleteController.php @@ -87,7 +87,7 @@ public function handleAutocomplete(Request $request, $target_type, $selection_ha $selection_settings = $this->keyValue->get($selection_settings_key, FALSE); if ($selection_settings !== FALSE) { $selection_settings_hash = Crypt::hmacBase64(serialize($selection_settings) . $target_type . $selection_handler, Settings::getHashSalt()); - if ($selection_settings_hash !== $selection_settings_key) { + if (!hash_equals($selection_settings_hash, $selection_settings_key)) { // Disallow access when the selection settings hash does not match the // passed-in key. throw new AccessDeniedHttpException('Invalid selection settings key.'); diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php index 106f4e291..f27cc01eb 100644 --- a/core/modules/system/src/Controller/SystemController.php +++ b/core/modules/system/src/Controller/SystemController.php @@ -100,7 +100,7 @@ public static function create(ContainerInterface $container) { public function overview($link_id) { // Check for status report errors. if ($this->systemManager->checkRequirements() && $this->currentUser()->hasPermission('administer site configuration')) { - $this->messenger()->addError($this->t('One or more problems were detected with your Drupal installation. Check the status report for more information.', [':status' => $this->url('system.status')])); + $this->messenger()->addError($this->t('One or more problems were detected with your Drupal installation. Check the status report for more information.', [':status' => Url::fromRoute('system.status')->toString()])); } // Load all menu links below it. $parameters = new MenuTreeParameters(); @@ -196,7 +196,8 @@ public function themesPage() { continue; } $theme->is_default = ($theme->getName() == $theme_default); - $theme->is_admin = ($theme->getName() == $admin_theme || ($theme->is_default && $admin_theme == '0')); + $theme->is_admin = ($theme->getName() == $admin_theme || ($theme->is_default && empty($admin_theme))); + $theme->is_experimental = isset($theme->info['experimental']) && $theme->info['experimental']; // Identify theme screenshot. $theme->screenshot = NULL; @@ -222,8 +223,6 @@ public function themesPage() { } if (empty($theme->status)) { - // Ensure this theme is compatible with this version of core. - $theme->incompatible_core = !isset($theme->info['core']) || ($theme->info['core'] != \DRUPAL::CORE_COMPATIBILITY); // Require the 'content' region to make sure the main page // content has a common place in all themes. $theme->incompatible_region = !isset($theme->info['regions']['content']); @@ -234,7 +233,7 @@ public function themesPage() { $theme->incompatible_engine = isset($theme->info['engine']) && !isset($theme->owner); } $theme->operations = []; - if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine) { + if (!empty($theme->status) || !$theme->info['core_incompatible'] && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine) { // Create the operations links. $query['theme'] = $theme->getName(); if ($this->themeAccess->checkAccess($theme->getName())) { @@ -271,7 +270,7 @@ public function themesPage() { 'attributes' => ['title' => $this->t('Set @theme as default theme', ['@theme' => $theme->info['name']])], ]; } - $admin_theme_options[$theme->getName()] = $theme->info['name']; + $admin_theme_options[$theme->getName()] = $theme->info['name'] . ($theme->is_experimental ? ' (' . t('Experimental') . ')' : ''); } else { $theme->operations[] = [ @@ -289,7 +288,8 @@ public function themesPage() { } } - // Add notes to default and administration theme. + // Add notes to default theme, administration theme and experimental + // themes. $theme->notes = []; if ($theme->is_default) { $theme->notes[] = $this->t('default theme'); @@ -297,6 +297,9 @@ public function themesPage() { if ($theme->is_admin) { $theme->notes[] = $this->t('administration theme'); } + if ($theme->is_experimental) { + $theme->notes[] = $this->t('experimental theme'); + } // Sort installed and uninstalled themes into their own groups. $theme_groups[$theme->status ? 'installed' : 'uninstalled'][] = $theme; diff --git a/core/modules/system/src/Controller/ThemeController.php b/core/modules/system/src/Controller/ThemeController.php index 31be59ed1..0a8ad31cf 100644 --- a/core/modules/system/src/Controller/ThemeController.php +++ b/core/modules/system/src/Controller/ThemeController.php @@ -6,7 +6,10 @@ use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\UnmetDependenciesException; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Extension\ThemeExtensionList; use Drupal\Core\Extension\ThemeHandlerInterface; +use Drupal\Core\Extension\ThemeInstallerInterface; +use Drupal\system\Form\ThemeExperimentalConfirmForm; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -23,17 +26,37 @@ class ThemeController extends ControllerBase { */ protected $themeHandler; + /** + * An extension discovery instance. + * + * @var \Drupal\Core\Extension\ThemeExtensionList + */ + protected $themeList; + + /** + * The theme installer service. + * + * @var \Drupal\Core\Extension\ThemeInstallerInterface + */ + protected $themeInstaller; + /** * Constructs a new ThemeController. * * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. + * @param \Drupal\Core\Extension\ThemeExtensionList $theme_list + * The theme extension list. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. + * @param \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer + * The theme installer. */ - public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory) { + public function __construct(ThemeHandlerInterface $theme_handler, ThemeExtensionList $theme_list, ConfigFactoryInterface $config_factory, ThemeInstallerInterface $theme_installer) { $this->themeHandler = $theme_handler; + $this->themeList = $theme_list; $this->configFactory = $config_factory; + $this->themeInstaller = $theme_installer; } /** @@ -42,7 +65,9 @@ public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryI public static function create(ContainerInterface $container) { return new static( $container->get('theme_handler'), - $container->get('config.factory') + $container->get('extension.list.theme'), + $container->get('config.factory'), + $container->get('theme_installer') ); } @@ -74,7 +99,7 @@ public function uninstall(Request $request) { $this->messenger()->addError($this->t('%theme is the default theme and cannot be uninstalled.', ['%theme' => $themes[$theme]->info['name']])); } else { - $this->themeHandler->uninstall([$theme]); + $this->themeInstaller->uninstall([$theme]); $this->messenger()->addStatus($this->t('The %theme theme has been uninstalled.', ['%theme' => $themes[$theme]->info['name']])); } } @@ -94,8 +119,9 @@ public function uninstall(Request $request) { * @param \Symfony\Component\HttpFoundation\Request $request * A request object containing a theme name and a valid token. * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - * Redirects back to the appearance admin page. + * @return \Symfony\Component\HttpFoundation\RedirectResponse|array + * Redirects back to the appearance admin page or the confirmation form + * if an experimental theme will be installed. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * Throws access denied when no theme or token is set in the request or when @@ -105,8 +131,13 @@ public function install(Request $request) { $theme = $request->query->get('theme'); if (isset($theme)) { + // Display confirmation form in case of experimental theme. + if ($this->willInstallExperimentalTheme($theme)) { + return $this->formBuilder()->getForm(ThemeExperimentalConfirmForm::class, $theme); + } + try { - if ($this->themeHandler->install([$theme])) { + if ($this->themeInstaller->install([$theme])) { $themes = $this->themeHandler->listInfo(); $this->messenger()->addStatus($this->t('The %theme theme has been installed.', ['%theme' => $themes[$theme]->info['name']])); } @@ -137,14 +168,38 @@ public function install(Request $request) { throw new AccessDeniedHttpException(); } + /** + * Checks if the given theme requires the installation of experimental themes. + * + * @param string $theme + * The name of the theme to check. + * + * @return bool + * Whether experimental themes will be installed. + */ + protected function willInstallExperimentalTheme($theme) { + $all_themes = $this->themeList->getList(); + $dependencies = array_keys($all_themes[$theme]->requires); + $themes_to_enable = array_merge([$theme], $dependencies); + + foreach ($themes_to_enable as $name) { + if (!empty($all_themes[$name]->info['experimental']) && $all_themes[$name]->status === 0) { + return TRUE; + } + } + + return FALSE; + } + /** * Set the default theme. * * @param \Symfony\Component\HttpFoundation\Request $request * A request object containing a theme name. * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - * Redirects back to the appearance admin page. + * @return \Symfony\Component\HttpFoundation\RedirectResponse|array + * Redirects back to the appearance admin page or the confirmation form + * if an experimental theme will be installed. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * Throws access denied when no theme is set in the request. @@ -156,10 +211,14 @@ public function setDefaultTheme(Request $request) { if (isset($theme)) { // Get current list of themes. $themes = $this->themeHandler->listInfo(); + // Display confirmation form if an experimental theme is being installed. + if ($this->willInstallExperimentalTheme($theme)) { + return $this->formBuilder()->getForm(ThemeExperimentalConfirmForm::class, $theme, TRUE); + } // Check if the specified theme is one recognized by the system. // Or try to install the theme. - if (isset($themes[$theme]) || $this->themeHandler->install([$theme])) { + if (isset($themes[$theme]) || $this->themeInstaller->install([$theme])) { $themes = $this->themeHandler->listInfo(); // Set the default theme. @@ -169,7 +228,7 @@ public function setDefaultTheme(Request $request) { // use: a value of 0 means the admin theme is set to be the default // theme. $admin_theme = $config->get('admin'); - if ($admin_theme != 0 && $admin_theme != $theme) { + if (!empty($admin_theme) && $admin_theme != $theme) { $this->messenger() ->addStatus($this->t('Please note that the administration theme is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', [ '%admin_theme' => $themes[$admin_theme]->info['name'], diff --git a/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php b/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php index 50b3a5780..a6a19d6d5 100644 --- a/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php +++ b/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php @@ -17,7 +17,8 @@ class AdminRouteSubscriber extends RouteSubscriberBase { */ protected function alterRoutes(RouteCollection $collection) { foreach ($collection->all() as $route) { - if (strpos($route->getPath(), '/admin') === 0 && !$route->hasOption('_admin_route') && static::isHtmlRoute($route)) { + $path = $route->getPath(); + if (($path == '/admin' || strpos($path, '/admin/') === 0) && !$route->hasOption('_admin_route') && static::isHtmlRoute($route)) { $route->setOption('_admin_route', TRUE); } } diff --git a/core/modules/system/src/FileDownloadController.php b/core/modules/system/src/FileDownloadController.php index 08b1632b2..9bf889084 100644 --- a/core/modules/system/src/FileDownloadController.php +++ b/core/modules/system/src/FileDownloadController.php @@ -3,16 +3,48 @@ namespace Drupal\system; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpFoundation\BinaryFileResponse; /** * System file controller. */ class FileDownloadController extends ControllerBase { + /** + * The stream wrapper manager. + * + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface + */ + protected $streamWrapperManager; + + /** + * FileDownloadController constructor. + * + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager + * The stream wrapper manager. + */ + public function __construct(StreamWrapperManagerInterface $streamWrapperManager = NULL) { + if (!$streamWrapperManager) { + @trigger_error('Calling FileDownloadController::__construct() without the $streamWrapperManager argument is deprecated in drupal:8.8.0. The $streamWrapperManager argument will be required in drupal:9.0.0. See https://www.drupal.org/node/3035273', E_USER_DEPRECATED); + $streamWrapperManager = \Drupal::service('stream_wrapper_manager'); + } + $this->streamWrapperManager = $streamWrapperManager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('stream_wrapper_manager') + ); + } + /** * Handles private file transfers. * @@ -43,7 +75,7 @@ public function download(Request $request, $scheme = 'private') { // Merge remaining path arguments into relative file path. $uri = $scheme . '://' . $target; - if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) { + if ($this->streamWrapperManager->isValidScheme($scheme) && file_exists($uri)) { // Let other modules provide headers and controls access to the file. $headers = $this->moduleHandler()->invokeAll('file_download', [$uri]); diff --git a/core/modules/system/src/Form/CronForm.php b/core/modules/system/src/Form/CronForm.php index a585aa57c..79c5e3976 100644 --- a/core/modules/system/src/Form/CronForm.php +++ b/core/modules/system/src/Form/CronForm.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\State\StateInterface; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Form\ConfigFormBaseTrait; @@ -114,7 +115,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#markup' => $status, ]; - $cron_url = $this->url('system.cron', ['key' => $this->state->get('system.cron_key')], ['absolute' => TRUE]); + $cron_url = Url::fromRoute('system.cron', ['key' => $this->state->get('system.cron_key')], ['absolute' => TRUE])->toString(); $form['cron_url'] = [ '#markup' => '

' . t('To run cron from outside the site, go to @cron', [':cron' => $cron_url, '@cron' => $cron_url]) . '

', ]; diff --git a/core/modules/system/src/Form/DateFormatFormBase.php b/core/modules/system/src/Form/DateFormatFormBase.php index 547b27d36..8e6cd82c9 100644 --- a/core/modules/system/src/Form/DateFormatFormBase.php +++ b/core/modules/system/src/Form/DateFormatFormBase.php @@ -50,7 +50,7 @@ public function __construct(DateFormatterInterface $date_formatter, ConfigEntity public static function create(ContainerInterface $container) { return new static( $container->get('date.formatter'), - $container->get('entity.manager')->getStorage('date_format') + $container->get('entity_type.manager')->getStorage('date_format') ); } diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php index dedf32a69..d81488b44 100644 --- a/core/modules/system/src/Form/FileSystemForm.php +++ b/core/modules/system/src/Form/FileSystemForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StreamWrapper\PrivateStream; use Drupal\Core\StreamWrapper\PublicStream; @@ -33,6 +34,13 @@ class FileSystemForm extends ConfigFormBase { */ protected $streamWrapperManager; + /** + * The file system. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + /** * Constructs a FileSystemForm object. * @@ -42,11 +50,18 @@ class FileSystemForm extends ConfigFormBase { * The date formatter service. * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager * The stream wrapper manager. + * @param \Drupal\Core\File\FileSystemInterface $file_system + * The file system. */ - public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager) { + public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager, FileSystemInterface $file_system = NULL) { parent::__construct($config_factory); $this->dateFormatter = $date_formatter; $this->streamWrapperManager = $stream_wrapper_manager; + if (!$file_system) { + @trigger_error('Calling FileSystemForm::__construct() without the $file_system argument is deprecated in drupal:8.8.0. The $file_system argument will be required in drupal:9.0.0. See https://www.drupal.org/node/3039255', E_USER_DEPRECATED); + $file_system = \Drupal::service('file_system'); + } + $this->fileSystem = $file_system; } /** @@ -56,7 +71,8 @@ public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), $container->get('date.formatter'), - $container->get('stream_wrapper_manager') + $container->get('stream_wrapper_manager'), + $container->get('file_system') ); } @@ -101,12 +117,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['file_temporary_path'] = [ - '#type' => 'textfield', + '#type' => 'item', '#title' => t('Temporary directory'), - '#default_value' => $config->get('path.temporary'), - '#maxlength' => 255, - '#description' => t('A local file system path where temporary files will be stored. This directory should not be accessible over the web.'), - '#after_build' => ['system_check_directory'], + '#markup' => $this->fileSystem->getTempDirectory(), + '#description' => t('A local file system path where temporary files will be stored. This directory should not be accessible over the web. This must be changed in settings.php.'), ]; // Any visible, writeable wrapper can potentially be used for the files // directory, including a remote file system that integrates with a CDN. @@ -141,7 +155,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { $config = $this->config('system.file') - ->set('path.temporary', $form_state->getValue('file_temporary_path')) ->set('temporary_maximum_age', $form_state->getValue('temporary_maximum_age')); if ($form_state->hasValue('file_default_scheme')) { diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index c2ade11b7..f32dad090 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -2,12 +2,12 @@ namespace Drupal\system\Form; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\UnmetDependenciesException; use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\InfoParserException; +use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\Form\FormBase; @@ -66,6 +66,13 @@ class ModulesListForm extends FormBase { */ protected $permissionHandler; + /** + * The module extension list. + * + * @var \Drupal\Core\Extension\ModuleExtensionList + */ + protected $moduleExtensionList; + /** * {@inheritdoc} */ @@ -76,7 +83,8 @@ public static function create(ContainerInterface $container) { $container->get('keyvalue.expirable')->get('module_list'), $container->get('access_manager'), $container->get('current_user'), - $container->get('user.permissions') + $container->get('user.permissions'), + $container->get('extension.list.module') ); } @@ -95,8 +103,11 @@ public static function create(ContainerInterface $container) { * The current user. * @param \Drupal\user\PermissionHandlerInterface $permission_handler * The permission handler. + * @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module + * The module extension list. */ - public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, AccessManagerInterface $access_manager, AccountInterface $current_user, PermissionHandlerInterface $permission_handler) { + public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, AccessManagerInterface $access_manager, AccountInterface $current_user, PermissionHandlerInterface $permission_handler, ModuleExtensionList $extension_list_module) { + $this->moduleExtensionList = $extension_list_module; $this->moduleHandler = $module_handler; $this->moduleInstaller = $module_installer; $this->keyValueExpirable = $key_value_expirable; @@ -145,7 +156,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Sort all modules by their names. try { - $modules = system_rebuild_module_data(); + // The module list needs to be reset so that it can re-scan and include + // any new modules that may have been added directly into the filesystem. + $modules = $this->moduleExtensionList->reset()->getList(); uasort($modules, 'system_sort_modules_by_info_name'); } catch (InfoParserException $e) { @@ -281,10 +294,14 @@ protected function buildRow(array $modules, Extension $module, $distribution) { $reasons = []; // Check the core compatibility. - if ($module->info['core'] != \Drupal::CORE_COMPATIBILITY) { + if ($module->info['core_incompatible']) { $compatible = FALSE; $reasons[] = $this->t('This version is not compatible with Drupal @core_version and should be replaced.', [ - '@core_version' => \Drupal::CORE_COMPATIBILITY, + '@core_version' => \Drupal::VERSION, + ]); + $row['#requires']['core'] = $this->t('Drupal Core (@core_requirement) (incompatible with version @core_version)', [ + '@core_requirement' => isset($module->info['core_version_requirement']) ? $module->info['core_version_requirement'] : $module->info['core'], + '@core_version' => \Drupal::VERSION, ]); } @@ -310,7 +327,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) { /** @var \Drupal\Core\Extension\Dependency $dependency_object */ foreach ($module->requires as $dependency => $dependency_object) { if (!isset($modules[$dependency])) { - $row['#requires'][$dependency] = $this->t('@module (missing)', ['@module' => Unicode::ucfirst($dependency)]); + $row['#requires'][$dependency] = $this->t('@module (missing)', ['@module' => $dependency]); $row['enable']['#disabled'] = TRUE; } // Only display visible modules. @@ -328,7 +345,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) { } // Disable the checkbox if the dependency is incompatible with this // version of Drupal core. - elseif ($modules[$dependency]->info['core'] != \Drupal::CORE_COMPATIBILITY) { + elseif ($modules[$dependency]->info['core_incompatible']) { $row['#requires'][$dependency] = $this->t('@module (incompatible with this version of Drupal core)', [ '@module' => $name, ]); @@ -377,7 +394,7 @@ protected function buildModuleList(FormStateInterface $form_state) { 'experimental' => [], ]; - $data = system_rebuild_module_data(); + $data = $this->moduleExtensionList->getList(); foreach ($data as $name => $module) { // If the module is installed there is nothing to do. if ($this->moduleHandler->moduleExists($name)) { diff --git a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php index 4a66020dc..37717e2d2 100644 --- a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php +++ b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\Entity\ConfigDependencyDeleteFormTrait; use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\FormStateInterface; @@ -64,6 +65,13 @@ class ModulesUninstallConfirmForm extends ConfirmFormBase { */ protected $modules = []; + /** + * The module extension list. + * + * @var \Drupal\Core\Extension\ModuleExtensionList + */ + protected $moduleExtensionList; + /** * Constructs a ModulesUninstallConfirmForm object. * @@ -75,12 +83,15 @@ class ModulesUninstallConfirmForm extends ConfirmFormBase { * The configuration manager. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. + * @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module + * The module extension list. */ - public function __construct(ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ConfigManagerInterface $config_manager, EntityTypeManagerInterface $entity_type_manager) { + public function __construct(ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ConfigManagerInterface $config_manager, EntityTypeManagerInterface $entity_type_manager, ModuleExtensionList $extension_list_module) { $this->moduleInstaller = $module_installer; $this->keyValueExpirable = $key_value_expirable; $this->configManager = $config_manager; $this->entityTypeManager = $entity_type_manager; + $this->moduleExtensionList = $extension_list_module; } /** @@ -91,7 +102,8 @@ public static function create(ContainerInterface $container) { $container->get('module_installer'), $container->get('keyvalue.expirable')->get('modules_uninstall'), $container->get('config.manager'), - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('extension.list.module') ); } @@ -144,7 +156,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { return $this->redirect('system.modules_uninstall'); } - $data = system_rebuild_module_data(); + $data = $this->moduleExtensionList->getList(); $form['text']['#markup'] = '

' . $this->t('The following modules will be completely uninstalled from your site, and all data from these modules will be lost!') . '

'; $form['modules'] = [ '#theme' => 'item_list', diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php index 841a741e4..9370050d1 100644 --- a/core/modules/system/src/Form/ModulesUninstallForm.php +++ b/core/modules/system/src/Form/ModulesUninstallForm.php @@ -2,6 +2,7 @@ namespace Drupal\system\Form; +use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\Form\FormBase; @@ -37,6 +38,13 @@ class ModulesUninstallForm extends FormBase { */ protected $keyValueExpirable; + /** + * The module extension list. + * + * @var \Drupal\Core\Extension\ModuleExtensionList + */ + protected $moduleExtensionList; + /** * {@inheritdoc} */ @@ -44,7 +52,8 @@ public static function create(ContainerInterface $container) { return new static( $container->get('module_handler'), $container->get('module_installer'), - $container->get('keyvalue.expirable')->get('modules_uninstall') + $container->get('keyvalue.expirable')->get('modules_uninstall'), + $container->get('extension.list.module') ); } @@ -57,8 +66,11 @@ public static function create(ContainerInterface $container) { * The module installer. * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable * The key value expirable factory. + * @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module + * The module extension list. */ - public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable) { + public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ModuleExtensionList $extension_list_module) { + $this->moduleExtensionList = $extension_list_module; $this->moduleHandler = $module_handler; $this->moduleInstaller = $module_installer; $this->keyValueExpirable = $key_value_expirable; @@ -78,10 +90,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Make sure the install API is available. include_once DRUPAL_ROOT . '/core/includes/install.inc'; - // Get a list of all available modules. - $modules = system_rebuild_module_data(); - $uninstallable = array_filter($modules, function ($module) use ($modules) { - return empty($modules[$module->getName()]->info['required']) && $module->status; + // Get a list of all available modules that can be uninstalled. + $uninstallable = array_filter($this->moduleExtensionList->getList(), function ($module) { + return empty($module->info['required']) && $module->status; }); // Include system.admin.inc so we can use the sort callbacks. diff --git a/core/modules/system/src/Form/PerformanceForm.php b/core/modules/system/src/Form/PerformanceForm.php index 9d19b7599..dc13796bb 100644 --- a/core/modules/system/src/Form/PerformanceForm.php +++ b/core/modules/system/src/Form/PerformanceForm.php @@ -143,7 +143,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $disabled = !$is_writable; $disabled_message = ''; if (!$is_writable) { - $disabled_message = ' ' . t('Set up the public files directory to make these optimizations available.', [':file-system' => $this->url('system.file_system_settings')]); + $disabled_message = ' ' . t('Set up the public files directory to make these optimizations available.', [':file-system' => Url::fromRoute('system.file_system_settings')->toString()]); } $form['bandwidth_optimization'] = [ diff --git a/core/modules/system/src/Form/SiteInformationForm.php b/core/modules/system/src/Form/SiteInformationForm.php index 4e4e1571f..c4d95b53d 100644 --- a/core/modules/system/src/Form/SiteInformationForm.php +++ b/core/modules/system/src/Form/SiteInformationForm.php @@ -3,11 +3,12 @@ namespace Drupal\system\Form; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Path\AliasManagerInterface as CoreAliasManagerInterface; use Drupal\Core\Path\PathValidatorInterface; use Drupal\Core\Routing\RequestContext; +use Drupal\path_alias\AliasManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -20,7 +21,7 @@ class SiteInformationForm extends ConfigFormBase { /** * The path alias manager. * - * @var \Drupal\Core\Path\AliasManagerInterface + * @var \Drupal\path_alias\AliasManagerInterface */ protected $aliasManager; @@ -43,16 +44,21 @@ class SiteInformationForm extends ConfigFormBase { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. - * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager + * @param \Drupal\path_alias\AliasManagerInterface $alias_manager * The path alias manager. * @param \Drupal\Core\Path\PathValidatorInterface $path_validator * The path validator. * @param \Drupal\Core\Routing\RequestContext $request_context * The request context. */ - public function __construct(ConfigFactoryInterface $config_factory, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) { + public function __construct(ConfigFactoryInterface $config_factory, $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) { parent::__construct($config_factory); + if (!$alias_manager instanceof AliasManagerInterface) { + @trigger_error('Calling \\' . __METHOD__ . ' with \\' . CoreAliasManagerInterface::class . ' instead of \\' . AliasManagerInterface::class . ' is deprecated in drupal:8.8.0. The new service will be required in drupal:9.0.0. See https://www.drupal.org/node/3092086', E_USER_DEPRECATED); + $alias_manager = \Drupal::service('path_alias.manager'); + } + $this->aliasManager = $alias_manager; $this->pathValidator = $path_validator; $this->requestContext = $request_context; @@ -64,7 +70,7 @@ public function __construct(ConfigFactoryInterface $config_factory, AliasManager public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), - $container->get('path.alias_manager'), + $container->get('path_alias.manager'), $container->get('path.validator'), $container->get('router.request_context') ); diff --git a/core/modules/system/src/Form/SiteMaintenanceModeForm.php b/core/modules/system/src/Form/SiteMaintenanceModeForm.php index dae500df4..e87e96a86 100644 --- a/core/modules/system/src/Form/SiteMaintenanceModeForm.php +++ b/core/modules/system/src/Form/SiteMaintenanceModeForm.php @@ -6,6 +6,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Url; use Drupal\user\PermissionHandlerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -82,7 +83,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#type' => 'checkbox', '#title' => t('Put site into maintenance mode'), '#default_value' => $this->state->get('system.maintenance_mode'), - '#description' => t('Visitors will only see the maintenance mode message. Only users with the "@permission-label" permission will be able to access the site. Authorized users can log in directly via the user login page.', ['@permission-label' => $permission_label, ':permissions-url' => $this->url('user.admin_permissions'), ':user-login' => $this->url('user.login')]), + '#description' => t('Visitors will only see the maintenance mode message. Only users with the "@permission-label" permission will be able to access the site. Authorized users can log in directly via the user login page.', ['@permission-label' => $permission_label, ':permissions-url' => Url::fromRoute('user.admin_permissions')->toString(), ':user-login' => Url::fromRoute('user.login')->toString()]), ]; $form['maintenance_mode_message'] = [ '#type' => 'textarea', diff --git a/core/modules/system/src/Form/ThemeAdminForm.php b/core/modules/system/src/Form/ThemeAdminForm.php index 1c85a7889..1951f5482 100644 --- a/core/modules/system/src/Form/ThemeAdminForm.php +++ b/core/modules/system/src/Form/ThemeAdminForm.php @@ -38,7 +38,7 @@ public function buildForm(array $form, FormStateInterface $form_state, array $th ]; $form['admin_theme']['admin_theme'] = [ '#type' => 'select', - '#options' => [0 => $this->t('Default theme')] + $theme_options, + '#options' => ['' => $this->t('Default theme')] + $theme_options, '#title' => $this->t('Administration theme'), '#description' => $this->t('Choose "Default theme" to always use the same theme as the rest of the site.'), '#default_value' => $this->config('system.theme')->get('admin'), diff --git a/core/modules/system/src/Form/ThemeExperimentalConfirmForm.php b/core/modules/system/src/Form/ThemeExperimentalConfirmForm.php new file mode 100644 index 000000000..d83cd422e --- /dev/null +++ b/core/modules/system/src/Form/ThemeExperimentalConfirmForm.php @@ -0,0 +1,189 @@ +themeList = $theme_list; + $this->themeInstaller = $theme_installer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('extension.list.theme'), + $container->get('theme_installer') + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Are you sure you wish to install an experimental theme?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('system.themes_page'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Continue'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('Would you like to continue with the above?'); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'system_themes_experimental_confirm_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $theme = $form_state->getBuildInfo()['args'][0] ? $form_state->getBuildInfo()['args'][0] : NULL; + $all_themes = $this->themeList->getList(); + if (!isset($all_themes[$theme])) { + return $this->redirect('system.themes_page'); + } + $this->messenger()->addWarning($this->t('Experimental themes are provided for testing purposes only. Use at your own risk.')); + + $dependencies = array_keys($all_themes[$theme]->requires); + $themes = array_merge([$theme], $dependencies); + $is_experimental = function ($theme) use ($all_themes) { + return isset($all_themes[$theme]) && isset($all_themes[$theme]->info['experimental']) && $all_themes[$theme]->info['experimental']; + }; + $get_label = function ($theme) use ($all_themes) { + return $all_themes[$theme]->info['name']; + }; + + $items = []; + if (!empty($dependencies)) { + // Display a list of required themes that have to be installed as well. + $items[] = $this->formatPlural(count($dependencies), 'You must enable the @required theme to install @theme.', 'You must enable the @required themes to install @theme.', [ + '@theme' => $get_label($theme), + // It is safe to implode this because theme names are not translated + // markup and so will not be double-escaped. + '@required' => implode(', ', array_map($get_label, $dependencies)), + ]); + } + // Add the list of experimental themes after any other messages. + $items[] = $this->t('The following themes are experimental: @themes', ['@themes' => implode(', ', array_map($get_label, array_filter($themes, $is_experimental)))]); + $form['message'] = [ + '#theme' => 'item_list', + '#items' => $items, + ]; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $args = $form_state->getBuildInfo()['args']; + $theme = isset($args[0]) ? $args[0] : NULL; + $set_default = isset($args[1]) ? $args[1] : FALSE; + $themes = $this->themeList->getList(); + $config = $this->configFactory()->getEditable('system.theme'); + try { + if ($this->themeInstaller->install([$theme])) { + if ($set_default) { + // Set the default theme. + $config->set('default', $theme)->save(); + + // The status message depends on whether an admin theme is currently + // in use: an empty string means the admin theme is set to be the + // default theme. + $admin_theme = $config->get('admin'); + if (!empty($admin_theme) && $admin_theme !== $theme) { + $this->messenger() + ->addStatus($this->t('Please note that the administration theme is still set to the %admin_theme theme; consequently, the theme on this page remains unchanged. All non-administrative sections of the site, however, will show the selected %selected_theme theme by default.', [ + '%admin_theme' => $themes[$admin_theme]->info['name'], + '%selected_theme' => $themes[$theme]->info['name'], + ])); + } + else { + $this->messenger()->addStatus($this->t('%theme is now the default theme.', ['%theme' => $themes[$theme]->info['name']])); + } + } + else { + $this->messenger()->addStatus($this->t('The %theme theme has been installed.', ['%theme' => $themes[$theme]->info['name']])); + } + } + else { + $this->messenger()->addError($this->t('The %theme theme was not found.', ['%theme' => $theme])); + } + } + catch (PreExistingConfigException $e) { + $config_objects = $e->flattenConfigObjects($e->getConfigObjects()); + $this->messenger()->addError( + $this->formatPlural( + count($config_objects), + 'Unable to install @extension, %config_names already exists in active configuration.', + 'Unable to install @extension, %config_names already exist in active configuration.', + [ + '%config_names' => implode(', ', $config_objects), + '@extension' => $theme, + ]) + ); + } + catch (UnmetDependenciesException $e) { + $this->messenger()->addError($e->getTranslatedMessage($this->getStringTranslation(), $theme)); + } + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php index 83a8d1563..2a65c6b52 100644 --- a/core/modules/system/src/Form/ThemeSettingsForm.php +++ b/core/modules/system/src/Form/ThemeSettingsForm.php @@ -8,6 +8,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -295,8 +296,9 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme = // directory; stream wrappers are not end-user friendly. $original_path = $element['#default_value']; $friendly_path = NULL; - if (file_uri_scheme($original_path) == 'public') { - $friendly_path = file_uri_target($original_path); + + if (StreamWrapperManager::getScheme($original_path) == 'public') { + $friendly_path = StreamWrapperManager::getTarget($original_path); $element['#default_value'] = $friendly_path; } @@ -313,7 +315,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme = $element['#description'] = t('Examples: @implicit-public-file (for a file in the public filesystem), @explicit-file, or @local-file.', [ '@implicit-public-file' => isset($friendly_path) ? $friendly_path : $default, - '@explicit-file' => file_uri_scheme($original_path) !== FALSE ? $original_path : 'public://' . $default, + '@explicit-file' => StreamWrapperManager::getScheme($original_path) !== FALSE ? $original_path : 'public://' . $default, '@local-file' => $local_file, ]); } @@ -461,9 +463,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // If the user uploaded a new logo or favicon, save it to a permanent location // and use it in place of the default theme-provided file. + $default_scheme = $this->config('system.file')->get('default_scheme'); try { if (!empty($values['logo_upload'])) { - $filename = $this->fileSystem->copy($values['logo_upload']->getFileUri(), file_default_scheme() . '://'); + $filename = $this->fileSystem->copy($values['logo_upload']->getFileUri(), $default_scheme . '://'); $values['default_logo'] = 0; $values['logo_path'] = $filename; } @@ -473,7 +476,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } try { if (!empty($values['favicon_upload'])) { - $filename = $this->fileSystem->copy($values['favicon_upload']->getFileUri(), file_default_scheme() . '://'); + $filename = $this->fileSystem->copy($values['favicon_upload']->getFileUri(), $default_scheme . '://'); $values['default_favicon'] = 0; $values['favicon_path'] = $filename; $values['toggle_favicon'] = 1; @@ -525,7 +528,7 @@ protected function validatePath($path) { return $path; } // Prepend 'public://' for relative file paths within public filesystem. - if (file_uri_scheme($path) === FALSE) { + if (StreamWrapperManager::getScheme($path) === FALSE) { $path = 'public://' . $path; } if (is_file($path)) { diff --git a/core/modules/system/src/Plugin/Condition/RequestPath.php b/core/modules/system/src/Plugin/Condition/RequestPath.php index 1f7362477..38dc216ca 100644 --- a/core/modules/system/src/Plugin/Condition/RequestPath.php +++ b/core/modules/system/src/Plugin/Condition/RequestPath.php @@ -4,10 +4,11 @@ use Drupal\Core\Condition\ConditionPluginBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Path\AliasManagerInterface; +use Drupal\Core\Path\AliasManagerInterface as CoreAliasManagerInterface; use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\Path\PathMatcherInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\path_alias\AliasManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -24,7 +25,7 @@ class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginI /** * An alias manager to find the alias for the current system path. * - * @var \Drupal\Core\Path\AliasManagerInterface + * @var \Drupal\path_alias\AliasManagerInterface */ protected $aliasManager; @@ -52,7 +53,7 @@ class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginI /** * Constructs a RequestPath condition plugin. * - * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager + * @param \Drupal\path_alias\AliasManagerInterface $alias_manager * An alias manager to find the alias for the current system path. * @param \Drupal\Core\Path\PathMatcherInterface $path_matcher * The path matcher service. @@ -67,8 +68,14 @@ class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginI * @param array $plugin_definition * The plugin implementation definition. */ - public function __construct(AliasManagerInterface $alias_manager, PathMatcherInterface $path_matcher, RequestStack $request_stack, CurrentPathStack $current_path, array $configuration, $plugin_id, array $plugin_definition) { + public function __construct($alias_manager, PathMatcherInterface $path_matcher, RequestStack $request_stack, CurrentPathStack $current_path, array $configuration, $plugin_id, array $plugin_definition) { parent::__construct($configuration, $plugin_id, $plugin_definition); + + if (!$alias_manager instanceof AliasManagerInterface) { + @trigger_error('Calling \\' . __METHOD__ . ' with \\' . CoreAliasManagerInterface::class . ' instead of \\' . AliasManagerInterface::class . ' is deprecated in drupal:8.8.0. The new service will be required in drupal:9.0.0. See https://www.drupal.org/node/3092086', E_USER_DEPRECATED); + $alias_manager = \Drupal::service('path_alias.manager'); + } + $this->aliasManager = $alias_manager; $this->pathMatcher = $path_matcher; $this->requestStack = $request_stack; @@ -80,7 +87,7 @@ public function __construct(AliasManagerInterface $alias_manager, PathMatcherInt */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( - $container->get('path.alias_manager'), + $container->get('path_alias.manager'), $container->get('path.matcher'), $container->get('request_stack'), $container->get('path.current'), diff --git a/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php b/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php index 5da502717..d99c1454b 100644 --- a/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php +++ b/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php @@ -36,7 +36,7 @@ public function __construct(EntityStorageInterface $menu_storage) { */ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( - $container->get('entity.manager')->getStorage('menu') + $container->get('entity_type.manager')->getStorage('menu') ); } diff --git a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php index d5cf58c5e..c07c9cf3c 100644 --- a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php @@ -10,6 +10,7 @@ use Drupal\Core\ImageToolkit\ImageToolkitBase; use Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface; use Drupal\Core\StreamWrapper\StreamWrapperInterface; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -130,7 +131,7 @@ public static function create(ContainerInterface $container, array $configuratio * @param resource $resource * The GD image resource. * - * @return \Drupal\system\Plugin\ImageToolkit\GDToolkit + * @return $this * An instance of the current toolkit object. */ public function setResource($resource) { @@ -230,9 +231,9 @@ public function isValid() { * {@inheritdoc} */ public function save($destination) { - $scheme = file_uri_scheme($destination); + $scheme = StreamWrapperManager::getScheme($destination); // Work around lack of stream wrapper support in imagejpeg() and imagepng(). - if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { + if ($scheme && $this->streamWrapperManager->isValidScheme($scheme)) { // If destination is not local, save image to temporary local file. $local_wrappers = $this->streamWrapperManager->getWrappers(StreamWrapperInterface::LOCAL); if (!isset($local_wrappers[$scheme])) { diff --git a/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php index cb57cc489..fef47e901 100644 --- a/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php @@ -52,7 +52,7 @@ protected function arguments() { protected function validateArguments(array $arguments) { // Assure extension is supported. if (!in_array($arguments['extension'], $this->getToolkit()->getSupportedExtensions())) { - throw new \InvalidArgumentException("Invalid extension ('{$arguments['extension']}') specified for the image 'convert' operation"); + throw new \InvalidArgumentException("Invalid extension ('{$arguments['extension']}') specified for the image 'create_new' operation"); } // Assure integers for width and height. diff --git a/core/modules/system/src/Plugin/views/field/BulkForm.php b/core/modules/system/src/Plugin/views/field/BulkForm.php index e34e6a551..9505ab089 100644 --- a/core/modules/system/src/Plugin/views/field/BulkForm.php +++ b/core/modules/system/src/Plugin/views/field/BulkForm.php @@ -2,8 +2,9 @@ namespace Drupal\system\Plugin\views\field; -@trigger_error(__NAMESPACE__ . '\BulkForm is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\views\Plugin\views\field\BulkForm instead. See https://www.drupal.org/node/2916716.', E_USER_DEPRECATED); - +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\views\Plugin\views\field\BulkForm as ViewsBulkForm; /** @@ -11,11 +12,19 @@ * * @ViewsField("legacy_bulk_form") * - * @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. Use * \Drupal\views\Plugin\views\field\BulkForm instead. * * @see https://www.drupal.org/node/2916716 */ class BulkForm extends ViewsBulkForm { + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, MessengerInterface $messenger) { + @trigger_error(__NAMESPACE__ . '\BulkForm is deprecated in drupal:8.5.0, will be removed before drupal:9.0.0. Use \Drupal\views\Plugin\views\field\BulkForm instead. See https://www.drupal.org/node/2916716.', E_USER_DEPRECATED); + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $language_manager, $messenger); + } + } diff --git a/core/modules/system/src/SystemManager.php b/core/modules/system/src/SystemManager.php index d1dd3cf60..91fec322e 100644 --- a/core/modules/system/src/SystemManager.php +++ b/core/modules/system/src/SystemManager.php @@ -2,7 +2,6 @@ namespace Drupal\system; -use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Menu\MenuActiveTrailInterface; use Drupal\Core\Menu\MenuLinkTreeInterface; use Drupal\Core\Menu\MenuLinkInterface; @@ -79,7 +78,7 @@ class SystemManager { * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail * The active menu trail service. */ - public function __construct(ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, RequestStack $request_stack, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) { + public function __construct(ModuleHandlerInterface $module_handler, $entity_manager, RequestStack $request_stack, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) { $this->moduleHandler = $module_handler; $this->requestStack = $request_stack; $this->menuTree = $menu_tree; diff --git a/core/modules/system/src/SystemRequirements.php b/core/modules/system/src/SystemRequirements.php index 969350bfc..ebd1b8c51 100644 --- a/core/modules/system/src/SystemRequirements.php +++ b/core/modules/system/src/SystemRequirements.php @@ -2,6 +2,8 @@ namespace Drupal\system; +@trigger_error(__NAMESPACE__ . '\SystemRequirements is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. All supported PHP versions support disabling multi-statement queries in MySQL. See https://www.drupal.org/node/3054692', E_USER_DEPRECATED); + /** * Class for helper methods used for the system requirements. */ @@ -13,8 +15,15 @@ class SystemRequirements { * @param string $phpversion * * @return bool + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. All + * supported PHP versions support disabling multi-statement queries in + * MySQL. + * + * @see https://www.drupal.org/node/3054692 */ public static function phpVersionWithPdoDisallowMultipleStatements($phpversion) { + @trigger_error(__NAMESPACE__ . '\SystemRequirements::phpVersionWithPdoDisallowMultipleStatements() is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. All supported PHP versions support disabling multi-statement queries in MySQL. See https://www.drupal.org/node/3054692', E_USER_DEPRECATED); // PDO::MYSQL_ATTR_MULTI_STATEMENTS was introduced in PHP versions 5.5.21 // and 5.6.5. return (version_compare($phpversion, '5.5.21', '>=') && version_compare($phpversion, '5.6.0', '<')) diff --git a/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php index b115a61ee..8fe3fa44d 100644 --- a/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php +++ b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php @@ -10,7 +10,7 @@ * * Can be used by test classes that extend \Drupal\simpletest\WebTestBase. * - * @deprecated Scheduled for removal in Drupal 9.0.0. Use + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. Use * \Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait * instead. * diff --git a/core/modules/system/src/Tests/Cache/CacheTestBase.php b/core/modules/system/src/Tests/Cache/CacheTestBase.php index d6a97e323..dc2f601c5 100644 --- a/core/modules/system/src/Tests/Cache/CacheTestBase.php +++ b/core/modules/system/src/Tests/Cache/CacheTestBase.php @@ -9,7 +9,7 @@ /** * Provides helper methods for cache tests. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\system\Functional\Cache\CacheTestBase instead. * * @see https://www.drupal.org/node/2999939 diff --git a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php index 4634196f1..4abca9e57 100644 --- a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php +++ b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php @@ -18,7 +18,7 @@ * @see DatabaseBackendUnitTestCase * For a full working implementation. * - * @deprecated as of Drupal 8.2.x, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.2.0 and is removed from drupal:9.0.0. Use * \Drupal\KernelTests\Core\Cache\GenericCacheBackendUnitTestBase instead. */ abstract class GenericCacheBackendUnitTestBase extends KernelTestBase { diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php b/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php index ec67a3492..578eb9e71 100644 --- a/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php @@ -11,7 +11,7 @@ /** * Provides helper methods for page cache tags tests. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase instead. * * @see https://www.drupal.org/node/2999939 diff --git a/core/modules/system/src/Tests/Database/DatabaseWebTestBase.php b/core/modules/system/src/Tests/Database/DatabaseWebTestBase.php index 8a250baf3..ed40e58d5 100644 --- a/core/modules/system/src/Tests/Database/DatabaseWebTestBase.php +++ b/core/modules/system/src/Tests/Database/DatabaseWebTestBase.php @@ -10,7 +10,7 @@ /** * Base class for databases database tests. * - * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Instead * use \Drupal\Tests\system\Functional\Database\DatabaseTestBase. */ abstract class DatabaseWebTestBase extends WebTestBase { diff --git a/core/modules/system/src/Tests/Database/FakeRecord.php b/core/modules/system/src/Tests/Database/FakeRecord.php index 375e60f24..5cd9988db 100644 --- a/core/modules/system/src/Tests/Database/FakeRecord.php +++ b/core/modules/system/src/Tests/Database/FakeRecord.php @@ -11,7 +11,7 @@ * rather than just a stdClass or array. This class is for testing that * functionality. (See testQueryFetchClass() below) * - * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Instead * use \Drupal\Tests\system\Functional\Database\FakeRecord. */ class FakeRecord {} diff --git a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php index 92587b362..625baa5a7 100644 --- a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php @@ -19,7 +19,7 @@ /** * Provides helper methods for Entity cache tags tests. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase instead. * * @see https://www.drupal.org/node/2946549 @@ -91,7 +91,7 @@ protected function setUp() { // Reload the entity now that a new field has been added to it. $storage = $this->container - ->get('entity.manager') + ->get('entity_type.manager') ->getStorage($this->entity->getEntityTypeId()); $storage->resetCache(); $this->entity = $storage->load($this->entity->id()); @@ -210,7 +210,7 @@ protected function getAdditionalCacheTagsForEntityListing() { * chooses 'default'. */ protected function selectViewMode($entity_type) { - $view_modes = \Drupal::entityManager() + $view_modes = \Drupal::entityTypeManager() ->getStorage('entity_view_mode') ->loadByProperties(['targetEntityType' => $entity_type]); @@ -272,8 +272,11 @@ protected function createReferenceTestEntities($referenced_entity) { ], ], ])->save(); + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + if (!$this->entity->getEntityType()->hasHandlerClass('view_builder')) { - entity_get_display($entity_type, $bundle, 'full') + $display_repository->getViewDisplay($entity_type, $bundle, 'full') ->setComponent($field_name, [ 'type' => 'entity_reference_label', ]) @@ -281,7 +284,7 @@ protected function createReferenceTestEntities($referenced_entity) { } else { $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId()); - entity_get_display($entity_type, $bundle, 'full') + $display_repository->getViewDisplay($entity_type, $bundle, 'full') ->setComponent($field_name, [ 'type' => 'entity_reference_entity_view', 'settings' => [ @@ -292,7 +295,7 @@ protected function createReferenceTestEntities($referenced_entity) { } // Create an entity that does reference the entity being tested. - $label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label'); + $label_key = \Drupal::entityTypeManager()->getDefinition($entity_type)->getKey('label'); $referencing_entity = $this->container->get('entity_type.manager') ->getStorage($entity_type) ->create([ @@ -359,7 +362,7 @@ public function testReferencedEntity() { $view_cache_tag = []; if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) { - $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type) + $view_cache_tag = \Drupal::entityTypeManager()->getViewBuilder($entity_type) ->getCacheTags(); } @@ -367,7 +370,7 @@ public function testReferencedEntity() { $cache_context_tags = $context_metadata->getCacheTags(); // Generate the cache tags for the (non) referencing entities. - $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()); + $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityTypeManager()->getViewBuilder('entity_test')->getCacheTags()); // Includes the main entity's cache tags, since this entity references it. $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->entity->getCacheTags()); $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity)); @@ -375,7 +378,7 @@ public function testReferencedEntity() { $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $cache_context_tags); $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['rendered']); - $non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()); + $non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity->getCacheTags(), \Drupal::entityTypeManager()->getViewBuilder('entity_test')->getCacheTags()); $non_referencing_entity_cache_tags = Cache::mergeTags($non_referencing_entity_cache_tags, ['rendered']); // Generate the cache tags for all two possible entity listing paths. @@ -497,7 +500,8 @@ public function testReferencedEntity() { // entities, but not for any other routes. $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId()); $this->pass("Test modification of referenced entity's '$referenced_entity_view_mode' display.", 'Debug'); - $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $referenced_entity_view_mode); + $entity_display = \Drupal::service('entity_display.repository') + ->getViewDisplay($entity_type, $this->entity->bundle(), $referenced_entity_view_mode); $entity_display->save(); $this->verifyPageCache($referencing_entity_url, 'MISS'); $this->verifyPageCache($listing_url, 'MISS'); @@ -636,7 +640,7 @@ public function testReferencedEntity() { $this->verifyPageCache($non_referencing_entity_url, 'HIT'); // Verify cache hits. - $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()); + $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityTypeManager()->getViewBuilder('entity_test')->getCacheTags()); $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['http_response', 'rendered']); $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing()); diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php index 0817aa798..f41d426c8 100644 --- a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php @@ -10,7 +10,7 @@ /** * Provides some test methods used to update existing entity definitions. * - * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. * Use \Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait. * * @see https://www.drupal.org/node/2946549 diff --git a/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php b/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php index 9997ca565..9ec744a72 100644 --- a/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php @@ -12,7 +12,7 @@ /** * Defines an abstract test base for entity unit tests. * - * @deprecated in Drupal 8.1.0, will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use * \Drupal\KernelTests\Core\Entity\EntityKernelTestBase instead. */ abstract class EntityUnitTestBase extends KernelTestBase { diff --git a/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php index 69b35028a..9d49c882c 100644 --- a/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityWithUriCacheTagsTestBase.php @@ -12,7 +12,7 @@ /** * Provides helper methods for Entity cache tags tests; for entities with URIs. * - * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. * Use \Drupal\Tests\system\Functional\Entity\EntityWithUriCacheTagsTestBase. * * @see https://www.drupal.org/node/2946549 @@ -38,7 +38,7 @@ public function testEntityUri() { // Generate the standardized entity cache tags. $cache_tag = $this->entity->getCacheTags(); - $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)->getCacheTags(); + $view_cache_tag = \Drupal::entityTypeManager()->getViewBuilder($entity_type)->getCacheTags(); $render_cache_tag = 'rendered'; $this->pass("Test entity.", 'Debug'); @@ -49,7 +49,7 @@ public function testEntityUri() { // Also verify the existence of an entity render cache entry, if this entity // type supports render caching. - if (\Drupal::entityManager()->getDefinition($entity_type)->isRenderCacheable()) { + if (\Drupal::entityTypeManager()->getDefinition($entity_type)->isRenderCacheable()) { $cache_keys = ['entity_view', $entity_type, $this->entity->id(), $view_mode]; $cid = $this->createCacheId($cache_keys, $entity_cache_contexts); $redirected_cid = NULL; @@ -73,7 +73,8 @@ public function testEntityUri() { // Verify that after modifying the entity's display, there is a cache miss. $this->pass("Test modification of entity's '$view_mode' display.", 'Debug'); - $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $view_mode); + $entity_display = \Drupal::service('entity_display.repository') + ->getViewDisplay($entity_type, $this->entity->bundle(), $view_mode); $entity_display->save(); $this->verifyPageCache($entity_url, 'MISS'); diff --git a/core/modules/system/src/Tests/Image/ToolkitTestBase.php b/core/modules/system/src/Tests/Image/ToolkitTestBase.php index ef85f0c50..fd8c7e245 100644 --- a/core/modules/system/src/Tests/Image/ToolkitTestBase.php +++ b/core/modules/system/src/Tests/Image/ToolkitTestBase.php @@ -10,7 +10,7 @@ /** * Base class for image manipulation testing. * - * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. * Use Drupal\FunctionalTests\Image\ToolkitTestBase instead. * * @see https://www.drupal.org/node/2862641 diff --git a/core/modules/system/src/Tests/Installer/ConfigAfterInstallerTestBase.php b/core/modules/system/src/Tests/Installer/ConfigAfterInstallerTestBase.php index 43d8d48ec..ea83c7569 100644 --- a/core/modules/system/src/Tests/Installer/ConfigAfterInstallerTestBase.php +++ b/core/modules/system/src/Tests/Installer/ConfigAfterInstallerTestBase.php @@ -13,7 +13,7 @@ /** * Provides a class for install profiles to check their installed config. * - * @deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. * Use \Drupal\FunctionalTests\Installer\ConfigAfterInstallerTestBase. */ abstract class ConfigAfterInstallerTestBase extends InstallerTestBase { diff --git a/core/modules/system/src/Tests/JsMessageTestCases.php b/core/modules/system/src/Tests/JsMessageTestCases.php deleted file mode 100644 index 05e1040dd..000000000 --- a/core/modules/system/src/Tests/JsMessageTestCases.php +++ /dev/null @@ -1,32 +0,0 @@ -assertTrue($pass, format_string('Breadcrumb %parts found on @path.', [ + $this->assertTrue($pass, new FormattableMarkup('Breadcrumb %parts found on @path.', [ '%parts' => implode(' » ', $trail), '@path' => $this->getUrl(), ])); diff --git a/core/modules/system/src/Tests/Menu/AssertMenuActiveTrailTrait.php b/core/modules/system/src/Tests/Menu/AssertMenuActiveTrailTrait.php index e64177a8c..ff4ce1b44 100644 --- a/core/modules/system/src/Tests/Menu/AssertMenuActiveTrailTrait.php +++ b/core/modules/system/src/Tests/Menu/AssertMenuActiveTrailTrait.php @@ -4,12 +4,13 @@ @trigger_error(__NAMESPACE__ . '\AssertMenuActiveTrailTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\system\Functional\Menu\AssertMenuActiveTrailTrait', E_USER_DEPRECATED); +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; /** * Provides test assertions for verifying the active menu trail. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\system\Functional\Menu\AssertMenuActiveTrailTrait instead. */ trait AssertMenuActiveTrailTrait { @@ -61,7 +62,7 @@ protected function assertMenuActiveTrail($tree, $last_active) { ':title' => $active_link_title, ]; $elements = $this->xpath($xpath, $args); - $this->assertTrue(!empty($elements), format_string('Active link %title was found in menu tree, including active trail links %tree.', [ + $this->assertTrue(!empty($elements), new FormattableMarkup('Active link %title was found in menu tree, including active trail links %tree.', [ '%title' => $active_link_title, '%tree' => implode(' » ', $tree), ])); diff --git a/core/modules/system/src/Tests/Menu/MenuTestBase.php b/core/modules/system/src/Tests/Menu/MenuTestBase.php index c77f079c0..476929338 100644 --- a/core/modules/system/src/Tests/Menu/MenuTestBase.php +++ b/core/modules/system/src/Tests/Menu/MenuTestBase.php @@ -9,7 +9,7 @@ /** * Base class for Menu tests. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\BrowserTestBase instead. */ abstract class MenuTestBase extends WebTestBase { diff --git a/core/modules/system/src/Tests/Module/ModuleTestBase.php b/core/modules/system/src/Tests/Module/ModuleTestBase.php index e733dfa4c..4c2e3e4c1 100644 --- a/core/modules/system/src/Tests/Module/ModuleTestBase.php +++ b/core/modules/system/src/Tests/Module/ModuleTestBase.php @@ -4,6 +4,7 @@ @trigger_error(__NAMESPACE__ . '\ModuleTestBase is deprecated for removal before Drupal 9.0.0. Use \Drupal\Tests\system\Functional\Module\ModuleTestBase instead. See https://www.drupal.org/node/2999939', E_USER_DEPRECATED); +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Config\InstallStorage; use Drupal\Core\Database\Database; use Drupal\Core\Config\FileStorage; @@ -13,7 +14,7 @@ /** * Helper class for module test cases. * - * @deprecated Scheduled for removal in Drupal 9.0.0. + * @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. * Use \Drupal\Tests\system\Functional\Module\ModuleTestBase instead. * * @see https://www.drupal.org/node/2999939 @@ -50,9 +51,9 @@ public function assertTableCount($base_table, $count = TRUE) { $tables = $connection->schema()->findTables($connection->prefixTables('{' . $base_table . '}') . '%'); if ($count) { - return $this->assertTrue($tables, format_string('Tables matching "@base_table" found.', ['@base_table' => $base_table])); + return $this->assertTrue($tables, new FormattableMarkup('Tables matching "@base_table" found.', ['@base_table' => $base_table])); } - return $this->assertFalse($tables, format_string('Tables matching "@base_table" not found.', ['@base_table' => $base_table])); + return $this->assertFalse($tables, new FormattableMarkup('Tables matching "@base_table" not found.', ['@base_table' => $base_table])); } /** @@ -70,7 +71,7 @@ public function assertModuleTablesExist($module) { $tables_exist = FALSE; } } - return $this->assertTrue($tables_exist, format_string('All database tables defined by the @module module exist.', ['@module' => $module])); + return $this->assertTrue($tables_exist, new FormattableMarkup('All database tables defined by the @module module exist.', ['@module' => $module])); } /** @@ -88,7 +89,7 @@ public function assertModuleTablesDoNotExist($module) { $tables_exist = TRUE; } } - return $this->assertFalse($tables_exist, format_string('None of the database tables defined by the @module module exist.', ['@module' => $module])); + return $this->assertFalse($tables_exist, new FormattableMarkup('None of the database tables defined by the @module module exist.', ['@module' => $module])); } /** @@ -132,7 +133,7 @@ public function assertModuleConfig($module) { } // Verify that all configuration has been installed (which means that $names // is empty). - return $this->assertFalse($names, format_string('All default configuration of @module module found.', ['@module' => $module])); + return $this->assertFalse($names, new FormattableMarkup('All default configuration of @module module found.', ['@module' => $module])); } /** @@ -146,7 +147,7 @@ public function assertModuleConfig($module) { */ public function assertNoModuleConfig($module) { $names = \Drupal::configFactory()->listAll($module . '.'); - return $this->assertFalse($names, format_string('No configuration found for @module module.', ['@module' => $module])); + return $this->assertFalse($names, new FormattableMarkup('No configuration found for @module module.', ['@module' => $module])); } /** @@ -166,7 +167,7 @@ public function assertModules(array $modules, $enabled) { else { $message = 'Module "@module" is not enabled.'; } - $this->assertEqual($this->container->get('module_handler')->moduleExists($module), $enabled, format_string($message, ['@module' => $module])); + $this->assertEqual($this->container->get('module_handler')->moduleExists($module), $enabled, new FormattableMarkup($message, ['@module' => $module])); } } @@ -200,7 +201,7 @@ public function assertLogMessage($type, $message, $variables = [], $severity = R ->countQuery() ->execute() ->fetchField(); - $this->assertTrue($count > 0, format_string('watchdog table contains @count rows for @message', ['@count' => $count, '@message' => format_string($message, $variables)])); + $this->assertTrue($count > 0, new FormattableMarkup('watchdog table contains @count rows for @message', ['@count' => $count, '@message' => new FormattableMarkup($message, $variables)])); } } diff --git a/core/modules/system/src/Tests/Path/UrlAliasFixtures.php b/core/modules/system/src/Tests/Path/UrlAliasFixtures.php deleted file mode 100644 index 41fd896df..000000000 --- a/core/modules/system/src/Tests/Path/UrlAliasFixtures.php +++ /dev/null @@ -1,96 +0,0 @@ -tableDefinition(); - $schema = $connection->schema(); - - foreach ($tables as $name => $table) { - $schema->dropTable($name); - $schema->createTable($name, $table); - } - } - - /** - * Drop the tables used for the sample data. - * - * @param \Drupal\Core\Database\Connection $connection - * The connection to use to drop the tables. - */ - public function dropTables(Connection $connection) { - $tables = $this->tableDefinition(); - $schema = $connection->schema(); - - foreach ($tables as $name => $table) { - $schema->dropTable($name); - } - } - - /** - * Returns an array of URL aliases for testing. - * - * @return array of URL alias definitions. - */ - public function sampleUrlAliases() { - return [ - [ - 'source' => '/node/1', - 'alias' => '/alias_for_node_1_en', - 'langcode' => 'en', - ], - [ - 'source' => '/node/2', - 'alias' => '/alias_for_node_2_en', - 'langcode' => 'en', - ], - [ - 'source' => '/node/1', - 'alias' => '/alias_for_node_1_fr', - 'langcode' => 'fr', - ], - [ - 'source' => '/node/1', - 'alias' => '/alias_for_node_1_und', - 'langcode' => 'und', - ], - ]; - } - - /** - * Returns the table definition for the URL alias fixtures. - * - * @return array - * Table definitions. - */ - public function tableDefinition() { - $tables = []; - - // Prime the drupal_get_filename() cache with the location of the system - // module as its location is known and shouldn't change. - // @todo Remove as part of https://www.drupal.org/node/2186491 - drupal_get_filename('module', 'system', 'core/modules/system/system.info.yml'); - module_load_install('system'); - $schema = system_schema(); - - $tables['url_alias'] = AliasStorage::schemaDefinition(); - $tables['key_value'] = $schema['key_value']; - - return $tables; - } - -} diff --git a/core/modules/system/src/Tests/Routing/MockAliasManager.php b/core/modules/system/src/Tests/Routing/MockAliasManager.php index 09f36366f..dcbfbe716 100644 --- a/core/modules/system/src/Tests/Routing/MockAliasManager.php +++ b/core/modules/system/src/Tests/Routing/MockAliasManager.php @@ -2,7 +2,7 @@ namespace Drupal\system\Tests\Routing; -use Drupal\Core\Path\AliasManagerInterface; +use Drupal\path_alias\AliasManagerInterface; /** * An easily configurable mock alias manager. diff --git a/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php b/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php index 0abbfd3f1..be134c771 100644 --- a/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php +++ b/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php @@ -4,6 +4,7 @@ @trigger_error('\Drupal\system\Tests\System\SystemConfigFormTestBase is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use \Drupal\KernelTests\ConfigFormTestBase instead.', E_USER_DEPRECATED); +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Form\FormState; use Drupal\simpletest\WebTestBase; @@ -13,7 +14,7 @@ * @see UserAdminSettingsFormTest * For a full working implementation. * - * @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.6.0 and is removed from drupal:9.0.0. Use * \Drupal\KernelTests\ConfigFormTestBase instead. * * @see https://www.drupal.org/node/2941907 @@ -64,7 +65,7 @@ public function testConfigForm() { '%values' => print_r($values, TRUE), '%errors' => $valid_form ? t('None') : implode(' ', $errors), ]; - $this->assertTrue($valid_form, format_string('Input values: %values
Validation handler errors: %errors', $args)); + $this->assertTrue($valid_form, new FormattableMarkup('Input values: %values
Validation handler errors: %errors', $args)); foreach ($this->values as $data) { $this->assertEqual($data['#value'], $this->config($data['#config_name'])->get($data['#config_key'])); diff --git a/core/modules/system/src/Tests/Update/DbUpdatesTrait.php b/core/modules/system/src/Tests/Update/DbUpdatesTrait.php index 8a8ae8468..e25832b7b 100644 --- a/core/modules/system/src/Tests/Update/DbUpdatesTrait.php +++ b/core/modules/system/src/Tests/Update/DbUpdatesTrait.php @@ -13,7 +13,7 @@ * * This should be used only by classes extending \Drupal\simpletest\WebTestBase. * - * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. * Use \Drupal\FunctionalTests\Update\DbUpdatesTrait. * @see https://www.drupal.org/node/2896640 */ diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php index 49d96ac4b..e231862c6 100644 --- a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php +++ b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php @@ -36,7 +36,7 @@ * * @ingroup update_api * - * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. * Use \Drupal\FunctionalTests\Update\UpdatePathTestBase. * @see https://www.drupal.org/node/2896640 * @@ -187,9 +187,6 @@ protected function setUp() { $container = $this->initKernel($request); $this->initConfig($container); - // Add the config directories to settings.php. - drupal_install_config_directories(); - // Restore the original Simpletest batch. $this->restoreBatch(); diff --git a/core/modules/system/src/TimeZoneResolver.php b/core/modules/system/src/TimeZoneResolver.php new file mode 100644 index 000000000..551a8d5cd --- /dev/null +++ b/core/modules/system/src/TimeZoneResolver.php @@ -0,0 +1,100 @@ +configFactory = $config_factory; + $this->currentUser = $current_user; + } + + /** + * Sets the default time zone. + */ + public function setDefaultTimeZone() { + if ($time_zone = $this->getTimeZone()) { + date_default_timezone_set($time_zone); + } + } + + /** + * Updates the default time zone when time zone config changes. + * + * @param \Drupal\Core\Config\ConfigCrudEvent $event + * The config crud event. + */ + public function onConfigSave(ConfigCrudEvent $event) { + $saved_config = $event->getConfig(); + if ($saved_config->getName() === 'system.date' && ($event->isChanged('timezone.default') || $event->isChanged('timezone.user.configurable'))) { + $this->setDefaultTimeZone(); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[ConfigEvents::SAVE][] = ['onConfigSave', 0]; + // The priority for this must run directly after the authentication + // subscriber. + $events[KernelEvents::REQUEST][] = ['setDefaultTimeZone', 299]; + $events[AccountEvents::SET_USER][] = ['setDefaultTimeZone']; + return $events; + } + + /** + * Gets the time zone based on site and user configuration. + * + * @return string|null + * The time zone, or NULL if nothing is set. + */ + protected function getTimeZone() { + $config = $this->configFactory->get('system.date'); + if ($config->get('timezone.user.configurable') && $this->currentUser->isAuthenticated() && $this->currentUser->getTimezone()) { + return $this->currentUser->getTimeZone(); + } + elseif ($default_timezone = $config->get('timezone.default')) { + return $default_timezone; + } + return NULL; + } + +} diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 799869bbc..661677b51 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -6,6 +6,7 @@ */ use Drupal\Component\Utility\Html; +use Drupal\Core\Link; use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; @@ -25,7 +26,7 @@ function template_preprocess_admin_block_content(&$variables) { if (!empty($variables['content'])) { $variables['compact'] = system_admin_compact_mode(); foreach ($variables['content'] as $key => $item) { - $variables['content'][$key]['link'] = \Drupal::l($item['title'], $item['url']); + $variables['content'][$key]['link'] = Link::fromTextAndUrl($item['title'], $item['url'])->toString(); if (!$variables['compact'] && isset($item['description'])) { $variables['content'][$key]['description'] = ['#markup' => $item['description']]; } @@ -292,8 +293,8 @@ function template_preprocess_system_themes_page(&$variables) { // Make sure to provide feedback on compatibility. $current_theme['incompatible'] = ''; - if (!empty($theme->incompatible_core)) { - $current_theme['incompatible'] = t("This theme is not compatible with Drupal @core_version. Check that the .info.yml file contains the correct 'core' value.", ['@core_version' => \Drupal::CORE_COMPATIBILITY]); + if (!empty($theme->info['core_incompatible'])) { + $current_theme['incompatible'] = t("This theme is not compatible with Drupal @core_version. Check that the .info.yml file contains a compatible 'core' or 'core_version_requirement' value.", ['@core_version' => \Drupal::VERSION]); } elseif (!empty($theme->incompatible_region)) { $current_theme['incompatible'] = t("This theme is missing a 'content' region."); diff --git a/core/modules/system/system.info.yml b/core/modules/system/system.info.yml index 20a4ba9da..1f2a06448 100644 --- a/core/modules/system/system.info.yml +++ b/core/modules/system/system.info.yml @@ -2,13 +2,7 @@ name: System type: module description: 'Handles general site configuration for administrators.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x required: true configure: system.admin_config_system - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 5a548735a..daf78612e 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -5,25 +5,27 @@ * Install, update and uninstall functions for the system module. */ +use Drupal\Component\FileSystem\FileSystem as FileSystemComponent; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Environment; -use Drupal\Component\FileSystem\FileSystem; use Drupal\Component\Utility\OpCodeCache; use Drupal\Component\Utility\Unicode; use Drupal\Core\Cache\Cache; -use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Path\AliasStorage; -use Drupal\Core\Url; use Drupal\Core\Database\Database; +use Drupal\Core\DrupalKernel; use Drupal\Core\Entity\ContentEntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\FieldableEntityInterface; -use Drupal\Core\DrupalKernel; use Drupal\Core\Extension\Extension; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\File\FileSystemInterface; +use Drupal\path_alias\Entity\PathAlias; +use Drupal\path_alias\PathAliasStorage; use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\PrivateStream; use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\Url; use Symfony\Component\HttpFoundation\Request; /** @@ -44,9 +46,9 @@ function system_requirements($phase) { // Display the currently active installation profile, if the site // is not running the default installation profile. - $profile = drupal_get_profile(); + $profile = \Drupal::installProfile(); if ($profile != 'standard') { - $info = system_get_info('module', $profile); + $info = \Drupal::service('extension.list.module')->getExtensionInfo($profile); $requirements['install_profile'] = [ 'title' => t('Installation profile'), 'value' => t('%profile_name (%profile-%version)', [ @@ -60,18 +62,33 @@ function system_requirements($phase) { } // Warn if any experimental modules are installed. - $experimental = []; + $experimental_modules = []; $enabled_modules = \Drupal::moduleHandler()->getModuleList(); foreach ($enabled_modules as $module => $data) { - $info = system_get_info('module', $module); + $info = \Drupal::service('extension.list.module')->getExtensionInfo($module); if (isset($info['package']) && $info['package'] === 'Core (Experimental)') { - $experimental[$module] = $info['name']; + $experimental_modules[$module] = $info['name']; } } - if (!empty($experimental)) { - $requirements['experimental'] = [ + if (!empty($experimental_modules)) { + $requirements['experimental_modules'] = [ 'title' => t('Experimental modules enabled'), - 'value' => t('Experimental modules found: %module_list. Experimental modules are provided for testing purposes only. Use at your own risk.', ['%module_list' => implode(', ', $experimental), ':url' => 'https://www.drupal.org/core/experimental']), + 'value' => t('Experimental modules found: %module_list. Experimental modules are provided for testing purposes only. Use at your own risk.', ['%module_list' => implode(', ', $experimental_modules), ':url' => 'https://www.drupal.org/core/experimental']), + 'severity' => REQUIREMENT_WARNING, + ]; + } + // Warn if any experimental themes are installed. + $experimental_themes = []; + $installed_themes = \Drupal::service('theme_handler')->listInfo(); + foreach ($installed_themes as $theme => $data) { + if (isset($data->info['experimental']) && $data->info['experimental']) { + $experimental_themes[$theme] = $data->info['name']; + } + } + if (!empty($experimental_themes)) { + $requirements['experimental_themes'] = [ + 'title' => t('Experimental themes enabled'), + 'value' => t('Experimental themes found: %theme_list. Experimental themes are provided for testing purposes only. Use at your own risk.', ['%theme_list' => implode(', ', $experimental_themes)]), 'severity' => REQUIREMENT_WARNING, ]; } @@ -498,37 +515,57 @@ function system_requirements($phase) { if ($phase == 'runtime') { // Try to write the .htaccess files first, to prevent false alarms in case // (for example) the /tmp directory was wiped. - file_ensure_htaccess(); - $file_system = \Drupal::service('file_system'); - $htaccess_files['public://.htaccess'] = [ - 'title' => t('Public files directory'), - 'directory' => $file_system->realpath('public://'), - ]; - if (PrivateStream::basePath()) { - $htaccess_files['private://.htaccess'] = [ - 'title' => t('Private files directory'), - 'directory' => $file_system->realpath('private://'), - ]; - } - $htaccess_files['temporary://.htaccess'] = [ - 'title' => t('Temporary files directory'), - 'directory' => $file_system->realpath('temporary://'), - ]; - foreach ($htaccess_files as $htaccess_file => $info) { + /** @var \Drupal\Core\File\HtaccessWriterInterface $htaccessWriter */ + $htaccessWriter = \Drupal::service("file.htaccess_writer"); + $htaccessWriter->ensure(); + foreach ($htaccessWriter->defaultProtectedDirs() as $protected_dir) { + $htaccess_file = $protected_dir->getPath() . '/.htaccess'; // Check for the string which was added to the recommended .htaccess file // in the latest security update. if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || strpos($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003') === FALSE) { $url = 'https://www.drupal.org/SA-CORE-2013-003'; $requirements[$htaccess_file] = [ - 'title' => $info['title'], + 'title' => new TranslatableMarkup($protected_dir->getTitle()), 'value' => t('Not fully protected'), 'severity' => REQUIREMENT_ERROR, - 'description' => t('See @url for information about the recommended .htaccess file which should be added to the %directory directory to help protect against arbitrary code execution.', [':url' => $url, '@url' => $url, '%directory' => $info['directory']]), + 'description' => t('See @url for information about the recommended .htaccess file which should be added to the %directory directory to help protect against arbitrary code execution.', [':url' => $url, '@url' => $url, '%directory' => $protected_dir->getPath()]), ]; } } } + // Test that path.temporary config is not set. + if ($phase == 'runtime') { + if (!Settings::get('file_temp_path')) { + $filesystem_config = \Drupal::config('system.file'); + if ($temp_path = $filesystem_config->get('path.temporary')) { + $requirements['temp_directory'] = [ + 'title' => t('Temporary Directory'), + 'severity' => REQUIREMENT_WARNING, + 'value' => 'Deprecated configuration', + 'description' => [ + [ + '#markup' => t('You are using deprecated configuration for the temporary files path.'), + '#suffix' => ' ', + ], + ], + ]; + if ($temp_path === FileSystemComponent::getOsTemporaryDirectory()) { + $requirements['temp_directory']['description'][] = [ + '#markup' => t('Your temporary directory configuration matches the OS default and can be safely removed.'), + '#suffix' => ' ', + ]; + } + else { + $requirements['temp_directory']['description'][] = [ + '#markup' => t('Remove the configuration and add the following to settings.php. $settings["file_temp_path"] = "%temp_path"', ['%temp_path' => $temp_path]), + '#suffix' => ' ', + ]; + } + } + } + } + // Report cron status. if ($phase == 'runtime') { $cron_config = \Drupal::config('system.cron'); @@ -596,7 +633,7 @@ function system_requirements($phase) { // By default no private files directory is configured. For private files // to be secure the admin needs to provide a path outside the webroot. PrivateStream::basePath(), - file_directory_temp(), + \Drupal::service('file_system')->getTempDirectory(), ]; } @@ -617,50 +654,74 @@ function system_requirements($phase) { if ($file_private_path = Settings::get('file_private_path')) { $directories[] = $file_private_path; } - if (!empty($GLOBALS['config']['system.file']['path']['temporary'])) { - $directories[] = $GLOBALS['config']['system.file']['path']['temporary']; + if (Settings::get('file_temp_path')) { + $directories[] = Settings::get('file_temp_path'); } else { // If the temporary directory is not overridden use an appropriate // temporary path for the system. - $directories[] = FileSystem::getOsTemporaryDirectory(); + $directories[] = FileSystemComponent::getOsTemporaryDirectory(); } } // Check the config directory if it is defined in settings.php. If it isn't // defined, the installer will create a valid config directory later, but // during runtime we must always display an error. - if (!empty($GLOBALS['config_directories'])) { - foreach (array_keys(array_filter($GLOBALS['config_directories'])) as $type) { - $directory = config_get_config_directory($type); - // If we're installing Drupal try and create the config sync directory. - if (!is_dir($directory) && $phase == 'install') { - \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + $config_sync_directory = Settings::get('config_sync_directory'); + if (!empty($config_sync_directory)) { + // If we're installing Drupal try and create the config sync directory. + if (!is_dir($config_sync_directory) && $phase == 'install') { + \Drupal::service('file_system')->prepareDirectory($config_sync_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + } + if (!is_dir($config_sync_directory)) { + if ($phase == 'install') { + $description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the online handbook.', ['%directory' => $config_sync_directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']); } - if (!is_dir($directory)) { - if ($phase == 'install') { - $description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the online handbook.', ['%directory' => $directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']); - } - else { - $description = t('The directory %directory does not exist.', ['%directory' => $directory]); - } - $requirements['config directory ' . $type] = [ - 'title' => t('Configuration directory: %type', ['%type' => $type]), - 'description' => $description, - 'severity' => REQUIREMENT_ERROR, - ]; + else { + $description = t('The directory %directory does not exist.', ['%directory' => $config_sync_directory]); } + $requirements['config sync directory'] = [ + 'title' => t('Configuration sync directory'), + 'description' => $description, + 'severity' => REQUIREMENT_ERROR, + ]; } } - if ($phase != 'install' && (empty($GLOBALS['config_directories']) || empty($GLOBALS['config_directories'][CONFIG_SYNC_DIRECTORY]))) { - $requirements['config directories'] = [ - 'title' => t('Configuration directories'), + if ($phase != 'install' && empty($config_sync_directory)) { + $requirements['config sync directory'] = [ + 'title' => t('Configuration sync directory'), 'value' => t('Not present'), - 'description' => t('Your %file file must define the $config_directories variable as an array containing the names of directories in which configuration files can be found. It must contain a %sync_key key.', ['%file' => $site_path . '/settings.php', '%sync_key' => CONFIG_SYNC_DIRECTORY]), + 'description' => t("Your %file file must define the %setting setting as a string containing the directory in which configuration files can be found.", ['%file' => $site_path . '/settings.php', '%setting' => "\$settings['config_sync_directory']"]), 'severity' => REQUIREMENT_ERROR, ]; } + // Handle other configuration directories. This will be removed in Drupal 9. + // See https://www.drupal.org/node/3018145. + $bc_config_directories = isset($GLOBALS['config_directories']) ? $GLOBALS['config_directories'] : []; + unset($bc_config_directories['sync']); + foreach (array_keys(array_filter($bc_config_directories)) as $type) { + @trigger_error("Automatic creation of '$type' configuration directory will be removed from drupal:9.0.0. See https://www.drupal.org/node/3018145.", E_USER_DEPRECATED); + $directory = config_get_config_directory($type); + // If we're installing Drupal try and create the config sync directory. + if (!is_dir($directory) && $phase == 'install') { + \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); + } + if (!is_dir($directory)) { + if ($phase == 'install') { + $description = t('An automated attempt to create the directory %directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the online handbook.', ['%directory' => $directory, ':handbook_url' => 'https://www.drupal.org/server-permissions']); + } + else { + $description = t('The directory %directory does not exist.', ['%directory' => $directory]); + } + $requirements['config directory ' . $type] = [ + 'title' => t('Configuration directory: %type', ['%type' => $type]), + 'description' => $description, + 'severity' => REQUIREMENT_ERROR, + ]; + } + } + $requirements['file system'] = [ 'title' => t('File system'), ]; @@ -711,7 +772,7 @@ function system_requirements($phase) { else { // This function can be called before the config_cache table has been // created. - if ($phase == 'install' || file_default_scheme() == 'public') { + if ($phase == 'install' || \Drupal::config('system.file')->get('default_scheme') == 'public') { $requirements['file system']['value'] = t('Writable (public download method)'); } else { @@ -762,7 +823,7 @@ function system_requirements($phase) { if ($change_list = \Drupal::entityDefinitionUpdateManager()->getChangeSummary()) { $build = []; foreach ($change_list as $entity_type_id => $changes) { - $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id); + $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id); $build[] = [ '#theme' => 'item_list', '#title' => $entity_type->getLabel(), @@ -796,8 +857,8 @@ function system_requirements($phase) { // Display an error if a newly introduced dependency in a module is not resolved. if ($phase == 'update') { - $profile = drupal_get_profile(); - $files = system_rebuild_module_data(); + $profile = \Drupal::installProfile(); + $files = \Drupal::service('extension.list.module')->getList(); foreach ($files as $module => $file) { // Ignore disabled modules and installation profiles. if (!$file->status || $module == $profile) { @@ -952,12 +1013,7 @@ function system_requirements($phase) { // Warning for httpoxy on IIS with affected PHP versions // @see https://www.drupal.org/node/2783079 - if (strpos($software, 'Microsoft-IIS') !== FALSE - && ( - version_compare(PHP_VERSION, '5.5.38', '<') - || (version_compare(PHP_VERSION, '5.6.0', '>=') && version_compare(PHP_VERSION, '5.6.24', '<')) - || (version_compare(PHP_VERSION, '7.0.0', '>=') && version_compare(PHP_VERSION, '7.0.9', '<')) - )) { + if (strpos($software, 'Microsoft-IIS') !== FALSE && version_compare(PHP_VERSION, '7.0.9', '<')) { $dom = new \DOMDocument('1.0', 'UTF-8'); $webconfig = file_get_contents('web.config'); // If you are here the web.config file must - of course - be well formed. @@ -1037,6 +1093,23 @@ function system_requirements($phase) { } } + // Prevent installation or update if the Pathauto module is installed and its + // version is less than 1.6. + if ($phase === 'install' || $phase === 'update') { + if (\Drupal::moduleHandler()->moduleExists('pathauto')) { + $info = \Drupal::service('extension.list.module')->getExtensionInfo('pathauto'); + if (version_compare($info['version'], '8.x-1.5') <= 0) { + $requirements['pathauto_module_incompatibility'] = [ + 'title' => t('Pathauto'), + 'description' => t('The Pathauto module is not compatible with the current version of Drupal core. Update the Pathauto module to 8.x-1.6 or later.', [ + ':url' => 'https://drupal.org/project/pathauto', + ]), + 'severity' => REQUIREMENT_ERROR, + ]; + } + } + } + return $requirements; } @@ -1189,11 +1262,6 @@ function system_schema() { ], ]; - // Create the url_alias table. The alias_storage service can auto-create its - // table, but this relies on exceptions being thrown. These exceptions will be - // thrown every request until an alias is created. - $schema['url_alias'] = AliasStorage::schemaDefinition(); - return $schema; } @@ -1330,7 +1398,7 @@ function system_update_8004() { // https://www.drupal.org/node/2542748. Regenerate the related schemas to // ensure they match the currently expected status. $manager = \Drupal::entityDefinitionUpdateManager(); - foreach (array_keys(\Drupal::entityManager() + foreach (array_keys(\Drupal::entityTypeManager() ->getDefinitions()) as $entity_type_id) { // Only update the entity type if it already exists. This condition is // needed in case new entity types are introduced after this update. @@ -1591,7 +1659,7 @@ function _system_update_create_block($name, $theme_name, array $values) { function system_update_8007() { $database = \Drupal::database(); $database_schema = $database->schema(); - $entity_types = \Drupal::entityManager()->getDefinitions(); + $entity_types = \Drupal::entityTypeManager()->getDefinitions(); $schema = \Drupal::keyValue('entity.storage_schema.sql')->getAll(); $schema_copy = $schema; @@ -1649,7 +1717,7 @@ function system_update_8007() { * Purge field schema data for uninstalled entity types. */ function system_update_8008() { - $entity_types = \Drupal::entityManager()->getDefinitions(); + $entity_types = \Drupal::entityTypeManager()->getDefinitions(); /** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $schema */ $schema = \Drupal::keyValue('entity.storage_schema.sql'); foreach ($schema->getAll() as $key => $item) { @@ -1808,20 +1876,20 @@ function system_update_8011() { * Enable automated cron module and move the config into it. */ function system_update_8013() { - $config_factory = \Drupal::configFactory(); - $system_cron_config = $config_factory->getEditable('system.cron'); - if ($autorun = $system_cron_config->get('threshold.autorun')) { + $autorun = \Drupal::configFactory()->getEditable('system.cron')->get('threshold.autorun'); + if ($autorun) { // Install 'automated_cron' module. \Drupal::service('module_installer')->install(['automated_cron'], FALSE); // Copy 'autorun' value into the new module's 'interval' setting. - $config_factory->getEditable('automated_cron.settings') + \Drupal::configFactory()->getEditable('automated_cron.settings') ->set('interval', $autorun) ->save(TRUE); } // Remove the 'autorun' key in system module config. - $system_cron_config + \Drupal::configFactory() + ->getEditable('system.cron') ->clear('threshold.autorun') ->save(TRUE); } @@ -1835,12 +1903,13 @@ function system_update_8014() { return; } $theme_handler->refreshInfo(); + $theme_installer = \Drupal::service('theme_installer'); foreach ($theme_handler->listInfo() as $theme) { // We first check that a base theme is set because if it's set to false then // it's unset in // \Drupal\Core\Extension\ThemeExtensionList::createExtensionInfo(). if (isset($theme->info['base theme']) && $theme->info['base theme'] == 'stable') { - $theme_handler->install(['stable']); + $theme_installer->install(['stable']); return; } } @@ -2292,3 +2361,163 @@ function system_update_8702() { } \Drupal::entityTypeManager()->useCaches(TRUE); } + +/** + * Remove 'path.temporary' config if redundant. + */ +function system_update_8801() { + // If settings is already being used, or the config is set to the OS default, + // clear the config value. + $config = Drupal::configFactory()->getEditable('system.file'); + if (Settings::get('file_temp_path') || $config->get('path.temporary') === FileSystemComponent::getOsTemporaryDirectory()) { + $config->clear('path.temporary') + ->save(TRUE); + } +} + +/** + * Fix system.theme:admin when the default theme is used as the admin theme. + */ +function system_update_8802() { + $config = Drupal::configFactory()->getEditable('system.theme'); + // Replace '0' with an empty string as '0' is not a valid value. + if ($config->get('admin') == '0') { + $config + ->set('admin', '') + ->save(TRUE); + } +} + +/** + * Install the 'path_alias' entity type. + */ +function system_update_8803() { + // Enable the Path Alias module if needed. + if (!\Drupal::moduleHandler()->moduleExists('path_alias')) { + \Drupal::service('module_installer')->install(['path_alias'], FALSE); + return t('The "path_alias" entity type has been installed.'); + } +} + +/** + * Convert path aliases to entities. + */ +function system_update_8804(&$sandbox = NULL) { + // Bail out early if the entity type is not using the default storage class. + $storage = \Drupal::entityTypeManager()->getStorage('path_alias'); + if (!$storage instanceof PathAliasStorage) { + return; + } + + if (!isset($sandbox['current_id'])) { + // This must be the first run. Initialize the sandbox. + $sandbox['progress'] = 0; + $sandbox['current_id'] = 0; + } + + $database = \Drupal::database(); + $step_size = 200; + $url_aliases = $database->select('url_alias', 't') + ->condition('t.pid', $sandbox['current_id'], '>') + ->fields('t') + ->orderBy('pid', 'ASC') + ->range(0, $step_size) + ->execute() + ->fetchAll(); + + if ($url_aliases) { + /** @var \Drupal\Component\Uuid\UuidInterface $uuid */ + $uuid = \Drupal::service('uuid'); + + $base_table_insert = $database->insert('path_alias'); + $base_table_insert->fields(['id', 'revision_id', 'uuid', 'path', 'alias', 'langcode', 'status']); + $revision_table_insert = $database->insert('path_alias_revision'); + $revision_table_insert->fields(['id', 'revision_id', 'path', 'alias', 'langcode', 'status', 'revision_default']); + foreach ($url_aliases as $url_alias) { + $values = [ + 'id' => $url_alias->pid, + 'revision_id' => $url_alias->pid, + 'uuid' => $uuid->generate(), + 'path' => $url_alias->source, + 'alias' => $url_alias->alias, + 'langcode' => $url_alias->langcode, + 'status' => 1, + ]; + $base_table_insert->values($values); + + unset($values['uuid']); + $values['revision_default'] = 1; + $revision_table_insert->values($values); + } + $base_table_insert->execute(); + $revision_table_insert->execute(); + + $sandbox['progress'] += count($url_aliases); + $last_url_alias = end($url_aliases); + $sandbox['current_id'] = $last_url_alias->pid; + + // If we're not in maintenance mode, the number of path aliases could change + // at any time so make sure that we always use the latest record count. + $missing = $database->select('url_alias', 't') + ->condition('t.pid', $sandbox['current_id'], '>') + ->orderBy('pid', 'ASC') + ->countQuery() + ->execute() + ->fetchField(); + $sandbox['#finished'] = $missing ? $sandbox['progress'] / ($sandbox['progress'] + (int) $missing) : 1; + } + else { + $sandbox['#finished'] = 1; + } + + if ($sandbox['#finished'] >= 1) { + // Keep a backup of the old 'url_alias' table if requested. + if (Settings::get('entity_update_backup', TRUE)) { + $old_table_name = 'old_' . substr(uniqid(), 0, 6) . '_url_alias'; + if (!$database->schema()->tableExists($old_table_name)) { + $database->schema()->renameTable('url_alias', $old_table_name); + } + } + else { + $database->schema()->dropTable('url_alias'); + } + + return t('Path aliases have been converted to entities.'); + } +} + +/** + * Change the provider of the 'path_alias' entity type and its base fields. + */ +function system_update_8805() { + // If the path alias module is not installed, it means that + // system_update_8803() ran as part of Drupal 8.8.0-alpha1, in which case we + // need to enable the module and change the provider of the 'path_alias' + // entity type. + if (!\Drupal::moduleHandler()->moduleExists('path_alias')) { + \Drupal::service('module_installer')->install(['path_alias'], FALSE); + + /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */ + $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository'); + $entity_type = $last_installed_schema_repository->getLastInstalledDefinition('path_alias'); + + // Set the new class for the entity type. + $entity_type->setClass(PathAlias::class); + + // Set the new provider for the entity type. + $reflection = new ReflectionClass($entity_type); + $property = $reflection->getProperty('provider'); + $property->setAccessible(TRUE); + $property->setValue($entity_type, 'path_alias'); + + $last_installed_schema_repository->setLastInstalledDefinition($entity_type); + + $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('path_alias'); + foreach ($field_storage_definitions as $field_storage_definition) { + if ($field_storage_definition->isBaseField()) { + $field_storage_definition->setProvider('path_alias'); + $last_installed_schema_repository->setLastInstalledFieldStorageDefinition($field_storage_definition); + } + } + } +} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 014153958..0baa96a6b 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -5,34 +5,37 @@ * Configuration system that lets administrators modify the workings of the site. */ +use Drupal\Component\FileSecurity\FileSecurity; use Drupal\Component\Gettext\PoItem; -use Drupal\Core\Extension\Dependency; use Drupal\Component\Render\PlainTextOutput; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Asset\AttachedAssetsInterface; +use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Cache\Cache; -use Drupal\Core\File\Exception\FileException; -use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Queue\QueueGarbageCollectionInterface; use Drupal\Core\Database\Query\AlterableInterface; +use Drupal\Core\Extension\Dependency; use Drupal\Core\Extension\Extension; +use Drupal\Core\Entity\Display\EntityFormDisplayInterface; +use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget; +use Drupal\Core\File\Exception\FileException; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\PageCache\RequestPolicyInterface; +use Drupal\Core\Queue\QueueGarbageCollectionInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\StackedRouteMatchInterface; -use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Url; -use Drupal\Core\Block\BlockPluginInterface; use Drupal\user\UserInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; use GuzzleHttp\Exception\RequestException; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * New users will be set to the default time zone at registration. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. * Use \Drupal\user\UserInterface::TIMEZONE_DEFAULT instead. * * @see https://www.drupal.org/node/2831620 @@ -42,7 +45,7 @@ const DRUPAL_USER_TIMEZONE_DEFAULT = 0; /** * New users will get an empty time zone at registration. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. * Use \Drupal\user\UserInterface::TIMEZONE_EMPTY instead. * * @see https://www.drupal.org/node/2831620 @@ -52,7 +55,7 @@ const DRUPAL_USER_TIMEZONE_EMPTY = 1; /** * New users will select their own timezone at registration. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. * Use \Drupal\user\UserInterface::TIMEZONE_SELECT instead. * * @see https://www.drupal.org/node/2831620 @@ -77,7 +80,7 @@ const DRUPAL_REQUIRED = 2; /** * Return only visible regions. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. * Use \Drupal\block\BlockRepositoryInterface::REGIONS_VISIBLE instead. * * @see system_region_list() @@ -88,7 +91,7 @@ const REGIONS_VISIBLE = 'visible'; /** * Return all regions. * - * @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. * Use \Drupal\block\BlockRepositoryInterface::REGIONS_ALL instead. * * @see system_region_list() @@ -316,7 +319,20 @@ function system_theme_suggestions_html(array $variables) { */ function system_theme_suggestions_page(array $variables) { $path_args = explode('/', trim(\Drupal::service('path.current')->getPath(), '/')); - return theme_get_suggestions($path_args, 'page'); + $suggestions = theme_get_suggestions($path_args, 'page'); + + $http_error_suggestions = [ + 'system.401' => 'page__401', + 'system.403' => 'page__403', + 'system.404' => 'page__404', + ]; + $route_name = \Drupal::routeMatch()->getRouteName(); + if (isset($http_error_suggestions[$route_name])) { + $suggestions[] = 'page__4xx'; + $suggestions[] = $http_error_suggestions[$route_name]; + } + + return $suggestions; } /** @@ -935,11 +951,11 @@ function system_check_directory($form_element, FormStateInterface $form_state) { elseif (is_dir($directory)) { if ($form_element['#name'] == 'file_public_path') { // Create public .htaccess file. - file_save_htaccess($directory, FALSE); + FileSecurity::writeHtaccess($directory, FALSE); } else { // Create private .htaccess file. - file_save_htaccess($directory); + FileSecurity::writeHtaccess($directory); } } @@ -965,10 +981,16 @@ function system_check_directory($form_element, FormStateInterface $form_state) { * information for $name, if given. If no records are available, an empty * array is returned. * - * @see system_rebuild_module_data() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal::service('extension.list.$type')->getExtensionInfo() or + * \Drupal::service('extension.list.$type')->getAllInstalledInfo() instead. + * + * @see https://www.drupal.org/node/2709919 + * @see \Drupal\Core\Extension\ModuleExtensionList::getList() * @see \Drupal\Core\Extension\ThemeExtensionList */ function system_get_info($type, $name = NULL) { + @trigger_error("system_get_info() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal::service('extension.list.$type')->getExtensionInfo() or \Drupal::service('extension.list.$type')->getAllInstalledInfo() instead. See https://www.drupal.org/node/2709919", E_USER_DEPRECATED); /** @var \Drupal\Core\Extension\ExtensionList $extension_list */ $extension_list = \Drupal::service('extension.list.' . $type); if (isset($name)) { @@ -990,7 +1012,7 @@ function system_get_info($type, $name = NULL) { * @param \Drupal\Core\Extension\Extension[] $modules * The array of all module info. * - * @deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. This + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. This * function is no longer used in Drupal core. * * @see https://www.drupal.org/node/2709919 @@ -1016,7 +1038,7 @@ function _system_rebuild_module_data_ensure_required($module, &$modules) { * @return \Drupal\Core\Extension\Extension[] * An associative array of module information. * - * @deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. + * @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0. * Use \Drupal::service('extension.list.module')->reset()->getList() * instead. Note: You probably don't need the reset() method. * @@ -1032,8 +1054,15 @@ function _system_rebuild_module_data() { * * @return \Drupal\Core\Extension\Extension[] * Array of all available modules and their data. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. + * Use \Drupal::service('extension.list.module')->getList() instead. + * Note: use reset() only when you really need to rescan and rebuild the list. + * + * @see https://www.drupal.org/node/2709919 */ function system_rebuild_module_data() { + @trigger_error('system_rebuild_module_data() is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Instead, you should use \Drupal::service("extension.list.module")->getList(). See https://www.drupal.org/node/2709919', E_USER_DEPRECATED); return \Drupal::service('extension.list.module')->reset()->getList(); } @@ -1147,7 +1176,8 @@ function system_admin_compact_mode() { * @param string $module * Module name. * @param array $info - * The module's information, as provided by system_get_info(). + * The module's information, as provided by + * \Drupal::service('extension.list.module')->getExtensionInfo(). * * @return array * An array of task links. @@ -1213,7 +1243,8 @@ function system_get_module_admin_tasks($module, array $info) { /** * Implements hook_cron(). * - * Remove older rows from flood, batch cache and expirable keyvalue tables. + * Remove older rows from flood, batch cache and expirable keyvalue tables. Also + * ensure files directories have .htaccess files. */ function system_cron() { // Clean up the flood. @@ -1239,6 +1270,10 @@ function system_cron() { $queue->garbageCollection(); } } + + // Ensure that all of Drupal's standard directories (e.g., the public files + // directory and config directory) have appropriate .htaccess files. + \Drupal::service('file.htaccess_writer')->ensure(); } /** @@ -1321,10 +1356,10 @@ function system_time_zones($blank = NULL, $grouped = FALSE) { * registered in the database. * @param int $replace * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE: Replace the existing file. - * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR: Do nothing and return FALSE. + * - FileSystemInterface::EXISTS_REPLACE: Replace the existing file. + * - FileSystemInterface::EXISTS_RENAME: Append _{incrementing number} until + * the filename is unique. + * - FileSystemInterface::EXISTS_ERROR: Do nothing and return FALSE. * * @return mixed * One of these possibilities: @@ -1396,41 +1431,69 @@ function system_block_view_system_main_block_alter(array &$build, BlockPluginInt } /** - * Implements hook_path_update(). + * Implements hook_query_TAG_alter() for entity reference selection handlers. */ -function system_path_update($path) { - $alias_manager = \Drupal::service('path.alias_manager'); - $alias_manager->cacheClear($path['source']); - $alias_manager->cacheClear($path['original']['source']); +function system_query_entity_reference_alter(AlterableInterface $query) { + $handler = $query->getMetadata('entity_reference_selection_handler'); + $handler->entityQueryAlter($query); } /** - * Implements hook_path_insert(). + * Implements hook_element_info_alter(). */ -function system_path_insert($path) { - \Drupal::service('path.alias_manager')->cacheClear($path['source']); +function system_element_info_alter(&$type) { + if (isset($type['page'])) { + $type['page']['#theme_wrappers']['off_canvas_page_wrapper'] = ['#weight' => -1000]; + } } /** - * Implements hook_path_delete(). + * Implements hook_modules_uninstalled(). */ -function system_path_delete($path) { - \Drupal::service('path.alias_manager')->cacheClear($path['source']); -} +function system_modules_uninstalled($modules) { + // @todo Remove this when modules are able to maintain their revision metadata + // keys. + // @see https://www.drupal.org/project/drupal/issues/3074333 + if (!in_array('workspaces', $modules, TRUE)) { + return; + } -/** - * Implements hook_query_TAG_alter() for entity reference selection handlers. - */ -function system_query_entity_reference_alter(AlterableInterface $query) { - $handler = $query->getMetadata('entity_reference_selection_handler'); - $handler->entityQueryAlter($query); + $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); + foreach ($entity_definition_update_manager->getEntityTypes() as $entity_type) { + $revision_metadata_keys = $entity_type->get('revision_metadata_keys'); + if ($revision_metadata_keys && array_key_exists('workspace', $revision_metadata_keys)) { + unset($revision_metadata_keys['workspace']); + $entity_type->set('revision_metadata_keys', $revision_metadata_keys); + $entity_definition_update_manager->updateEntityType($entity_type); + } + } } /** - * Implements hook_element_info_alter(). + * Implements hook_ENTITY_TYPE_presave() for entity_form_display entities. + * + * Provides a BC layer for modules providing old configurations. + * + * @todo Remove this hook in Drupal 9.0.x https://www.drupal.org/project/drupal/issues/3086388 */ -function system_element_info_alter(&$type) { - if (isset($type['page'])) { - $type['page']['#theme_wrappers']['off_canvas_page_wrapper'] = ['#weight' => -1000]; +function system_entity_form_display_presave(EntityFormDisplayInterface $display) { + /** @var \Drupal\Core\Field\WidgetPluginManager $field_widget_manager */ + $field_widget_manager = \Drupal::service('plugin.manager.field.widget'); + + foreach ($display->getComponents() as $field_name => $component) { + if (empty($component['type'])) { + continue; + } + + $plugin_definition = $field_widget_manager->getDefinition($component['type'], FALSE); + if (!is_a($plugin_definition['class'], EntityReferenceAutocompleteWidget::class, TRUE)) { + continue; + } + + if (!isset($component['settings']['match_limit'])) { + @trigger_error(sprintf('Any entity_reference_autocomplete component of an entity_form_display must have a match_limit setting. The %s field on the %s form display is missing it. This BC layer will be removed before 9.0.0. See https://www.drupal.org/node/2863188', $field_name, $display->id()), E_USER_DEPRECATED); + $component['settings']['match_limit'] = 10; + $display->setComponent($field_name, $component); + } } } diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index 11f72ecfe..114d28912 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -10,6 +10,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget; /** * Re-save all configuration entities to recalculate dependencies. @@ -206,3 +207,36 @@ function system_post_update_add_expand_all_items_key_in_system_menu_block(&$sand function system_post_update_clear_menu_cache() { // Empty post-update hook. } + +/** + * Clear the schema cache. + */ +function system_post_update_layout_plugin_schema_change() { + // Empty post-update hook. +} + +/** + * Populate the new 'match_limit' setting for the ER autocomplete widget. + */ +function system_post_update_entity_reference_autocomplete_match_limit(&$sandbox = NULL) { + $config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class); + /** @var \Drupal\Core\Field\WidgetPluginManager $field_widget_manager */ + $field_widget_manager = \Drupal::service('plugin.manager.field.widget'); + + $callback = function (EntityDisplayInterface $display) use ($field_widget_manager) { + foreach ($display->getComponents() as $field_name => $component) { + if (empty($component['type'])) { + continue; + } + + $plugin_definition = $field_widget_manager->getDefinition($component['type'], FALSE); + if (is_a($plugin_definition['class'], EntityReferenceAutocompleteWidget::class, TRUE)) { + return TRUE; + } + } + + return FALSE; + }; + + $config_entity_updater->update($sandbox, 'entity_form_display', $callback); +} diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 9cfe2ca54..9591d84d6 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -138,7 +138,7 @@ system.cron: system.admin_compact_page: path: '/admin/compact/{mode}' defaults: - _controller: 'Drupal\system\Controller\SystemController::compactPage' + _controller: '\Drupal\system\Controller\SystemController::compactPage' mode: 'off' requirements: _permission: 'access administration pages' @@ -153,7 +153,7 @@ system.machine_name_transliterate: system.site_information_settings: path: '/admin/config/system/site-information' defaults: - _form: 'Drupal\system\Form\SiteInformationForm' + _form: '\Drupal\system\Form\SiteInformationForm' _title: 'Basic site settings' requirements: _permission: 'administer site configuration' @@ -161,7 +161,7 @@ system.site_information_settings: system.cron_settings: path: '/admin/config/system/cron' defaults: - _form: 'Drupal\system\Form\CronForm' + _form: '\Drupal\system\Form\CronForm' _title: 'Cron' requirements: _permission: 'administer site configuration' @@ -169,7 +169,7 @@ system.cron_settings: system.logging_settings: path: '/admin/config/development/logging' defaults: - _form: 'Drupal\system\Form\LoggingForm' + _form: '\Drupal\system\Form\LoggingForm' _title: 'Logging and errors' requirements: _permission: 'administer site configuration' @@ -177,7 +177,7 @@ system.logging_settings: system.performance_settings: path: '/admin/config/development/performance' defaults: - _form: 'Drupal\system\Form\PerformanceForm' + _form: '\Drupal\system\Form\PerformanceForm' _title: 'Performance' requirements: _permission: 'administer site configuration' @@ -185,7 +185,7 @@ system.performance_settings: system.file_system_settings: path: '/admin/config/media/file-system' defaults: - _form: 'Drupal\system\Form\FileSystemForm' + _form: '\Drupal\system\Form\FileSystemForm' _title: 'File system' requirements: _permission: 'administer site configuration' @@ -193,7 +193,7 @@ system.file_system_settings: system.rss_feeds_settings: path: '/admin/config/services/rss-publishing' defaults: - _form: 'Drupal\system\Form\RssFeedsForm' + _form: '\Drupal\system\Form\RssFeedsForm' _title: 'RSS publishing' requirements: _permission: 'administer site configuration' @@ -201,7 +201,7 @@ system.rss_feeds_settings: system.regional_settings: path: '/admin/config/regional/settings' defaults: - _form: 'Drupal\system\Form\RegionalForm' + _form: '\Drupal\system\Form\RegionalForm' _title: 'Regional settings' requirements: _permission: 'administer site configuration' @@ -209,7 +209,7 @@ system.regional_settings: system.image_toolkit_settings: path: '/admin/config/media/image-toolkit' defaults: - _form: 'Drupal\system\Form\ImageToolkitForm' + _form: '\Drupal\system\Form\ImageToolkitForm' _title: 'Image toolkit' requirements: _permission: 'administer site configuration' @@ -217,7 +217,7 @@ system.image_toolkit_settings: system.site_maintenance_mode: path: '/admin/config/development/maintenance' defaults: - _form: 'Drupal\system\Form\SiteMaintenanceModeForm' + _form: '\Drupal\system\Form\SiteMaintenanceModeForm' _title: 'Maintenance mode' requirements: _permission: 'administer site configuration' @@ -269,14 +269,14 @@ system.modules_list: defaults: _title: 'Extend' _title_context: 'With components' - _form: 'Drupal\system\Form\ModulesListForm' + _form: '\Drupal\system\Form\ModulesListForm' requirements: _permission: 'administer modules' system.modules_list_confirm: path: '/admin/modules/list/confirm' defaults: - _form: 'Drupal\system\Form\ModulesListConfirmForm' + _form: '\Drupal\system\Form\ModulesListConfirmForm' _title: 'Some required modules must be enabled' requirements: _permission: 'administer modules' @@ -284,7 +284,7 @@ system.modules_list_confirm: system.modules_list_experimental_confirm: path: '/admin/modules/list/confirm-experimental' defaults: - _form: 'Drupal\system\Form\ModulesListExperimentalConfirmForm' + _form: '\Drupal\system\Form\ModulesListExperimentalConfirmForm' _title: 'Experimental modules' requirements: _permission: 'administer modules' @@ -292,7 +292,7 @@ system.modules_list_experimental_confirm: system.theme_uninstall: path: '/admin/appearance/uninstall' defaults: - _controller: 'Drupal\system\Controller\ThemeController::uninstall' + _controller: '\Drupal\system\Controller\ThemeController::uninstall' requirements: _permission: 'administer themes' _csrf_token: 'TRUE' @@ -300,7 +300,7 @@ system.theme_uninstall: system.theme_install: path: '/admin/appearance/install' defaults: - _controller: 'Drupal\system\Controller\ThemeController::install' + _controller: '\Drupal\system\Controller\ThemeController::install' requirements: _permission: 'administer themes' _csrf_token: 'TRUE' @@ -316,7 +316,7 @@ system.status: system.php: path: '/admin/reports/status/php' defaults: - _controller: 'Drupal\system\Controller\SystemInfoController::php' + _controller: '\Drupal\system\Controller\SystemInfoController::php' requirements: _permission: 'administer site configuration' # This page should not be treated as administrative since it outputs its own @@ -335,7 +335,7 @@ system.admin_index: system.files: path: '/system/files/{scheme}' defaults: - _controller: 'Drupal\system\FileDownloadController::download' + _controller: '\Drupal\system\FileDownloadController::download' scheme: private requirements: _access: 'TRUE' @@ -343,7 +343,7 @@ system.files: system.private_file_download: path: '/system/files/{filepath}' defaults: - _controller: 'Drupal\system\FileDownloadController::download' + _controller: '\Drupal\system\FileDownloadController::download' requirements: # Permissive regex to allow slashes in filepath see # http://symfony.com/doc/current/cookbook/routing/slash_in_parameter.html @@ -418,7 +418,7 @@ system.theme_settings_theme: system.modules_uninstall: path: '/admin/modules/uninstall' defaults: - _form: 'Drupal\system\Form\ModulesUninstallForm' + _form: '\Drupal\system\Form\ModulesUninstallForm' _title: 'Uninstall' requirements: _permission: 'administer modules' @@ -426,7 +426,7 @@ system.modules_uninstall: system.modules_uninstall_confirm: path: '/admin/modules/uninstall/confirm' defaults: - _form: 'Drupal\system\Form\ModulesUninstallConfirmForm' + _form: '\Drupal\system\Form\ModulesUninstallConfirmForm' _title: 'Confirm uninstall' requirements: _permission: 'administer modules' diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index ccfabe88b..b52573fc0 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -9,7 +9,7 @@ services: - { name: access_check, applies_to: _access_system_update } system.manager: class: Drupal\system\SystemManager - arguments: ['@module_handler', '@entity.manager', '@request_stack', '@menu.link_tree', '@menu.active_trail'] + arguments: ['@module_handler', null, '@request_stack', '@menu.link_tree', '@menu.active_trail'] system.breadcrumb.default: class: Drupal\system\PathBasedBreadcrumbBuilder arguments: ['@router.request_context', '@access_manager', '@router', '@path_processor_manager', '@config.factory', '@title_resolver', '@current_user', '@path.current', '@path.matcher'] @@ -43,3 +43,8 @@ services: arguments: ['@theme_handler', '@cache_tags.invalidator'] tags: - { name: event_subscriber } + system.timezone_resolver: + class: Drupal\system\TimeZoneResolver + arguments: ['@current_user', '@config.factory'] + tags: + - { name: event_subscriber } diff --git a/core/modules/system/templates/block--system-branding-block.html.twig b/core/modules/system/templates/block--system-branding-block.html.twig index 2a9868766..7c34e2d32 100644 --- a/core/modules/system/templates/block--system-branding-block.html.twig +++ b/core/modules/system/templates/block--system-branding-block.html.twig @@ -17,7 +17,7 @@ #} {% block content %} {% if site_logo %} - + {{ 'Home'|t }} {% endif %} diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig index 5014deb7b..20e4ea719 100644 --- a/core/modules/system/templates/details.html.twig +++ b/core/modules/system/templates/details.html.twig @@ -7,6 +7,7 @@ * - attributes: A list of HTML attributes for the details element. * - errors: (optional) Any errors for this details element, may not be set. * - title: (optional) The title of the element, may not be set. + * - summary_attributes: A list of HTML attributes for the summary element. * - description: (optional) The description of the element, may not be set. * - children: (optional) The children of the element, may not be set. * - value: (optional) The value of the element, may not be set. diff --git a/core/modules/simpletest/files/invalid-img-zero-size.png b/core/modules/system/tests/fixtures/HtaccessTest/.htaccess similarity index 100% rename from core/modules/simpletest/files/invalid-img-zero-size.png rename to core/modules/system/tests/fixtures/HtaccessTest/.htaccess diff --git a/core/modules/simpletest/files/translations/drupal-8.0.0-beta2.hu.po b/core/modules/system/tests/fixtures/HtaccessTest/web.config similarity index 100% rename from core/modules/simpletest/files/translations/drupal-8.0.0-beta2.hu.po rename to core/modules/system/tests/fixtures/HtaccessTest/web.config diff --git a/core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml b/core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml index 1a8a9046f..e7ee5eda1 100644 --- a/core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml +++ b/core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml @@ -17,6 +17,7 @@ settings: label: Search provider: search label_display: visible + page_id: node_search cache: max_age: -1 status: true diff --git a/core/modules/system/tests/fixtures/update/drupal-8.8.0.bare.standard.php.gz b/core/modules/system/tests/fixtures/update/drupal-8.8.0.bare.standard.php.gz new file mode 100644 index 000000000..84a5b2177 Binary files /dev/null and b/core/modules/system/tests/fixtures/update/drupal-8.8.0.bare.standard.php.gz differ diff --git a/core/modules/system/tests/fixtures/update/drupal-8.8.0.filled.standard.php.gz b/core/modules/system/tests/fixtures/update/drupal-8.8.0.filled.standard.php.gz new file mode 100644 index 000000000..c1f695b27 Binary files /dev/null and b/core/modules/system/tests/fixtures/update/drupal-8.8.0.filled.standard.php.gz differ diff --git a/core/modules/system/tests/fixtures/update/drupal-8.admin_theme_0.php b/core/modules/system/tests/fixtures/update/drupal-8.admin_theme_0.php new file mode 100644 index 000000000..fb04d316d --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal-8.admin_theme_0.php @@ -0,0 +1,17 @@ +query("SELECT data FROM {config} where name = :name", [':name' => 'system.theme'])->fetchField()); +$config['admin'] = '0'; +$connection->update('config') + ->fields(['data' => serialize($config)]) + ->condition('name', 'system.theme') + ->execute(); diff --git a/core/modules/system/tests/fixtures/update/drupal-8.convert-path-aliases-to-entities-2336597.php b/core/modules/system/tests/fixtures/update/drupal-8.convert-path-aliases-to-entities-2336597.php new file mode 100644 index 000000000..8f7cb770c --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal-8.convert-path-aliases-to-entities-2336597.php @@ -0,0 +1,46 @@ +insert('url_alias') +->fields([ + 'pid', + 'source', + 'alias', + 'langcode', +]) +->values([ + 'pid' => '2', + 'source' => '/node/1', + 'alias' => '/test-article-new-alias', + 'langcode' => 'und', +]) +->values([ + 'pid' => '3', + 'source' => '/node/8', + 'alias' => '/test-alias-for-any-language', + 'langcode' => 'und', +]) +->values([ + 'pid' => '4', + 'source' => '/node/8', + 'alias' => '/test-alias-in-english', + 'langcode' => 'en', +]) +->values([ + 'pid' => '5', + 'source' => '/node/8', + 'alias' => '/test-alias-in-spanish', + 'langcode' => 'es', +]) +->execute(); diff --git a/core/modules/system/tests/fixtures/update/drupal-8.taxonomy-parent-multilingual-3066439.php b/core/modules/system/tests/fixtures/update/drupal-8.taxonomy-parent-multilingual-3066439.php new file mode 100644 index 000000000..caf941c99 --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal-8.taxonomy-parent-multilingual-3066439.php @@ -0,0 +1,51 @@ +randomString(); + + $tid = $connection->insert('taxonomy_term_data') + ->fields(['vid', 'uuid', 'langcode']) + ->values(['vid' => 'tags', 'uuid' => $uuid->generate(), 'langcode' => 'es']) + ->execute(); + + $connection->insert('taxonomy_term_field_data') + ->fields(['tid', 'vid', 'langcode', 'name', 'weight', 'changed', 'default_langcode']) + ->values(['tid' => $tid, 'vid' => 'tags', 'langcode' => 'en', 'name' => $name, 'weight' => 0, 'changed' => REQUEST_TIME, 'default_langcode' => 1]) + ->execute(); + + $connection->insert('taxonomy_term_field_data') + ->fields(['tid', 'vid', 'langcode', 'name', 'weight', 'changed', 'default_langcode']) + ->values(['tid' => $tid, 'vid' => 'tags', 'langcode' => 'es', 'name' => $name . ' es', 'weight' => 0, 'changed' => REQUEST_TIME, 'default_langcode' => 0]) + ->execute(); + + $tids[] = $tid; +} + +$query = $connection->insert('taxonomy_term_hierarchy')->fields(['tid', 'parent']); + +$previous_tid = 0; +foreach ($tids as $tid) { + $query->values(['tid' => $tid, 'parent' => $previous_tid]); + $previous_tid = $tid; +} + +// Insert an extra record with no corresponding term. +// See https://www.drupal.org/project/drupal/issues/2997982 +$query->values(['tid' => max($tids) + 1, 'parent' => 0]); + +$query->execute(); diff --git a/core/modules/system/tests/fixtures/update/drupal-8.update-test-semver-update-n-enabled.php b/core/modules/system/tests/fixtures/update/drupal-8.update-test-semver-update-n-enabled.php new file mode 100644 index 000000000..285b42c1e --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal-8.update-test-semver-update-n-enabled.php @@ -0,0 +1,38 @@ +merge('key_value') + ->condition('collection', 'system.schema') + ->condition('name', 'update_test_semver_update_n') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'update_test_semver_update_n', + 'value' => 'i:8000;', + ]) + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['update_test_semver_update_n'] = 8000; +$connection->update('config') + ->fields([ + 'data' => serialize($extensions), + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); diff --git a/core/modules/system/tests/fixtures/update/views.view.entity_test_mul_revlog_for_2248983.yml b/core/modules/system/tests/fixtures/update/views.view.entity_test_mul_revlog_for_2248983.yml index d9eba9a39..532b22504 100644 --- a/core/modules/system/tests/fixtures/update/views.view.entity_test_mul_revlog_for_2248983.yml +++ b/core/modules/system/tests/fixtures/update/views.view.entity_test_mul_revlog_for_2248983.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: entity_test_mul_revlog_property_data base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/fixtures/update/views.view.entity_test_revlog_for_2248983.yml b/core/modules/system/tests/fixtures/update/views.view.entity_test_revlog_for_2248983.yml index 412972c45..22b3d996e 100644 --- a/core/modules/system/tests/fixtures/update/views.view.entity_test_revlog_for_2248983.yml +++ b/core/modules/system/tests/fixtures/update/views.view.entity_test_revlog_for_2248983.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: entity_test_revlog base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/modules/accept_header_routing_test/accept_header_routing_test.info.yml b/core/modules/system/tests/modules/accept_header_routing_test/accept_header_routing_test.info.yml index 2fbd20104..2c678356c 100644 --- a/core/modules/system/tests/modules/accept_header_routing_test/accept_header_routing_test.info.yml +++ b/core/modules/system/tests/modules/accept_header_routing_test/accept_header_routing_test.info.yml @@ -1,11 +1,5 @@ name: Accept header based routing test -# core: 8.x +core: 8.x type: module package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/modules/action_test/action_test.info.yml b/core/modules/system/tests/modules/action_test/action_test.info.yml index b19377fa9..7cefe06e0 100644 --- a/core/modules/system/tests/modules/action_test/action_test.info.yml +++ b/core/modules/system/tests/modules/action_test/action_test.info.yml @@ -2,11 +2,5 @@ name: 'Action test' type: module description: 'Support module for action testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.info.yml b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.info.yml index f8020314b..ba31a29db 100644 --- a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.info.yml +++ b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.info.yml @@ -1,12 +1,6 @@ name: 'AJAX form test mock module' type: module description: 'Test for AJAX form calls.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml index d0ba5b131..dbd359664 100644 --- a/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml +++ b/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml @@ -2,13 +2,7 @@ name: 'AJAX Test' type: module description: 'Support module for AJAX framework tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:contact - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml index 875b7caa9..40937fa6f 100644 --- a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml +++ b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml @@ -77,3 +77,11 @@ ajax_test.render_error: _controller: '\Drupal\ajax_test\Controller\AjaxTestController::renderError' requirements: _access: 'TRUE' + +ajax_test.message_form: + path: '/ajax-test/message' + defaults: + _title: 'Ajax Message Form' + _form: '\Drupal\ajax_test\Form\AjaxTestMessageCommandForm' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestMessageCommandForm.php b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestMessageCommandForm.php new file mode 100644 index 000000000..6c37a09a9 --- /dev/null +++ b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestMessageCommandForm.php @@ -0,0 +1,107 @@ + 'container', + '#id' => 'alternate-message-container', + ]; + $form['button_default'] = [ + '#type' => 'submit', + '#name' => 'makedefaultmessage', + '#value' => 'Make Message In Default Location', + '#ajax' => [ + 'callback' => '::makeMessageDefault', + ], + ]; + $form['button_alternate'] = [ + '#type' => 'submit', + '#name' => 'makealternatemessage', + '#value' => 'Make Message In Alternate Location', + '#ajax' => [ + 'callback' => '::makeMessageAlternate', + ], + ]; + $form['button_warning'] = [ + '#type' => 'submit', + '#name' => 'makewarningmessage', + '#value' => 'Make Warning Message', + '#ajax' => [ + 'callback' => '::makeMessageWarning', + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + } + + /** + * Callback for testing MessageCommand with default settings. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The AJAX response. + */ + public function makeMessageDefault() { + $response = new AjaxResponse(); + return $response->addCommand(new MessageCommand('I am a message in the default location.')); + } + + /** + * Callback for testing MessageCommand using an alternate message location. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The AJAX response. + */ + public function makeMessageAlternate() { + $response = new AjaxResponse(); + return $response->addCommand(new MessageCommand('I am a message in an alternate location.', '#alternate-message-container', [], FALSE)); + } + + /** + * Callback for testing MessageCommand with warning status. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The AJAX response. + */ + public function makeMessageWarning() { + $response = new AjaxResponse(); + return $response->addCommand(new MessageCommand('I am a warning message in the default location.', NULL, ['type' => 'warning', 'announce' => ''])); + } + +} diff --git a/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc b/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc index eb1832185..35c18fec7 100644 --- a/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc +++ b/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc @@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; * Performs a simple batch operation. */ function _batch_test_callback_1($id, $sleep, &$context) { - // No-op, but ensure the batch take a couple iterations. + // No-op, but ensure the batch takes a couple iterations. // Batch needs time to run for the test, so sleep a bit. usleep($sleep); // Track execution, and store some result for post-processing in the @@ -39,7 +39,7 @@ function _batch_test_callback_2($start, $total, $sleep, &$context) { // Process by groups of 5 (arbitrary value). $limit = 5; for ($i = 0; $i < $limit && $context['sandbox']['count'] < $total; $i++) { - // No-op, but ensure the batch take a couple iterations. + // No-op, but ensure the batch takes a couple iterations. // Batch needs time to run for the test, so sleep a bit. usleep($sleep); // Track execution, and store some result for post-processing in the @@ -63,7 +63,7 @@ function _batch_test_callback_2($start, $total, $sleep, &$context) { * Implements callback_batch_operation(). */ function _batch_test_callback_5($id, $sleep, &$context) { - // No-op, but ensure the batch take a couple iterations. + // No-op, but ensure the batch takes a couple iterations. // Batch needs time to run for the test, so sleep a bit. usleep($sleep); // Track execution, and store some result for post-processing in the @@ -77,15 +77,50 @@ function _batch_test_callback_5($id, $sleep, &$context) { /** * Implements callback_batch_operation(). * - * Performs a batch operation setting up its own batch. + * Performs a simple batch operation. + */ +function _batch_test_callback_6($id, $sleep, &$context) { + // No-op, but ensure the batch takes a couple iterations. + // Batch needs time to run for the test, so sleep a bit. + usleep($sleep); + // Track execution, and store some result for post-processing in the + // 'finished' callback. + batch_test_stack("op 6 id $id"); + $context['results'][6][] = $id; +} + +/** + * Implements callback_batch_operation(). + * + * Performs a simple batch operation. */ -function _batch_test_nested_batch_callback() { - batch_test_stack('setting up batch 2'); - batch_set(_batch_test_batch_2()); +function _batch_test_callback_7($id, $sleep, &$context) { + // No-op, but ensure the batch takes a couple iterations. + // Batch needs time to run for the test, so sleep a bit. + usleep($sleep); + // Track execution, and store some result for post-processing in the + // 'finished' callback. + batch_test_stack("op 7 id $id"); + $context['results'][7][] = $id; } /** - * Provides a common 'finished' callback for batches 1 to 4. + * Implements callback_batch_operation(). + * + * Performs a batch operation setting up its own batch(es). + */ +function _batch_test_nested_batch_callback(array $batches = []) { + foreach ($batches as $batch) { + batch_test_stack("setting up batch $batch"); + $function = '_batch_test_batch_' . $batch; + batch_set($function()); + } + \Drupal::state() + ->set('batch_test_nested_order_multiple_batches', batch_get()); +} + +/** + * Provides a common 'finished' callback for batches 1 to 7. */ function _batch_test_finished_helper($batch_id, $success, $results, $operations) { if ($results) { @@ -182,3 +217,21 @@ function _batch_test_finished_4($success, $results, $operations) { function _batch_test_finished_5($success, $results, $operations) { _batch_test_finished_helper(5, $success, $results, $operations); } + +/** + * Implements callback_batch_finished(). + * + * Triggers 'finished' callback for batch 6. + */ +function _batch_test_finished_6($success, $results, $operations) { + _batch_test_finished_helper(6, $success, $results, $operations); +} + +/** + * Implements callback_batch_finished(). + * + * Triggers 'finished' callback for batch 7. + */ +function _batch_test_finished_7($success, $results, $operations) { + _batch_test_finished_helper(7, $success, $results, $operations); +} diff --git a/core/modules/system/tests/modules/batch_test/batch_test.info.yml b/core/modules/system/tests/modules/batch_test/batch_test.info.yml index 3a121b0ba..072017e72 100644 --- a/core/modules/system/tests/modules/batch_test/batch_test.info.yml +++ b/core/modules/system/tests/modules/batch_test/batch_test.info.yml @@ -2,11 +2,5 @@ name: 'Batch API test' type: module description: 'Support module for Batch API tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/batch_test/batch_test.module b/core/modules/system/tests/modules/batch_test/batch_test.module index 59327de4f..03d1e8e28 100644 --- a/core/modules/system/tests/modules/batch_test/batch_test.module +++ b/core/modules/system/tests/modules/batch_test/batch_test.module @@ -24,6 +24,7 @@ function _batch_test_batch_0() { 'operations' => [], 'finished' => '_batch_test_finished_0', 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_0', ]; return $batch; } @@ -46,6 +47,7 @@ function _batch_test_batch_1() { 'operations' => $operations, 'finished' => '_batch_test_finished_1', 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_1', ]; return $batch; } @@ -67,6 +69,7 @@ function _batch_test_batch_2() { 'operations' => $operations, 'finished' => '_batch_test_finished_2', 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_2', ]; return $batch; } @@ -98,6 +101,7 @@ function _batch_test_batch_3() { 'operations' => $operations, 'finished' => '_batch_test_finished_3', 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_3', ]; return $batch; } @@ -119,7 +123,7 @@ function _batch_test_batch_4() { for ($i = 1; $i <= round($total / 2); $i++) { $operations[] = ['_batch_test_callback_1', [$i, $sleep]]; } - $operations[] = ['_batch_test_nested_batch_callback', []]; + $operations[] = ['_batch_test_nested_batch_callback', [[2]]]; for ($i = round($total / 2) + 1; $i <= $total; $i++) { $operations[] = ['_batch_test_callback_1', [$i, $sleep]]; } @@ -127,6 +131,7 @@ function _batch_test_batch_4() { 'operations' => $operations, 'finished' => '_batch_test_finished_4', 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_4', ]; return $batch; } @@ -149,6 +154,61 @@ function _batch_test_batch_5() { 'operations' => $operations, 'finished' => '_batch_test_finished_5', 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_5', + ]; + return $batch; +} + +/** + * Batch 6: Repeats a simple operation. + * + * Operations: op 6 from 1 to 10. + */ +function _batch_test_batch_6() { + // Ensure the batch takes at least two iterations. + $total = 10; + $sleep = (1000000 / $total) * 2; + + $operations = []; + for ($i = 1; $i <= $total; $i++) { + $operations[] = ['_batch_test_callback_6', [$i, $sleep]]; + } + $batch = [ + 'operations' => $operations, + 'finished' => '_batch_test_finished_6', + 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_6', + ]; + return $batch; +} + +/** + * Batch 7: Performs two batches within a batch. + * + * Operations: + * - op 7 from 1 to 5, + * - set batch 5 (op 5 from 1 to 10, should run at the end before batch 2) + * - set batch 6 (op 6 from 1 to 10, should run at the end after batch 1) + * - op 7 from 6 to 10, + */ +function _batch_test_batch_7() { + // Ensure the batch takes at least two iterations. + $total = 10; + $sleep = (1000000 / $total) * 2; + + $operations = []; + for ($i = 1; $i <= $total / 2; $i++) { + $operations[] = ['_batch_test_callback_7', [$i, $sleep]]; + } + $operations[] = ['_batch_test_nested_batch_callback', [[6, 5]]]; + for ($i = ($total / 2) + 1; $i <= $total; $i++) { + $operations[] = ['_batch_test_callback_7', [$i, $sleep]]; + } + $batch = [ + 'operations' => $operations, + 'finished' => '_batch_test_finished_7', + 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc', + 'batch_test_id' => 'batch_7', ]; return $batch; } @@ -187,13 +247,14 @@ function _batch_test_title_callback() { * Helper function: Stores or retrieves traced execution data. */ function batch_test_stack($data = NULL, $reset = FALSE) { + $state = \Drupal::state(); if ($reset) { - \Drupal::state()->delete('batch_test.stack'); + $state->delete('batch_test.stack'); } if (!isset($data)) { - return \Drupal::state()->get('batch_test.stack'); + return $state->get('batch_test.stack'); } - $stack = \Drupal::state()->get('batch_test.stack'); + $stack = $state->get('batch_test.stack'); $stack[] = $data; - \Drupal::state()->set('batch_test.stack', $stack); + $state->set('batch_test.stack', $stack); } diff --git a/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php b/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php index f00d610d1..0cf0b301d 100644 --- a/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php +++ b/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php @@ -32,7 +32,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { 'batch_2' => 'batch 2', 'batch_3' => 'batch 3', 'batch_4' => 'batch 4', + 'batch_6' => 'batch 6', + 'batch_7' => 'batch 7', ], + '#multiple' => TRUE, ]; $form['submit'] = [ '#type' => 'submit', @@ -48,8 +51,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { batch_test_stack(NULL, TRUE); - $function = '_batch_test_' . $form_state->getValue('batch'); - batch_set($function()); + foreach ($form_state->getValue('batch') as $batch) { + $function = '_batch_test_' . $batch; + batch_set($function()); + } $form_state->setRedirect('batch_test.redirect'); } diff --git a/core/modules/system/tests/modules/cache_test/cache_test.info.yml b/core/modules/system/tests/modules/cache_test/cache_test.info.yml index 825d89323..329e0d661 100644 --- a/core/modules/system/tests/modules/cache_test/cache_test.info.yml +++ b/core/modules/system/tests/modules/cache_test/cache_test.info.yml @@ -2,11 +2,5 @@ name: 'Cache test' type: module description: 'Support module for cache system testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/common_test/common_test.info.yml b/core/modules/system/tests/modules/common_test/common_test.info.yml index 20b7b6e96..2efb155b9 100644 --- a/core/modules/system/tests/modules/common_test/common_test.info.yml +++ b/core/modules/system/tests/modules/common_test/common_test.info.yml @@ -2,11 +2,5 @@ name: 'Common Test' type: module description: 'Support module for Common tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/common_test_cron_helper/common_test_cron_helper.info.yml b/core/modules/system/tests/modules/common_test_cron_helper/common_test_cron_helper.info.yml index f66882417..5d1ab7f6d 100644 --- a/core/modules/system/tests/modules/common_test_cron_helper/common_test_cron_helper.info.yml +++ b/core/modules/system/tests/modules/common_test_cron_helper/common_test_cron_helper.info.yml @@ -2,11 +2,5 @@ name: 'Common Test Cron Helper' type: module description: 'Helper module for CronRunTestCase::testCronExceptions().' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/condition_test/condition_test.info.yml b/core/modules/system/tests/modules/condition_test/condition_test.info.yml index 2464e1fed..b66450241 100644 --- a/core/modules/system/tests/modules/condition_test/condition_test.info.yml +++ b/core/modules/system/tests/modules/condition_test/condition_test.info.yml @@ -2,11 +2,5 @@ name: "Condition Test Support" type: module description: "Test general form component for condition plugins." package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/conneg_test/conneg_test.info.yml b/core/modules/system/tests/modules/conneg_test/conneg_test.info.yml index 2b37dd93e..256ddf43f 100644 --- a/core/modules/system/tests/modules/conneg_test/conneg_test.info.yml +++ b/core/modules/system/tests/modules/conneg_test/conneg_test.info.yml @@ -2,11 +2,5 @@ name: Content negotiation test type: module description: 'Support testing content negotiation variations.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/cron_queue_test/cron_queue_test.info.yml b/core/modules/system/tests/modules/cron_queue_test/cron_queue_test.info.yml index 0153ba8db..014e9188c 100644 --- a/core/modules/system/tests/modules/cron_queue_test/cron_queue_test.info.yml +++ b/core/modules/system/tests/modules/cron_queue_test/cron_queue_test.info.yml @@ -2,11 +2,5 @@ name: 'Cron Queue test' type: module description: 'Support module for the cron queue runner.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/csrf_race_test/csrf_race_test.info.yml b/core/modules/system/tests/modules/csrf_race_test/csrf_race_test.info.yml new file mode 100644 index 000000000..158bc54bb --- /dev/null +++ b/core/modules/system/tests/modules/csrf_race_test/csrf_race_test.info.yml @@ -0,0 +1,5 @@ +name: CSRF race test +type: module +description: 'Check that CSRF token is generated once.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/csrf_race_test/csrf_race_test.routing.yml b/core/modules/system/tests/modules/csrf_race_test/csrf_race_test.routing.yml new file mode 100644 index 000000000..b4bbf167a --- /dev/null +++ b/core/modules/system/tests/modules/csrf_race_test/csrf_race_test.routing.yml @@ -0,0 +1,12 @@ +csrf_race_test.csrftoken: + path: '/csrf_race/get_csrf_token/{num}' + defaults: + _controller: '\Drupal\csrf_race_test\Controller\TestController::getCsrfToken' + requirements: + _access: 'TRUE' +csrf_race_test.test: + path: '/csrf_race/test' + defaults: + _controller: '\Drupal\csrf_race_test\Controller\TestController::testMethod' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/csrf_race_test/src/Controller/TestController.php b/core/modules/system/tests/modules/csrf_race_test/src/Controller/TestController.php new file mode 100644 index 000000000..a98115420 --- /dev/null +++ b/core/modules/system/tests/modules/csrf_race_test/src/Controller/TestController.php @@ -0,0 +1,67 @@ +get('csrf_token') + ); + } + + /** + * Controller constructor. + */ + public function __construct(CsrfTokenGenerator $token_generator) { + $this->tokenGenerator = $token_generator; + } + + /** + * Helper page to load jQuery in test. + * + * @return array + * Empty page with jQuery. + */ + public function testMethod() { + return [ + '#markup' => '', + '#attached' => [ + 'library' => 'core/jquery', + ], + ]; + } + + /** + * Just return generated CSRF token for concurrent requests. + * + * We delay the response to the first request to make sure the second request + * is made when the first is not yet finished. + * + * @return \Symfony\Component\HttpFoundation\Response + * CSRF token. + */ + public function getCsrfToken($num) { + sleep($num); + return new JsonResponse($this->tokenGenerator->get()); + } + +} diff --git a/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml b/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml index 5e2ff638d..c830f5840 100644 --- a/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml +++ b/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml @@ -2,11 +2,5 @@ name: CSRF test type: module description: 'Support testing protecting routes with CSRF token.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/css_disable_transitions_test/css/disable_transitions.theme.css b/core/modules/system/tests/modules/css_disable_transitions_test/css/disable_transitions.theme.css new file mode 100644 index 000000000..55221fa99 --- /dev/null +++ b/core/modules/system/tests/modules/css_disable_transitions_test/css/disable_transitions.theme.css @@ -0,0 +1,39 @@ +/** + * Remove CSS animation effects that can cause random test failures. + */ +* { + /* CSS transitions. */ + -o-transition: none !important; + -moz-transition: none !important; + -ms-transition: none !important; + -webkit-transition: none !important; + transition: none !important; + -o-transition-property: none !important; + -moz-transition-property: none !important; + -ms-transition-property: none !important; + -webkit-transition-property: none !important; + transition-property: none !important; + /* CSS transforms. */ + -o-transform: none !important; + -moz-transform: none !important; + -ms-transform: none !important; + -webkit-transform: none !important; + transform: none !important; + /* CSS animations. */ + -webkit-animation: none !important; + -moz-animation: none !important; + -o-animation: none !important; + -ms-animation: none !important; + animation: none !important; +} + +/** + * Prevent youtube and third party content in iFrames from affecting tests. + * + * @todo Remove once bug in Chromedriver is fixed. + https://bugs.chromium.org/p/chromedriver/issues/detail?id=2758 + */ +iframe.media-oembed-content { + width: 0 !important; + height: 0 !important; +} diff --git a/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.info.yml b/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.info.yml new file mode 100644 index 000000000..436b022db --- /dev/null +++ b/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.info.yml @@ -0,0 +1,6 @@ +name: 'Test disable CSS animations' +type: module +description: 'Disables CSS animations for tests.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.libraries.yml b/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.libraries.yml new file mode 100644 index 000000000..b1cb9f56e --- /dev/null +++ b/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.libraries.yml @@ -0,0 +1,7 @@ +testing.css_disable_transitions_test: + version: VERSION + css: + theme: + css/disable_transitions.theme.css: { weight: 100 } + js: + js/disable_transitions.theme.js: {} diff --git a/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.module b/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.module new file mode 100644 index 000000000..4b915f3f3 --- /dev/null +++ b/core/modules/system/tests/modules/css_disable_transitions_test/css_disable_transitions_test.module @@ -0,0 +1,14 @@ +loggedStatements[] = str_replace(array_keys($stringified_args), array_values($stringified_args), $query); + } + return parent::query($query, $args, $options); + } + + /** + * Resets logged statements. + * + * @return $this + */ + public function resetLoggedStatements() { + $this->loggedStatements = []; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getDriverClass($class) { + // Override because the base class uses reflection to determine namespace + // based on object, which would break. + $namespace = (new \ReflectionClass(get_parent_class($this)))->getNamespaceName(); + $driver_class = $namespace . '\\' . $class; + return class_exists($driver_class) ? $driver_class : $class; + } + + /** + * Returns the executed queries. + * + * @return string[] + */ + public function getLoggedStatements() { + return $this->loggedStatements; + } + +} diff --git a/core/modules/system/tests/modules/database_statement_monitoring_test/src/mysql/Connection.php b/core/modules/system/tests/modules/database_statement_monitoring_test/src/mysql/Connection.php new file mode 100644 index 000000000..dc5dab25a --- /dev/null +++ b/core/modules/system/tests/modules/database_statement_monitoring_test/src/mysql/Connection.php @@ -0,0 +1,14 @@ + 'A duplicate version of the test table, used for fetch_style PDO::FETCH_CLASSTYPE tests.', + 'fields' => [ + 'classname' => [ + 'description' => "A custom class name", + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ], + 'name' => [ + 'description' => "A person's name", + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ], + 'age' => [ + 'description' => "The person's age", + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ], + 'job' => [ + 'description' => "The person's job", + 'type' => 'varchar_ascii', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ], + ], + 'primary key' => ['job'], + 'indexes' => [ + 'ages' => ['age'], + ], + ]; + // This is an alternate version of the same table that is structured the same // but has a non-serial Primary Key. $schema['test_people'] = [ diff --git a/core/modules/system/tests/modules/default_format_test/default_format_test.info.yml b/core/modules/system/tests/modules/default_format_test/default_format_test.info.yml index 63dbf8696..0a02a0688 100644 --- a/core/modules/system/tests/modules/default_format_test/default_format_test.info.yml +++ b/core/modules/system/tests/modules/default_format_test/default_format_test.info.yml @@ -2,11 +2,5 @@ name: 'Default format test' type: module description: 'Support module for testing default route format.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/delay_cache_tags_invalidation/delay_cache_tags_invalidation.info.yml b/core/modules/system/tests/modules/delay_cache_tags_invalidation/delay_cache_tags_invalidation.info.yml new file mode 100644 index 000000000..5fe56c9d5 --- /dev/null +++ b/core/modules/system/tests/modules/delay_cache_tags_invalidation/delay_cache_tags_invalidation.info.yml @@ -0,0 +1,5 @@ +name: 'Delay Cache Tags Invalidation Test' +type: module +core: 8.x +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/delay_cache_tags_invalidation/delay_cache_tags_invalidation.module b/core/modules/system/tests/modules/delay_cache_tags_invalidation/delay_cache_tags_invalidation.module new file mode 100644 index 000000000..357f4d5d9 --- /dev/null +++ b/core/modules/system/tests/modules/delay_cache_tags_invalidation/delay_cache_tags_invalidation.module @@ -0,0 +1,47 @@ +get('delay_cache_tags_invalidation_exception')) { + throw new \Exception('Abort entity save to trigger transaction rollback.'); + } + + // Read the pre-transaction cache writes. + // @see \Drupal\KernelTests\Core\Cache\EndOfTransactionQueriesTest::testEntitySave() + \Drupal::state()->set(__FUNCTION__ . '__pretransaction_foobar', \Drupal::cache()->get('test_cache_pretransaction_foobar')); + \Drupal::state()->set(__FUNCTION__ . '__pretransaction_entity_test_list', \Drupal::cache()->get('test_cache_pretransaction_entity_test_list')); + + // Write during the transaction. + \Drupal::cache()->set(__FUNCTION__ . '__during_transaction_foobar', 'something', Cache::PERMANENT, ['foobar']); + \Drupal::cache()->set(__FUNCTION__ . '__during_transaction_entity_test_list', 'something', Cache::PERMANENT, ['entity_test_list']); + + // Trigger a nested entity save and hence a nested transaction. + User::create([ + 'name' => 'johndoe', + 'status' => 1, + ])->save(); +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + */ +function delay_cache_tags_invalidation_user_insert(UserInterface $entity) { + if ($entity->getAccountName() === 'johndoe') { + // Read the in-transaction cache writes. + // @see delay_cache_tags_invalidation_entity_test_insert() + \Drupal::state()->set(__FUNCTION__ . '__during_transaction_foobar', \Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_foobar')); + \Drupal::state()->set(__FUNCTION__ . '__during_transaction_entity_test_list', \Drupal::cache()->get('delay_cache_tags_invalidation_entity_test_insert__during_transaction_entity_test_list')); + } +} diff --git a/core/modules/system/tests/modules/deprecation_test/deprecation_test.info.yml b/core/modules/system/tests/modules/deprecation_test/deprecation_test.info.yml index 4b53f320b..5b289513c 100644 --- a/core/modules/system/tests/modules/deprecation_test/deprecation_test.info.yml +++ b/core/modules/system/tests/modules/deprecation_test/deprecation_test.info.yml @@ -2,11 +2,5 @@ name: 'Deprecation test' type: module description: 'Support module for testing deprecation behaviors.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/deprecation_test/deprecation_test.module b/core/modules/system/tests/modules/deprecation_test/deprecation_test.module index 69217db45..920fd8162 100644 --- a/core/modules/system/tests/modules/deprecation_test/deprecation_test.module +++ b/core/modules/system/tests/modules/deprecation_test/deprecation_test.module @@ -11,7 +11,7 @@ * @return string * A known return value of 'known_return_value'. * - * @deprecated in Drupal 8.4.x. Might be removed before Drupal 9.0.0. This is + * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. This is * the deprecation message for deprecated_test_function(). */ function deprecation_test_function() { diff --git a/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml index 1be491f2b..8dde142ae 100644 --- a/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml +++ b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml @@ -1,12 +1,6 @@ name: 'Dialog Renderer Test' type: module description: 'Support module for Dialog Renderer tests.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml index 7360aa547..4be467310 100644 --- a/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml +++ b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml @@ -3,11 +3,11 @@ services: # behave differently depending on the 2nd argument. main_content_renderer.wide_modal: class: Drupal\dialog_renderer_test\Render\MainContent\WideModalRenderer - arguments: ['@title_resolver', 'wide'] + arguments: ['@title_resolver', '@renderer', 'wide'] tags: - { name: render.main_content_renderer, format: drupal_modal.wide } main_content_renderer.extra_wide_modal: class: Drupal\dialog_renderer_test\Render\MainContent\WideModalRenderer - arguments: ['@title_resolver', 'extra_wide'] + arguments: ['@title_resolver', '@renderer', 'extra_wide'] tags: - { name: render.main_content_renderer, format: drupal_modal.extra_wide } diff --git a/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php b/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php index 4b3aee92d..aecb4dd0d 100644 --- a/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php +++ b/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php @@ -6,6 +6,7 @@ use Drupal\Core\Ajax\OpenModalDialogCommand; use Drupal\Core\Controller\TitleResolverInterface; use Drupal\Core\Render\MainContent\ModalRenderer; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Component\HttpFoundation\Request; @@ -30,11 +31,13 @@ class WideModalRenderer extends ModalRenderer { * * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver * The title resolver. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. * @param string $mode * The mode, either 'wide' or 'extra_wide'. */ - public function __construct(TitleResolverInterface $title_resolver, $mode = 'wide') { - parent::__construct($title_resolver); + public function __construct(TitleResolverInterface $title_resolver, RendererInterface $renderer, $mode = 'wide') { + parent::__construct($title_resolver, $renderer); $this->mode = $mode; } @@ -45,7 +48,7 @@ public function renderResponse(array $main_content, Request $request, RouteMatch $response = new AjaxResponse(); // First render the main content, because it might provide a title. - $content = drupal_render_root($main_content); + $content = $this->renderer->renderRoot($main_content); // Attach the library necessary for using the OpenModalDialogCommand and set // the attachments for this Ajax response. diff --git a/core/modules/system/tests/modules/display_variant_test/display_variant_test.info.yml b/core/modules/system/tests/modules/display_variant_test/display_variant_test.info.yml index 0da6228ef..53e543c3a 100644 --- a/core/modules/system/tests/modules/display_variant_test/display_variant_test.info.yml +++ b/core/modules/system/tests/modules/display_variant_test/display_variant_test.info.yml @@ -2,11 +2,5 @@ name: 'Display variant tests' type: module description: 'Support module for testing display variants.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info.yml b/core/modules/system/tests/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info.yml index c16ffeda7..30bccf712 100644 --- a/core/modules/system/tests/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info.yml +++ b/core/modules/system/tests/modules/drupal_system_listing_compatible_test/drupal_system_listing_compatible_test.info.yml @@ -2,11 +2,5 @@ name: 'Drupal system listing compatible test' type: module description: 'Support module for testing the drupal_system_listing function.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.info.yml b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.info.yml index f317a949a..44ab45247 100644 --- a/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.info.yml +++ b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.info.yml @@ -2,11 +2,5 @@ name: 'Early rendering controller test' type: module description: 'Support module for EarlyRenderingControllerTest.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/element_info_test/element_info_test.info.yml b/core/modules/system/tests/modules/element_info_test/element_info_test.info.yml new file mode 100644 index 000000000..9e43148ee --- /dev/null +++ b/core/modules/system/tests/modules/element_info_test/element_info_test.info.yml @@ -0,0 +1,5 @@ +name: 'Element info test' +type: module +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/element_info_test/element_info_test.module b/core/modules/system/tests/modules/element_info_test/element_info_test.module new file mode 100644 index 000000000..74e472413 --- /dev/null +++ b/core/modules/system/tests/modules/element_info_test/element_info_test.module @@ -0,0 +1,33 @@ + []]; + /* @see \Drupal\KernelTests\Core\Render\Element\WeightTest::testProcessWeightSelectMax() */ + $info['number']['#pre_render'][] = 'element_info_test_element_pre_render'; +} + +/** + * Implements hook_element_plugin_alter(). + */ +function element_info_test_element_plugin_alter(array &$definitions) { + if (\Drupal::state()->get('hook_element_plugin_alter:remove_weight', FALSE)) { + unset($definitions['weight']); + } +} + +/** + * {@inheritdoc} + * + * @see \Drupal\KernelTests\Core\Render\Element\WeightTest::testProcessWeightSelectMax() + */ +function element_info_test_element_pre_render(array $element) { + return $element; +} diff --git a/core/modules/system/tests/modules/entity_crud_hook_test/entity_crud_hook_test.info.yml b/core/modules/system/tests/modules/entity_crud_hook_test/entity_crud_hook_test.info.yml index 68a8d92c5..7d3550399 100644 --- a/core/modules/system/tests/modules/entity_crud_hook_test/entity_crud_hook_test.info.yml +++ b/core/modules/system/tests/modules/entity_crud_hook_test/entity_crud_hook_test.info.yml @@ -1,12 +1,6 @@ name: 'Entity CRUD Hooks Test' type: module description: 'Support module for CRUD hook tests.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference.yml b/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference.yml index 1bcb19c45..417707109 100644 --- a/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference.yml +++ b/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference.yml @@ -2,17 +2,15 @@ langcode: en status: true dependencies: module: - - entity_reference_test - node - user id: test_entity_reference label: 'Entity reference' -module: entity_reference_test +module: views description: '' tag: '' base_table: node_field_data base_field: nid -core: '8' display: default: display_plugin: default @@ -35,6 +33,71 @@ display: row: type: fields fields: + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: type + plugin_id: field title: id: title table: node_field_data @@ -99,14 +162,30 @@ display: entity_type: node entity_field: status sorts: - created: - id: created + nid: + id: nid table: node_field_data - field: created - order: DESC - plugin_id: date + field: nid + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' entity_type: node - entity_field: created + entity_field: nid + plugin_id: standard + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } entity_reference_1: display_plugin: entity_reference id: entity_reference_1 @@ -123,3 +202,19 @@ display: type: none options: offset: 0 + display_extenders: { } + row: + type: entity_reference + options: + default_field_elements: true + inline: { } + separator: ': ' + hide_empty: false + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference_entity_test.yml b/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference_entity_test.yml index 752e528d6..ee0e41016 100644 --- a/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference_entity_test.yml +++ b/core/modules/system/tests/modules/entity_reference_test/config/install/views.view.test_entity_reference_entity_test.yml @@ -11,7 +11,6 @@ description: '' tag: '' base_table: entity_test base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/modules/entity_reference_test/entity_reference_test.info.yml b/core/modules/system/tests/modules/entity_reference_test/entity_reference_test.info.yml index d9220e5c2..d617a0f6d 100644 --- a/core/modules/system/tests/modules/entity_reference_test/entity_reference_test.info.yml +++ b/core/modules/system/tests/modules/entity_reference_test/entity_reference_test.info.yml @@ -1,17 +1,11 @@ name: "Entity Reference Test" type: module description: "Support module for the Entity Reference tests." -# core: 8.x +core: 8.x package: Testing -# version: VERSION +version: VERSION dependencies: - drupal:node - drupal:user - drupal:views - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/entity_reference_test_views/entity_reference_test_views.info.yml b/core/modules/system/tests/modules/entity_reference_test_views/entity_reference_test_views.info.yml index a91ab9b0a..0209b78e4 100644 --- a/core/modules/system/tests/modules/entity_reference_test_views/entity_reference_test_views.info.yml +++ b/core/modules/system/tests/modules/entity_reference_test_views/entity_reference_test_views.info.yml @@ -2,13 +2,7 @@ name: 'Entity reference test views' type: module description: 'Provides default views for views entity reference tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:views - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_mul_view.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_mul_view.yml index 0d06377ba..cbc74a05e 100644 --- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_mul_view.yml +++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_mul_view.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: entity_test_mul_property_data base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view.yml index 5c9bc5f94..cbe109a03 100644 --- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view.yml +++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: entity_test base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view_long.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view_long.yml index 997820574..aa601ceef 100644 --- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view_long.yml +++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_entity_test_view_long.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: entity_test_mul_changed_property base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml index 5c9352412..f643b09f8 100644 --- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml +++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml @@ -8,7 +8,6 @@ description: '' tag: '' base_table: entity_test base_field: id -core: '8' display: default: display_options: diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_mul_view.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_mul_view.yml index a1153b66d..8a42ea35b 100644 --- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_mul_view.yml +++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_mul_view.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: entity_test base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_view.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_view.yml index 1412b7142..0238be271 100644 --- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_view.yml +++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_reverse_entity_test_view.yml @@ -10,7 +10,6 @@ description: '' tag: '' base_table: entity_test_mul_property_data base_field: id -core: 8.x display: default: display_plugin: default diff --git a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml index f29e9cf42..b1d814255 100644 --- a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml +++ b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.info.yml @@ -2,13 +2,7 @@ name: 'Entity schema test module' type: module description: 'Provides entity and field definitions to test entity schema.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/entity_test/entity_test.info.yml b/core/modules/system/tests/modules/entity_test/entity_test.info.yml index e9c09338f..2f1859403 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.info.yml +++ b/core/modules/system/tests/modules/entity_test/entity_test.info.yml @@ -2,14 +2,9 @@ name: 'Entity CRUD test module' type: module description: 'Provides entity types based upon the CRUD API.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:field - drupal:text - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 + - drupal:system diff --git a/core/modules/system/tests/modules/entity_test/entity_test.install b/core/modules/system/tests/modules/entity_test/entity_test.install index c796c9adc..9a846afd6 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.install +++ b/core/modules/system/tests/modules/entity_test/entity_test.install @@ -5,7 +5,6 @@ * Install, update and uninstall functions for the entity_test module. */ -use Drupal\system\Tests\Update\DbUpdatesTrait; use Drupal\field\Entity\FieldStorageConfig; use Drupal\field\Entity\FieldConfig; @@ -29,7 +28,8 @@ function entity_test_install() { 'translatable' => FALSE, ])->save(); - entity_get_form_display($entity_type, $entity_type, 'default') + \Drupal::service('entity_display.repository') + ->getFormDisplay($entity_type, $entity_type) ->setComponent('field_test_text', ['type' => 'text_textfield']) ->save(); } @@ -54,5 +54,7 @@ function entity_test_schema() { return $schema; } -DbUpdatesTrait::includeUpdates('entity_test', 'entity_definition_updates'); -DbUpdatesTrait::includeUpdates('entity_test', 'status_report'); +$index = \Drupal::state()->get('entity_test.db_updates.entity_definition_updates'); +module_load_include('inc', 'entity_test', 'update/entity_definition_updates_' . $index); +$index = \Drupal::state()->get('entity_test.db_updates.status_report'); +module_load_include('inc', 'entity_test', 'update/status_report_' . $index); diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 0ee7e35b8..d8fb46e0c 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -5,6 +5,7 @@ * Test module for the entity API providing several entity types for testing. */ +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Access\AccessResult; use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Entity\ContentEntityInterface; @@ -96,6 +97,7 @@ function entity_test_entity_type_alter(array &$entity_types) { // Allow entity_test_rev tests to override the entity type definition. $entity_types['entity_test_rev'] = $state->get('entity_test_rev.entity_type', $entity_types['entity_test_rev']); + $entity_types['entity_test_revpub'] = $state->get('entity_test_revpub.entity_type', $entity_types['entity_test_revpub']); // Enable the entity_test_new only when needed. if (!$state->get('entity_test_new')) { @@ -221,7 +223,7 @@ function entity_test_delete_bundle($bundle, $entity_type = 'entity_test') { */ function entity_test_entity_bundle_info() { $bundles = []; - $entity_types = \Drupal::entityManager()->getDefinitions(); + $entity_types = \Drupal::entityTypeManager()->getDefinitions(); foreach ($entity_types as $entity_type_id => $entity_type) { if ($entity_type->getProvider() == 'entity_test' && $entity_type_id != 'entity_test_with_bundle') { $bundles[$entity_type_id] = \Drupal::state()->get($entity_type_id . '.bundles') ?: [$entity_type_id => ['label' => 'Entity Test Bundle']]; @@ -254,7 +256,7 @@ function entity_test_entity_bundle_info_alter(&$bundles) { * Implements hook_entity_view_mode_info_alter(). */ function entity_test_entity_view_mode_info_alter(&$view_modes) { - $entity_info = \Drupal::entityManager()->getDefinitions(); + $entity_info = \Drupal::entityTypeManager()->getDefinitions(); foreach ($entity_info as $entity_type => $info) { if ($entity_info[$entity_type]->getProvider() == 'entity_test' && !isset($view_modes[$entity_type])) { $view_modes[$entity_type] = [ @@ -277,14 +279,12 @@ function entity_test_entity_view_mode_info_alter(&$view_modes) { * Implements hook_entity_form_mode_info_alter(). */ function entity_test_entity_form_mode_info_alter(&$form_modes) { - $entity_info = \Drupal::entityManager()->getDefinitions(); + $entity_info = \Drupal::entityTypeManager()->getDefinitions(); foreach ($entity_info as $entity_type => $info) { if ($entity_info[$entity_type]->getProvider() == 'entity_test') { - $form_modes[$entity_type] = [ - 'compact' => [ - 'label' => t('Compact version'), - 'status' => TRUE, - ], + $form_modes[$entity_type]['compact'] = [ + 'label' => t('Compact version'), + 'status' => TRUE, ]; } } @@ -366,7 +366,7 @@ function entity_test_form_node_form_alter(&$form, FormStateInterface $form_state * @return \Drupal\entity_test\Entity\EntityTest * The loaded entity object, or NULL if the entity cannot be loaded. * - * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal::entityTypeManager()->getStorage('entity_test')->load(). * * @see https://www.drupal.org/node/2266845 @@ -391,7 +391,7 @@ function entity_test_load($id, $reset = FALSE) { * @return \Drupal\entity_test\Entity\EntityTestRev * The loaded entity object, or NULL if the entity cannot be loaded. * - * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal::entityTypeManager()->getStorage('entity_test_rev')->load(). * * @see https://www.drupal.org/node/2266845 @@ -416,7 +416,7 @@ function entity_test_rev_load($id, $reset = FALSE) { * @return \Drupal\entity_test\Entity\EntityTestMul * The loaded entity object, or FALSE if the entity cannot be loaded. * - * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal::entityTypeManager()->getStorage('entity_test_mul')->load(). * * @see https://www.drupal.org/node/2266845 @@ -441,7 +441,7 @@ function entity_test_mul_load($id, $reset = FALSE) { * @return \Drupal\entity_test\Entity\EntityTestMulRev * The loaded entity object, or NULL if the entity cannot be loaded. * - * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0. Use + * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use * \Drupal::entityTypeManager()->getStorage('entity_test_mulrev_load')->load(). * * @see https://www.drupal.org/node/2266845 @@ -574,7 +574,7 @@ function entity_test_entity_operation_alter(array &$operations, EntityInterface if (in_array($entity->getEntityTypeId(), $valid_entity_type_ids)) { if (\Drupal::service('router.route_provider')->getRouteByName("entity.{$entity->getEntityTypeId()}.test_operation")) { $operations['test_operation'] = [ - 'title' => format_string('Test Operation: @label', ['@label' => $entity->label()]), + 'title' => new FormattableMarkup('Test Operation: @label', ['@label' => $entity->label()]), 'url' => Url::fromRoute("entity.{$entity->getEntityTypeId()}.test_operation", [$entity->getEntityTypeId() => $entity->id()]), 'weight' => 50, ]; diff --git a/core/modules/system/tests/modules/entity_test/entity_test.services.yml b/core/modules/system/tests/modules/entity_test/entity_test.services.yml index 75e1bf33c..212de69c1 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.services.yml +++ b/core/modules/system/tests/modules/entity_test/entity_test.services.yml @@ -1,7 +1,7 @@ services: entity_test.definition.subscriber: class: Drupal\entity_test\EntityTestDefinitionSubscriber - arguments: ['@state'] + arguments: ['@state', '@entity.last_installed_schema.repository', '@entity_type.manager', '@entity_field.manager'] tags: - { name: event_subscriber } cache_context.entity_test_view_grants: diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php index 771d81a9a..ecf193b9d 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php @@ -26,7 +26,6 @@ * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\entity_test\EntityTestViewsData" * }, * base_table = "entity_test", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestAdminRoutes.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestAdminRoutes.php index eda9c988f..8071177fe 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestAdminRoutes.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestAdminRoutes.php @@ -15,7 +15,6 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php index 7fe9ce211..1d36eabb7 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php @@ -17,7 +17,6 @@ * "form" = { * "default" = "Drupal\entity_test\EntityTestForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", * }, diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php index dfbb9f04b..e253393d7 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php @@ -13,7 +13,6 @@ * "form" = { * "default" = "Drupal\entity_test\EntityTestForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler" * }, * base_table = "entity_test_cache", * entity_keys = { diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestFieldMethods.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestFieldMethods.php index 9160c1fdd..faacc0742 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestFieldMethods.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestFieldMethods.php @@ -18,7 +18,6 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php index 0c1ca9a53..fcae79452 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php @@ -17,7 +17,6 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php index dd3da09dc..4cede02bc 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php @@ -23,7 +23,6 @@ * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData" * }, * base_table = "entity_test_mul_changed", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php index bcbb54196..743f3c0bd 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php @@ -18,7 +18,6 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData" * }, * base_table = "entity_test_mul_default_value", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php index 94948a344..82ae16147 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php @@ -15,7 +15,6 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php index eeafa14d1..905b57ff8 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php @@ -18,7 +18,6 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php index 13a169df4..6f85124c8 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php @@ -21,7 +21,6 @@ * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData" * }, * base_table = "entity_test_mulrev_changed", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php index 8fa69a32b..8b8f5d616 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php @@ -20,7 +20,6 @@ * "delete" = "Drupal\entity_test\EntityTestDeleteForm", * "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php index e99505eed..f51ce8805 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php @@ -20,7 +20,6 @@ * "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm" * }, * "view_builder" = "Drupal\entity_test\EntityTestViewBuilder", - * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", * "route_provider" = { * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php new file mode 100644 index 000000000..175a6a029 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRevPub.php @@ -0,0 +1,64 @@ +state = $state; + $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository; + $this->entityTypeManager = $entity_type_manager; + $this->entityFieldManager = $entity_field_manager; } /** @@ -53,6 +87,24 @@ public static function getSubscribedEvents() { * {@inheritdoc} */ public function onEntityTypeCreate(EntityTypeInterface $entity_type) { + if ($this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id())) { + $this->storeDefinitionUpdate(EntityTypeEvents::CREATE); + } + $this->storeEvent(EntityTypeEvents::CREATE); + + // Retrieve the live entity type definition in order to warm the static + // cache and then insert the new entity type definition, so we can test that + // the cache doesn't get stale after the event has fired. + if ($this->updateLiveDefinitions) { + $this->entityTypeManager->getDefinition($entity_type->id()); + $this->state->set('entity_test_rev.entity_type', $entity_type); + } + } + + /** + * {@inheritdoc} + */ + public function onFieldableEntityTypeCreate(EntityTypeInterface $entity_type, array $field_storage_definitions) { $this->storeEvent(EntityTypeEvents::CREATE); } @@ -60,7 +112,20 @@ public function onEntityTypeCreate(EntityTypeInterface $entity_type) { * {@inheritdoc} */ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + $last_installed_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id()); + if ((string) $last_installed_definition->getLabel() === 'Updated entity test rev') { + $this->storeDefinitionUpdate(EntityTypeEvents::UPDATE); + } + $this->storeEvent(EntityTypeEvents::UPDATE); + + // Retrieve the live entity type definition in order to warm the static + // cache and then insert the new entity type definition, so we can test that + // the cache doesn't get stale after the event has fired. + if ($this->updateLiveDefinitions) { + $this->entityTypeManager->getDefinition($entity_type->id()); + $this->state->set('entity_test_rev.entity_type', $entity_type); + } } /** @@ -74,28 +139,73 @@ public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, En * {@inheritdoc} */ public function onEntityTypeDelete(EntityTypeInterface $entity_type) { + if (!$this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id())) { + $this->storeDefinitionUpdate(EntityTypeEvents::DELETE); + } $this->storeEvent(EntityTypeEvents::DELETE); + + // Retrieve the live entity type definition in order to warm the static + // cache and then delete the new entity type definition, so we can test that + // the cache doesn't get stale after the event has fired. + if ($this->updateLiveDefinitions) { + $this->entityTypeManager->getDefinition($entity_type->id()); + $this->state->set('entity_test_rev.entity_type', ''); + } } /** * {@inheritdoc} */ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { + if (isset($this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()])) { + $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::CREATE); + } $this->storeEvent(FieldStorageDefinitionEvents::CREATE); + + // Retrieve the live field storage definitions in order to warm the static + // cache and then insert the new storage definition, so we can test that the + // cache doesn't get stale after the event has fired. + if ($this->updateLiveDefinitions) { + $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId()); + $this->state->set('entity_test_rev.additional_base_field_definitions', [$storage_definition->getName() => $storage_definition]); + } } /** * {@inheritdoc} */ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { + $last_installed_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()]; + if ((string) $last_installed_definition->getLabel() === 'Updated field storage test') { + $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::UPDATE); + } $this->storeEvent(FieldStorageDefinitionEvents::UPDATE); + + // Retrieve the live field storage definitions in order to warm the static + // cache and then insert the new storage definition, so we can test that the + // cache doesn't get stale after the event has fired. + if ($this->updateLiveDefinitions) { + $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId()); + $this->state->set('entity_test_rev.additional_base_field_definitions', [$storage_definition->getName() => $storage_definition]); + } } /** * {@inheritdoc} */ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) { + if (!isset($this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()])) { + $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::DELETE); + } $this->storeEvent(FieldStorageDefinitionEvents::DELETE); + + // Retrieve the live field storage definitions in order to warm the static + // cache and then remove the new storage definition, so we can test that the + // cache doesn't get stale after the event has fired. + if ($this->updateLiveDefinitions) { + $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId()); + $this->state->set('entity_test_rev.additional_base_field_definitions', []); + } } /** @@ -105,6 +215,13 @@ public function enableEventTracking() { $this->trackEvents = TRUE; } + /** + * Enables live definition updates. + */ + public function enableLiveDefinitionUpdates() { + $this->updateLiveDefinitions = TRUE; + } + /** * Checks whether an event has been dispatched. * @@ -130,4 +247,30 @@ protected function storeEvent($event_name) { } } + /** + * Checks whether the installed definitions were updated before the event. + * + * @param string $event_name + * The event name. + * + * @return bool + * TRUE if the last installed entity type of field storage definitions have + * been updated before the event was fired, FALSE otherwise. + */ + public function hasDefinitionBeenUpdated($event_name) { + return (bool) $this->state->get($event_name . '_updated_definition'); + } + + /** + * Stores the installed definition state for the specified event. + * + * @param string $event_name + * The event name. + */ + protected function storeDefinitionUpdate($event_name) { + if ($this->trackEvents) { + $this->state->set($event_name . '_updated_definition', TRUE); + } + } + } diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/AutoIncrementingTestItem.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/AutoIncrementingTestItem.php index baa175e76..d4b53defe 100644 --- a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/AutoIncrementingTestItem.php +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/AutoIncrementingTestItem.php @@ -32,8 +32,8 @@ public function preSave() { * The incremented field value. */ private static function getIncrementedFieldValue() { - $current_value = &drupal_static(__METHOD__, 0); - return ++$current_value; + static $cache = 0; + return ++$cache; } } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonAnonTest.php index 77988aa03..2802bee92 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonAnonTest.php @@ -19,6 +19,11 @@ class EntityTestBundleHalJsonAnonTest extends EntityTestBundleResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonBasicAuthTest.php index 47e44d2d1..0557bfe74 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class EntityTestBundleHalJsonBasicAuthTest extends EntityTestBundleResourceTestB */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonCookieTest.php index 3e8b442de..c604aa900 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestBundleHalJsonCookieTest.php @@ -17,6 +17,11 @@ class EntityTestBundleHalJsonCookieTest extends EntityTestBundleResourceTestBase */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonAnonTest.php index c272a1aa6..17a65df4c 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonAnonTest.php @@ -22,6 +22,11 @@ class EntityTestHalJsonAnonTest extends EntityTestResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonBasicAuthTest.php index 18fad4bee..faef8d1cf 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class EntityTestHalJsonBasicAuthTest extends EntityTestHalJsonAnonTest { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonCookieTest.php index bee58edc3..10bf4a1ca 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonCookieTest.php @@ -16,4 +16,9 @@ class EntityTestHalJsonCookieTest extends EntityTestHalJsonAnonTest { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonInternalPropertyNormalizerTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonInternalPropertyNormalizerTest.php index 73d70d3ff..d68315ceb 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonInternalPropertyNormalizerTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestHalJsonInternalPropertyNormalizerTest.php @@ -22,6 +22,11 @@ class EntityTestHalJsonInternalPropertyNormalizerTest extends EntityTestHalJsonA */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonAnonTest.php index ca6e39535..22c621205 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonAnonTest.php @@ -20,6 +20,11 @@ class EntityTestLabelHalJsonAnonTest extends EntityTestLabelResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonBasicAuthTest.php index dfae2e402..4af4063cb 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class EntityTestLabelHalJsonBasicAuthTest extends EntityTestLabelHalJsonAnonTest */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonCookieTest.php index 3e373404e..f46f586a3 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestLabelHalJsonCookieTest.php @@ -16,4 +16,9 @@ class EntityTestLabelHalJsonCookieTest extends EntityTestLabelHalJsonAnonTest { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestMapFieldHalJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestMapFieldHalJsonAnonTest.php index af1ef4cea..dfee26fbd 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestMapFieldHalJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Hal/EntityTestMapFieldHalJsonAnonTest.php @@ -20,6 +20,11 @@ class EntityTestMapFieldHalJsonAnonTest extends EntityTestMapFieldResourceTestBa */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonAnonTest.php index 08eb70047..c1abebd0b 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonAnonTest.php @@ -21,4 +21,9 @@ class EntityTestBundleJsonAnonTest extends EntityTestBundleResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonBasicAuthTest.php index d1a7adbc9..a6a437ada 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class EntityTestBundleJsonBasicAuthTest extends EntityTestBundleResourceTestBase */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonCookieTest.php index 313c9b990..b239f41ef 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleJsonCookieTest.php @@ -26,4 +26,9 @@ class EntityTestBundleJsonCookieTest extends EntityTestBundleResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlAnonTest.php index e88314b42..8c2d234da 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlAnonTest.php @@ -23,4 +23,9 @@ class EntityTestBundleXmlAnonTest extends EntityTestBundleResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlBasicAuthTest.php index 701c15ba9..059ca8efb 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class EntityTestBundleXmlBasicAuthTest extends EntityTestBundleResourceTestBase */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlCookieTest.php index 2f3ee3310..5252b3f35 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestBundleXmlCookieTest.php @@ -28,4 +28,9 @@ class EntityTestBundleXmlCookieTest extends EntityTestBundleResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonAnonTest.php index 58d15b3aa..ff36b51b4 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonAnonTest.php @@ -23,4 +23,9 @@ class EntityTestJsonAnonTest extends EntityTestResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonBasicAuthTest.php index cd647e8fe..b79bf3cf0 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class EntityTestJsonBasicAuthTest extends EntityTestResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonCookieTest.php index b76de42b3..9ddb7a700 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonCookieTest.php @@ -26,4 +26,9 @@ class EntityTestJsonCookieTest extends EntityTestResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonInternalPropertyNormalizerTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonInternalPropertyNormalizerTest.php index 7ac9d8985..ec7868ec4 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonInternalPropertyNormalizerTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestJsonInternalPropertyNormalizerTest.php @@ -26,6 +26,11 @@ class EntityTestJsonInternalPropertyNormalizerTest extends EntityTestResourceTes */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonAnonTest.php index 31a476de3..a122830c9 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonAnonTest.php @@ -21,4 +21,9 @@ class EntityTestLabelJsonAnonTest extends EntityTestLabelResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonBasicAuthTest.php index 61cfea15b..1332774b4 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class EntityTestLabelJsonBasicAuthTest extends EntityTestLabelResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonCookieTest.php index 20bb70184..5a6153f45 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelJsonCookieTest.php @@ -26,4 +26,9 @@ class EntityTestLabelJsonCookieTest extends EntityTestLabelResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlAnonTest.php index ac1bd8685..dc6697698 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlAnonTest.php @@ -23,4 +23,9 @@ class EntityTestLabelXmlAnonTest extends EntityTestLabelResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlBasicAuthTest.php index e671098f2..067780894 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class EntityTestLabelXmlBasicAuthTest extends EntityTestLabelResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlCookieTest.php index f859c7ff8..5aef8f746 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestLabelXmlCookieTest.php @@ -28,4 +28,9 @@ class EntityTestLabelXmlCookieTest extends EntityTestLabelResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestMapFieldJsonAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestMapFieldJsonAnonTest.php index 044068032..5751c63f5 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestMapFieldJsonAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestMapFieldJsonAnonTest.php @@ -21,4 +21,9 @@ class EntityTestMapFieldJsonAnonTest extends EntityTestMapFieldResourceTestBase */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestTextItemNormalizerTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestTextItemNormalizerTest.php index 3ae0044ae..9c3b978cc 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestTextItemNormalizerTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestTextItemNormalizerTest.php @@ -19,6 +19,11 @@ class EntityTestTextItemNormalizerTest extends EntityTestResourceTestBase { */ public static $modules = ['filter_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlAnonTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlAnonTest.php index a5548f9a7..1ddd84f81 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlAnonTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlAnonTest.php @@ -25,4 +25,9 @@ class EntityTestXmlAnonTest extends EntityTestResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlBasicAuthTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlBasicAuthTest.php index d0501afb9..71f0e81fd 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlBasicAuthTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class EntityTestXmlBasicAuthTest extends EntityTestResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlCookieTest.php b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlCookieTest.php index 7fe4930fc..2fe903523 100644 --- a/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlCookieTest.php +++ b/core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestXmlCookieTest.php @@ -28,4 +28,9 @@ class EntityTestXmlCookieTest extends EntityTestResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/modules/entity_test_constraints/entity_test_constraints.info.yml b/core/modules/system/tests/modules/entity_test_constraints/entity_test_constraints.info.yml index d434cb01e..c59ea9ae1 100644 --- a/core/modules/system/tests/modules/entity_test_constraints/entity_test_constraints.info.yml +++ b/core/modules/system/tests/modules/entity_test_constraints/entity_test_constraints.info.yml @@ -2,13 +2,7 @@ name: 'Entity constraints test module' type: module description: 'Tests extending and altering entity constraints.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.info.yml b/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.info.yml index 14f9fc743..9d6852eb9 100644 --- a/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.info.yml +++ b/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.info.yml @@ -2,13 +2,7 @@ name: 'Entity test extra' type: module description: 'Provides extra fields for entity test entity types.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/entity_test_operation/entity_test_operation.info.yml b/core/modules/system/tests/modules/entity_test_operation/entity_test_operation.info.yml index 45959433b..eb905d4f6 100644 --- a/core/modules/system/tests/modules/entity_test_operation/entity_test_operation.info.yml +++ b/core/modules/system/tests/modules/entity_test_operation/entity_test_operation.info.yml @@ -2,11 +2,5 @@ name: 'Entity Operation Test' type: module description: 'Provides a test operation to entities.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/entity_test_revlog/entity_test_revlog.info.yml b/core/modules/system/tests/modules/entity_test_revlog/entity_test_revlog.info.yml index d2f484afb..4afba6215 100644 --- a/core/modules/system/tests/modules/entity_test_revlog/entity_test_revlog.info.yml +++ b/core/modules/system/tests/modules/entity_test_revlog/entity_test_revlog.info.yml @@ -2,11 +2,5 @@ name: 'Entity test revision log' type: module description: 'Provides two entity types with revision logging capabilities.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/entity_test_schema_converter/entity_test_schema_converter.info.yml b/core/modules/system/tests/modules/entity_test_schema_converter/entity_test_schema_converter.info.yml index d7112ccdf..210ced832 100644 --- a/core/modules/system/tests/modules/entity_test_schema_converter/entity_test_schema_converter.info.yml +++ b/core/modules/system/tests/modules/entity_test_schema_converter/entity_test_schema_converter.info.yml @@ -2,13 +2,7 @@ name: 'Entity Schema Converter Test' type: module description: 'Provides testing for the entity schema converter.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:entity_test_update - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/entity_test_third_party/entity_test_third_party.info.yml b/core/modules/system/tests/modules/entity_test_third_party/entity_test_third_party.info.yml index f1dce482d..00560c72e 100644 --- a/core/modules/system/tests/modules/entity_test_third_party/entity_test_third_party.info.yml +++ b/core/modules/system/tests/modules/entity_test_third_party/entity_test_third_party.info.yml @@ -2,13 +2,7 @@ name: 'Entity test third-party settings module' type: module description: 'Provides third-party settings for test entity types.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/entity_test_update/entity_test_update.info.yml b/core/modules/system/tests/modules/entity_test_update/entity_test_update.info.yml index 104054a73..330028a7a 100644 --- a/core/modules/system/tests/modules/entity_test_update/entity_test_update.info.yml +++ b/core/modules/system/tests/modules/entity_test_update/entity_test_update.info.yml @@ -2,11 +2,5 @@ name: 'Entity Update Test' type: module description: 'Provides an entity type for testing definition and schema updates.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/entity_test_update/entity_test_update.install b/core/modules/system/tests/modules/entity_test_update/entity_test_update.install index 142330850..7e709f159 100644 --- a/core/modules/system/tests/modules/entity_test_update/entity_test_update.install +++ b/core/modules/system/tests/modules/entity_test_update/entity_test_update.install @@ -5,6 +5,5 @@ * Install, update and uninstall functions for the Entity Test Update module. */ -use Drupal\system\Tests\Update\DbUpdatesTrait; - -DbUpdatesTrait::includeUpdates('entity_test_update', 'entity_rev_pub_updates'); +$index = \Drupal::state()->get('entity_test_update.db_updates.entity_rev_pub_updates'); +module_load_include('inc', 'entity_test_update', 'update/entity_rev_pub_updates_' . $index); diff --git a/core/modules/system/tests/modules/entity_test_update/entity_test_update.services.yml b/core/modules/system/tests/modules/entity_test_update/entity_test_update.services.yml new file mode 100644 index 000000000..45d1606da --- /dev/null +++ b/core/modules/system/tests/modules/entity_test_update/entity_test_update.services.yml @@ -0,0 +1,6 @@ +services: + entity_test_update.entity_schema_listener: + class: Drupal\entity_test_update\EventSubscriber\EntitySchemaSubscriber + arguments: ['@entity.definition_update_manager', '@state'] + tags: + - { name: 'event_subscriber' } diff --git a/core/modules/system/tests/modules/entity_test_update/src/Entity/EntityTestUpdate.php b/core/modules/system/tests/modules/entity_test_update/src/Entity/EntityTestUpdate.php index 0a6c0cf44..87b7df195 100644 --- a/core/modules/system/tests/modules/entity_test_update/src/Entity/EntityTestUpdate.php +++ b/core/modules/system/tests/modules/entity_test_update/src/Entity/EntityTestUpdate.php @@ -21,6 +21,7 @@ * label = @Translation("Test entity update"), * handlers = { * "storage_schema" = "Drupal\entity_test_update\EntityTestUpdateStorageSchema", + * "storage" = "Drupal\entity_test_update\EntityTestUpdateStorage", * }, * base_table = "entity_test_update", * persistent_cache = FALSE, @@ -46,17 +47,6 @@ public static function preCreate(EntityStorageInterface $storage, array &$values } } - /** - * {@inheritdoc} - */ - public function preSave(EntityStorageInterface $storage) { - // Simulate an error during the 'restore' process of a test entity. - if (\Drupal::state()->get('entity_test_update.throw_exception', FALSE)) { - throw new \Exception('Peekaboo!'); - } - parent::preSave($storage); - } - /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/entity_test_update/src/EntityTestUpdateStorage.php b/core/modules/system/tests/modules/entity_test_update/src/EntityTestUpdateStorage.php new file mode 100644 index 000000000..6bcf2e1ea --- /dev/null +++ b/core/modules/system/tests/modules/entity_test_update/src/EntityTestUpdateStorage.php @@ -0,0 +1,26 @@ +get('entity_test_update.throw_exception', FALSE)) { + throw new \Exception('Peekaboo!'); + } + parent::saveToDedicatedTables($entity, $update, $names); + } + +} diff --git a/core/modules/system/tests/modules/entity_test_update/src/EventSubscriber/EntitySchemaSubscriber.php b/core/modules/system/tests/modules/entity_test_update/src/EventSubscriber/EntitySchemaSubscriber.php new file mode 100644 index 000000000..a53aff8a8 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test_update/src/EventSubscriber/EntitySchemaSubscriber.php @@ -0,0 +1,93 @@ +entityDefinitionUpdateManager = $entityDefinitionUpdateManager; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return static::getEntityTypeEvents(); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeCreate(EntityTypeInterface $entity_type) { + // Only add the new base field when a test needs it. + if (!$this->state->get('entity_test_update.install_new_base_field_during_create', FALSE)) { + return; + } + + // Add a new base field when the entity type is created. + $definitions = $this->state->get('entity_test_update.additional_base_field_definitions', []); + $definitions['new_base_field'] = BaseFieldDefinition::create('string') + ->setName('new_base_field') + ->setLabel(new TranslatableMarkup('A new base field')); + $this->state->set('entity_test_update.additional_base_field_definitions', $definitions); + + $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test_update', $definitions['new_base_field']); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + // Only add the new base field when a test needs it. + if (!$this->state->get('entity_test_update.install_new_base_field_during_update', FALSE)) { + return; + } + + // Add a new base field when the entity type is updated. + $definitions = $this->state->get('entity_test_update.additional_base_field_definitions', []); + $definitions['new_base_field'] = BaseFieldDefinition::create('string') + ->setName('new_base_field') + ->setLabel(new TranslatableMarkup('A new base field')); + $this->state->set('entity_test_update.additional_base_field_definitions', $definitions); + + $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test_update', $definitions['new_base_field']); + } + +} diff --git a/core/modules/system/tests/modules/error_service_test/error_service_test.info.yml b/core/modules/system/tests/modules/error_service_test/error_service_test.info.yml index 87502c2de..6a5f99c40 100644 --- a/core/modules/system/tests/modules/error_service_test/error_service_test.info.yml +++ b/core/modules/system/tests/modules/error_service_test/error_service_test.info.yml @@ -2,11 +2,5 @@ name: 'Error service test' type: module description: 'Support module for causing bedlam in container rebuilds.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/error_test/error_test.info.yml b/core/modules/system/tests/modules/error_test/error_test.info.yml index 40e59c04d..f91c7b357 100644 --- a/core/modules/system/tests/modules/error_test/error_test.info.yml +++ b/core/modules/system/tests/modules/error_test/error_test.info.yml @@ -2,11 +2,5 @@ name: 'Error test' type: module description: 'Support module for error and exception testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/experimental_module_dependency_test/experimental_module_dependency_test.info.yml b/core/modules/system/tests/modules/experimental_module_dependency_test/experimental_module_dependency_test.info.yml index b045e0628..f84f616c4 100644 --- a/core/modules/system/tests/modules/experimental_module_dependency_test/experimental_module_dependency_test.info.yml +++ b/core/modules/system/tests/modules/experimental_module_dependency_test/experimental_module_dependency_test.info.yml @@ -4,11 +4,5 @@ description: 'Module with a dependency in the experimental package.' package: Testing dependencies: - drupal:experimental_module_test -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.info.yml b/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.info.yml index 9bb8a2c18..8ac724b99 100644 --- a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.info.yml +++ b/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.info.yml @@ -2,11 +2,5 @@ name: 'Experimental Requirements Test' type: module description: 'Module in the experimental package to test hook_requirements() with an experimental module.' package: Core (Experimental) -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.info.yml b/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.info.yml index 183ff3709..6cf19cd06 100644 --- a/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.info.yml +++ b/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.info.yml @@ -2,11 +2,5 @@ name: 'Experimental Test' type: module description: 'Module in the experimental package to test experimental functionality.' package: Core (Experimental) -# version: 8.y.x-unstable -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: 8.y.x-unstable +core: 8.x diff --git a/core/modules/system/tests/modules/form_test/form_test.info.yml b/core/modules/system/tests/modules/form_test/form_test.info.yml index 413d7e360..fdf1076ef 100644 --- a/core/modules/system/tests/modules/form_test/form_test.info.yml +++ b/core/modules/system/tests/modules/form_test/form_test.info.yml @@ -2,14 +2,8 @@ name: 'FormAPI Test' type: module description: 'Support module for Form API tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - file - filter - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestInputForgeryForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestInputForgeryForm.php index 67d71b361..b581c9e48 100644 --- a/core/modules/system/tests/modules/form_test/src/Form/FormTestInputForgeryForm.php +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestInputForgeryForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Security\TrustedCallbackInterface; use Symfony\Component\HttpFoundation\JsonResponse; /** @@ -11,7 +12,7 @@ * * @internal */ -class FormTestInputForgeryForm extends FormBase { +class FormTestInputForgeryForm extends FormBase implements TrustedCallbackInterface { /** * {@inheritdoc} @@ -68,4 +69,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) { return new JsonResponse($form_state->getValues()); } + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['postRender']; + } + } diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestSelectForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestSelectForm.php index d1b14d8d0..becdef161 100644 --- a/core/modules/system/tests/modules/form_test/src/Form/FormTestSelectForm.php +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestSelectForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; use Symfony\Component\HttpFoundation\JsonResponse; /** @@ -125,10 +126,91 @@ public function buildForm(array $form, FormStateInterface $form_state) { ], ]; + // Add a select that should have its options left alone. + $form['unsorted'] = [ + '#type' => 'select', + '#options' => $this->makeSortableOptions('uso'), + ]; + + // Add a select to test sorting at the top level, and with some of the + // option groups sorted, some left alone, and at least one with #sort_start + // set to a non-default value. + $sortable_options = $this->makeSortableOptions('sso'); + $sortable_options['sso_zzgroup']['#sort_options'] = TRUE; + $sortable_options['sso_xxgroup']['#sort_options'] = TRUE; + $sortable_options['sso_xxgroup']['#sort_start'] = 1; + // Do not use a sort start on this one. + $form['sorted'] = [ + '#type' => 'select', + '#options' => $sortable_options, + '#sort_options' => TRUE, + ]; + + // Add a select to test sorting with a -NONE- option included, + // and #sort_start set. + $sortable_none_options = $this->makeSortableOptions('sno'); + $sortable_none_options['sno_zzgroup']['#sort_options'] = TRUE; + $form['sorted_none'] = [ + '#type' => 'select', + '#options' => $sortable_none_options, + '#sort_options' => TRUE, + '#sort_start' => 4, + '#empty_value' => 'sno_empty', + ]; + + // Add a select to test sorting with a -NONE- option included, + // and #sort_start not set. + $sortable_none_nostart_options = $this->makeSortableOptions('snn'); + $sortable_none_nostart_options['snn_zzgroup']['#sort_options'] = TRUE; + $form['sorted_none_nostart'] = [ + '#type' => 'select', + '#options' => $sortable_none_nostart_options, + '#sort_options' => TRUE, + '#empty_value' => 'snn_empty', + ]; + $form['submit'] = ['#type' => 'submit', '#value' => 'Submit']; return $form; } + /** + * Makes and returns a set of options to test sorting on. + * + * @param string $prefix + * Prefix for the keys of the options. + * + * @return array + * Options array, including option groups, for testing. + */ + protected function makeSortableOptions($prefix) { + return [ + // Don't use $this->t() here, to avoid adding strings to + // localize.drupal.org. Do use TranslatableMarkup in places, to test + // that labels are cast to strings before sorting. + $prefix . '_first_element' => new TranslatableMarkup('first element'), + $prefix . '_second' => new TranslatableMarkup('second element'), + $prefix . '_zzgroup' => [ + $prefix . '_gc' => new TranslatableMarkup('group c'), + $prefix . '_ga' => new TranslatableMarkup('group a'), + $prefix . '_gb' => 'group b', + ], + $prefix . '_yygroup' => [ + $prefix . '_ge' => new TranslatableMarkup('group e'), + $prefix . '_gd' => new TranslatableMarkup('group d'), + $prefix . '_gf' => new TranslatableMarkup('group f'), + ], + $prefix . '_xxgroup' => [ + $prefix . '_gz' => new TranslatableMarkup('group z'), + $prefix . '_gi' => new TranslatableMarkup('group i'), + $prefix . '_gh' => new TranslatableMarkup('group h'), + ], + $prefix . '_d' => 'd', + $prefix . '_c' => new TranslatableMarkup('main c'), + $prefix . '_b' => new TranslatableMarkup('main b'), + $prefix . '_a' => 'a', + ]; + } + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/modules/hold_test/hold_test.info.yml b/core/modules/system/tests/modules/hold_test/hold_test.info.yml index a7fa3b0ad..f76775142 100644 --- a/core/modules/system/tests/modules/hold_test/hold_test.info.yml +++ b/core/modules/system/tests/modules/hold_test/hold_test.info.yml @@ -2,11 +2,5 @@ name: Hold test type: module description: 'Support testing with request/response hold.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.info.yml b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.info.yml index 8b04271ed..5f1375f28 100644 --- a/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.info.yml +++ b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.info.yml @@ -2,11 +2,5 @@ name: 'HttpKernel test' type: module description: 'Support module for httpkernel tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/image_test/image_test.info.yml b/core/modules/system/tests/modules/image_test/image_test.info.yml index b10b7f09a..2e54287d5 100644 --- a/core/modules/system/tests/modules/image_test/image_test.info.yml +++ b/core/modules/system/tests/modules/image_test/image_test.info.yml @@ -2,11 +2,5 @@ name: 'Image test' type: module description: 'Support module for image toolkit tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/invalid_module_name_over_the_maximum_allowed_character_length/invalid_module_name_over_the_maximum_allowed_character_length.info.yml b/core/modules/system/tests/modules/invalid_module_name_over_the_maximum_allowed_character_length/invalid_module_name_over_the_maximum_allowed_character_length.info.yml index 30625706c..db5cdcc57 100644 --- a/core/modules/system/tests/modules/invalid_module_name_over_the_maximum_allowed_character_length/invalid_module_name_over_the_maximum_allowed_character_length.info.yml +++ b/core/modules/system/tests/modules/invalid_module_name_over_the_maximum_allowed_character_length/invalid_module_name_over_the_maximum_allowed_character_length.info.yml @@ -2,11 +2,5 @@ name: 'Module name length test' type: module description: 'Test module with a name over the maximum allowed characters.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_ajax_test/js_ajax_test.info.yml b/core/modules/system/tests/modules/js_ajax_test/js_ajax_test.info.yml index 2e55b3f05..a5ec155ce 100644 --- a/core/modules/system/tests/modules/js_ajax_test/js_ajax_test.info.yml +++ b/core/modules/system/tests/modules/js_ajax_test/js_ajax_test.info.yml @@ -2,11 +2,5 @@ name: 'JS Ajax test' description: 'Provides custom ajax commands used for tests' type: module package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.es6.js b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.es6.js new file mode 100644 index 000000000..90019bd65 --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.es6.js @@ -0,0 +1,21 @@ +/** + * @file + * Testing tools for deprecating JavaScript functions and class properties. + */ +(function() { + if (typeof console !== 'undefined' && console.warn) { + const originalWarnFunction = console.warn; + console.warn = warning => { + const warnings = JSON.parse( + sessionStorage.getItem('js_deprecation_log_test.warnings') || + JSON.stringify([]), + ); + warnings.push(warning); + sessionStorage.setItem( + 'js_deprecation_log_test.warnings', + JSON.stringify(warnings), + ); + originalWarnFunction(warning); + }; + } +})(); diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.js b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.js new file mode 100644 index 000000000..e1724ebe4 --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.js @@ -0,0 +1,18 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function () { + if (typeof console !== 'undefined' && console.warn) { + var originalWarnFunction = console.warn; + console.warn = function (warning) { + var warnings = JSON.parse(sessionStorage.getItem('js_deprecation_log_test.warnings') || JSON.stringify([])); + warnings.push(warning); + sessionStorage.setItem('js_deprecation_log_test.warnings', JSON.stringify(warnings)); + originalWarnFunction(warning); + }; + } +})(); \ No newline at end of file diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml new file mode 100644 index 000000000..b2589c6e2 --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml @@ -0,0 +1,6 @@ +name: 'JS Deprecation log test' +description: 'Stores all JS deprecation calls to allow JS tests to determine if they have been called.' +type: module +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml new file mode 100644 index 000000000..1744815c9 --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml @@ -0,0 +1,6 @@ +deprecation_log: + version: VERSION + js: + js/js_deprecation_log.js: { weight: -100 } + dependencies: + - core/drupal diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module new file mode 100644 index 000000000..a86a2f21f --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module @@ -0,0 +1,21 @@ + { + deprecationError({ + message: 'This function is deprecated for testing purposes.', + }); + }; + const objectWithDeprecatedProperty = deprecatedProperty({ + target: { deprecatedProperty: 'Kitten' }, + deprecatedProperty: 'deprecatedProperty', + message: 'This property is deprecated for testing purposes.', + }); + + behaviors.testDeprecations = { + attach: () => { + deprecatedFunction(); + const deprecatedProperty = + objectWithDeprecatedProperty.deprecatedProperty; + }, + }; +})(Drupal); diff --git a/core/modules/system/tests/modules/js_deprecation_test/js/js_deprecation_test.js b/core/modules/system/tests/modules/js_deprecation_test/js/js_deprecation_test.js new file mode 100644 index 000000000..e1c617869 --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_test/js/js_deprecation_test.js @@ -0,0 +1,30 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function (_ref) { + var deprecationError = _ref.deprecationError, + deprecatedProperty = _ref.deprecatedProperty, + behaviors = _ref.behaviors; + + var deprecatedFunction = function deprecatedFunction() { + deprecationError({ + message: 'This function is deprecated for testing purposes.' + }); + }; + var objectWithDeprecatedProperty = deprecatedProperty({ + target: { deprecatedProperty: 'Kitten' }, + deprecatedProperty: 'deprecatedProperty', + message: 'This property is deprecated for testing purposes.' + }); + + behaviors.testDeprecations = { + attach: function attach() { + deprecatedFunction(); + var deprecatedProperty = objectWithDeprecatedProperty.deprecatedProperty; + } + }; +})(Drupal); \ No newline at end of file diff --git a/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.info.yml b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.info.yml new file mode 100644 index 000000000..0a1b6153b --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.info.yml @@ -0,0 +1,6 @@ +name: 'JS Deprecation test' +description: 'Provides deprecated code that can be used for tests' +type: module +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.libraries.yml b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.libraries.yml new file mode 100644 index 000000000..0ae3e7ce3 --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.libraries.yml @@ -0,0 +1,6 @@ +deprecation_test: + version: VERSION + js: + js/js_deprecation_test.js: {} + dependencies: + - core/drupal diff --git a/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.module b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.module new file mode 100644 index 000000000..523c28687 --- /dev/null +++ b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.module @@ -0,0 +1,13 @@ + ['library' => ['js_deprecation_test/deprecation_test']], + ]; + } + +} diff --git a/core/modules/system/tests/modules/js_message_test/js/js_message_test.es6.js b/core/modules/system/tests/modules/js_message_test/js/js_message_test.es6.js index 08d250a33..e9efaa6ef 100644 --- a/core/modules/system/tests/modules/js_message_test/js/js_message_test.es6.js +++ b/core/modules/system/tests/modules/js_message_test/js/js_message_test.es6.js @@ -18,6 +18,8 @@ }, multiple: [], }; + // Ensure clear() can be called on a newly created message object. + messageObjects.default.zone.clear(); testMessages.selectors.filter(Boolean).forEach(selector => { messageObjects[selector] = { diff --git a/core/modules/system/tests/modules/js_message_test/js/js_message_test.js b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js index 33c497999..c9b18fd96 100644 --- a/core/modules/system/tests/modules/js_message_test/js/js_message_test.js +++ b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js @@ -22,6 +22,8 @@ multiple: [] }; + messageObjects.default.zone.clear(); + testMessages.selectors.filter(Boolean).forEach(function (selector) { messageObjects[selector] = { zone: new Drupal.Message(document.querySelector(selector)), diff --git a/core/modules/system/tests/modules/js_message_test/js_message_test.info.yml b/core/modules/system/tests/modules/js_message_test/js_message_test.info.yml index 075d3f189..e8bc73b06 100644 --- a/core/modules/system/tests/modules/js_message_test/js_message_test.info.yml +++ b/core/modules/system/tests/modules/js_message_test/js_message_test.info.yml @@ -2,11 +2,5 @@ name: 'JS Message test module' type: module description: 'Module for the JSMessageTest test.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php index 57d38284f..c83aae77c 100644 --- a/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php +++ b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php @@ -2,13 +2,33 @@ namespace Drupal\js_message_test\Controller; -use Drupal\system\Tests\JsMessageTestCases; - /** * Test Controller to show message links. */ class JSMessageTestController { + /** + * Gets the test types. + * + * @return string[] + * The test types. + */ + public static function getTypes() { + return ['status', 'error', 'warning']; + } + + /** + * Gets the test messages selectors. + * + * @return string[] + * The test test messages selectors. + * + * @see core/modules/system/tests/themes/test_messages/templates/status-messages.html.twig + */ + public static function getMessagesSelectors() { + return ['', '[data-drupal-messages-other]']; + } + /** * Displays links to show messages via Javascript. * @@ -17,7 +37,7 @@ class JSMessageTestController { */ public function messageLinks() { $buttons = []; - foreach (JsMessageTestCases::getMessagesSelectors() as $messagesSelector) { + foreach (static::getMessagesSelectors() as $messagesSelector) { $buttons[$messagesSelector] = [ '#type' => 'details', '#open' => TRUE, @@ -26,7 +46,7 @@ public function messageLinks() { 'data-drupal-messages-area' => $messagesSelector, ], ]; - foreach (JsMessageTestCases::getTypes() as $type) { + foreach (static::getTypes() as $type) { $buttons[$messagesSelector]["add-$type"] = [ '#type' => 'html_tag', '#tag' => 'button', @@ -52,7 +72,7 @@ public function messageLinks() { } } // Add alternative message area. - $buttons[JsMessageTestCases::getMessagesSelectors()[1]]['messages-other-area'] = [ + $buttons[static::getMessagesSelectors()[1]]['messages-other-area'] = [ '#type' => 'html_tag', '#tag' => 'div', '#attributes' => [ @@ -128,8 +148,8 @@ public function messageLinks() { ], 'drupalSettings' => [ 'testMessages' => [ - 'selectors' => JsMessageTestCases::getMessagesSelectors(), - 'types' => JsMessageTestCases::getTypes(), + 'selectors' => static::getMessagesSelectors(), + 'types' => static::getTypes(), ], ], ], diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.no_element_after_wait.es6.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.no_element_after_wait.es6.js new file mode 100644 index 000000000..984125d8a --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.no_element_after_wait.es6.js @@ -0,0 +1,30 @@ +/** + * @file + * Testing behavior for JSWebAssertTest. + */ + +(($, Drupal) => { + /** + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Makes changes in the DOM to be able to test the completion of AJAX in assertWaitOnAjaxRequest. + */ + Drupal.behaviors.js_webassert_test_wait_for_ajax_request = { + attach() { + $('#edit-test-assert-no-element-after-wait-pass').on('click', e => { + e.preventDefault(); + setTimeout(() => { + $('#edit-test-assert-no-element-after-wait-pass').remove(); + }, 500); + }); + + $('#edit-test-assert-no-element-after-wait-fail').on('click', e => { + e.preventDefault(); + setTimeout(() => { + $('#edit-test-assert-no-element-after-wait-fail').remove(); + }, 2000); + }); + }, + }; +})(jQuery, Drupal); diff --git a/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.no_element_after_wait.js b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.no_element_after_wait.js new file mode 100644 index 000000000..c529bb3e1 --- /dev/null +++ b/core/modules/system/tests/modules/js_webassert_test/js/js_webassert_test.no_element_after_wait.js @@ -0,0 +1,26 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function ($, Drupal) { + Drupal.behaviors.js_webassert_test_wait_for_ajax_request = { + attach: function attach() { + $('#edit-test-assert-no-element-after-wait-pass').on('click', function (e) { + e.preventDefault(); + setTimeout(function () { + $('#edit-test-assert-no-element-after-wait-pass').remove(); + }, 500); + }); + + $('#edit-test-assert-no-element-after-wait-fail').on('click', function (e) { + e.preventDefault(); + setTimeout(function () { + $('#edit-test-assert-no-element-after-wait-fail').remove(); + }, 2000); + }); + } + }; +})(jQuery, Drupal); \ No newline at end of file diff --git a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.info.yml b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.info.yml index 5972858cf..787ef146f 100644 --- a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.info.yml +++ b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.info.yml @@ -2,11 +2,5 @@ name: 'JS WebAssert test module' type: module description: 'Module for the JSWebAssert test.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml index 3a17b1dcf..898e63750 100644 --- a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml +++ b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.libraries.yml @@ -12,3 +12,11 @@ wait_for_element: dependencies: - core/jquery - core/drupal + +no_element_after_wait: + version: VERSION + js: + js/js_webassert_test.no_element_after_wait.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.routing.yml b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.routing.yml index 2be8984a9..5d2678949 100644 --- a/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.routing.yml +++ b/core/modules/system/tests/modules/js_webassert_test/js_webassert_test.routing.yml @@ -1,7 +1,7 @@ js_webassert_test.js_webassert_test_form: path: '/js_webassert_test_form' defaults: - _form: 'Drupal\js_webassert_test\Form\JsWebAssertTestForm' + _form: '\Drupal\js_webassert_test\Form\JsWebAssertTestForm' _title: 'JsWebAssertForm' requirements: _access: 'TRUE' diff --git a/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php b/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php index 3197aa5be..c8815a11f 100644 --- a/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php +++ b/core/modules/system/tests/modules/js_webassert_test/src/Form/JsWebAssertTestForm.php @@ -113,6 +113,22 @@ public function buildForm(array $form, FormStateInterface $form_state) { 'wrapper' => 'js_webassert_test_form_wrapper', ], ]; + + // Button to test the assertNoElementAfterWait() assertion, will pass. + $form['test_assert_no_element_after_wait_pass'] = [ + '#type' => 'submit', + '#value' => $this->t('Test assertNoElementAfterWait: pass'), + '#button_type' => 'primary', + '#attached' => ['library' => 'js_webassert_test/no_element_after_wait'], + ]; + + // Button to test the assertNoElementAfterWait() assertion, will fail. + $form['test_assert_no_element_after_wait_fail'] = [ + '#type' => 'submit', + '#value' => $this->t('Test assertNoElementAfterWait: fail'), + '#button_type' => 'primary', + ]; + return $form; } diff --git a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml index 8479754ed..1da31bcc5 100644 --- a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml +++ b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml @@ -1,15 +1,9 @@ name: 'KeyValue tests' type: module description: 'A support module to test key value storage.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION +version: VERSION hidden: true dependencies: - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/layout_test/layout_test.info.yml b/core/modules/system/tests/modules/layout_test/layout_test.info.yml index bdd15b727..bfed2a5bb 100644 --- a/core/modules/system/tests/modules/layout_test/layout_test.info.yml +++ b/core/modules/system/tests/modules/layout_test/layout_test.info.yml @@ -2,11 +2,5 @@ name: 'Layout test' type: module description: 'Support module for testing layouts.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/lazy_route_provider_install_test/lazy_route_provider_install_test.info.yml b/core/modules/system/tests/modules/lazy_route_provider_install_test/lazy_route_provider_install_test.info.yml index a42ddbacc..e7e55e503 100644 --- a/core/modules/system/tests/modules/lazy_route_provider_install_test/lazy_route_provider_install_test.info.yml +++ b/core/modules/system/tests/modules/lazy_route_provider_install_test/lazy_route_provider_install_test.info.yml @@ -2,11 +2,5 @@ name: 'Lazy route provider install test' description: 'Helps test a bug triggered by the url_generator maintaining a stale route provider.' type: module package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/link_generation_test/link_generation_test.info.yml b/core/modules/system/tests/modules/link_generation_test/link_generation_test.info.yml index 7d796191d..2f200a423 100644 --- a/core/modules/system/tests/modules/link_generation_test/link_generation_test.info.yml +++ b/core/modules/system/tests/modules/link_generation_test/link_generation_test.info.yml @@ -2,11 +2,5 @@ name: 'Link generation test support' type: module description: 'Test hooks fired in link generation.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/mail_cancel_test/mail_cancel_test.info.yml b/core/modules/system/tests/modules/mail_cancel_test/mail_cancel_test.info.yml new file mode 100644 index 000000000..5d918b93b --- /dev/null +++ b/core/modules/system/tests/modules/mail_cancel_test/mail_cancel_test.info.yml @@ -0,0 +1,6 @@ +name: 'Cancel mail test support' +description: 'Test that the mail can be cancelled.' +type: module +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/mail_cancel_test/mail_cancel_test.module b/core/modules/system/tests/modules/mail_cancel_test/mail_cancel_test.module new file mode 100644 index 000000000..3e147b293 --- /dev/null +++ b/core/modules/system/tests/modules/mail_cancel_test/mail_cancel_test.module @@ -0,0 +1,19 @@ +=8.x) - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/module_test/module_test.info.yml b/core/modules/system/tests/modules/module_test/module_test.info.yml index 1d1b1606f..5c63da21a 100644 --- a/core/modules/system/tests/modules/module_test/module_test.info.yml +++ b/core/modules/system/tests/modules/module_test/module_test.info.yml @@ -2,11 +2,5 @@ name: 'Module test' type: module description: 'Support module for module system testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.info.yml b/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.info.yml new file mode 100644 index 000000000..eab08261b --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.info.yml @@ -0,0 +1,8 @@ +name: 'New Dependency test' +type: module +description: 'Support module for update testing.' +package: Testing +version: VERSION +core: 8.x +dependencies: + - new_dependency_test_with_service diff --git a/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.install b/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.install new file mode 100644 index 000000000..2ab3e6b49 --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.install @@ -0,0 +1,51 @@ +set( + 'new_dependency_test_update_8001.decorated_service', + \Drupal::service('new_dependency_test.another_service')->isDecorated() + ); + + \Drupal::state()->set( + 'new_dependency_test_update_8001.decorated_service_custom_inner', + \Drupal::service('new_dependency_test.another_service_two')->isDecorated() + ); + + $map = []; + foreach ($services as $id) { + $map[$id] = \Drupal::hasService($id); + } + \Drupal::state()->set('new_dependency_test_update_8001.has_before_install', $map); + + // During the update hooks the container is cleaned up to contain only + // services that have their dependencies met. Core services are available. + \Drupal::getContainer()->get('module_installer')->install(['new_dependency_test_with_service']); + + // Gather the state of the services after installing the + // new_dependency_test_with_service module. + $map = []; + foreach ($services as $id) { + $map[$id] = \Drupal::hasService($id); + } + \Drupal::state()->set('new_dependency_test_update_8001.has_after_install', $map); +} diff --git a/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.services.yml b/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.services.yml new file mode 100644 index 000000000..a3283daea --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test/new_dependency_test.services.yml @@ -0,0 +1,36 @@ +services: + new_dependency_test.alias2: + alias: new_dependency_test.alias_dependency + new_dependency_test.alias_dependency2: + class: Drupal\new_dependency_test\ServiceWithDependency + arguments: ['@new_dependency_test.alias2'] + new_dependency_test.alias_dependency: + class: Drupal\new_dependency_test\ServiceWithDependency + arguments: ['@new_dependency_test.alias'] + new_dependency_test.recursion: + class: Drupal\new_dependency_test\ServiceWithDependency + arguments: ['@new_dependency_test.hard_dependency'] + new_dependency_test.alias: + alias: new_dependency_test.dependent + new_dependency_test.dependent: + class: Drupal\new_dependency_test\InjectedService + arguments: ['@new_dependency_test_with_service.service'] + new_dependency_test.hard_dependency: + class: Drupal\new_dependency_test\ServiceWithDependency + arguments: ['@new_dependency_test.dependent'] + new_dependency_test.optional_dependency: + class: Drupal\new_dependency_test\ServiceWithDependency + arguments: ['@?new_dependency_test.dependent'] + new_dependency_test.another_service: + class: Drupal\new_dependency_test\Service + new_dependency_test.another_service.decorated: + class: Drupal\new_dependency_test\Service + decorates: new_dependency_test.another_service + arguments: ['@new_dependency_test.another_service.decorated.inner'] + new_dependency_test.another_service_two: + class: Drupal\new_dependency_test\Service + new_dependency_test.another_service_two.decorated: + class: Drupal\new_dependency_test\Service + decorates: new_dependency_test.another_service_two + decoration_inner_name: new_dependency_test.foo + arguments: ['@new_dependency_test.foo'] diff --git a/core/modules/system/tests/modules/new_dependency_test/src/InjectedService.php b/core/modules/system/tests/modules/new_dependency_test/src/InjectedService.php new file mode 100644 index 000000000..7f0b68bfa --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test/src/InjectedService.php @@ -0,0 +1,39 @@ +service = $service; + } + + /** + * Get the simple greeting from the service. + * + * @return string + * The greeting. + */ + public function greet() { + return $this->service->greet(); + } + +} diff --git a/core/modules/system/tests/modules/new_dependency_test/src/Service.php b/core/modules/system/tests/modules/new_dependency_test/src/Service.php new file mode 100644 index 000000000..413226ea4 --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test/src/Service.php @@ -0,0 +1,39 @@ +inner = $inner; + } + + /** + * Determines if the service is decorated. + * + * @return bool + * TRUE if the services is decorated, FALSE if not. + */ + public function isDecorated() { + return isset($this->inner); + } + +} diff --git a/core/modules/system/tests/modules/new_dependency_test/src/ServiceWithDependency.php b/core/modules/system/tests/modules/new_dependency_test/src/ServiceWithDependency.php new file mode 100644 index 000000000..c5c57d500 --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test/src/ServiceWithDependency.php @@ -0,0 +1,42 @@ +service = $service; + } + + /** + * Gets a greeting from the injected service and adds to it. + * + * @return string + * The greeting. + */ + public function greet() { + if (isset($this->service)) { + return $this->service->greet() . ' World'; + } + return 'Sorry, no service.'; + } + +} diff --git a/core/modules/system/tests/modules/new_dependency_test_with_service/new_dependency_test_with_service.info.yml b/core/modules/system/tests/modules/new_dependency_test_with_service/new_dependency_test_with_service.info.yml new file mode 100644 index 000000000..5091a7f79 --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test_with_service/new_dependency_test_with_service.info.yml @@ -0,0 +1,6 @@ +name: 'New Dependency test with service' +type: module +description: 'Support module for update testing.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/new_dependency_test_with_service/new_dependency_test_with_service.services.yml b/core/modules/system/tests/modules/new_dependency_test_with_service/new_dependency_test_with_service.services.yml new file mode 100644 index 000000000..26ba284e2 --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test_with_service/new_dependency_test_with_service.services.yml @@ -0,0 +1,3 @@ +services: + new_dependency_test_with_service.service: + class: Drupal\new_dependency_test_with_service\NewService diff --git a/core/modules/system/tests/modules/new_dependency_test_with_service/src/NewService.php b/core/modules/system/tests/modules/new_dependency_test_with_service/src/NewService.php new file mode 100644 index 000000000..6137fc2ec --- /dev/null +++ b/core/modules/system/tests/modules/new_dependency_test_with_service/src/NewService.php @@ -0,0 +1,20 @@ +getPager($element); + if ($pager->getTotalPages() <= 1) { + return; + } + + foreach ($variables['items']['pages'] as $index => &$pager_item) { + $pager_item['attributes']['pager-test'] = 'yes'; + } + unset($pager_item); + + foreach (['first', 'previous', 'next', 'last'] as $special_pager_item) { + if (isset($variables['items'][$special_pager_item])) { + $variables['items'][$special_pager_item]['attributes']['pager-test'] = $special_pager_item; + } + } +} diff --git a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php index 4e01479f3..2cdba6601 100644 --- a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php +++ b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php @@ -5,11 +5,38 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Database\Database; use Drupal\Core\Database\Query\PagerSelectExtender; +use Drupal\Core\Pager\PagerParametersInterface; +use Drupal\Core\Security\TrustedCallbackInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Controller routine for testing the pager. */ -class PagerTestController extends ControllerBase { +class PagerTestController extends ControllerBase implements TrustedCallbackInterface { + + /** + * The pager request service. + * + * @var \Drupal\Core\Pager\PagerParametersInterface + */ + protected $pagerParams; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('pager.parameters')); + } + + /** + * Construct a new PagerTestController object. + * + * @param \Drupal\Core\Pager\PagerParametersInterface $pager_params + * The pager parameters. + */ + public function __construct(PagerParametersInterface $pager_params) { + $this->pagerParams = $pager_params; + } /** * Builds a render array for a pageable test table. @@ -58,7 +85,7 @@ public function queryParameters() { $build['pager_table_0'] = $this->buildTestTable(0, 5); // Counter of calls to the current pager. - $query_params = pager_get_query_parameters(); + $query_params = $this->pagerParams->getQueryParameters(); $pager_calls = isset($query_params['pager_calls']) ? ($query_params['pager_calls'] ? $query_params['pager_calls'] : 0) : 0; $build['l_pager_pager_0'] = ['#markup' => $this->t('Pager calls: @pager_calls', ['@pager_calls' => $pager_calls])]; @@ -124,4 +151,11 @@ public static function showPagerCacheContext(array $pager) { return $pager; } + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['showPagerCacheContext']; + } + } diff --git a/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.info.yml b/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.info.yml index fa04ff4a5..1953d9294 100644 --- a/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.info.yml +++ b/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.info.yml @@ -2,11 +2,5 @@ name: "ParamConverter test" type: module description: "Support module for paramconverter testing." package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/path_deprecated_test/path_deprecated_test.info.yml b/core/modules/system/tests/modules/path_deprecated_test/path_deprecated_test.info.yml new file mode 100644 index 000000000..15b9357e7 --- /dev/null +++ b/core/modules/system/tests/modules/path_deprecated_test/path_deprecated_test.info.yml @@ -0,0 +1,6 @@ +name: 'Path deprecated test' +type: module +description: 'Support module for testing deprecated functionality for path aliases.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/path_deprecated_test/path_deprecated_test.module b/core/modules/system/tests/modules/path_deprecated_test/path_deprecated_test.module new file mode 100644 index 000000000..30b9b91b7 --- /dev/null +++ b/core/modules/system/tests/modules/path_deprecated_test/path_deprecated_test.module @@ -0,0 +1,27 @@ +set('path_test.results', []); -} - -/** - * Implements hook_path_update(). - */ -function path_test_path_update($path) { - $results = \Drupal::state()->get('path_test.results') ?: []; - $results['hook_path_update'] = $path; - \Drupal::state()->set('path_test.results', $results); -} diff --git a/core/modules/system/tests/modules/phpunit_test/phpunit_test.info.yml b/core/modules/system/tests/modules/phpunit_test/phpunit_test.info.yml index 62a1d0efd..e0a4c6e8d 100644 --- a/core/modules/system/tests/modules/phpunit_test/phpunit_test.info.yml +++ b/core/modules/system/tests/modules/phpunit_test/phpunit_test.info.yml @@ -2,11 +2,5 @@ name: PHPUnit Test type: module description: 'Provides dummy classes for use by SimpleTest tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/plugin_test/plugin_test.info.yml b/core/modules/system/tests/modules/plugin_test/plugin_test.info.yml index 5983fd733..950a1d02d 100644 --- a/core/modules/system/tests/modules/plugin_test/plugin_test.info.yml +++ b/core/modules/system/tests/modules/plugin_test/plugin_test.info.yml @@ -2,11 +2,5 @@ name: 'Plugin Test Support' type: module description: 'Test that plugins can provide plugins and provide namespace discovery for plugin test implementations.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/plugin_test_extended/plugin_test_extended.info.yml b/core/modules/system/tests/modules/plugin_test_extended/plugin_test_extended.info.yml index f9ab3b6eb..f3d0a5ba0 100644 --- a/core/modules/system/tests/modules/plugin_test_extended/plugin_test_extended.info.yml +++ b/core/modules/system/tests/modules/plugin_test_extended/plugin_test_extended.info.yml @@ -2,11 +2,5 @@ name: 'Plugin Test Extended' type: module description: 'Test annotations can extend other annotations in a different namespace.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/render_array_non_html_subscriber_test/render_array_non_html_subscriber_test.info.yml b/core/modules/system/tests/modules/render_array_non_html_subscriber_test/render_array_non_html_subscriber_test.info.yml index 9dd21f014..9441db065 100644 --- a/core/modules/system/tests/modules/render_array_non_html_subscriber_test/render_array_non_html_subscriber_test.info.yml +++ b/core/modules/system/tests/modules/render_array_non_html_subscriber_test/render_array_non_html_subscriber_test.info.yml @@ -2,11 +2,5 @@ name: 'Array rendering for non-HTML requests subscriber test' type: module description: 'Support module for RenderArrayNonHtmlSubscriberTest.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/render_attached_test/render_attached_test.info.yml b/core/modules/system/tests/modules/render_attached_test/render_attached_test.info.yml index d681a1113..7662e3077 100644 --- a/core/modules/system/tests/modules/render_attached_test/render_attached_test.info.yml +++ b/core/modules/system/tests/modules/render_attached_test/render_attached_test.info.yml @@ -2,13 +2,7 @@ name: 'Rendering #attached test' type: module description: 'Support module for HtmlResponseAttachmentsTest.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:block - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml b/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml index 3de5b677b..532695bc0 100644 --- a/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml +++ b/core/modules/system/tests/modules/render_placeholder_message_test/render_placeholder_message_test.info.yml @@ -2,11 +2,5 @@ name: 'Placeholder setting a message test' type: module description: 'Support module for PlaceholderMessageTest.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php index ddb76e6dc..d59ed4edd 100644 --- a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php +++ b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php @@ -2,11 +2,12 @@ namespace Drupal\render_placeholder_message_test; +use Drupal\Core\Security\TrustedCallbackInterface; use Drupal\Core\Render\RenderContext; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; -class RenderPlaceholderMessageTestController implements ContainerAwareInterface { +class RenderPlaceholderMessageTestController implements ContainerAwareInterface, TrustedCallbackInterface { use ContainerAwareTrait; @@ -97,4 +98,11 @@ public static function setAndLogMessage($message) { return ['#markup' => '

Message: ' . $message . '

']; } + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['setAndLogMessage']; + } + } diff --git a/core/modules/system/tests/modules/requirements1_test/requirements1_test.info.yml b/core/modules/system/tests/modules/requirements1_test/requirements1_test.info.yml index 7316473cb..6288bf3e5 100644 --- a/core/modules/system/tests/modules/requirements1_test/requirements1_test.info.yml +++ b/core/modules/system/tests/modules/requirements1_test/requirements1_test.info.yml @@ -2,11 +2,5 @@ name: 'Requirements 1 Test' type: module description: 'Tests that a module is not installed when it fails hook_requirements(''install'').' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/requirements2_test/requirements2_test.info.yml b/core/modules/system/tests/modules/requirements2_test/requirements2_test.info.yml index 698176fe5..4c1a367db 100644 --- a/core/modules/system/tests/modules/requirements2_test/requirements2_test.info.yml +++ b/core/modules/system/tests/modules/requirements2_test/requirements2_test.info.yml @@ -5,11 +5,5 @@ dependencies: - drupal:requirements1_test - drupal:comment package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/router_test_directory/router_test.info.yml b/core/modules/system/tests/modules/router_test_directory/router_test.info.yml index 04771e235..27a082783 100644 --- a/core/modules/system/tests/modules/router_test_directory/router_test.info.yml +++ b/core/modules/system/tests/modules/router_test_directory/router_test.info.yml @@ -2,11 +2,5 @@ name: 'Router test' type: module description: 'Support module for routing testing. In a directory that does not match the module name to test that use case.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml b/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml index 4d5d241ba..2006738d6 100644 --- a/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml +++ b/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml @@ -165,7 +165,7 @@ router_test.25: router_test.26: path: '/router_test/test26' defaults: - _form: 'Drupal\system\Form\LoggingForm' + _form: '\Drupal\system\Form\LoggingForm' _title: 'Cron' requirements: _access: 'TRUE' diff --git a/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php b/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php index 0cbe9db9d..a1437999d 100644 --- a/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php +++ b/core/modules/system/tests/modules/router_test_directory/src/Access/TestAccessCheck.php @@ -13,8 +13,8 @@ class TestAccessCheck implements AccessInterface { /** * Checks access. * - * @return string - * A \Drupal\Core\Access\AccessInterface constant value. + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. */ public function access() { // No opinion, so other access checks should decide if access should be diff --git a/core/modules/system/tests/modules/service_provider_test/service_provider_test.info.yml b/core/modules/system/tests/modules/service_provider_test/service_provider_test.info.yml index 2e64dd092..ba84020c7 100644 --- a/core/modules/system/tests/modules/service_provider_test/service_provider_test.info.yml +++ b/core/modules/system/tests/modules/service_provider_test/service_provider_test.info.yml @@ -2,11 +2,5 @@ name: 'Service Provider test' type: module description: 'Support module for service provider testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/session_exists_cache_context_test/session_exists_cache_context_test.info.yml b/core/modules/system/tests/modules/session_exists_cache_context_test/session_exists_cache_context_test.info.yml index 6379f04ff..b376a57e5 100644 --- a/core/modules/system/tests/modules/session_exists_cache_context_test/session_exists_cache_context_test.info.yml +++ b/core/modules/system/tests/modules/session_exists_cache_context_test/session_exists_cache_context_test.info.yml @@ -2,11 +2,5 @@ name: 'session.exists cache context test' type: module description: 'Support module for session.exists cache context testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/session_test/session_test.info.yml b/core/modules/system/tests/modules/session_test/session_test.info.yml index dc87a8517..b9abfef0a 100644 --- a/core/modules/system/tests/modules/session_test/session_test.info.yml +++ b/core/modules/system/tests/modules/session_test/session_test.info.yml @@ -2,11 +2,5 @@ name: 'Session test' type: module description: 'Support module for session data testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/system_core_incompatible_semver_test/system_core_incompatible_semver_test.info.yml b/core/modules/system/tests/modules/system_core_incompatible_semver_test/system_core_incompatible_semver_test.info.yml new file mode 100644 index 000000000..152d2a66e --- /dev/null +++ b/core/modules/system/tests/modules/system_core_incompatible_semver_test/system_core_incompatible_semver_test.info.yml @@ -0,0 +1,6 @@ +name: 'System core incompatible semver test' +type: module +description: 'Support module for testing core incompatible semver.' +package: Testing +version: 1.0.0 +core_version_requirement: ^7 diff --git a/core/modules/system/tests/modules/system_core_semver_test/system_core_semver_test.info.yml b/core/modules/system/tests/modules/system_core_semver_test/system_core_semver_test.info.yml new file mode 100644 index 000000000..47eec87f4 --- /dev/null +++ b/core/modules/system/tests/modules/system_core_semver_test/system_core_semver_test.info.yml @@ -0,0 +1,6 @@ +name: 'System core ^8 version test' +type: module +description: 'Support module for testing core using semver.' +package: Testing +version: 1.0.0 +core_version_requirement: ^8 diff --git a/core/modules/system/tests/modules/system_dependencies_test/system_dependencies_test.info.yml b/core/modules/system/tests/modules/system_dependencies_test/system_dependencies_test.info.yml index d412389b0..fae988033 100644 --- a/core/modules/system/tests/modules/system_dependencies_test/system_dependencies_test.info.yml +++ b/core/modules/system/tests/modules/system_dependencies_test/system_dependencies_test.info.yml @@ -2,13 +2,7 @@ name: 'System dependency test' type: module description: 'Support module for testing system dependencies.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:_missing_dependency - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/system_incompatible_core_version_dependencies_test/system_incompatible_core_version_dependencies_test.info.yml b/core/modules/system/tests/modules/system_incompatible_core_version_dependencies_test/system_incompatible_core_version_dependencies_test.info.yml index 06495c278..449980e7d 100644 --- a/core/modules/system/tests/modules/system_incompatible_core_version_dependencies_test/system_incompatible_core_version_dependencies_test.info.yml +++ b/core/modules/system/tests/modules/system_incompatible_core_version_dependencies_test/system_incompatible_core_version_dependencies_test.info.yml @@ -2,13 +2,7 @@ name: 'System incompatible core version dependencies test' type: module description: 'Support module for testing system dependencies.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:system_incompatible_core_version_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/system_incompatible_core_version_test/system_incompatible_core_version_test.info.yml b/core/modules/system/tests/modules/system_incompatible_core_version_test/system_incompatible_core_version_test.info.yml index 7f38633ea..d99300b83 100644 --- a/core/modules/system/tests/modules/system_incompatible_core_version_test/system_incompatible_core_version_test.info.yml +++ b/core/modules/system/tests/modules/system_incompatible_core_version_test/system_incompatible_core_version_test.info.yml @@ -2,11 +2,5 @@ name: 'System incompatible core version test' type: module description: 'Support module for testing system dependencies.' package: Testing -# version: VERSION -# core: 5.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 5.x diff --git a/core/modules/system/tests/modules/system_incompatible_core_version_test_1x/system_incompatible_core_version_test_1x.info.yml b/core/modules/system/tests/modules/system_incompatible_core_version_test_1x/system_incompatible_core_version_test_1x.info.yml new file mode 100644 index 000000000..f79d71dff --- /dev/null +++ b/core/modules/system/tests/modules/system_incompatible_core_version_test_1x/system_incompatible_core_version_test_1x.info.yml @@ -0,0 +1,6 @@ +name: 'System incompatible core 1.x version test' +type: module +description: 'Support module for testing system core incompatibility.' +package: Testing +version: 1.0.0 +core: 1.x diff --git a/core/modules/system/tests/modules/system_incompatible_module_version_dependencies_test/system_incompatible_module_version_dependencies_test.info.yml b/core/modules/system/tests/modules/system_incompatible_module_version_dependencies_test/system_incompatible_module_version_dependencies_test.info.yml index 65e280bac..cafa61c9b 100644 --- a/core/modules/system/tests/modules/system_incompatible_module_version_dependencies_test/system_incompatible_module_version_dependencies_test.info.yml +++ b/core/modules/system/tests/modules/system_incompatible_module_version_dependencies_test/system_incompatible_module_version_dependencies_test.info.yml @@ -2,13 +2,7 @@ name: 'System incompatible module version dependencies test' type: module description: 'Support module for testing system dependencies.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - 'drupal:system_incompatible_module_version_test (>2.0)' - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/system_incompatible_module_version_test/system_incompatible_module_version_test.info.yml b/core/modules/system/tests/modules/system_incompatible_module_version_test/system_incompatible_module_version_test.info.yml index 4da5607c7..f0076b1e4 100644 --- a/core/modules/system/tests/modules/system_incompatible_module_version_test/system_incompatible_module_version_test.info.yml +++ b/core/modules/system/tests/modules/system_incompatible_module_version_test/system_incompatible_module_version_test.info.yml @@ -2,11 +2,5 @@ name: 'System incompatible module version test' type: module description: 'Support module for testing system dependencies.' package: Testing -# version: '1.0' -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: '1.0' +core: 8.x diff --git a/core/modules/system/tests/modules/system_incompatible_php_version_test/system_incompatible_php_version_test.info.yml b/core/modules/system/tests/modules/system_incompatible_php_version_test/system_incompatible_php_version_test.info.yml index 929d81b37..a3a26ee55 100644 --- a/core/modules/system/tests/modules/system_incompatible_php_version_test/system_incompatible_php_version_test.info.yml +++ b/core/modules/system/tests/modules/system_incompatible_php_version_test/system_incompatible_php_version_test.info.yml @@ -2,12 +2,6 @@ name: 'System incompatible PHP version test' type: module description: 'Support module for testing system dependencies.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x php: 6502 - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/system_mail_failure_test/system_mail_failure_test.info.yml b/core/modules/system/tests/modules/system_mail_failure_test/system_mail_failure_test.info.yml index a6b1725ef..12ad6e249 100644 --- a/core/modules/system/tests/modules/system_mail_failure_test/system_mail_failure_test.info.yml +++ b/core/modules/system/tests/modules/system_mail_failure_test/system_mail_failure_test.info.yml @@ -2,11 +2,5 @@ name: 'System mail failure test' type: module description: 'Provides a malfunctioning mail service for testing purposes.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/system_module_test/system_module_test.info.yml b/core/modules/system/tests/modules/system_module_test/system_module_test.info.yml index ed3e82869..6b787f5e0 100644 --- a/core/modules/system/tests/modules/system_module_test/system_module_test.info.yml +++ b/core/modules/system/tests/modules/system_module_test/system_module_test.info.yml @@ -2,11 +2,5 @@ name: 'System test' type: module description: 'Provides hook implementations for testing System module functionality.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/system_project_namespace_test/system_project_namespace_test.info.yml b/core/modules/system/tests/modules/system_project_namespace_test/system_project_namespace_test.info.yml index 7fb54d815..bcea14fb7 100644 --- a/core/modules/system/tests/modules/system_project_namespace_test/system_project_namespace_test.info.yml +++ b/core/modules/system/tests/modules/system_project_namespace_test/system_project_namespace_test.info.yml @@ -2,13 +2,7 @@ name: 'System project namespace' type: module description: 'Support module for testing project namespace system dependencies.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:filter - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php index cfd0f2f79..2f86b8981 100644 --- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php +++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php @@ -6,6 +6,7 @@ use Drupal\Core\Cache\CacheableResponse; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Security\TrustedCallbackInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\Markup; use Drupal\Core\Session\AccountInterface; @@ -19,7 +20,7 @@ /** * Controller routines for system_test routes. */ -class SystemTestController extends ControllerBase { +class SystemTestController extends ControllerBase implements TrustedCallbackInterface { /** * The lock service. @@ -398,4 +399,11 @@ public function getCacheableResponseWithCustomCacheControl() { return new CacheableResponse('Foo', 200, ['Cache-Control' => 'bar']); } + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['preRenderCacheTags']; + } + } diff --git a/core/modules/system/tests/modules/system_test/system_test.info.yml b/core/modules/system/tests/modules/system_test/system_test.info.yml index 3f6506f3f..722870945 100644 --- a/core/modules/system/tests/modules/system_test/system_test.info.yml +++ b/core/modules/system/tests/modules/system_test/system_test.info.yml @@ -2,14 +2,8 @@ name: 'System test' type: module description: 'Support module for system testing.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x configure: system_test.configure configure_parameters: foo: bar - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module index affc20ea4..7f0ba88cd 100644 --- a/core/modules/system/tests/modules/system_test/system_test.module +++ b/core/modules/system/tests/modules/system_test/system_test.module @@ -71,6 +71,7 @@ function system_test_system_info_alter(&$info, Extension $file, $type) { 'system_incompatible_core_version_dependencies_test', 'system_incompatible_module_version_test', 'system_incompatible_core_version_test', + 'system_incompatible_core_version_test_1x', ])) { $info['hidden'] = FALSE; } diff --git a/core/modules/system/tests/modules/tabledrag_test/tabledrag_test.info.yml b/core/modules/system/tests/modules/tabledrag_test/tabledrag_test.info.yml index af18e974e..98d6bb4b9 100644 --- a/core/modules/system/tests/modules/tabledrag_test/tabledrag_test.info.yml +++ b/core/modules/system/tests/modules/tabledrag_test/tabledrag_test.info.yml @@ -1,12 +1,6 @@ type: module name: 'TableDrag test' description: 'Draggable table test module.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/modules/test_batch_test/test_batch_test.info.yml b/core/modules/system/tests/modules/test_batch_test/test_batch_test.info.yml index 6a6dd916d..bc4a3eef2 100644 --- a/core/modules/system/tests/modules/test_batch_test/test_batch_test.info.yml +++ b/core/modules/system/tests/modules/test_batch_test/test_batch_test.info.yml @@ -2,13 +2,7 @@ name: 'Test install batch test' type: module description: 'Support module for functional tests.' package: Testing -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x dependencies: - drupal:entity_test - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php index 8867b8ba2..08ff61551 100644 --- a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php +++ b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php @@ -2,6 +2,7 @@ namespace Drupal\test_page_test\Controller; +use Drupal\Core\Render\Markup; use Drupal\Core\Url; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -116,6 +117,32 @@ public function renderPipeInLink() { return ['#markup' => 'foo|bar|baz']; } + public function escapedCharacters() { + return [ + '#prefix' => '
', + '#plain_text' => 'Escaped: <"\'&>', + '#suffix' => '
', + ]; + } + + public function escapedScript() { + return [ + '#prefix' => '
', + // We use #plain_text because #markup would be filtered and that is not + // being tested here. + '#plain_text' => "", + '#suffix' => '
', + ]; + } + + public function unEscapedScript() { + return [ + '#prefix' => '
', + '#markup' => Markup::create(""), + '#suffix' => '
', + ]; + } + /** * Loads a page that does a redirect. * @@ -128,4 +155,17 @@ public function metaRefresh() { return new RedirectResponse(Url::fromRoute('test_page_test.test_page', [], ['absolute' => TRUE])->toString(), 302); } + /** + * Returns a page while triggering deprecation notices. + */ + public function deprecations() { + // Create 2 identical deprecation messages. This should only trigger a + // single response header. + @trigger_error('Test deprecation message', E_USER_DEPRECATED); + @trigger_error('Test deprecation message', E_USER_DEPRECATED); + return [ + '#markup' => 'Content that triggers deprecation messages', + ]; + } + } diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.info.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.info.yml index 2fa2689b3..f01c59717 100644 --- a/core/modules/system/tests/modules/test_page_test/test_page_test.info.yml +++ b/core/modules/system/tests/modules/test_page_test/test_page_test.info.yml @@ -2,11 +2,5 @@ name: 'Test page' type: module description: 'Provides a test page for automated tests.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml index e64a88d00..336f6c704 100644 --- a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml +++ b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml @@ -32,7 +32,7 @@ test_page_test.dynamic_title: path: '/test-page-dynamic-title' defaults: _controller: '\Drupal\test_page_test\Controller\Test::staticTitle' - _title_callback: 'Drupal\test_page_test\Controller\Test::dynamicTitle' + _title_callback: '\Drupal\test_page_test\Controller\Test::dynamicTitle' requirements: _access: 'TRUE' @@ -83,6 +83,30 @@ test_page_test.field_xpath: requirements: _access: 'TRUE' +test_page_test.escaped_characters: + path: '/test-escaped-characters' + defaults: + _controller: '\Drupal\test_page_test\Controller\Test::escapedCharacters' + code: 200 + requirements: + _access: 'TRUE' + +test_page_test.escaped_script: + path: '/test-escaped-script' + defaults: + _controller: '\Drupal\test_page_test\Controller\Test::escapedScript' + code: 200 + requirements: + _access: 'TRUE' + +test_page_test.unescaped_script: + path: '/test-unescaped-script' + defaults: + _controller: '\Drupal\test_page_test\Controller\Test::unescapedScript' + code: 200 + requirements: + _access: 'TRUE' + test_page_test.meta_refresh: path: '/test-meta-refresh' defaults: @@ -90,3 +114,11 @@ test_page_test.meta_refresh: _controller: '\Drupal\test_page_test\Controller\Test::metaRefresh' requirements: _access: 'TRUE' + +test_page_test.deprecations: + path: '/test-deprecations' + defaults: + _title: 'Page with deprecation notices' + _controller: '\Drupal\test_page_test\Controller\Test::deprecations' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/theme_page_test/theme_page_test.info.yml b/core/modules/system/tests/modules/theme_page_test/theme_page_test.info.yml index 1e71fedba..da50bb11e 100644 --- a/core/modules/system/tests/modules/theme_page_test/theme_page_test.info.yml +++ b/core/modules/system/tests/modules/theme_page_test/theme_page_test.info.yml @@ -2,11 +2,5 @@ name: 'Theme page test' type: module description: 'Support module for theme system testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/theme_region_test/theme_region_test.info.yml b/core/modules/system/tests/modules/theme_region_test/theme_region_test.info.yml index 62683186a..0cf5fe313 100644 --- a/core/modules/system/tests/modules/theme_region_test/theme_region_test.info.yml +++ b/core/modules/system/tests/modules/theme_region_test/theme_region_test.info.yml @@ -2,11 +2,5 @@ name: 'Theme region test' type: module description: 'Provides hook implementations for testing regions.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.info.yml b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.info.yml index 4b48637b0..fe61d0d91 100644 --- a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.info.yml +++ b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.info.yml @@ -2,11 +2,5 @@ name: 'Theme suggestions test' type: module description: 'Support module for testing theme suggestions.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/theme_test/theme_test.info.yml b/core/modules/system/tests/modules/theme_test/theme_test.info.yml index 694087191..0fe8c1019 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.info.yml +++ b/core/modules/system/tests/modules/theme_test/theme_test.info.yml @@ -2,11 +2,5 @@ name: 'Theme test' type: module description: 'Support module for theme system testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/token_test/token_test.info.yml b/core/modules/system/tests/modules/token_test/token_test.info.yml index 879d1188e..8f3bc473f 100644 --- a/core/modules/system/tests/modules/token_test/token_test.info.yml +++ b/core/modules/system/tests/modules/token_test/token_test.info.yml @@ -1,14 +1,8 @@ name: Token test type: module -# core: 8.x +core: 8.x package: Testing -# version: VERSION +version: VERSION dependencies: - drupal:user - drupal:node - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 diff --git a/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.info.yml b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.info.yml index 90a90a650..20f41bcb8 100644 --- a/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.info.yml +++ b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.info.yml @@ -1,11 +1,5 @@ type: module name: 'Trusted hosts test module' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.routing.yml b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.routing.yml index 834aeace0..607710873 100644 --- a/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.routing.yml +++ b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.routing.yml @@ -1,6 +1,6 @@ trusted_hosts_test.fake_request: path: '/trusted-hosts-test/fake-request' defaults: - _controller: 'Drupal\trusted_hosts_test\Controller\TrustedHostsTestController::fakeRequestHost' + _controller: '\Drupal\trusted_hosts_test\Controller\TrustedHostsTestController::fakeRequestHost' requirements: _access: 'TRUE' diff --git a/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.info.yml b/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.info.yml index 6ab43bc40..5aee1e269 100644 --- a/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.info.yml +++ b/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.info.yml @@ -2,11 +2,5 @@ name: 'Twig Extension Test' type: module description: 'Support module for testing Twig extensions.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php b/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php index bd23809e2..e9a05b0ad 100644 --- a/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php +++ b/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php @@ -14,7 +14,7 @@ class TestLoader implements \Twig_LoaderInterface, \Twig_ExistsLoaderInterface, */ public function getSourceContext($name) { $name = (string) $name; - $value = $this->getSource($name); + $value = $name === 'kittens' ? 'kittens' : 'cats'; return new Source($value, $name); } @@ -22,12 +22,7 @@ public function getSourceContext($name) { * {@inheritdoc} */ public function getSource($name) { - if ($name == 'kittens') { - return $name; - } - else { - return 'cats'; - } + return $this->getSourceContext($name)->getCode(); } /** diff --git a/core/modules/system/tests/modules/twig_loader_test/twig_loader_test.info.yml b/core/modules/system/tests/modules/twig_loader_test/twig_loader_test.info.yml index 957fa4847..cf04aaeae 100644 --- a/core/modules/system/tests/modules/twig_loader_test/twig_loader_test.info.yml +++ b/core/modules/system/tests/modules/twig_loader_test/twig_loader_test.info.yml @@ -2,11 +2,5 @@ name: 'Twig Loader Test' type: module description: 'Support module for testing adding Twig loaders.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_a/twig_namespace_a.info.yml b/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_a/twig_namespace_a.info.yml index afcff3595..7584b57cd 100644 --- a/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_a/twig_namespace_a.info.yml +++ b/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_a/twig_namespace_a.info.yml @@ -2,11 +2,5 @@ name: 'Twig namespace test: Module A' type: module description: 'Support module for Twig namespace testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_b/twig_namespace_b.info.yml b/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_b/twig_namespace_b.info.yml index afcff3595..7584b57cd 100644 --- a/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_b/twig_namespace_b.info.yml +++ b/core/modules/system/tests/modules/twig_theme_test/modules/twig_namespace_b/twig_namespace_b.info.yml @@ -2,11 +2,5 @@ name: 'Twig namespace test: Module A' type: module description: 'Support module for Twig namespace testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.info.yml b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.info.yml index a83c64c42..751531592 100644 --- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.info.yml +++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.info.yml @@ -2,11 +2,5 @@ name: 'Twig theme test' type: module description: 'Support module for Twig theme system testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml b/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml index 7bb38f416..a667c6217 100644 --- a/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml +++ b/core/modules/system/tests/modules/unique_field_constraint_test/unique_field_constraint_test.info.yml @@ -2,11 +2,5 @@ name: 'UniqueField Constraint Test' type: module description: 'Support module for UniqueField Constraint testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_script_test/update_script_test.info.yml b/core/modules/system/tests/modules/update_script_test/update_script_test.info.yml index 522d95c7b..b582c1ab8 100644 --- a/core/modules/system/tests/modules/update_script_test/update_script_test.info.yml +++ b/core/modules/system/tests/modules/update_script_test/update_script_test.info.yml @@ -2,11 +2,5 @@ name: 'Update script test' type: module description: 'Support module for update script testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_0/update_test_0.info.yml b/core/modules/system/tests/modules/update_test_0/update_test_0.info.yml index 5f9a09bb7..4162fd02c 100644 --- a/core/modules/system/tests/modules/update_test_0/update_test_0.info.yml +++ b/core/modules/system/tests/modules/update_test_0/update_test_0.info.yml @@ -2,11 +2,5 @@ name: 'Update test 0' type: module description: 'Support module for update testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_1/update_test_1.info.yml b/core/modules/system/tests/modules/update_test_1/update_test_1.info.yml index 7ccad51f8..5a7e2e1fa 100644 --- a/core/modules/system/tests/modules/update_test_1/update_test_1.info.yml +++ b/core/modules/system/tests/modules/update_test_1/update_test_1.info.yml @@ -2,11 +2,5 @@ name: 'Update test' type: module description: 'Support module for update testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_2/update_test_2.info.yml b/core/modules/system/tests/modules/update_test_2/update_test_2.info.yml index 7ccad51f8..5a7e2e1fa 100644 --- a/core/modules/system/tests/modules/update_test_2/update_test_2.info.yml +++ b/core/modules/system/tests/modules/update_test_2/update_test_2.info.yml @@ -2,11 +2,5 @@ name: 'Update test' type: module description: 'Support module for update testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_3/update_test_3.info.yml b/core/modules/system/tests/modules/update_test_3/update_test_3.info.yml index 7ccad51f8..5a7e2e1fa 100644 --- a/core/modules/system/tests/modules/update_test_3/update_test_3.info.yml +++ b/core/modules/system/tests/modules/update_test_3/update_test_3.info.yml @@ -2,11 +2,5 @@ name: 'Update test' type: module description: 'Support module for update testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_failing/update_test_failing.info.yml b/core/modules/system/tests/modules/update_test_failing/update_test_failing.info.yml index 6a648c0d3..f6841f32d 100644 --- a/core/modules/system/tests/modules/update_test_failing/update_test_failing.info.yml +++ b/core/modules/system/tests/modules/update_test_failing/update_test_failing.info.yml @@ -2,11 +2,5 @@ name: 'Update test failing' type: module description: 'Support module for update testing when an update hook is failing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_invalid_hook/update_test_invalid_hook.info.yml b/core/modules/system/tests/modules/update_test_invalid_hook/update_test_invalid_hook.info.yml index 20560cbc3..b9aefba42 100644 --- a/core/modules/system/tests/modules/update_test_invalid_hook/update_test_invalid_hook.info.yml +++ b/core/modules/system/tests/modules/update_test_invalid_hook/update_test_invalid_hook.info.yml @@ -2,11 +2,5 @@ name: 'Update test with an invalid hook_update_8000().' type: module description: 'Support module for update testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_postupdate/update_test_postupdate.info.yml b/core/modules/system/tests/modules/update_test_postupdate/update_test_postupdate.info.yml index 0c8ff45bd..f2445dd45 100644 --- a/core/modules/system/tests/modules/update_test_postupdate/update_test_postupdate.info.yml +++ b/core/modules/system/tests/modules/update_test_postupdate/update_test_postupdate.info.yml @@ -1,11 +1,5 @@ -# core: 8.x +core: 8.x name: Update test after type: module package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/modules/update_test_schema/update_test_schema.info.yml b/core/modules/system/tests/modules/update_test_schema/update_test_schema.info.yml index f0d46fe26..620fb92b2 100644 --- a/core/modules/system/tests/modules/update_test_schema/update_test_schema.info.yml +++ b/core/modules/system/tests/modules/update_test_schema/update_test_schema.info.yml @@ -2,11 +2,5 @@ name: 'Update test schema' type: module description: 'Support module for update testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/update_test_semver_update_n/update_test_semver_update_n.info.yml b/core/modules/system/tests/modules/update_test_semver_update_n/update_test_semver_update_n.info.yml new file mode 100644 index 000000000..74e908530 --- /dev/null +++ b/core/modules/system/tests/modules/update_test_semver_update_n/update_test_semver_update_n.info.yml @@ -0,0 +1,6 @@ +name: 'Update test hook_update_n semver' +type: module +description: 'Support module for update testing with core semver value.' +package: Testing +version: VERSION +core_version_requirement: ^8 diff --git a/core/modules/system/tests/modules/update_test_semver_update_n/update_test_semver_update_n.install b/core/modules/system/tests/modules/update_test_semver_update_n/update_test_semver_update_n.install new file mode 100644 index 000000000..aacb59571 --- /dev/null +++ b/core/modules/system/tests/modules/update_test_semver_update_n/update_test_semver_update_n.install @@ -0,0 +1,13 @@ +set('update_test_semver_update_n_update_8001', 'Yes, I was run. Thanks for testing!'); +} diff --git a/core/modules/system/tests/modules/update_test_with_7x/update_test_with_7x.info.yml b/core/modules/system/tests/modules/update_test_with_7x/update_test_with_7x.info.yml index d17fa34a8..2b47a0490 100644 --- a/core/modules/system/tests/modules/update_test_with_7x/update_test_with_7x.info.yml +++ b/core/modules/system/tests/modules/update_test_with_7x/update_test_with_7x.info.yml @@ -2,11 +2,5 @@ name: 'Update test with 7.x updates left in the codebase.' type: module description: 'Support module for update testing.' package: Testing -# version: VERSION -# core: 8.x - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/url_alter_test/url_alter_test.info.yml b/core/modules/system/tests/modules/url_alter_test/url_alter_test.info.yml index 051ed34c6..95aba3085 100644 --- a/core/modules/system/tests/modules/url_alter_test/url_alter_test.info.yml +++ b/core/modules/system/tests/modules/url_alter_test/url_alter_test.info.yml @@ -1,12 +1,6 @@ name: 'Url_alter tests' type: module description: 'A support module to test altering the inbound and outbound path.' -# core: 8.x +core: 8.x package: Testing -# version: VERSION - -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +version: VERSION diff --git a/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php b/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php index cc7b782d5..077c237af 100644 --- a/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php +++ b/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php @@ -20,6 +20,11 @@ */ class FrameworkTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php b/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php index c78672212..919e1e792 100644 --- a/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php +++ b/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php @@ -14,6 +14,11 @@ */ class OffCanvasDialogTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Modules to enable. * diff --git a/core/modules/system/tests/src/Functional/Batch/PageTest.php b/core/modules/system/tests/src/Functional/Batch/PageTest.php index b1e46a2ea..239b40c51 100644 --- a/core/modules/system/tests/src/Functional/Batch/PageTest.php +++ b/core/modules/system/tests/src/Functional/Batch/PageTest.php @@ -18,13 +18,18 @@ class PageTest extends BrowserTestBase { */ public static $modules = ['batch_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that the batch API progress page uses the correct theme. */ public function testBatchProgressPageTheme() { // Make sure that the page which starts the batch (an administrative page) // is using a different theme than would normally be used by the batch API. - $this->container->get('theme_handler')->install(['seven', 'bartik']); + $this->container->get('theme_installer')->install(['seven', 'bartik']); $this->config('system.theme') ->set('default', 'bartik') ->set('admin', 'seven') diff --git a/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php b/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php index 97dc6e7a5..6d73c1a8b 100644 --- a/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php +++ b/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php @@ -19,6 +19,11 @@ class ProcessingTest extends BrowserTestBase { */ public static $modules = ['batch_test', 'test_page_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Tests batches triggered outside of form submission. */ @@ -84,6 +89,31 @@ public function testBatchForm() { $this->assertBatchMessages($this->_resultMessages('batch_4'), 'Nested batch performed successfully.'); $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_4'), 'Execution order was correct.'); $this->assertText('Redirection successful.', 'Redirection after batch execution is correct.'); + + // Submit batches 4 and 7. Batch 4 will trigger batch 2. Batch 7 will + // trigger batches 6 and 5. + $edit = ['batch' => ['batch_4', 'batch_7']]; + $this->drupalPostForm('batch-test', $edit, 'Submit'); + $this->assertSession()->assertNoEscaped('<'); + $this->assertSession()->responseContains('Redirection successful.'); + $this->assertBatchMessages($this->_resultMessages('batch_4'), 'Nested batch performed successfully.'); + $this->assertBatchMessages($this->_resultMessages('batch_7'), 'Nested batch performed successfully.'); + $expected_stack = array_merge($this->_resultStack('batch_4'), $this->_resultStack('batch_7')); + $this->assertEquals($expected_stack, batch_test_stack(), 'Execution order was correct.'); + $batch = \Drupal::state()->get('batch_test_nested_order_multiple_batches'); + $this->assertEquals(5, count($batch['sets'])); + // Ensure correct queue mapping. + foreach ($batch['sets'] as $index => $batch_set) { + $this->assertEquals('drupal_batch:' . $batch['id'] . ':' . $index, $batch_set['queue']['name']); + } + // Ensure correct order of the nested batches. We reset the indexes in + // order to directly access the batches by their order. + $batch_sets = array_values($batch['sets']); + $this->assertEquals('batch_4', $batch_sets[0]['batch_test_id']); + $this->assertEquals('batch_2', $batch_sets[1]['batch_test_id']); + $this->assertEquals('batch_7', $batch_sets[2]['batch_test_id']); + $this->assertEquals('batch_6', $batch_sets[3]['batch_test_id']); + $this->assertEquals('batch_5', $batch_sets[4]['batch_test_id']); } /** @@ -113,7 +143,7 @@ public function testBatchFormMultistep() { $this->drupalGet('batch-test/multistep', ['query' => ['big_tree' => 'small_axe']]); $this->drupalPostForm(NULL, [], 'Submit'); $this->assertText('step 2', 'Form is displayed in step 2.'); - $this->assertTrue(strpos($this->getUrl(), 'batch-test/multistep?big_tree=small_axe'), 'Query argument was persisted and another extra argument was added.'); + $this->assertContains('batch-test/multistep?big_tree=small_axe', $this->getUrl(), 'Query argument was persisted and another extra argument was added.'); } /** @@ -241,6 +271,25 @@ public function _resultStack($id, $value = 0) { } break; + case 'batch_6': + for ($i = 1; $i <= 10; $i++) { + $stack[] = "op 6 id $i"; + } + break; + + case 'batch_7': + for ($i = 1; $i <= 5; $i++) { + $stack[] = "op 7 id $i"; + } + $stack[] = 'setting up batch 6'; + $stack[] = 'setting up batch 5'; + for ($i = 6; $i <= 10; $i++) { + $stack[] = "op 7 id $i"; + } + $stack = array_merge($stack, $this->_resultStack('batch_6')); + $stack = array_merge($stack, $this->_resultStack('batch_5')); + break; + case 'chained': $stack[] = 'submit handler 1'; $stack[] = 'value = ' . $value; @@ -290,6 +339,16 @@ public function _resultMessages($id) { $messages[] = 'results for batch 5
  • op 5: processed 10 elements
'; break; + case 'batch_6': + $messages[] = 'results for batch 6
  • op 6: processed 10 elements
'; + break; + + case 'batch_7': + $messages[] = 'results for batch 7
  • op 7: processed 10 elements
'; + $messages = array_merge($messages, $this->_resultMessages('batch_6')); + $messages = array_merge($messages, $this->_resultMessages('batch_5')); + break; + case 'chained': $messages = array_merge($messages, $this->_resultMessages('batch_1')); $messages = array_merge($messages, $this->_resultMessages('batch_2')); diff --git a/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php b/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php index 51404ef18..56db87601 100644 --- a/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php +++ b/core/modules/system/tests/src/Functional/Bootstrap/DrupalMessengerServiceTest.php @@ -19,6 +19,11 @@ class DrupalMessengerServiceTest extends BrowserTestBase { */ public static $modules = ['system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests Messenger service. */ diff --git a/core/modules/system/tests/src/Functional/Cache/AssertPageCacheContextsAndTagsTrait.php b/core/modules/system/tests/src/Functional/Cache/AssertPageCacheContextsAndTagsTrait.php index 6854b44e1..e03a90f67 100644 --- a/core/modules/system/tests/src/Functional/Cache/AssertPageCacheContextsAndTagsTrait.php +++ b/core/modules/system/tests/src/Functional/Cache/AssertPageCacheContextsAndTagsTrait.php @@ -94,22 +94,6 @@ protected function assertPageCacheContextsAndTags(Url $url, array $expected_cont $cache_entry = \Drupal::cache('page')->get($cid); sort($cache_entry->tags); $this->assertEqual($cache_entry->tags, $expected_tags); - $this->debugCacheTags($cache_entry->tags, $expected_tags); - } - - /** - * Provides debug information for cache tags. - * - * @param string[] $actual_tags - * The actual cache tags. - * @param string[] $expected_tags - * The expected cache tags. - */ - protected function debugCacheTags(array $actual_tags, array $expected_tags) { - if ($actual_tags !== $expected_tags) { - debug('Unwanted cache tags in response: ' . implode(',', array_diff($actual_tags, $expected_tags))); - debug('Missing cache tags in response: ' . implode(',', array_diff($expected_tags, $actual_tags))); - } } /** @@ -133,7 +117,6 @@ protected function assertCacheTags(array $expected_tags, $include_default_tags = sort($expected_tags); sort($actual_tags); $this->assertIdentical($actual_tags, $expected_tags); - $this->debugCacheTags($actual_tags, $expected_tags); } /** @@ -163,17 +146,8 @@ protected function assertCacheContexts(array $expected_contexts, $message = NULL $actual_contexts = $this->getCacheHeaderValues('X-Drupal-Cache-Contexts'); sort($expected_contexts); sort($actual_contexts); - $match = $actual_contexts === $expected_contexts; - if (!$match) { - debug('Unwanted cache contexts in response: ' . implode(',', array_diff($actual_contexts, $expected_contexts))); - debug('Missing cache contexts in response: ' . implode(',', array_diff($expected_contexts, $actual_contexts))); - } - $this->assertIdentical($actual_contexts, $expected_contexts, $message); - - // For compatibility with both BrowserTestBase and WebTestBase always return - // a boolean. - return $match; + return $actual_contexts === $expected_contexts; } /** @@ -183,10 +157,7 @@ protected function assertCacheContexts(array $expected_contexts, $message = NULL */ protected function assertCacheMaxAge($max_age) { $cache_control_header = $this->drupalGetHeader('Cache-Control'); - if (strpos($cache_control_header, 'max-age:' . $max_age) === FALSE) { - debug('Expected max-age:' . $max_age . '; Response max-age:' . $cache_control_header); - } - $this->assertTrue(strpos($cache_control_header, 'max-age:' . $max_age)); + $this->assertContains('max-age:' . $max_age, $cache_control_header); } } diff --git a/core/modules/system/tests/src/Functional/Cache/ClearTest.php b/core/modules/system/tests/src/Functional/Cache/ClearTest.php index bec6d502f..97f424e02 100644 --- a/core/modules/system/tests/src/Functional/Cache/ClearTest.php +++ b/core/modules/system/tests/src/Functional/Cache/ClearTest.php @@ -7,10 +7,16 @@ * * @group Cache */ +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Cache\Cache; class ClearTest extends CacheTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { $this->defaultBin = 'render'; $this->defaultValue = $this->randomMachineName(10); @@ -24,7 +30,7 @@ protected function setUp() { public function testFlushAllCaches() { // Create cache entries for each flushed cache bin. $bins = Cache::getBins(); - $this->assertTrue($bins, 'Cache::getBins() returned bins to flush.'); + $this->assertNotEmpty($bins, 'Cache::getBins() returned bins to flush.'); foreach ($bins as $bin => $cache_backend) { $cid = 'test_cid_clear' . $bin; $cache_backend->set($cid, $this->defaultValue); @@ -35,7 +41,7 @@ public function testFlushAllCaches() { foreach ($bins as $bin => $cache_backend) { $cid = 'test_cid_clear' . $bin; - $this->assertFalse($this->checkCacheExists($cid, $this->defaultValue, $bin), format_string('All cache entries removed from @bin.', ['@bin' => $bin])); + $this->assertFalse($this->checkCacheExists($cid, $this->defaultValue, $bin), new FormattableMarkup('All cache entries removed from @bin.', ['@bin' => $bin])); } } diff --git a/core/modules/system/tests/src/Functional/Cache/SessionExistsCacheContextTest.php b/core/modules/system/tests/src/Functional/Cache/SessionExistsCacheContextTest.php index 78fb983a6..23ad7abaa 100644 --- a/core/modules/system/tests/src/Functional/Cache/SessionExistsCacheContextTest.php +++ b/core/modules/system/tests/src/Functional/Cache/SessionExistsCacheContextTest.php @@ -19,6 +19,11 @@ class SessionExistsCacheContextTest extends BrowserTestBase { */ public static $modules = ['session_exists_cache_context_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests \Drupal\Core\Cache\Context\SessionExistsCacheContext::getContext(). */ diff --git a/core/modules/system/tests/src/Functional/Common/AlterTest.php b/core/modules/system/tests/src/Functional/Common/AlterTest.php index e529e9d1c..e96dd4c2d 100644 --- a/core/modules/system/tests/src/Functional/Common/AlterTest.php +++ b/core/modules/system/tests/src/Functional/Common/AlterTest.php @@ -18,13 +18,18 @@ class AlterTest extends BrowserTestBase { */ public static $modules = ['block', 'common_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests if the theme has been altered. */ public function testDrupalAlter() { // This test depends on Bartik, so make sure that it is always the current // active theme. - \Drupal::service('theme_handler')->install(['bartik']); + \Drupal::service('theme_installer')->install(['bartik']); \Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->initTheme('bartik')); $array = ['foo' => 'bar']; diff --git a/core/modules/system/tests/src/Functional/Common/EarlyRenderingControllerTest.php b/core/modules/system/tests/src/Functional/Common/EarlyRenderingControllerTest.php index 9d6c0af5d..9595abbfa 100644 --- a/core/modules/system/tests/src/Functional/Common/EarlyRenderingControllerTest.php +++ b/core/modules/system/tests/src/Functional/Common/EarlyRenderingControllerTest.php @@ -22,6 +22,11 @@ class EarlyRenderingControllerTest extends BrowserTestBase { */ public static $modules = ['system', 'early_rendering_controller_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests theme preprocess functions being able to attach assets. */ diff --git a/core/modules/system/tests/src/Functional/Common/FormatDateTest.php b/core/modules/system/tests/src/Functional/Common/FormatDateTest.php index a595db778..b2ff7b1a0 100644 --- a/core/modules/system/tests/src/Functional/Common/FormatDateTest.php +++ b/core/modules/system/tests/src/Functional/Common/FormatDateTest.php @@ -11,6 +11,11 @@ */ class FormatDateTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests admin-defined formats in DateFormatterInterface::format(). */ diff --git a/core/modules/system/tests/src/Functional/Common/RenderWebTest.php b/core/modules/system/tests/src/Functional/Common/RenderWebTest.php index 65d260403..5e64bfa92 100644 --- a/core/modules/system/tests/src/Functional/Common/RenderWebTest.php +++ b/core/modules/system/tests/src/Functional/Common/RenderWebTest.php @@ -23,6 +23,11 @@ class RenderWebTest extends BrowserTestBase { */ public static $modules = ['common_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Asserts the cache context for the wrapper format is always present. */ diff --git a/core/modules/system/tests/src/Functional/Common/UrlTest.php b/core/modules/system/tests/src/Functional/Common/UrlTest.php index 362868c31..7d3eadad4 100644 --- a/core/modules/system/tests/src/Functional/Common/UrlTest.php +++ b/core/modules/system/tests/src/Functional/Common/UrlTest.php @@ -2,9 +2,11 @@ namespace Drupal\Tests\system\Functional\Common; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Cache\Cache; use Drupal\Core\Language\Language; +use Drupal\Core\Link; use Drupal\Core\Render\RenderContext; use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; @@ -22,21 +24,26 @@ class UrlTest extends BrowserTestBase { public static $modules = ['common_test', 'url_alter_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Confirms that invalid URLs are filtered in link generating functions. */ public function testLinkXSS() { - // Test \Drupal::l(). + // Test link generator. $text = $this->randomMachineName(); $path = ""; $encoded_path = "3CSCRIPT%3Ealert%28%27XSS%27%29%3C/SCRIPT%3E"; - $link = \Drupal::l($text, Url::fromUserInput('/' . $path)); - $this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, format_string('XSS attack @path was filtered by \Drupal\Core\Utility\LinkGeneratorInterface::generate().', ['@path' => $path])); + $link = Link::fromTextAndUrl($text, Url::fromUserInput('/' . $path))->toString(); + $this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, new FormattableMarkup('XSS attack @path was filtered by \Drupal\Core\Utility\LinkGeneratorInterface::generate().', ['@path' => $path])); // Test \Drupal\Core\Url. $link = Url::fromUri('base:' . $path)->toString(); - $this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, format_string('XSS attack @path was filtered by #theme', ['@path' => $path])); + $this->assertTrue(strpos($link, $encoded_path) !== FALSE && strpos($link, $path) === FALSE, new FormattableMarkup('XSS attack @path was filtered by #theme', ['@path' => $path])); } /** @@ -93,10 +100,10 @@ public function testLinkAttributes() { $hreflang_override_link['#options']['attributes']['hreflang'] = 'foo'; $rendered = $renderer->renderRoot($hreflang_link); - $this->assertTrue($this->hasAttribute('hreflang', $rendered, $langcode), format_string('hreflang attribute with value @langcode is present on a rendered link when langcode is provided in the render array.', ['@langcode' => $langcode])); + $this->assertTrue($this->hasAttribute('hreflang', $rendered, $langcode), new FormattableMarkup('hreflang attribute with value @langcode is present on a rendered link when langcode is provided in the render array.', ['@langcode' => $langcode])); $rendered = $renderer->renderRoot($hreflang_override_link); - $this->assertTrue($this->hasAttribute('hreflang', $rendered, 'foo'), format_string('hreflang attribute with value @hreflang is present on a rendered link when @hreflang is provided in the render array.', ['@hreflang' => 'foo'])); + $this->assertTrue($this->hasAttribute('hreflang', $rendered, 'foo'), new FormattableMarkup('hreflang attribute with value @hreflang is present on a rendered link when @hreflang is provided in the render array.', ['@hreflang' => 'foo'])); // Test the active class in links produced by // \Drupal\Core\Utility\LinkGeneratorInterface::generate() and #type 'link'. @@ -138,8 +145,8 @@ public function testLinkAttributes() { // \Drupal\Core\Utility\LinkGeneratorInterface::generate() and #type 'link'. // Test the link generator. $class_l = $this->randomMachineName(); - $link_l = \Drupal::l($this->randomMachineName(), new Url('', [], ['attributes' => ['class' => [$class_l]]])); - $this->assertTrue($this->hasAttribute('class', $link_l, $class_l), format_string('Custom class @class is present on link when requested by l()', ['@class' => $class_l])); + $link_l = Link::fromTextAndUrl($this->randomMachineName(), Url::fromRoute('', [], ['attributes' => ['class' => [$class_l]]]))->toString(); + $this->assertTrue($this->hasAttribute('class', $link_l, $class_l), new FormattableMarkup('Custom class @class is present on link when requested by Link::toString()', ['@class' => $class_l])); // Test #type. $class_theme = $this->randomMachineName(); @@ -154,7 +161,7 @@ public function testLinkAttributes() { ], ]; $link_theme = $renderer->renderRoot($type_link); - $this->assertTrue($this->hasAttribute('class', $link_theme, $class_theme), format_string('Custom class @class is present on link when requested by #type', ['@class' => $class_theme])); + $this->assertTrue($this->hasAttribute('class', $link_theme, $class_theme), new FormattableMarkup('Custom class @class is present on link when requested by #type', ['@class' => $class_theme])); } /** @@ -165,12 +172,12 @@ public function testLinkRenderArrayText() { $renderer = $this->container->get('renderer'); // Build a link with the link generator for reference. - $l = \Drupal::l('foo', Url::fromUri('https://www.drupal.org')); + $l = Link::fromTextAndUrl('foo', Url::fromUri('https://www.drupal.org'))->toString(); // Test a renderable array passed to the link generator. $renderer->executeInRenderContext(new RenderContext(), function () use ($renderer, $l) { $renderable_text = ['#markup' => 'foo']; - $l_renderable_text = \Drupal::l($renderable_text, Url::fromUri('https://www.drupal.org')); + $l_renderable_text = \Drupal::service('link_generator')->generate($renderable_text, Url::fromUri('https://www.drupal.org')); $this->assertEqual($l_renderable_text, $l); }); @@ -205,7 +212,7 @@ public function testLinkRenderArrayText() { * TRUE if the class is found, FALSE otherwise. */ private function hasAttribute($attribute, $link, $class) { - return preg_match('|' . $attribute . '="([^\"\s]+\s+)*' . $class . '|', $link); + return (bool) preg_match('|' . $attribute . '="([^\"\s]+\s+)*' . $class . '|', $link); } /** diff --git a/core/modules/system/tests/src/Functional/Condition/ConditionFormTest.php b/core/modules/system/tests/src/Functional/Condition/ConditionFormTest.php index adefc4392..2cdaabeb3 100644 --- a/core/modules/system/tests/src/Functional/Condition/ConditionFormTest.php +++ b/core/modules/system/tests/src/Functional/Condition/ConditionFormTest.php @@ -18,6 +18,11 @@ class ConditionFormTest extends BrowserTestBase { public static $modules = ['node', 'condition_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Submit the condition_node_type_test_form to test condition forms. */ diff --git a/core/modules/system/tests/src/Functional/CsrfRequestHeaderTest.php b/core/modules/system/tests/src/Functional/CsrfRequestHeaderTest.php index 0bb9016c4..970714173 100644 --- a/core/modules/system/tests/src/Functional/CsrfRequestHeaderTest.php +++ b/core/modules/system/tests/src/Functional/CsrfRequestHeaderTest.php @@ -19,6 +19,11 @@ class CsrfRequestHeaderTest extends BrowserTestBase { */ public static $modules = ['system', 'csrf_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests access to routes protected by CSRF request header requirements. * diff --git a/core/modules/system/tests/src/Functional/Database/SelectPagerDefaultTest.php b/core/modules/system/tests/src/Functional/Database/SelectPagerDefaultTest.php index 16f1074d4..a38a5f018 100644 --- a/core/modules/system/tests/src/Functional/Database/SelectPagerDefaultTest.php +++ b/core/modules/system/tests/src/Functional/Database/SelectPagerDefaultTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Database; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Database\Database; use Symfony\Component\HttpFoundation\Request; @@ -12,6 +13,11 @@ */ class SelectPagerDefaultTest extends DatabaseTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Confirms that a pager query returns the correct results. * @@ -24,7 +30,7 @@ public function testEvenPagerQuery() { // information forward to the actual query on the other side of the // HTTP request. $limit = 2; - $count = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); + $count = Database::getConnection()->query('SELECT COUNT(*) FROM {test}')->fetchField(); $correct_number = $limit; $num_pages = floor($count / $limit); @@ -42,7 +48,7 @@ public function testEvenPagerQuery() { $correct_number = $count - ($limit * $page); } - $this->assertCount($correct_number, $data->names, format_string('Correct number of records returned by pager: @number', ['@number' => $correct_number])); + $this->assertCount($correct_number, $data->names, new FormattableMarkup('Correct number of records returned by pager: @number', ['@number' => $correct_number])); } } @@ -58,7 +64,7 @@ public function testOddPagerQuery() { // information forward to the actual query on the other side of the // HTTP request. $limit = 2; - $count = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField(); + $count = Database::getConnection()->query('SELECT COUNT(*) FROM {test_task}')->fetchField(); $correct_number = $limit; $num_pages = floor($count / $limit); @@ -76,7 +82,7 @@ public function testOddPagerQuery() { $correct_number = $count - ($limit * $page); } - $this->assertCount($correct_number, $data->names, format_string('Correct number of records returned by pager: @number', ['@number' => $correct_number])); + $this->assertCount($correct_number, $data->names, new FormattableMarkup('Correct number of records returned by pager: @number', ['@number' => $correct_number])); } } diff --git a/core/modules/system/tests/src/Functional/Database/SelectTableSortDefaultTest.php b/core/modules/system/tests/src/Functional/Database/SelectTableSortDefaultTest.php index 2904d31fa..7e2e2e9a6 100644 --- a/core/modules/system/tests/src/Functional/Database/SelectTableSortDefaultTest.php +++ b/core/modules/system/tests/src/Functional/Database/SelectTableSortDefaultTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\system\Functional\Database; +use Drupal\Component\Render\FormattableMarkup; + /** * Tests the tablesort query extender. * @@ -9,6 +11,11 @@ */ class SelectTableSortDefaultTest extends DatabaseTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Confirms that a tablesort query returns the correct results. * @@ -60,8 +67,8 @@ public function testTableSortQueryFirst() { $first = array_shift($data->tasks); $last = array_pop($data->tasks); - $this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', ['@field' => $sort['field'], '@sort' => $sort['sort']])); - $this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', ['@field' => $sort['field'], '@sort' => $sort['sort']])); + $this->assertEqual($first->task, $sort['first'], new FormattableMarkup('Items appear in the correct order sorting by @field @sort.', ['@field' => $sort['field'], '@sort' => $sort['sort']])); + $this->assertEqual($last->task, $sort['last'], new FormattableMarkup('Items appear in the correct order sorting by @field @sort.', ['@field' => $sort['field'], '@sort' => $sort['sort']])); } } diff --git a/core/modules/system/tests/src/Functional/Database/TemporaryQueryTest.php b/core/modules/system/tests/src/Functional/Database/TemporaryQueryTest.php index 3e2cf5259..c31b96bb5 100644 --- a/core/modules/system/tests/src/Functional/Database/TemporaryQueryTest.php +++ b/core/modules/system/tests/src/Functional/Database/TemporaryQueryTest.php @@ -16,6 +16,11 @@ class TemporaryQueryTest extends DatabaseTestBase { */ public static $modules = ['database_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Returns the number of rows of a table. */ @@ -38,7 +43,7 @@ public function testTemporaryQuery() { $this->fail('The creation of the temporary table failed.'); } - // Now try to run two db_query_temporary() in the same request. + // Now try to run two temporary queries in the same request. $table_name_test = $connection->queryTemporary('SELECT name FROM {test}', []); $table_name_task = $connection->queryTemporary('SELECT pid FROM {test_task}', []); diff --git a/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php b/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php index 488fa6ef4..cb57699c5 100644 --- a/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php +++ b/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php @@ -18,6 +18,11 @@ class DrupalDateTimeTest extends BrowserTestBase { */ public static $modules = []; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test setup. */ @@ -88,7 +93,7 @@ public function testDateTimezone() { $this->drupalPostForm('user/' . $test_user->id() . '/edit', $edit, t('Save')); // Reload the user and reset the timezone in AccountProxy::setAccount(). - \Drupal::entityManager()->getStorage('user')->resetCache(); + \Drupal::entityTypeManager()->getStorage('user')->resetCache(); $this->container->get('current_user')->setAccount(User::load($test_user->id())); // Create a date object with an unspecified timezone, which should diff --git a/core/modules/system/tests/src/Functional/DrupalKernel/ContainerRebuildWebTest.php b/core/modules/system/tests/src/Functional/DrupalKernel/ContainerRebuildWebTest.php index 828b92f28..93deab12b 100644 --- a/core/modules/system/tests/src/Functional/DrupalKernel/ContainerRebuildWebTest.php +++ b/core/modules/system/tests/src/Functional/DrupalKernel/ContainerRebuildWebTest.php @@ -16,6 +16,11 @@ class ContainerRebuildWebTest extends BrowserTestBase { */ public static $modules = ['service_provider_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Sets a different deployment identifier. */ diff --git a/core/modules/system/tests/src/Functional/DrupalKernel/ContentNegotiationTest.php b/core/modules/system/tests/src/Functional/DrupalKernel/ContentNegotiationTest.php index 58157369b..a57b941e2 100644 --- a/core/modules/system/tests/src/Functional/DrupalKernel/ContentNegotiationTest.php +++ b/core/modules/system/tests/src/Functional/DrupalKernel/ContentNegotiationTest.php @@ -11,6 +11,11 @@ */ class ContentNegotiationTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Verifies HTML responses for bogus Accept headers. * diff --git a/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php b/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php index a16614f14..3e8d187db 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityAddUITest.php @@ -19,6 +19,11 @@ class EntityAddUITest extends BrowserTestBase { */ public static $modules = ['entity_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the add page for an entity type using bundle entities. */ diff --git a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php index 36d8d0670..a5555aa15 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php @@ -84,7 +84,7 @@ protected function setUp() { // Reload the entity now that a new field has been added to it. $storage = $this->container - ->get('entity.manager') + ->get('entity_type.manager') ->getStorage($this->entity->getEntityTypeId()); $storage->resetCache(); $this->entity = $storage->load($this->entity->id()); @@ -203,7 +203,7 @@ protected function getAdditionalCacheTagsForEntityListing() { * chooses 'default'. */ protected function selectViewMode($entity_type) { - $view_modes = \Drupal::entityManager() + $view_modes = \Drupal::entityTypeManager() ->getStorage('entity_view_mode') ->loadByProperties(['targetEntityType' => $entity_type]); @@ -265,8 +265,10 @@ protected function createReferenceTestEntities($referenced_entity) { ], ], ])->save(); + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); if (!$this->entity->getEntityType()->hasHandlerClass('view_builder')) { - entity_get_display($entity_type, $bundle, 'full') + $display_repository->getViewDisplay($entity_type, $bundle, 'full') ->setComponent($field_name, [ 'type' => 'entity_reference_label', ]) @@ -274,7 +276,7 @@ protected function createReferenceTestEntities($referenced_entity) { } else { $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId()); - entity_get_display($entity_type, $bundle, 'full') + $display_repository->getViewDisplay($entity_type, $bundle, 'full') ->setComponent($field_name, [ 'type' => 'entity_reference_entity_view', 'settings' => [ @@ -285,7 +287,7 @@ protected function createReferenceTestEntities($referenced_entity) { } // Create an entity that does reference the entity being tested. - $label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label'); + $label_key = \Drupal::entityTypeManager()->getDefinition($entity_type)->getKey('label'); $referencing_entity = $this->container->get('entity_type.manager') ->getStorage($entity_type) ->create([ @@ -352,7 +354,7 @@ public function testReferencedEntity() { $view_cache_tag = []; if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) { - $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type) + $view_cache_tag = \Drupal::entityTypeManager()->getViewBuilder($entity_type) ->getCacheTags(); } @@ -360,7 +362,7 @@ public function testReferencedEntity() { $cache_context_tags = $context_metadata->getCacheTags(); // Generate the cache tags for the (non) referencing entities. - $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()); + $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityTypeManager()->getViewBuilder('entity_test')->getCacheTags()); // Includes the main entity's cache tags, since this entity references it. $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->entity->getCacheTags()); $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity)); @@ -368,7 +370,7 @@ public function testReferencedEntity() { $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $cache_context_tags); $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['rendered']); - $non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()); + $non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity->getCacheTags(), \Drupal::entityTypeManager()->getViewBuilder('entity_test')->getCacheTags()); $non_referencing_entity_cache_tags = Cache::mergeTags($non_referencing_entity_cache_tags, ['rendered']); // Generate the cache tags for all two possible entity listing paths. @@ -490,7 +492,9 @@ public function testReferencedEntity() { // entities, but not for any other routes. $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId()); $this->pass("Test modification of referenced entity's '$referenced_entity_view_mode' display.", 'Debug'); - $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $referenced_entity_view_mode); + /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */ + $display_repository = \Drupal::service('entity_display.repository'); + $entity_display = $display_repository->getViewDisplay($entity_type, $this->entity->bundle(), $referenced_entity_view_mode); $entity_display->save(); $this->verifyPageCache($referencing_entity_url, 'MISS'); $this->verifyPageCache($listing_url, 'MISS'); @@ -629,7 +633,7 @@ public function testReferencedEntity() { $this->verifyPageCache($non_referencing_entity_url, 'HIT'); // Verify cache hits. - $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()); + $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityTypeManager()->getViewBuilder('entity_test')->getCacheTags()); $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['http_response', 'rendered']); $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing()); @@ -674,7 +678,7 @@ protected function createCacheId(array $keys, array $contexts) { protected function verifyRenderCache($cid, array $tags, $redirected_cid = NULL) { // Also verify the existence of an entity render cache entry. $cache_entry = \Drupal::cache('render')->get($cid); - $this->assertTrue($cache_entry, 'A render cache entry exists.'); + $this->assertInstanceOf(\stdClass::class, $cache_entry, 'A render cache entry exists.'); sort($cache_entry->tags); sort($tags); $this->assertIdentical($cache_entry->tags, $tags); diff --git a/core/modules/system/tests/src/Functional/Entity/EntityFormTest.php b/core/modules/system/tests/src/Functional/Entity/EntityFormTest.php index 4f0b7f0f7..eebe04058 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityFormTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityFormTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Entity; +use Drupal\Component\Render\FormattableMarkup; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Tests\BrowserTestBase; @@ -19,6 +20,11 @@ class EntityFormTest extends BrowserTestBase { */ public static $modules = ['entity_test', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $web_user = $this->drupalCreateUser(['administer entity_test content', 'view test entity']); @@ -76,21 +82,21 @@ protected function doTestFormCRUD($entity_type) { $this->drupalPostForm($entity_type . '/add', $edit, t('Save')); $entity = $this->loadEntityByName($entity_type, $name1); - $this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', ['%entity_type' => $entity_type])); + $this->assertNotNull($entity, new FormattableMarkup('%entity_type: Entity found in the database.', ['%entity_type' => $entity_type])); $edit['name[0][value]'] = $name2; $this->drupalPostForm($entity_type . '/manage/' . $entity->id() . '/edit', $edit, t('Save')); $entity = $this->loadEntityByName($entity_type, $name1); - $this->assertFalse($entity, format_string('%entity_type: The entity has been modified.', ['%entity_type' => $entity_type])); + $this->assertNull($entity, new FormattableMarkup('%entity_type: The entity has been modified.', ['%entity_type' => $entity_type])); $entity = $this->loadEntityByName($entity_type, $name2); - $this->assertTrue($entity, format_string('%entity_type: Modified entity found in the database.', ['%entity_type' => $entity_type])); - $this->assertNotEqual($entity->name->value, $name1, format_string('%entity_type: The entity name has been modified.', ['%entity_type' => $entity_type])); + $this->assertNotNull($entity, new FormattableMarkup('%entity_type: Modified entity found in the database.', ['%entity_type' => $entity_type])); + $this->assertNotEqual($entity->name->value, $name1, new FormattableMarkup('%entity_type: The entity name has been modified.', ['%entity_type' => $entity_type])); $this->drupalGet($entity_type . '/manage/' . $entity->id() . '/edit'); $this->clickLink(t('Delete')); $this->drupalPostForm(NULL, [], t('Delete')); $entity = $this->loadEntityByName($entity_type, $name2); - $this->assertFalse($entity, format_string('%entity_type: Entity not found in the database.', ['%entity_type' => $entity_type])); + $this->assertNull($entity, new FormattableMarkup('%entity_type: Entity not found in the database.', ['%entity_type' => $entity_type])); } /** @@ -111,26 +117,26 @@ protected function doTestMultilingualFormCRUD($entity_type_id) { $this->drupalPostForm($entity_type_id . '/add', $edit, t('Save')); $entity = $this->loadEntityByName($entity_type_id, $name1); - $this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', ['%entity_type' => $entity_type_id])); + $this->assertNotNull($entity, new FormattableMarkup('%entity_type: Entity found in the database.', ['%entity_type' => $entity_type_id])); // Add a translation to the newly created entity without using the Content // translation module. $entity->addTranslation('ro', ['name' => $name1_ro])->save(); $translated_entity = $this->loadEntityByName($entity_type_id, $name1)->getTranslation('ro'); - $this->assertEqual($translated_entity->name->value, $name1_ro, format_string('%entity_type: The translation has been added.', ['%entity_type' => $entity_type_id])); + $this->assertEqual($translated_entity->name->value, $name1_ro, new FormattableMarkup('%entity_type: The translation has been added.', ['%entity_type' => $entity_type_id])); $edit['name[0][value]'] = $name2_ro; $this->drupalPostForm('ro/' . $entity_type_id . '/manage/' . $entity->id() . '/edit', $edit, t('Save')); $translated_entity = $this->loadEntityByName($entity_type_id, $name1)->getTranslation('ro'); - $this->assertTrue($translated_entity, format_string('%entity_type: Modified translation found in the database.', ['%entity_type' => $entity_type_id])); - $this->assertEqual($translated_entity->name->value, $name2_ro, format_string('%entity_type: The name of the translation has been modified.', ['%entity_type' => $entity_type_id])); + $this->assertNotNull($translated_entity, new FormattableMarkup('%entity_type: Modified translation found in the database.', ['%entity_type' => $entity_type_id])); + $this->assertEqual($translated_entity->name->value, $name2_ro, new FormattableMarkup('%entity_type: The name of the translation has been modified.', ['%entity_type' => $entity_type_id])); $this->drupalGet('ro/' . $entity_type_id . '/manage/' . $entity->id() . '/edit'); $this->clickLink(t('Delete')); $this->drupalPostForm(NULL, [], t('Delete Romanian translation')); $entity = $this->loadEntityByName($entity_type_id, $name1); - $this->assertNotNull($entity, format_string('%entity_type: The original entity still exists.', ['%entity_type' => $entity_type_id])); - $this->assertFalse($entity->hasTranslation('ro'), format_string('%entity_type: Entity translation does not exist anymore.', ['%entity_type' => $entity_type_id])); + $this->assertNotNull($entity, new FormattableMarkup('%entity_type: The original entity still exists.', ['%entity_type' => $entity_type_id])); + $this->assertFalse($entity->hasTranslation('ro'), new FormattableMarkup('%entity_type: Entity translation does not exist anymore.', ['%entity_type' => $entity_type_id])); } /** @@ -139,7 +145,7 @@ protected function doTestMultilingualFormCRUD($entity_type_id) { protected function loadEntityByName($entity_type, $name) { // Always load the entity from the database to ensure that changes are // correctly picked up. - $entity_storage = $this->container->get('entity.manager')->getStorage($entity_type); + $entity_storage = $this->container->get('entity_type.manager')->getStorage($entity_type); $entity_storage->resetCache(); $entities = $entity_storage->loadByProperties(['name' => $name]); return $entities ? current($entities) : NULL; diff --git a/core/modules/system/tests/src/Functional/Entity/EntityListBuilderTest.php b/core/modules/system/tests/src/Functional/Entity/EntityListBuilderTest.php index 94225f4e9..5a03e80ef 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityListBuilderTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityListBuilderTest.php @@ -18,6 +18,11 @@ class EntityListBuilderTest extends BrowserTestBase { */ public static $modules = ['entity_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -58,7 +63,7 @@ public function testPager() { */ public function testCacheContexts() { /** @var \Drupal\Core\Entity\EntityListBuilderInterface $list_builder */ - $list_builder = $this->container->get('entity.manager')->getListBuilder('entity_test'); + $list_builder = $this->container->get('entity_type.manager')->getListBuilder('entity_test'); $build = $list_builder->render(); $this->container->get('renderer')->renderRoot($build); diff --git a/core/modules/system/tests/src/Functional/Entity/EntityOperationsTest.php b/core/modules/system/tests/src/Functional/Entity/EntityOperationsTest.php index bcb385bf1..07d58b651 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityOperationsTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityOperationsTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Entity; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Tests\BrowserTestBase; /** @@ -18,6 +19,11 @@ class EntityOperationsTest extends BrowserTestBase { */ public static $modules = ['entity_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -36,7 +42,7 @@ public function testEntityOperationAlter() { $roles = user_roles(); foreach ($roles as $role) { $this->assertLinkByHref($role->toUrl()->toString() . '/test_operation'); - $this->assertLink(format_string('Test Operation: @label', ['@label' => $role->label()])); + $this->assertLink(new FormattableMarkup('Test Operation: @label', ['@label' => $role->label()])); } } diff --git a/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php b/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php index aefaec23a..07468af5c 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Entity\EntityReferenceSelection; +use Drupal\Component\Render\FormattableMarkup; use Drupal\comment\Tests\CommentTestTrait; use Drupal\Component\Utility\Html; use Drupal\Core\Language\LanguageInterface; @@ -86,7 +87,7 @@ protected function assertReferenceable(array $selection_options, $tests, $handle foreach ($tests as $test) { foreach ($test['arguments'] as $arguments) { $result = call_user_func_array([$handler, 'getReferenceableEntities'], $arguments); - $this->assertEqual($result, $test['result'], format_string('Valid result set returned by @handler.', ['@handler' => $handler_name])); + $this->assertEqual($result, $test['result'], new FormattableMarkup('Valid result set returned by @handler.', ['@handler' => $handler_name])); $result = call_user_func_array([$handler, 'countReferenceableEntities'], $arguments); if (!empty($test['result'])) { @@ -97,7 +98,7 @@ protected function assertReferenceable(array $selection_options, $tests, $handle $count = 0; } - $this->assertEqual($result, $count, format_string('Valid count returned by @handler.', ['@handler' => $handler_name])); + $this->assertEqual($result, $count, new FormattableMarkup('Valid count returned by @handler.', ['@handler' => $handler_name])); } } } diff --git a/core/modules/system/tests/src/Functional/Entity/EntityRevisionsTest.php b/core/modules/system/tests/src/Functional/Entity/EntityRevisionsTest.php index bf8bcb337..18bfc1824 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityRevisionsTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityRevisionsTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Entity; +use Drupal\Component\Render\FormattableMarkup; use Drupal\entity_test\Entity\EntityTestMulRev; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -23,6 +24,11 @@ class EntityRevisionsTest extends BrowserTestBase { */ public static $modules = ['entity_test', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to administer entity_test content. * @@ -79,7 +85,7 @@ protected function runRevisionsTests($entity_type) { ]); $field->save(); - entity_get_form_display($entity_type, $entity_type, 'default') + \Drupal::service('entity_display.repository')->getFormDisplay($entity_type, $entity_type, 'default') ->setComponent('translatable_test_field') ->save(); @@ -139,9 +145,9 @@ protected function runRevisionsTests($entity_type) { } // Check that the fields and properties contain new content. - $this->assertTrue($entity->revision_id->value > $legacy_revision_id, format_string('%entity_type: Revision ID changed.', ['%entity_type' => $entity_type])); - $this->assertNotEqual($entity->name->value, $legacy_name, format_string('%entity_type: Name changed.', ['%entity_type' => $entity_type])); - $this->assertNotEqual($entity->translatable_test_field->value, $legacy_text, format_string('%entity_type: Text changed.', ['%entity_type' => $entity_type])); + $this->assertTrue($entity->revision_id->value > $legacy_revision_id, new FormattableMarkup('%entity_type: Revision ID changed.', ['%entity_type' => $entity_type])); + $this->assertNotEqual($entity->name->value, $legacy_name, new FormattableMarkup('%entity_type: Name changed.', ['%entity_type' => $entity_type])); + $this->assertNotEqual($entity->translatable_test_field->value, $legacy_text, new FormattableMarkup('%entity_type: Text changed.', ['%entity_type' => $entity_type])); } $revisions = $storage->loadMultipleRevisions($revision_ids); @@ -150,29 +156,29 @@ protected function runRevisionsTests($entity_type) { $entity_revision = $revisions[$revision_ids[$i]]; // Check if properties and fields contain the revision specific content. - $this->assertEqual($entity_revision->revision_id->value, $revision_ids[$i], format_string('%entity_type: Revision ID matches.', ['%entity_type' => $entity_type])); - $this->assertEqual($entity_revision->name->value, $values['en'][$i]['name'], format_string('%entity_type: Name matches.', ['%entity_type' => $entity_type])); - $this->assertEqual($entity_revision->translatable_test_field[0]->value, $values['en'][$i]['translatable_test_field'][0], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type])); - $this->assertEqual($entity_revision->translatable_test_field[1]->value, $values['en'][$i]['translatable_test_field'][1], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type])); + $this->assertEqual($entity_revision->revision_id->value, $revision_ids[$i], new FormattableMarkup('%entity_type: Revision ID matches.', ['%entity_type' => $entity_type])); + $this->assertEqual($entity_revision->name->value, $values['en'][$i]['name'], new FormattableMarkup('%entity_type: Name matches.', ['%entity_type' => $entity_type])); + $this->assertEqual($entity_revision->translatable_test_field[0]->value, $values['en'][$i]['translatable_test_field'][0], new FormattableMarkup('%entity_type: Text matches.', ['%entity_type' => $entity_type])); + $this->assertEqual($entity_revision->translatable_test_field[1]->value, $values['en'][$i]['translatable_test_field'][1], new FormattableMarkup('%entity_type: Text matches.', ['%entity_type' => $entity_type])); // Check the translated values. if ($entity->getEntityType()->isTranslatable()) { $revision_translation = $entity_revision->getTranslation('de'); - $this->assertEqual($revision_translation->name->value, $values['de'][$i]['name'], format_string('%entity_type: Name matches.', ['%entity_type' => $entity_type])); - $this->assertEqual($revision_translation->translatable_test_field[0]->value, $values['de'][$i]['translatable_test_field'][0], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type])); - $this->assertEqual($revision_translation->translatable_test_field[1]->value, $values['de'][$i]['translatable_test_field'][1], format_string('%entity_type: Text matches.', ['%entity_type' => $entity_type])); + $this->assertEqual($revision_translation->name->value, $values['de'][$i]['name'], new FormattableMarkup('%entity_type: Name matches.', ['%entity_type' => $entity_type])); + $this->assertEqual($revision_translation->translatable_test_field[0]->value, $values['de'][$i]['translatable_test_field'][0], new FormattableMarkup('%entity_type: Text matches.', ['%entity_type' => $entity_type])); + $this->assertEqual($revision_translation->translatable_test_field[1]->value, $values['de'][$i]['translatable_test_field'][1], new FormattableMarkup('%entity_type: Text matches.', ['%entity_type' => $entity_type])); } // Check non-revisioned values are loaded. - $this->assertTrue(isset($entity_revision->created->value), format_string('%entity_type: Non-revisioned field is loaded.', ['%entity_type' => $entity_type])); - $this->assertEqual($entity_revision->created->value, $values['en'][2]['created'], format_string('%entity_type: Non-revisioned field value is the same between revisions.', ['%entity_type' => $entity_type])); + $this->assertTrue(isset($entity_revision->created->value), new FormattableMarkup('%entity_type: Non-revisioned field is loaded.', ['%entity_type' => $entity_type])); + $this->assertEqual($entity_revision->created->value, $values['en'][2]['created'], new FormattableMarkup('%entity_type: Non-revisioned field value is the same between revisions.', ['%entity_type' => $entity_type])); } // Confirm the correct revision text appears in the edit form. $entity = $storage->load($entity->id->value); $this->drupalGet($entity_type . '/manage/' . $entity->id->value . '/edit'); - $this->assertFieldById('edit-name-0-value', $entity->name->value, format_string('%entity_type: Name matches in UI.', ['%entity_type' => $entity_type])); - $this->assertFieldById('edit-translatable-test-field-0-value', $entity->translatable_test_field->value, format_string('%entity_type: Text matches in UI.', ['%entity_type' => $entity_type])); + $this->assertFieldById('edit-name-0-value', $entity->name->value, new FormattableMarkup('%entity_type: Name matches in UI.', ['%entity_type' => $entity_type])); + $this->assertFieldById('edit-translatable-test-field-0-value', $entity->translatable_test_field->value, new FormattableMarkup('%entity_type: Text matches in UI.', ['%entity_type' => $entity_type])); } /** diff --git a/core/modules/system/tests/src/Functional/Entity/EntityTranslationFormTest.php b/core/modules/system/tests/src/Functional/Entity/EntityTranslationFormTest.php index e008e71e1..5cf93e727 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityTranslationFormTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityTranslationFormTest.php @@ -5,6 +5,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\field\Entity\FieldStorageConfig; use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\node\Entity\Node; use Drupal\Tests\BrowserTestBase; /** @@ -21,6 +22,11 @@ class EntityTranslationFormTest extends BrowserTestBase { */ public static $modules = ['entity_test', 'language', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected $langcodes; protected function setUp() { @@ -94,7 +100,7 @@ public function testEntityFormLanguage() { // Check to make sure the node was created. $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); - $this->assertTrue($node, 'Node found in database.'); + $this->assertInstanceOf(Node::class, $node, 'Node found in database.'); // Make body translatable. $field_storage = FieldStorageConfig::loadByName('node', 'body'); diff --git a/core/modules/system/tests/src/Functional/Entity/EntityViewControllerTest.php b/core/modules/system/tests/src/Functional/Entity/EntityViewControllerTest.php index 363736975..b5c1db7c3 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityViewControllerTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityViewControllerTest.php @@ -19,6 +19,11 @@ class EntityViewControllerTest extends BrowserTestBase { */ public static $modules = ['entity_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Array of test entities. * @@ -85,7 +90,8 @@ public function testEntityViewController() { */ public function testFieldItemAttributes() { // Make sure the test field will be rendered. - entity_get_display('entity_test', 'entity_test', 'default') + \Drupal::service('entity_display.repository') + ->getViewDisplay('entity_test', 'entity_test') ->setComponent('field_test_text', ['type' => 'text_default']) ->save(); @@ -99,7 +105,7 @@ public function testFieldItemAttributes() { // field item HTML markup. $this->drupalGet('entity_test/' . $entity->id()); $xpath = $this->xpath('//div[@data-field-item-attr="foobar"]/p[text()=:value]', [':value' => $test_value]); - $this->assertTrue($xpath, 'The field item attribute has been found in the rendered output of the field.'); + $this->assertNotEmpty($xpath, 'The field item attribute has been found in the rendered output of the field.'); // Enable the RDF module to ensure that two modules can add attributes to // the same field item. @@ -116,7 +122,7 @@ public function testFieldItemAttributes() { // are rendered in the field item HTML markup. $this->drupalGet('entity_test/' . $entity->id()); $xpath = $this->xpath('//div[@data-field-item-attr="foobar" and @property="schema:text"]/p[text()=:value]', [':value' => $test_value]); - $this->assertTrue($xpath, 'The field item attributes from both modules have been found in the rendered output of the field.'); + $this->assertNotEmpty($xpath, 'The field item attributes from both modules have been found in the rendered output of the field.'); } /** @@ -143,7 +149,7 @@ protected function createTestEntity($entity_type) { 'bundle' => $entity_type, 'name' => $this->randomMachineName(), ]; - return $this->container->get('entity.manager')->getStorage($entity_type)->create($data); + return $this->container->get('entity_type.manager')->getStorage($entity_type)->create($data); } } diff --git a/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php index 27f896b9a..e62d343ce 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php @@ -31,7 +31,7 @@ public function testEntityUri() { // Generate the standardized entity cache tags. $cache_tag = $this->entity->getCacheTags(); - $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)->getCacheTags(); + $view_cache_tag = \Drupal::entityTypeManager()->getViewBuilder($entity_type)->getCacheTags(); $render_cache_tag = 'rendered'; $this->pass("Test entity.", 'Debug'); @@ -42,7 +42,7 @@ public function testEntityUri() { // Also verify the existence of an entity render cache entry, if this entity // type supports render caching. - if (\Drupal::entityManager()->getDefinition($entity_type)->isRenderCacheable()) { + if (\Drupal::entityTypeManager()->getDefinition($entity_type)->isRenderCacheable()) { $cache_keys = ['entity_view', $entity_type, $this->entity->id(), $view_mode]; $cid = $this->createCacheId($cache_keys, $entity_cache_contexts); $redirected_cid = NULL; @@ -66,7 +66,7 @@ public function testEntityUri() { // Verify that after modifying the entity's display, there is a cache miss. $this->pass("Test modification of entity's '$view_mode' display.", 'Debug'); - $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $view_mode); + $entity_display = \Drupal::service('entity_display.repository')->getViewDisplay($entity_type, $this->entity->bundle(), $view_mode); $entity_display->save(); $this->verifyPageCache($entity_url, 'MISS'); diff --git a/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php b/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php index 00148b466..1f4cff344 100644 --- a/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php +++ b/core/modules/system/tests/src/Functional/Entity/Traits/EntityDefinitionTestTrait.php @@ -227,12 +227,19 @@ protected function updateEntityTypeToRevisionableAndTranslatable($perform_update * @param bool $is_revisionable * (optional) If the base field should be revisionable or not. Defaults to * FALSE. + * @param bool $set_label + * (optional) If the base field should have a label or not. Defaults to + * TRUE. */ - protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE) { + protected function addBaseField($type = 'string', $entity_type_id = 'entity_test_update', $is_revisionable = FALSE, $set_label = TRUE) { $definitions['new_base_field'] = BaseFieldDefinition::create($type) ->setName('new_base_field') - ->setRevisionable($is_revisionable) - ->setLabel(t('A new base field')); + ->setRevisionable($is_revisionable); + + if ($set_label) { + $definitions['new_base_field']->setLabel(t('A new base field')); + } + $this->state->set($entity_type_id . '.additional_base_field_definitions', $definitions); } @@ -456,7 +463,6 @@ protected function getUpdatedEntityTypeDefinition($revisionable = FALSE, $transl $this->container->get('entity_type.manager')->clearCachedDefinitions(); $this->container->get('entity_type.bundle.info')->clearCachedBundles(); $this->container->get('entity_field.manager')->clearCachedFieldDefinitions(); - $this->container->get('entity_type.repository')->clearCachedDefinitions(); return $entity_type; } diff --git a/core/modules/system/tests/src/Functional/Entity/Update/MoveRevisionMetadataFieldsUpdateTest.php b/core/modules/system/tests/src/Functional/Entity/Update/MoveRevisionMetadataFieldsUpdateTest.php index 2334f3373..4333c1a30 100644 --- a/core/modules/system/tests/src/Functional/Entity/Update/MoveRevisionMetadataFieldsUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Entity/Update/MoveRevisionMetadataFieldsUpdateTest.php @@ -34,6 +34,7 @@ public function setDatabaseDumpFiles() { * @expectedDeprecation The revision_user revision metadata key is not set for entity type: entity_test_mul_revlog See: https://www.drupal.org/node/2831499 * @expectedDeprecation The revision_created revision metadata key is not set for entity type: entity_test_mul_revlog See: https://www.drupal.org/node/2831499 * @expectedDeprecation The revision_log_message revision metadata key is not set for entity type: entity_test_mul_revlog See: https://www.drupal.org/node/2831499 + * @expectedDeprecation Support for pre-8.3.0 revision table names in imported views is deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Imported views must reference the correct tables. See https://www.drupal.org/node/2831499 */ public function testSystemUpdate8400() { $this->runUpdates(); diff --git a/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTestBase.php b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTestBase.php index 05876bff6..33f2ca5cd 100644 --- a/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTestBase.php +++ b/core/modules/system/tests/src/Functional/Entity/Update/SqlContentEntityStorageSchemaConverterTestBase.php @@ -14,13 +14,6 @@ abstract class SqlContentEntityStorageSchemaConverterTestBase extends UpdatePath use EntityDefinitionTestTrait; use ExpectDeprecationTrait; - /** - * The entity manager service. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - /** * The entity definition update manager. * @@ -55,7 +48,6 @@ abstract class SqlContentEntityStorageSchemaConverterTestBase extends UpdatePath protected function setUp() { parent::setUp(); - $this->entityManager = \Drupal::entityManager(); $this->entityDefinitionUpdateManager = \Drupal::entityDefinitionUpdateManager(); $this->lastInstalledSchemaRepository = \Drupal::service('entity.last_installed_schema.repository'); $this->installedStorageSchema = \Drupal::keyValue('entity.storage_schema.sql'); diff --git a/core/modules/system/tests/src/Functional/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php b/core/modules/system/tests/src/Functional/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php index 07352cbc0..7efc11b74 100644 --- a/core/modules/system/tests/src/Functional/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php @@ -22,6 +22,11 @@ class UpdateApiEntityDefinitionUpdateTest extends BrowserTestBase { */ protected static $modules = ['entity_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/File/ConfigTest.php b/core/modules/system/tests/src/Functional/File/ConfigTest.php index caf691591..f44e8a924 100644 --- a/core/modules/system/tests/src/Functional/File/ConfigTest.php +++ b/core/modules/system/tests/src/Functional/File/ConfigTest.php @@ -11,6 +11,11 @@ */ class ConfigTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $this->drupalLogin($this->drupalCreateUser(['administer site configuration'])); @@ -27,7 +32,6 @@ public function testFileConfigurationPage() { // upon form submission. $file_path = $this->publicFilesDirectory; $fields = [ - 'file_temporary_path' => $file_path . '/file_config_page_test/temporary', 'file_default_scheme' => 'private', ]; diff --git a/core/modules/system/tests/src/Functional/File/FileSaveHtaccessLoggingTest.php b/core/modules/system/tests/src/Functional/File/FileSaveHtaccessLoggingTest.php index 003373de7..62a0b7a23 100644 --- a/core/modules/system/tests/src/Functional/File/FileSaveHtaccessLoggingTest.php +++ b/core/modules/system/tests/src/Functional/File/FileSaveHtaccessLoggingTest.php @@ -2,7 +2,7 @@ namespace Drupal\Tests\system\Functional\File; -use Drupal\Component\PhpStorage\FileStorage; +use Drupal\Component\FileSecurity\FileSecurity; use Drupal\Tests\BrowserTestBase; /** @@ -14,6 +14,11 @@ class FileSaveHtaccessLoggingTest extends BrowserTestBase { protected static $modules = ['dblog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests file_save_htaccess(). */ @@ -24,14 +29,16 @@ public function testHtaccessSave() { // Verify that file_save_htaccess() returns FALSE if .htaccess cannot be // written and writes a correctly formatted message to the error log. Set // $private to TRUE so all possible .htaccess lines are written. - $this->assertFalse(file_save_htaccess($private, TRUE)); + /** @var \Drupal\Core\File\HtaccessWriterInterface $htaccess */ + $htaccess = \Drupal::service('file.htaccess_writer'); + $this->assertFalse($htaccess->write($private, TRUE)); $this->drupalLogin($this->rootUser); $this->drupalGet('admin/reports/dblog'); $this->clickLink("Security warning: Couldn't write .htaccess file. Please…"); - $lines = FileStorage::htaccessLines(TRUE); + $lines = FileSecurity::htaccessLines(TRUE); foreach (array_filter(explode("\n", $lines)) as $line) { - $this->assertEscaped($line); + $this->assertSession()->assertEscaped($line); } } diff --git a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php index a15a92a6d..3ec2cbccd 100644 --- a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php +++ b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php @@ -18,6 +18,11 @@ class FileTransferTest extends BrowserTestBase { protected $password = 'password'; protected $port = '42'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $this->testConnection = TestFileTransfer::factory($this->root, ['hostname' => $this->hostname, 'username' => $this->username, 'password' => $this->password, 'port' => $this->port]); diff --git a/core/modules/system/tests/src/Functional/Form/AlterTest.php b/core/modules/system/tests/src/Functional/Form/AlterTest.php index 622a3d1ea..d1f9667ac 100644 --- a/core/modules/system/tests/src/Functional/Form/AlterTest.php +++ b/core/modules/system/tests/src/Functional/Form/AlterTest.php @@ -19,6 +19,11 @@ class AlterTest extends BrowserTestBase { */ public static $modules = ['block', 'form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter(). */ diff --git a/core/modules/system/tests/src/Functional/Form/ArbitraryRebuildTest.php b/core/modules/system/tests/src/Functional/Form/ArbitraryRebuildTest.php index e35ac6d08..ce1ebbd7c 100644 --- a/core/modules/system/tests/src/Functional/Form/ArbitraryRebuildTest.php +++ b/core/modules/system/tests/src/Functional/Form/ArbitraryRebuildTest.php @@ -20,6 +20,11 @@ class ArbitraryRebuildTest extends BrowserTestBase { */ public static $modules = ['text', 'form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -37,7 +42,8 @@ protected function setUp() { 'bundle' => 'user', 'label' => 'Test a multiple valued field', ])->save(); - entity_get_form_display('user', 'user', 'register') + \Drupal::service('entity_display.repository') + ->getFormDisplay('user', 'user', 'register') ->setComponent('test_multiple', [ 'type' => 'text_textfield', 'weight' => 0, diff --git a/core/modules/system/tests/src/Functional/Form/CheckboxTest.php b/core/modules/system/tests/src/Functional/Form/CheckboxTest.php index 60fad6a2e..66f7eab1d 100644 --- a/core/modules/system/tests/src/Functional/Form/CheckboxTest.php +++ b/core/modules/system/tests/src/Functional/Form/CheckboxTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Form; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Tests\BrowserTestBase; /** @@ -19,6 +20,11 @@ class CheckboxTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + public function testFormCheckbox() { // Ensure that the checked state is determined and rendered correctly for // tricky combinations of default and return values. @@ -48,7 +54,7 @@ public function testFormCheckbox() { $checked = ($default_value === '1foobar'); } $checked_in_html = strpos($form, 'checked') !== FALSE; - $message = format_string('#default_value is %default_value #return_value is %return_value.', ['%default_value' => var_export($default_value, TRUE), '%return_value' => var_export($return_value, TRUE)]); + $message = new FormattableMarkup('#default_value is %default_value #return_value is %return_value.', ['%default_value' => var_export($default_value, TRUE), '%return_value' => var_export($return_value, TRUE)]); $this->assertIdentical($checked, $checked_in_html, $message); } } @@ -78,7 +84,7 @@ public function testFormCheckbox() { foreach ($checkboxes as $checkbox) { $checked = $checkbox->isChecked(); $name = $checkbox->getAttribute('name'); - $this->assertIdentical($checked, $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', format_string('Checkbox %name correctly checked', ['%name' => $name])); + $this->assertIdentical($checked, $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', new FormattableMarkup('Checkbox %name correctly checked', ['%name' => $name])); } // Due to Mink driver differences, we cannot submit an empty checkbox value // to drupalPostForm(), even if that empty value is the 'true' value for @@ -92,7 +98,7 @@ public function testFormCheckbox() { foreach ($checkboxes as $checkbox) { $checked = $checkbox->isChecked(); $name = (string) $checkbox->getAttribute('name'); - $this->assertIdentical($checked, $name == 'checkbox_off[0]' || $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', format_string('Checkbox %name correctly checked', ['%name' => $name])); + $this->assertIdentical($checked, $name == 'checkbox_off[0]' || $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', new FormattableMarkup('Checkbox %name correctly checked', ['%name' => $name])); } } diff --git a/core/modules/system/tests/src/Functional/Form/ConfirmFormTest.php b/core/modules/system/tests/src/Functional/Form/ConfirmFormTest.php index ad5a74b69..421ef4196 100644 --- a/core/modules/system/tests/src/Functional/Form/ConfirmFormTest.php +++ b/core/modules/system/tests/src/Functional/Form/ConfirmFormTest.php @@ -20,6 +20,11 @@ class ConfirmFormTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + public function testConfirmForm() { // Test the building of the form. $this->drupalGet('form-test/confirm-form'); diff --git a/core/modules/system/tests/src/Functional/Form/ElementTest.php b/core/modules/system/tests/src/Functional/Form/ElementTest.php index 6eefa484b..4d256f3c0 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Form; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Tests\BrowserTestBase; /** @@ -18,6 +19,11 @@ class ElementTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Tests placeholder text for elements that support placeholders. */ @@ -30,7 +36,7 @@ public function testPlaceHolderText() { ':id' => 'edit-' . $type, ':expected' => $expected, ]); - $this->assertTrue(!empty($element), format_string('Placeholder text placed in @type.', ['@type' => $type])); + $this->assertTrue(!empty($element), new FormattableMarkup('Placeholder text placed in @type.', ['@type' => $type])); } // Test to make sure textarea has the proper placeholder text. @@ -82,7 +88,7 @@ public function testOptions() { ':id' => 'edit-' . $type . '-foo', ':class' => 'description', ]); - $this->assertTrue(count($elements), format_string('Custom %type option description found.', [ + $this->assertGreaterThan(0, count($elements), new FormattableMarkup('Custom %type option description found.', [ '%type' => $type, ])); } @@ -138,8 +144,8 @@ public function testWrapperIds() { foreach (['checkboxes', 'radios'] as $type) { $element_ids = $this->xpath('//div[@id=:id]', [':id' => 'edit-' . $type]); $wrapper_ids = $this->xpath('//fieldset[@id=:id]', [':id' => 'edit-' . $type . '--wrapper']); - $this->assertTrue(count($element_ids) == 1, format_string('A single element id found for type %type', ['%type' => $type])); - $this->assertTrue(count($wrapper_ids) == 1, format_string('A single wrapper id found for type %type', ['%type' => $type])); + $this->assertTrue(count($element_ids) == 1, new FormattableMarkup('A single element id found for type %type', ['%type' => $type])); + $this->assertTrue(count($wrapper_ids) == 1, new FormattableMarkup('A single wrapper id found for type %type', ['%type' => $type])); } } @@ -182,13 +188,13 @@ public function testGroupElements() { */ public function testRequiredFieldsetsAndDetails() { $this->drupalGet('form-test/group-details'); - $this->assertFalse($this->cssSelect('summary.form-required')); + $this->assertEmpty($this->cssSelect('summary.form-required')); $this->drupalGet('form-test/group-details/1'); - $this->assertTrue($this->cssSelect('summary.form-required')); + $this->assertNotEmpty($this->cssSelect('summary.form-required')); $this->drupalGet('form-test/group-fieldset'); - $this->assertFalse($this->cssSelect('span.form-required')); + $this->assertEmpty($this->cssSelect('span.form-required')); $this->drupalGet('form-test/group-fieldset/1'); - $this->assertTrue($this->cssSelect('span.form-required')); + $this->assertNotEmpty($this->cssSelect('span.form-required')); } /** @@ -228,7 +234,7 @@ public function testFormElementErrors() { */ public function testDetailsSummaryAttributes() { $this->drupalGet('form-test/group-details'); - $this->assertTrue($this->cssSelect('summary[data-summary-attribute="test"]')); + $this->assertSession()->elementExists('css', 'summary[data-summary-attribute="test"]'); } } diff --git a/core/modules/system/tests/src/Functional/Form/ElementsAccessTest.php b/core/modules/system/tests/src/Functional/Form/ElementsAccessTest.php index 1a92893c3..2167cbb58 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementsAccessTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementsAccessTest.php @@ -18,6 +18,11 @@ class ElementsAccessTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Ensures that child values are still processed when #access = FALSE. */ diff --git a/core/modules/system/tests/src/Functional/Form/ElementsContainerTest.php b/core/modules/system/tests/src/Functional/Form/ElementsContainerTest.php index a7d404b86..874cee6dc 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementsContainerTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementsContainerTest.php @@ -18,6 +18,11 @@ class ElementsContainerTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the #optional container property. */ diff --git a/core/modules/system/tests/src/Functional/Form/ElementsLabelsTest.php b/core/modules/system/tests/src/Functional/Form/ElementsLabelsTest.php index 91f3ec4a5..0065110ee 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementsLabelsTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementsLabelsTest.php @@ -19,6 +19,11 @@ class ElementsLabelsTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test form elements, labels, title attributes and required marks output * correctly and have the correct label option class if needed. diff --git a/core/modules/system/tests/src/Functional/Form/ElementsTableSelectTest.php b/core/modules/system/tests/src/Functional/Form/ElementsTableSelectTest.php index f377c0abf..7ea6be2fc 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementsTableSelectTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementsTableSelectTest.php @@ -19,6 +19,11 @@ class ElementsTableSelectTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test the display of checkboxes when #multiple is TRUE. */ @@ -46,7 +51,7 @@ public function testMultipleFalse() { $this->assertSession()->pageTextNotContains('Empty text.'); // Test for the absence of the Select all rows tableheader. - $this->assertFalse($this->xpath('//th[@class="select-all"]')); + $this->assertSession()->elementNotExists('xpath', '//th[@class="select-all"]'); $rows = ['row1', 'row2', 'row3']; foreach ($rows as $row) { @@ -127,18 +132,18 @@ public function testMultipleFalseSubmit() { public function testAdvancedSelect() { // When #multiple = TRUE a Select all checkbox should be displayed by default. $this->drupalGet('form_test/tableselect/advanced-select/multiple-true-default'); - $this->xpath('//th[@class="select-all"]'); + $this->assertSession()->elementExists('xpath', '//th[@class="select-all"]'); // When #js_select is set to FALSE, a "Select all" checkbox should not be displayed. $this->drupalGet('form_test/tableselect/advanced-select/multiple-true-no-advanced-select'); - $this->assertFalse($this->xpath('//th[@class="select-all"]')); + $this->assertSession()->elementNotExists('xpath', '//th[@class="select-all"]'); // A "Select all" checkbox never makes sense when #multiple = FALSE, regardless of the value of #js_select. $this->drupalGet('form_test/tableselect/advanced-select/multiple-false-default'); - $this->assertFalse($this->xpath('//th[@class="select-all"]')); + $this->assertSession()->elementNotExists('xpath', '//th[@class="select-all"]'); $this->drupalGet('form_test/tableselect/advanced-select/multiple-false-advanced-select'); - $this->assertFalse($this->xpath('//th[@class="select-all"]')); + $this->assertSession()->elementNotExists('xpath', '//th[@class="select-all"]'); } /** diff --git a/core/modules/system/tests/src/Functional/Form/ElementsVerticalTabsTest.php b/core/modules/system/tests/src/Functional/Form/ElementsVerticalTabsTest.php index 34ca1c9cf..e90c4dfe3 100644 --- a/core/modules/system/tests/src/Functional/Form/ElementsVerticalTabsTest.php +++ b/core/modules/system/tests/src/Functional/Form/ElementsVerticalTabsTest.php @@ -20,6 +20,11 @@ class ElementsVerticalTabsTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user with permission to access vertical_tab_test_tabs. * diff --git a/core/modules/system/tests/src/Functional/Form/EmailTest.php b/core/modules/system/tests/src/Functional/Form/EmailTest.php index 2fe6ebe90..678f081eb 100644 --- a/core/modules/system/tests/src/Functional/Form/EmailTest.php +++ b/core/modules/system/tests/src/Functional/Form/EmailTest.php @@ -19,6 +19,11 @@ class EmailTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that #type 'email' fields are properly validated. */ diff --git a/core/modules/system/tests/src/Functional/Form/FormObjectTest.php b/core/modules/system/tests/src/Functional/Form/FormObjectTest.php index e2c00f5d1..c2f89735c 100644 --- a/core/modules/system/tests/src/Functional/Form/FormObjectTest.php +++ b/core/modules/system/tests/src/Functional/Form/FormObjectTest.php @@ -18,6 +18,11 @@ class FormObjectTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests using an object as the form callback. * diff --git a/core/modules/system/tests/src/Functional/Form/FormStoragePageCacheTest.php b/core/modules/system/tests/src/Functional/Form/FormStoragePageCacheTest.php index 474a35b55..9f63ead12 100644 --- a/core/modules/system/tests/src/Functional/Form/FormStoragePageCacheTest.php +++ b/core/modules/system/tests/src/Functional/Form/FormStoragePageCacheTest.php @@ -16,6 +16,11 @@ class FormStoragePageCacheTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Form/FormTest.php b/core/modules/system/tests/src/Functional/Form/FormTest.php index 849944b0f..08d556dc1 100644 --- a/core/modules/system/tests/src/Functional/Form/FormTest.php +++ b/core/modules/system/tests/src/Functional/Form/FormTest.php @@ -12,6 +12,7 @@ use Drupal\Tests\BrowserTestBase; use Drupal\user\RoleInterface; use Drupal\filter\Entity\FilterFormat; +use Behat\Mink\Element\NodeElement; /** * Tests various form element validation mechanisms. @@ -27,6 +28,11 @@ class FormTest extends BrowserTestBase { */ public static $modules = ['filter', 'form_test', 'file', 'datetime']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected function setUp() { parent::setUp(); @@ -128,13 +134,13 @@ public function testRequiredFields() { $this->assertTrue(isset($errors[$element]), "Check empty($key) '$type' field '$element'"); if (!empty($form_output)) { // Make sure the form element is marked as required. - $this->assertTrue(preg_match($required_marker_preg, $form_output), "Required '$type' field is marked as required"); + $this->assertRegExp($required_marker_preg, (string) $form_output, "Required '$type' field is marked as required"); } } else { if (!empty($form_output)) { // Make sure the form element is *not* marked as required. - $this->assertFalse(preg_match($required_marker_preg, $form_output), "Optional '$type' field is not marked as required"); + $this->assertNotRegExp($required_marker_preg, (string) $form_output, "Optional '$type' field is not marked as required"); } if ($type == 'select') { // Select elements are going to have validation errors with empty @@ -143,7 +149,8 @@ public function testRequiredFields() { $this->assertTrue((empty($errors[$element]) || strpos('field is required', (string) $errors[$element]) === FALSE), "Optional '$type' field '$element' is not treated as a required element"); } else { - // Make sure there is *no* form error for this element. + // Make sure there is *no* form error for this element. We're + // not using assertEmpty() because the array key might not exist. $this->assertTrue(empty($errors[$element]), "Optional '$type' field '$element' has no errors with empty input"); } } @@ -191,7 +198,7 @@ public function testRequiredCheckboxesRadio() { $expected_key = array_search($error->getText(), $expected); // If the error message is not one of the expected messages, fail. if ($expected_key === FALSE) { - $this->fail(format_string("Unexpected error message: @error", ['@error' => $error[0]])); + $this->fail(new FormattableMarkup("Unexpected error message: @error", ['@error' => $error[0]])); } // Remove the expected message from the list once it is found. else { @@ -201,7 +208,7 @@ public function testRequiredCheckboxesRadio() { // Fail if any expected messages were not found. foreach ($expected as $not_found) { - $this->fail(format_string("Found error message: @error", ['@error' => $not_found])); + $this->fail(new FormattableMarkup("Found error message: @error", ['@error' => $not_found])); } // Verify that input elements are still empty. @@ -369,7 +376,7 @@ public function testCheckboxProcessing() { 'zero_checkbox_off' => 0, ]; foreach ($expected_values as $widget => $expected_value) { - $this->assertSame($values[$widget], $expected_value, format_string('Checkbox %widget returns expected value (expected: %expected, got: %value)', [ + $this->assertSame($values[$widget], $expected_value, new FormattableMarkup('Checkbox %widget returns expected value (expected: %expected, got: %value)', [ '%widget' => var_export($widget, TRUE), '%expected' => var_export($expected_value, TRUE), '%value' => var_export($values[$widget], TRUE), @@ -446,7 +453,7 @@ public function testSelect() { 'multiple_no_default_required' => ['three' => 'three'], ]; foreach ($expected as $key => $value) { - $this->assertIdentical($values[$key], $value, format_string('@name: @actual is equal to @expected.', [ + $this->assertIdentical($values[$key], $value, new FormattableMarkup('@name: @actual is equal to @expected.', [ '@name' => $key, '@actual' => var_export($values[$key], TRUE), '@expected' => var_export($value, TRUE), @@ -463,6 +470,126 @@ public function testEmptySelect() { $this->assertNoFieldByXPath("//select[1]/option", NULL, 'No option element found.'); } + /** + * Tests sorting and not sorting of options in a select element. + */ + public function testSelectSorting() { + $this->drupalGet('form-test/select'); + + // Verify the order of the select options. + $this->validateSelectSorting('unsorted', [ + 'uso_first_element', + 'uso_second', + 'uso_zzgroup', + 'uso_gc', + 'uso_ga', + 'uso_gb', + 'uso_yygroup', + 'uso_ge', + 'uso_gd', + 'uso_gf', + 'uso_xxgroup', + 'uso_gz', + 'uso_gi', + 'uso_gh', + 'uso_d', + 'uso_c', + 'uso_b', + 'uso_a', + ]); + + $this->validateSelectSorting('sorted', [ + 'sso_a', + 'sso_d', + 'sso_first_element', + 'sso_b', + 'sso_c', + 'sso_second', + 'sso_xxgroup', + 'sso_gz', + 'sso_gh', + 'sso_gi', + 'sso_yygroup', + 'sso_ge', + 'sso_gd', + 'sso_gf', + 'sso_zzgroup', + 'sso_ga', + 'sso_gb', + 'sso_gc', + ]); + + $this->validateSelectSorting('sorted_none', [ + 'sno_empty', + 'sno_first_element', + 'sno_second', + 'sno_zzgroup', + 'sno_ga', + 'sno_gb', + 'sno_gc', + 'sno_a', + 'sno_d', + 'sno_b', + 'sno_c', + 'sno_xxgroup', + 'sno_gz', + 'sno_gi', + 'sno_gh', + 'sno_yygroup', + 'sno_ge', + 'sno_gd', + 'sno_gf', + ]); + + $this->validateSelectSorting('sorted_none_nostart', [ + 'snn_empty', + 'snn_a', + 'snn_d', + 'snn_first_element', + 'snn_b', + 'snn_c', + 'snn_second', + 'snn_xxgroup', + 'snn_gz', + 'snn_gi', + 'snn_gh', + 'snn_yygroup', + 'snn_ge', + 'snn_gd', + 'snn_gf', + 'snn_zzgroup', + 'snn_ga', + 'snn_gb', + 'snn_gc', + ]); + + // Verify that #sort_order and #sort_start are not in the page. + $this->assertSession()->responseNotContains('#sort_order'); + $this->assertSession()->responseNotContains('#sort_start'); + } + + /** + * Validates that the options are in the right order in a select. + * + * @param string $select + * Name of the select to verify. + * @param string[] $order + * Expected order of its options. + */ + protected function validateSelectSorting($select, array $order) { + $option_map_function = function (NodeElement $node) { + return ($node->getTagName() === 'optgroup') ? + $node->getAttribute('label') : $node->getValue(); + }; + $option_nodes = $this->getSession() + ->getPage() + ->findField($select) + ->findAll('css', 'option, optgroup'); + + $options = array_map($option_map_function, $option_nodes); + $this->assertIdentical($order, $options); + } + /** * Tests validation of #type 'number' and 'range' elements. */ @@ -517,10 +644,10 @@ public function testNumber() { // Check if the error exists on the page, if the current message ID is // expected. Otherwise ensure that the error message is not present. if ($id === $error) { - $this->assertRaw(format_string($message, $placeholders)); + $this->assertRaw(new FormattableMarkup($message, $placeholders)); } else { - $this->assertNoRaw(format_string($message, $placeholders)); + $this->assertNoRaw(new FormattableMarkup($message, $placeholders)); } } } @@ -660,7 +787,7 @@ public function assertFormValuesDefault($values, $form) { // Checkboxes values are not filtered out. $values[$key] = array_filter($values[$key]); } - $this->assertIdentical($expected_value, $values[$key], format_string('Default value for %type: expected %expected, returned %returned.', ['%type' => $key, '%expected' => var_export($expected_value, TRUE), '%returned' => var_export($values[$key], TRUE)])); + $this->assertIdentical($expected_value, $values[$key], new FormattableMarkup('Default value for %type: expected %expected, returned %returned.', ['%type' => $key, '%expected' => var_export($expected_value, TRUE), '%returned' => var_export($values[$key], TRUE)])); } // Recurse children. @@ -717,7 +844,7 @@ public function testDisabledMarkup() { ':div-class' => $class, ':value' => isset($item['#value']) ? $item['#value'] : '', ]); - $this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', ['%type' => $item['#type']])); + $this->assertTrue(isset($element[0]), new FormattableMarkup('Disabled form element class found for #type %type.', ['%type' => $item['#type']])); } // Verify special element #type text-format. @@ -725,12 +852,12 @@ public function testDisabledMarkup() { ':name' => 'text_format[value]', ':div-class' => 'form-disabled', ]); - $this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', ['%type' => 'text_format[value]'])); + $this->assertTrue(isset($element[0]), new FormattableMarkup('Disabled form element class found for #type %type.', ['%type' => 'text_format[value]'])); $element = $this->xpath('//div[contains(@class, :div-class)]/descendant::select[@name=:name]', [ ':name' => 'text_format[format]', ':div-class' => 'form-disabled', ]); - $this->assertTrue(isset($element[0]), format_string('Disabled form element class found for #type %type.', ['%type' => 'text_format[format]'])); + $this->assertTrue(isset($element[0]), new FormattableMarkup('Disabled form element class found for #type %type.', ['%type' => 'text_format[format]'])); } /** @@ -759,7 +886,7 @@ public function testRequiredAttribute() { ':id' => 'edit-' . $type, ':expected' => $expected, ]); - $this->assertTrue(!empty($element), format_string('The @type has the proper required attribute.', ['@type' => $type])); + $this->assertTrue(!empty($element), new FormattableMarkup('The @type has the proper required attribute.', ['@type' => $type])); } // Test to make sure textarea has the proper required attribute. diff --git a/core/modules/system/tests/src/Functional/Form/LanguageSelectElementTest.php b/core/modules/system/tests/src/Functional/Form/LanguageSelectElementTest.php index 0bd9bb8e6..f9f10ca8b 100644 --- a/core/modules/system/tests/src/Functional/Form/LanguageSelectElementTest.php +++ b/core/modules/system/tests/src/Functional/Form/LanguageSelectElementTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Form; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Serialization\Json; use Drupal\Core\Language\LanguageInterface; use Drupal\language\Entity\ConfigurableLanguage; @@ -22,6 +23,11 @@ class LanguageSelectElementTest extends BrowserTestBase { */ public static $modules = ['form_test', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that the options printed by the language select element are correct. */ @@ -48,7 +54,7 @@ public function testLanguageSelectElementOptions() { 'edit-languages-config-and-locked' => LanguageInterface::STATE_CONFIGURABLE | LanguageInterface::STATE_LOCKED, ]; foreach ($ids as $id => $flags) { - $this->assertField($id, format_string('The @id field was found on the page.', ['@id' => $id])); + $this->assertField($id, new FormattableMarkup('The @id field was found on the page.', ['@id' => $id])); $options = []; /* @var $language_manager \Drupal\Core\Language\LanguageManagerInterface */ $language_manager = $this->container->get('language_manager'); @@ -59,7 +65,7 @@ public function testLanguageSelectElementOptions() { } // Test that the #options were not altered by #languages. - $this->assertField('edit-language-custom-options', format_string('The @id field was found on the page.', ['@id' => 'edit-language-custom-options'])); + $this->assertField('edit-language-custom-options', new FormattableMarkup('The @id field was found on the page.', ['@id' => 'edit-language-custom-options'])); $this->_testLanguageSelectElementOptions('edit-language-custom-options', ['opt1' => 'First option', 'opt2' => 'Second option', 'opt3' => 'Third option']); } @@ -76,7 +82,7 @@ public function testHiddenLanguageSelectElement() { // Check that the language fields were rendered on the page. $ids = ['edit-languages-all', 'edit-languages-configurable', 'edit-languages-locked', 'edit-languages-config-and-locked']; foreach ($ids as $id) { - $this->assertNoField($id, format_string('The @id field was not found on the page.', ['@id' => $id])); + $this->assertNoField($id, new FormattableMarkup('The @id field was not found on the page.', ['@id' => $id])); } // Check that the submitted values were the default values of the language @@ -112,7 +118,7 @@ protected function _testLanguageSelectElementOptions($id, $options) { $this->assertEqual($option->getText(), $option_title); next($options); } - $this->assertEqual($count, count($options), format_string('The number of languages and the number of options shown by the language element are the same: @languages languages, @number options', ['@languages' => count($options), '@number' => $count])); + $this->assertEqual($count, count($options), new FormattableMarkup('The number of languages and the number of options shown by the language element are the same: @languages languages, @number options', ['@languages' => count($options), '@number' => $count])); } } diff --git a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php index 52e4160cd..a210cd37a 100644 --- a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php +++ b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php @@ -16,6 +16,11 @@ class ModulesListFormWebTest extends BrowserTestBase { */ public static $modules = ['system_test', 'help']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -70,10 +75,10 @@ public function testModulesListFormWithInvalidInfoFile() { // Confirm that the error message is shown. $this->assertSession() - ->pageTextContains('Modules could not be listed due to an error: Missing required keys (core) in ' . $path . '/broken.info.yml'); + ->pageTextContains("The 'core' or the 'core_version_requirement' key must be present in " . $path . '/broken.info.yml'); // Check that the module filter text box is available. - $this->assertTrue($this->xpath('//input[@name="text"]')); + $this->assertSession()->elementExists('xpath', '//input[@name="text"]'); } } diff --git a/core/modules/system/tests/src/Functional/Form/RebuildTest.php b/core/modules/system/tests/src/Functional/Form/RebuildTest.php index 3e951e2e8..6b87f6304 100644 --- a/core/modules/system/tests/src/Functional/Form/RebuildTest.php +++ b/core/modules/system/tests/src/Functional/Form/RebuildTest.php @@ -18,6 +18,11 @@ class RebuildTest extends BrowserTestBase { */ public static $modules = ['node', 'form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user for testing. * diff --git a/core/modules/system/tests/src/Functional/Form/RedirectTest.php b/core/modules/system/tests/src/Functional/Form/RedirectTest.php index baaed0d1d..e66cc3830 100644 --- a/core/modules/system/tests/src/Functional/Form/RedirectTest.php +++ b/core/modules/system/tests/src/Functional/Form/RedirectTest.php @@ -19,6 +19,11 @@ class RedirectTest extends BrowserTestBase { */ public static $modules = ['form_test', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests form redirection. */ diff --git a/core/modules/system/tests/src/Functional/Form/ResponseTest.php b/core/modules/system/tests/src/Functional/Form/ResponseTest.php index de5d8fd6c..4707a47e5 100644 --- a/core/modules/system/tests/src/Functional/Form/ResponseTest.php +++ b/core/modules/system/tests/src/Functional/Form/ResponseTest.php @@ -19,6 +19,11 @@ class ResponseTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that enforced responses propagate through subscribers and middleware. */ diff --git a/core/modules/system/tests/src/Functional/Form/StateValuesCleanAdvancedTest.php b/core/modules/system/tests/src/Functional/Form/StateValuesCleanAdvancedTest.php index d30b4f110..56dab576c 100644 --- a/core/modules/system/tests/src/Functional/Form/StateValuesCleanAdvancedTest.php +++ b/core/modules/system/tests/src/Functional/Form/StateValuesCleanAdvancedTest.php @@ -25,8 +25,15 @@ class StateValuesCleanAdvancedTest extends BrowserTestBase { */ public static $modules = ['file', 'form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An image file path for uploading. + * + * @var string|bool */ protected $image; diff --git a/core/modules/system/tests/src/Functional/Form/StateValuesCleanTest.php b/core/modules/system/tests/src/Functional/Form/StateValuesCleanTest.php index cae0835b5..b5bd856de 100644 --- a/core/modules/system/tests/src/Functional/Form/StateValuesCleanTest.php +++ b/core/modules/system/tests/src/Functional/Form/StateValuesCleanTest.php @@ -21,6 +21,11 @@ class StateValuesCleanTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests \Drupal\Core\Form\FormState::cleanValues(). */ @@ -35,16 +40,16 @@ public function testFormStateValuesClean() { ]; // Verify that all internal Form API elements were removed. - $this->assertFalse(isset($values['form_id']), format_string('%element was removed.', ['%element' => 'form_id'])); - $this->assertFalse(isset($values['form_token']), format_string('%element was removed.', ['%element' => 'form_token'])); - $this->assertFalse(isset($values['form_build_id']), format_string('%element was removed.', ['%element' => 'form_build_id'])); - $this->assertFalse(isset($values['op']), format_string('%element was removed.', ['%element' => 'op'])); + $this->assertFalse(isset($values['form_id']), new FormattableMarkup('%element was removed.', ['%element' => 'form_id'])); + $this->assertFalse(isset($values['form_token']), new FormattableMarkup('%element was removed.', ['%element' => 'form_token'])); + $this->assertFalse(isset($values['form_build_id']), new FormattableMarkup('%element was removed.', ['%element' => 'form_build_id'])); + $this->assertFalse(isset($values['op']), new FormattableMarkup('%element was removed.', ['%element' => 'op'])); // Verify that all buttons were removed. - $this->assertFalse(isset($values['foo']), format_string('%element was removed.', ['%element' => 'foo'])); - $this->assertFalse(isset($values['bar']), format_string('%element was removed.', ['%element' => 'bar'])); - $this->assertFalse(isset($values['baz']['foo']), format_string('%element was removed.', ['%element' => 'foo'])); - $this->assertFalse(isset($values['baz']['baz']), format_string('%element was removed.', ['%element' => 'baz'])); + $this->assertFalse(isset($values['foo']), new FormattableMarkup('%element was removed.', ['%element' => 'foo'])); + $this->assertFalse(isset($values['bar']), new FormattableMarkup('%element was removed.', ['%element' => 'bar'])); + $this->assertFalse(isset($values['baz']['foo']), new FormattableMarkup('%element was removed.', ['%element' => 'foo'])); + $this->assertFalse(isset($values['baz']['baz']), new FormattableMarkup('%element was removed.', ['%element' => 'baz'])); // Verify values manually added for cleaning were removed. $this->assertFalse(isset($values['wine']), new FormattableMarkup('%element was removed.', ['%element' => 'wine'])); diff --git a/core/modules/system/tests/src/Functional/Form/StorageTest.php b/core/modules/system/tests/src/Functional/Form/StorageTest.php index 95969fde5..32c915e04 100644 --- a/core/modules/system/tests/src/Functional/Form/StorageTest.php +++ b/core/modules/system/tests/src/Functional/Form/StorageTest.php @@ -27,6 +27,11 @@ class StorageTest extends BrowserTestBase { */ public static $modules = ['form_test', 'dblog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Form/SystemConfigFormTest.php b/core/modules/system/tests/src/Functional/Form/SystemConfigFormTest.php index 09ad06ac8..ca56d2e6d 100644 --- a/core/modules/system/tests/src/Functional/Form/SystemConfigFormTest.php +++ b/core/modules/system/tests/src/Functional/Form/SystemConfigFormTest.php @@ -18,13 +18,18 @@ class SystemConfigFormTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the SystemConfigFormTestBase class. */ public function testSystemConfigForm() { $this->drupalGet('form-test/system-config-form'); $element = $this->xpath('//div[@id = :id]/input[contains(@class, :class)]', [':id' => 'edit-actions', ':class' => 'button--primary']); - $this->assertTrue($element, 'The primary action submit button was found.'); + $this->assertNotEmpty($element, 'The primary action submit button was found.'); $this->drupalPostForm(NULL, [], t('Save configuration')); $this->assertText(t('The configuration options have been saved.')); } diff --git a/core/modules/system/tests/src/Functional/Form/UrlTest.php b/core/modules/system/tests/src/Functional/Form/UrlTest.php index 82d227bbd..02197602f 100644 --- a/core/modules/system/tests/src/Functional/Form/UrlTest.php +++ b/core/modules/system/tests/src/Functional/Form/UrlTest.php @@ -21,6 +21,11 @@ class UrlTest extends BrowserTestBase { protected $profile = 'testing'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that #type 'url' fields are properly validated and trimmed. */ diff --git a/core/modules/system/tests/src/Functional/Form/ValidationTest.php b/core/modules/system/tests/src/Functional/Form/ValidationTest.php index 094a0af73..cadf42758 100644 --- a/core/modules/system/tests/src/Functional/Form/ValidationTest.php +++ b/core/modules/system/tests/src/Functional/Form/ValidationTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Form; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Render\Element; use Drupal\Tests\BrowserTestBase; @@ -19,6 +20,11 @@ class ValidationTest extends BrowserTestBase { */ public static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests #element_validate and #validate. */ @@ -100,7 +106,7 @@ public function testValidateLimitErrors() { ':id' => 'edit-' . $type, ':expected' => $expected, ]); - $this->assertTrue(!empty($element), format_string('The @type button has the proper formnovalidate attribute.', ['@type' => $type])); + $this->assertTrue(!empty($element), new FormattableMarkup('The @type button has the proper formnovalidate attribute.', ['@type' => $type])); } // The button with full server-side validation should not have the // 'formnovalidate' attribute. diff --git a/core/modules/system/tests/src/Functional/Hal/ActionHalJsonAnonTest.php b/core/modules/system/tests/src/Functional/Hal/ActionHalJsonAnonTest.php index eb19bac6f..6c0ad70cb 100644 --- a/core/modules/system/tests/src/Functional/Hal/ActionHalJsonAnonTest.php +++ b/core/modules/system/tests/src/Functional/Hal/ActionHalJsonAnonTest.php @@ -17,6 +17,11 @@ class ActionHalJsonAnonTest extends ActionResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Hal/ActionHalJsonBasicAuthTest.php b/core/modules/system/tests/src/Functional/Hal/ActionHalJsonBasicAuthTest.php index 7441f6ab4..13daf5b04 100644 --- a/core/modules/system/tests/src/Functional/Hal/ActionHalJsonBasicAuthTest.php +++ b/core/modules/system/tests/src/Functional/Hal/ActionHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class ActionHalJsonBasicAuthTest extends ActionResourceTestBase { */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Hal/ActionHalJsonCookieTest.php b/core/modules/system/tests/src/Functional/Hal/ActionHalJsonCookieTest.php index f013032e8..2a9c6178b 100644 --- a/core/modules/system/tests/src/Functional/Hal/ActionHalJsonCookieTest.php +++ b/core/modules/system/tests/src/Functional/Hal/ActionHalJsonCookieTest.php @@ -17,6 +17,11 @@ class ActionHalJsonCookieTest extends ActionResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Hal/MenuHalJsonAnonTest.php b/core/modules/system/tests/src/Functional/Hal/MenuHalJsonAnonTest.php index 0200624b1..04dd30e6c 100644 --- a/core/modules/system/tests/src/Functional/Hal/MenuHalJsonAnonTest.php +++ b/core/modules/system/tests/src/Functional/Hal/MenuHalJsonAnonTest.php @@ -17,6 +17,11 @@ class MenuHalJsonAnonTest extends MenuResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Hal/MenuHalJsonBasicAuthTest.php b/core/modules/system/tests/src/Functional/Hal/MenuHalJsonBasicAuthTest.php index cce118b11..3aaa10a0c 100644 --- a/core/modules/system/tests/src/Functional/Hal/MenuHalJsonBasicAuthTest.php +++ b/core/modules/system/tests/src/Functional/Hal/MenuHalJsonBasicAuthTest.php @@ -17,6 +17,11 @@ class MenuHalJsonBasicAuthTest extends MenuResourceTestBase { */ public static $modules = ['hal', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Hal/MenuHalJsonCookieTest.php b/core/modules/system/tests/src/Functional/Hal/MenuHalJsonCookieTest.php index 9f7346f25..0d706f458 100644 --- a/core/modules/system/tests/src/Functional/Hal/MenuHalJsonCookieTest.php +++ b/core/modules/system/tests/src/Functional/Hal/MenuHalJsonCookieTest.php @@ -17,6 +17,11 @@ class MenuHalJsonCookieTest extends MenuResourceTestBase { */ public static $modules = ['hal']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Lock/LockFunctionalTest.php b/core/modules/system/tests/src/Functional/Lock/LockFunctionalTest.php index 0b13f18d6..766e81f7b 100644 --- a/core/modules/system/tests/src/Functional/Lock/LockFunctionalTest.php +++ b/core/modules/system/tests/src/Functional/Lock/LockFunctionalTest.php @@ -18,6 +18,11 @@ class LockFunctionalTest extends BrowserTestBase { */ public static $modules = ['system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Confirms that we can acquire and release locks in two parallel requests. */ diff --git a/core/modules/system/tests/src/Functional/Mail/HtmlToTextTest.php b/core/modules/system/tests/src/Functional/Mail/HtmlToTextTest.php index 97092232d..bccc4f532 100644 --- a/core/modules/system/tests/src/Functional/Mail/HtmlToTextTest.php +++ b/core/modules/system/tests/src/Functional/Mail/HtmlToTextTest.php @@ -14,6 +14,11 @@ */ class HtmlToTextTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Converts a string to its PHP source equivalent for display in test messages. * @@ -237,10 +242,7 @@ public function testDrupalHtmlToTextBlockTagToNewline() { EOT; $input = str_replace(["\r", "\n"], '', $input); $output = MailFormatHelper::htmlToText($input); - $pass = $this->assertFalse( - preg_match('/\][^\n]*\[/s', $output), - 'Block-level HTML tags should force newlines' - ); + $pass = $this->assertNotRegExp('/\][^\n]*\[/s', $output, 'Block-level HTML tags should force newlines'); if (!$pass) { $this->verbose($this->stringToHtml($output)); } diff --git a/core/modules/system/tests/src/Functional/Mail/MailTest.php b/core/modules/system/tests/src/Functional/Mail/MailTest.php deleted file mode 100644 index ef59d0128..000000000 --- a/core/modules/system/tests/src/Functional/Mail/MailTest.php +++ /dev/null @@ -1,312 +0,0 @@ -config('system.mail')->set('interface.default', 'test_php_mail_failure')->save(); - - // Get the default MailInterface class instance. - $mail_backend = \Drupal::service('plugin.manager.mail')->getInstance(['module' => 'default', 'key' => 'default']); - - // Assert whether the default mail backend is an instance of the expected - // class. - $this->assertTrue($mail_backend instanceof TestPhpMailFailure, 'Default mail interface can be swapped.'); - - // Add a module-specific mail backend. - $this->config('system.mail')->set('interface.mymodule_testkey', 'test_mail_collector')->save(); - - // Get the added MailInterface class instance. - $mail_backend = \Drupal::service('plugin.manager.mail')->getInstance(['module' => 'mymodule', 'key' => 'testkey']); - - // Assert whether the added mail backend is an instance of the expected - // class. - $this->assertTrue($mail_backend instanceof TestMailCollector, 'Additional mail interfaces can be added.'); - } - - /** - * Test that message sending may be canceled. - * - * @see simpletest_mail_alter() - */ - public function testCancelMessage() { - $language_interface = \Drupal::languageManager()->getCurrentLanguage(); - - // Use the state system collector mail backend. - $this->config('system.mail')->set('interface.default', 'test_mail_collector')->save(); - // Reset the state variable that holds sent messages. - \Drupal::state()->set('system.test_mail_collector', []); - - // Send a test message that simpletest_mail_alter should cancel. - \Drupal::service('plugin.manager.mail')->mail('simpletest', 'cancel_test', 'cancel@example.com', $language_interface->getId()); - // Retrieve sent message. - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - - // Assert that the message was not actually sent. - $this->assertFalse($sent_message, 'Message was canceled.'); - } - - /** - * Checks the From: and Reply-to: headers. - */ - public function testFromAndReplyToHeader() { - $language = \Drupal::languageManager()->getCurrentLanguage(); - - // Use the state system collector mail backend. - $this->config('system.mail')->set('interface.default', 'test_mail_collector')->save(); - // Reset the state variable that holds sent messages. - \Drupal::state()->set('system.test_mail_collector', []); - // Send an email with a reply-to address specified. - $from_email = 'Drupal '; - $reply_email = 'someone_else@example.com'; - \Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language, [], $reply_email); - // Test that the reply-to email is just the email and not the site name - // and default sender email. - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - $this->assertEqual($from_email, $sent_message['headers']['From'], 'Message is sent from the site email account.'); - $this->assertEqual($reply_email, $sent_message['headers']['Reply-to'], 'Message reply-to headers are set.'); - $this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.'); - - // Test that long site names containing characters that need MIME encoding - // works as expected. - $this->config('system.site')->set('name', 'Drépal this is a very long test sentence to test what happens with very long site names')->save(); - // Send an email and check that the From-header contains the site name. - \Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language); - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - $this->assertEquals('=?UTF-8?B?RHLDqXBhbCB0aGlzIGlzIGEgdmVyeSBsb25nIHRlc3Qgc2VudGVuY2UgdG8gdGU=?= ', $sent_message['headers']['From'], 'From header is correctly encoded.'); - $this->assertEquals('Drépal this is a very long test sentence to te ', Unicode::mimeHeaderDecode($sent_message['headers']['From']), 'From header is correctly encoded.'); - $this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.'); - $this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.'); - - // Test RFC-2822 rules are respected for 'display-name' component of - // 'From:' header. Specials characters are not allowed, so randomly add one - // of them to the site name and check the string is wrapped in quotes. Also - // hardcode some double-quotes and backslash to validate these are escaped - // properly too. - $specials = '()<>[]:;@\,."'; - $site_name = 'Drupal' . $specials[rand(0, strlen($specials) - 1)] . ' "si\te"'; - $this->config('system.site')->set('name', $site_name)->save(); - // Send an email and check that the From-header contains the site name - // within double-quotes. Also make sure double-quotes and "\" are escaped. - \Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language); - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - $escaped_site_name = str_replace(['\\', '"'], ['\\\\', '\\"'], $site_name); - $this->assertEquals('"' . $escaped_site_name . '" ', $sent_message['headers']['From'], 'From header is correctly quoted.'); - - // Make sure display-name is not quoted nor escaped if part on an encoding. - $site_name = 'Drépal, "si\te"'; - $this->config('system.site')->set('name', $site_name)->save(); - // Send an email and check that the From-header contains the site name. - \Drupal::service('plugin.manager.mail')->mail('simpletest', 'from_test', 'from_test@example.com', $language); - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - $this->assertEquals('=?UTF-8?B?RHLDqXBhbCwgInNpXHRlIg==?= ', $sent_message['headers']['From'], 'From header is correctly encoded.'); - $this->assertEquals($site_name . ' ', Unicode::mimeHeaderDecode($sent_message['headers']['From']), 'From header is correctly encoded.'); - } - - /** - * Checks that relative paths in mails are converted into absolute URLs. - */ - public function testConvertRelativeUrlsIntoAbsolute() { - $language_interface = \Drupal::languageManager()->getCurrentLanguage(); - - // Use the HTML compatible state system collector mail backend. - $this->config('system.mail')->set('interface.default', 'test_html_mail_collector')->save(); - - // Fetch the hostname and port for matching against. - $http_host = \Drupal::request()->getSchemeAndHttpHost(); - - // Random generator. - $random = new Random(); - - // One random tag name. - $tag_name = strtolower($random->name(8, TRUE)); - - // Test root relative urls. - foreach (['href', 'src'] as $attribute) { - // Reset the state variable that holds sent messages. - \Drupal::state()->set('system.test_mail_collector', []); - - $html = "<$tag_name $attribute=\"/root-relative\">root relative url in mail test"; - $expected_html = "<$tag_name $attribute=\"{$http_host}/root-relative\">root relative url in mail test"; - - // Prepare render array. - $render = ['#markup' => Markup::create($html)]; - - // Send a test message that simpletest_mail_alter should cancel. - \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); - // Retrieve sent message. - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - - // Wrap the expected HTML and assert. - $expected_html = MailFormatHelper::wrapMail($expected_html); - $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails."); - } - - // Test protocol relative urls. - foreach (['href', 'src'] as $attribute) { - // Reset the state variable that holds sent messages. - \Drupal::state()->set('system.test_mail_collector', []); - - $html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test"; - $expected_html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test"; - - // Prepare render array. - $render = ['#markup' => Markup::create($html)]; - - // Send a test message that simpletest_mail_alter should cancel. - \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); - // Retrieve sent message. - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - - // Wrap the expected HTML and assert. - $expected_html = MailFormatHelper::wrapMail($expected_html); - $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails."); - } - - // Test absolute urls. - foreach (['href', 'src'] as $attribute) { - // Reset the state variable that holds sent messages. - \Drupal::state()->set('system.test_mail_collector', []); - - $html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test"; - $expected_html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test"; - - // Prepare render array. - $render = ['#markup' => Markup::create($html)]; - - // Send a test message that simpletest_mail_alter should cancel. - \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); - // Retrieve sent message. - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - - // Wrap the expected HTML and assert. - $expected_html = MailFormatHelper::wrapMail($expected_html); - $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails."); - } - } - - /** - * Checks that mails built from render arrays contain absolute paths. - * - * By default Drupal uses relative paths for images and links. When sending - * emails, absolute paths should be used instead. - */ - public function testRenderedElementsUseAbsolutePaths() { - $language_interface = \Drupal::languageManager()->getCurrentLanguage(); - - // Use the HTML compatible state system collector mail backend. - $this->config('system.mail')->set('interface.default', 'test_html_mail_collector')->save(); - - // Fetch the hostname and port for matching against. - $http_host = \Drupal::request()->getSchemeAndHttpHost(); - - // Random generator. - $random = new Random(); - $image_name = $random->name(); - - // Create an image file. - $file = File::create(['uri' => "public://{$image_name}.png", 'filename' => "{$image_name}.png"]); - $file->save(); - - $base_path = base_path(); - - $path_pairs = [ - 'root relative' => [$file->getFileUri(), "{$http_host}{$base_path}{$this->publicFilesDirectory}/{$image_name}.png"], - 'protocol relative' => ['//example.com/image.png', '//example.com/image.png'], - 'absolute' => ['http://example.com/image.png', 'http://example.com/image.png'], - ]; - - // Test images. - foreach ($path_pairs as $test_type => $paths) { - list($input_path, $expected_path) = $paths; - - // Reset the state variable that holds sent messages. - \Drupal::state()->set('system.test_mail_collector', []); - - // Build the render array. - $render = [ - '#theme' => 'image', - '#uri' => $input_path, - ]; - $expected_html = "\"\""; - - // Send a test message that simpletest_mail_alter should cancel. - \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); - // Retrieve sent message. - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - - // Wrap the expected HTML and assert. - $expected_html = MailFormatHelper::wrapMail($expected_html); - $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$test_type} paths are converted properly."); - } - - // Test links. - $path_pairs = [ - 'root relative' => [Url::fromUserInput('/path/to/something'), "{$http_host}{$base_path}path/to/something"], - 'protocol relative' => [Url::fromUri('//example.com/image.png'), '//example.com/image.png'], - 'absolute' => [Url::fromUri('http://example.com/image.png'), 'http://example.com/image.png'], - ]; - - foreach ($path_pairs as $paths) { - list($input_path, $expected_path) = $paths; - - // Reset the state variable that holds sent messages. - \Drupal::state()->set('system.test_mail_collector', []); - - // Build the render array. - $render = [ - '#title' => 'Link', - '#type' => 'link', - '#url' => $input_path, - ]; - $expected_html = "Link"; - - // Send a test message that simpletest_mail_alter should cancel. - \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); - // Retrieve sent message. - $captured_emails = \Drupal::state()->get('system.test_mail_collector'); - $sent_message = end($captured_emails); - - // Wrap the expected HTML and assert. - $expected_html = MailFormatHelper::wrapMail($expected_html); - $this->assertSame($expected_html, $sent_message['body']); - } - } - -} diff --git a/core/modules/system/tests/src/Functional/Menu/AssertBreadcrumbTrait.php b/core/modules/system/tests/src/Functional/Menu/AssertBreadcrumbTrait.php index 32793e350..e38dfbcb1 100644 --- a/core/modules/system/tests/src/Functional/Menu/AssertBreadcrumbTrait.php +++ b/core/modules/system/tests/src/Functional/Menu/AssertBreadcrumbTrait.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Menu; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\Html; use Drupal\Core\Url; @@ -88,7 +89,7 @@ protected function assertBreadcrumbParts($trail) { // No parts must be left, or an expected "Home" will always pass. $pass = ($pass && empty($parts)); - $this->assertTrue($pass, format_string('Breadcrumb %parts found on @path.', [ + $this->assertTrue($pass, new FormattableMarkup('Breadcrumb %parts found on @path.', [ '%parts' => implode(' » ', $trail), '@path' => $this->getUrl(), ])); diff --git a/core/modules/system/tests/src/Functional/Menu/AssertMenuActiveTrailTrait.php b/core/modules/system/tests/src/Functional/Menu/AssertMenuActiveTrailTrait.php index c5d60e542..7eb8394e9 100644 --- a/core/modules/system/tests/src/Functional/Menu/AssertMenuActiveTrailTrait.php +++ b/core/modules/system/tests/src/Functional/Menu/AssertMenuActiveTrailTrait.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Menu; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; /** @@ -56,7 +57,7 @@ protected function assertMenuActiveTrail($tree, $last_active) { ':title' => $active_link_title, ]; $elements = $this->xpath($xpath, $args); - $this->assertTrue(!empty($elements), format_string('Active link %title was found in menu tree, including active trail links %tree.', [ + $this->assertTrue(!empty($elements), new FormattableMarkup('Active link %title was found in menu tree, including active trail links %tree.', [ '%title' => $active_link_title, '%tree' => implode(' » ', $tree), ])); diff --git a/core/modules/system/tests/src/Functional/Menu/BreadcrumbFrontCacheContextsTest.php b/core/modules/system/tests/src/Functional/Menu/BreadcrumbFrontCacheContextsTest.php index 902720946..2ede1c967 100644 --- a/core/modules/system/tests/src/Functional/Menu/BreadcrumbFrontCacheContextsTest.php +++ b/core/modules/system/tests/src/Functional/Menu/BreadcrumbFrontCacheContextsTest.php @@ -25,6 +25,11 @@ class BreadcrumbFrontCacheContextsTest extends BrowserTestBase { 'user', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * A test node with path alias. * diff --git a/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php b/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php index 5b353eedb..5c8734779 100644 --- a/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php +++ b/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php @@ -7,6 +7,7 @@ use Drupal\node\Entity\NodeType; use Drupal\Tests\BrowserTestBase; use Drupal\user\RoleInterface; +use PHPUnit\Framework\ExpectationFailedException; /** * Tests breadcrumbs functionality. @@ -206,7 +207,7 @@ public function testBreadCrumbs() { 'link[0][uri]' => '/node', ]; $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => 'Root']); + $menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties(['title' => 'Root']); $link = reset($menu_links); $edit = [ @@ -240,7 +241,7 @@ public function testBreadCrumbs() { // the breadcrumb based on taxonomy term hierarchy. $parent_tid = 0; foreach ($tags as $name => $null) { - $terms = entity_load_multiple_by_properties('taxonomy_term', ['name' => $name]); + $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['name' => $name]); $term = reset($terms); $tags[$name]['term'] = $term; if ($parent_tid) { @@ -261,7 +262,7 @@ public function testBreadCrumbs() { 'enabled[value]' => 1, ]; $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link_content', [ + $menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties([ 'title' => $edit['title[0][value]'], 'link.uri' => 'internal:/taxonomy/term/' . $term->id(), ]); @@ -399,7 +400,7 @@ public function testAssertBreadcrumbTrait() { $this->assertBreadcrumb('menu-test/breadcrumb1', []); $this->fail($message); } - catch (\PHPUnit_Framework_ExpectationFailedException $e) { + catch (ExpectationFailedException $e) { $this->assertTrue(TRUE, $message); } @@ -409,7 +410,7 @@ public function testAssertBreadcrumbTrait() { $this->assertBreadcrumb('menu-test/breadcrumb1', $home); $this->fail($message); } - catch (\PHPUnit_Framework_ExpectationFailedException $e) { + catch (ExpectationFailedException $e) { $this->assertTrue(TRUE, $message); } @@ -426,7 +427,7 @@ public function testAssertBreadcrumbTrait() { $this->assertBreadcrumb('menu-test/breadcrumb1', $trail); $this->fail($message); } - catch (\PHPUnit_Framework_ExpectationFailedException $e) { + catch (ExpectationFailedException $e) { $this->assertTrue(TRUE, $message); } } diff --git a/core/modules/system/tests/src/Functional/Menu/LocalActionTest.php b/core/modules/system/tests/src/Functional/Menu/LocalActionTest.php index f7b9aafae..67875aa8d 100644 --- a/core/modules/system/tests/src/Functional/Menu/LocalActionTest.php +++ b/core/modules/system/tests/src/Functional/Menu/LocalActionTest.php @@ -20,6 +20,11 @@ class LocalActionTest extends BrowserTestBase { */ public static $modules = ['block', 'menu_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Menu/LocalTasksTest.php b/core/modules/system/tests/src/Functional/Menu/LocalTasksTest.php index 1a4933677..0e767d9fb 100644 --- a/core/modules/system/tests/src/Functional/Menu/LocalTasksTest.php +++ b/core/modules/system/tests/src/Functional/Menu/LocalTasksTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Menu; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\Html; use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; @@ -20,6 +21,11 @@ class LocalTasksTest extends BrowserTestBase { */ public static $modules = ['block', 'menu_test', 'entity_test', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * The local tasks block under testing. * @@ -51,12 +57,12 @@ protected function assertLocalTasks(array $routes, $level = 0) { $elements = $this->xpath('//*[contains(@class, :class)]//a', [ ':class' => $level == 0 ? 'tabs primary' : 'tabs secondary', ]); - $this->assertTrue(count($elements), 'Local tasks found.'); + $this->assertGreaterThan(0, count($elements), 'Local tasks found.'); foreach ($routes as $index => $route_info) { list($route_name, $route_parameters) = $route_info; $expected = Url::fromRoute($route_name, $route_parameters)->toString(); $method = ($elements[$index]->getAttribute('href') == $expected ? 'pass' : 'fail'); - $this->{$method}(format_string('Task @number href @value equals @expected.', [ + $this->{$method}(new FormattableMarkup('Task @number href @value equals @expected.', [ '@number' => $index + 1, '@value' => $elements[$index]->getAttribute('href'), '@expected' => $expected, @@ -92,7 +98,7 @@ protected function assertNoLocalTasks($level = 0) { $elements = $this->xpath('//*[contains(@class, :class)]//a', [ ':class' => $level == 0 ? 'tabs primary' : 'tabs secondary', ]); - $this->assertFalse(count($elements), 'Local tasks not found.'); + $this->assertEmpty($elements, 'Local tasks not found.'); } /** @@ -176,7 +182,7 @@ public function testPluginLocalTask() { // Test that we we correctly apply the active class to tabs where one of the // request attributes is upcast to an entity object. - $entity = \Drupal::entityManager()->getStorage('entity_test')->create(['bundle' => 'test']); + $entity = \Drupal::entityTypeManager()->getStorage('entity_test')->create(['bundle' => 'test']); $entity->save(); $this->drupalGet(Url::fromRoute('menu_test.local_task_test_upcasting_sub1', ['entity_test' => '1'])); diff --git a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php index 7d3c0a4b3..8c0b7ca49 100644 --- a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php +++ b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php @@ -19,6 +19,11 @@ class MenuAccessTest extends BrowserTestBase { */ public static $modules = ['block', 'menu_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Menu/MenuLinkSecurityTest.php b/core/modules/system/tests/src/Functional/Menu/MenuLinkSecurityTest.php index c58bf8e50..d9433be31 100644 --- a/core/modules/system/tests/src/Functional/Menu/MenuLinkSecurityTest.php +++ b/core/modules/system/tests/src/Functional/Menu/MenuLinkSecurityTest.php @@ -17,6 +17,11 @@ class MenuLinkSecurityTest extends BrowserTestBase { */ public static $modules = ['menu_link_content', 'block', 'menu_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Ensures that a menu link does not cause an XSS issue. */ diff --git a/core/modules/system/tests/src/Functional/Menu/MenuRouterTest.php b/core/modules/system/tests/src/Functional/Menu/MenuRouterTest.php index 4296df55b..f13707fff 100644 --- a/core/modules/system/tests/src/Functional/Menu/MenuRouterTest.php +++ b/core/modules/system/tests/src/Functional/Menu/MenuRouterTest.php @@ -20,18 +20,16 @@ class MenuRouterTest extends BrowserTestBase { public static $modules = ['block', 'menu_test', 'test_page_test']; /** - * Name of the administrative theme to use for tests. - * - * @var string + * {@inheritdoc} */ - protected $adminTheme; + protected $defaultTheme = 'stark'; /** - * Name of the default theme to use for tests. + * Name of the administrative theme to use for tests. * * @var string */ - protected $defaultTheme; + protected $adminTheme; protected function setUp() { // Enable dummy module that implements hook_menu. @@ -247,8 +245,9 @@ public function testThemeIntegration() { $this->defaultTheme = 'bartik'; $this->adminTheme = 'seven'; - $theme_handler = $this->container->get('theme_handler'); - $theme_handler->install([$this->defaultTheme, $this->adminTheme]); + /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */ + $theme_installer = $this->container->get('theme_installer'); + $theme_installer->install([$this->defaultTheme, $this->adminTheme]); $this->config('system.theme') ->set('default', $this->defaultTheme) ->set('admin', $this->adminTheme) @@ -305,14 +304,15 @@ protected function doTestThemeCallbackOptionalTheme() { $this->assertRaw('bartik/css/base/elements.css', "The default theme's CSS appears on the page."); // Now install the theme and request it again. - $theme_handler = $this->container->get('theme_handler'); - $theme_handler->install(['test_theme']); + /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */ + $theme_installer = $this->container->get('theme_installer'); + $theme_installer->install(['test_theme']); $this->drupalGet('menu-test/theme-callback/use-test-theme'); $this->assertText('Active theme: test_theme. Actual theme: test_theme.', 'The theme negotiation system uses an optional theme once it has been installed.'); $this->assertRaw('test_theme/kitten.css', "The optional theme's CSS appears on the page."); - $theme_handler->uninstall(['test_theme']); + $theme_installer->uninstall(['test_theme']); } /** diff --git a/core/modules/system/tests/src/Functional/Module/ClassLoaderTest.php b/core/modules/system/tests/src/Functional/Module/ClassLoaderTest.php index 335a03a32..db82fe25f 100644 --- a/core/modules/system/tests/src/Functional/Module/ClassLoaderTest.php +++ b/core/modules/system/tests/src/Functional/Module/ClassLoaderTest.php @@ -23,6 +23,11 @@ class ClassLoaderTest extends BrowserTestBase { */ protected $apcuEnsureUniquePrefix = TRUE; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that module-provided classes can be loaded when a module is enabled. * diff --git a/core/modules/system/tests/src/Functional/Module/DependencyTest.php b/core/modules/system/tests/src/Functional/Module/DependencyTest.php index d2938721a..2c1530790 100644 --- a/core/modules/system/tests/src/Functional/Module/DependencyTest.php +++ b/core/modules/system/tests/src/Functional/Module/DependencyTest.php @@ -11,6 +11,11 @@ */ class DependencyTest extends ModuleTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Checks functionality of project namespaces for dependencies. */ @@ -48,7 +53,7 @@ public function testEnableWithoutDependency() { // Assert that the language YAML files were created. $storage = $this->container->get('config.storage'); - $this->assertTrue(count($storage->listAll('language.entity.')) > 0, 'Language config entity files exist.'); + $this->assertNotEmpty($storage->listAll('language.entity.'), 'Language config entity files exist.'); } /** @@ -102,6 +107,29 @@ public function testIncompatiblePhpVersionDependency() { $this->assert(count($checkbox) == 1, 'Checkbox for the module is disabled.'); } + /** + * Tests enabling modules with different core version specifications. + */ + public function testCoreCompatibility() { + $assert_session = $this->assertSession(); + + // Test incompatible 'core_version_requirement'. + $this->drupalGet('admin/modules'); + $assert_session->fieldDisabled('modules[system_incompatible_core_version_test_1x][enable]'); + $assert_session->fieldDisabled('modules[system_core_incompatible_semver_test][enable]'); + + // Test compatible 'core_version_requirement' and compatible 'core'. + $this->drupalGet('admin/modules'); + $assert_session->fieldEnabled('modules[common_test][enable]'); + $assert_session->fieldEnabled('modules[system_core_semver_test][enable]'); + + // Ensure the modules can actually be installed. + $edit['modules[common_test][enable]'] = 'common_test'; + $edit['modules[system_core_semver_test][enable]'] = 'system_core_semver_test'; + $this->drupalPostForm('admin/modules', $edit, t('Install')); + $this->assertModules(['common_test', 'system_core_semver_test'], TRUE); + } + /** * Tests enabling a module that depends on a module which fails hook_requirements(). */ @@ -182,10 +210,10 @@ public function testUninstallDependents() { // Ensure taxonomy has been loaded into the test-runner after forum was // enabled. \Drupal::moduleHandler()->load('taxonomy'); - $terms = entity_load_multiple_by_properties('taxonomy_term', ['vid' => $vid]); - foreach ($terms as $term) { - $term->delete(); - } + $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $terms = $storage->loadByProperties(['vid' => $vid]); + $storage->delete($terms); + // Uninstall the forum module, and check that taxonomy now can also be // uninstalled. $edit = ['uninstall[forum]' => 'forum']; diff --git a/core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php b/core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php index 177fc5ba1..eba632f0f 100644 --- a/core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php +++ b/core/modules/system/tests/src/Functional/Module/ExperimentalModuleTest.php @@ -19,6 +19,11 @@ class ExperimentalModuleTest extends BrowserTestBase { */ protected $adminUser; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Module/HookRequirementsTest.php b/core/modules/system/tests/src/Functional/Module/HookRequirementsTest.php index 2ada3a285..317634b4d 100644 --- a/core/modules/system/tests/src/Functional/Module/HookRequirementsTest.php +++ b/core/modules/system/tests/src/Functional/Module/HookRequirementsTest.php @@ -9,6 +9,11 @@ */ class HookRequirementsTest extends ModuleTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Assert that a module cannot be installed if it fails hook_requirements(). */ diff --git a/core/modules/system/tests/src/Functional/Module/InstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallTest.php index ccbca6d31..08acec5b4 100644 --- a/core/modules/system/tests/src/Functional/Module/InstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/InstallTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\system\Functional\Module; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Database\Database; use Drupal\Core\Extension\ExtensionNameLengthException; use Drupal\Tests\BrowserTestBase; @@ -19,12 +21,17 @@ class InstallTest extends BrowserTestBase { */ public static $modules = ['module_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Verify that drupal_get_schema() can be used during module installation. */ public function testGetSchemaAtInstallTime() { // @see module_test_install() - $value = db_query("SELECT data FROM {module_test}")->fetchField(); + $value = Database::getConnection()->query("SELECT data FROM {module_test}")->fetchField(); $this->assertIdentical($value, 'varchar'); } @@ -70,7 +77,7 @@ public function testUninstallPostUpdateFunctions() { */ public function testModuleNameLength() { $module_name = 'invalid_module_name_over_the_maximum_allowed_character_length'; - $message = format_string('Exception thrown when enabling module %name with a name length over the allowed maximum', ['%name' => $module_name]); + $message = new FormattableMarkup('Exception thrown when enabling module %name with a name length over the allowed maximum', ['%name' => $module_name]); try { $this->container->get('module_installer')->install([$module_name]); $this->fail($message); @@ -80,7 +87,7 @@ public function testModuleNameLength() { } // Since for the UI, the submit callback uses FALSE, test that too. - $message = format_string('Exception thrown when enabling as if via the UI the module %name with a name length over the allowed maximum', ['%name' => $module_name]); + $message = new FormattableMarkup('Exception thrown when enabling as if via the UI the module %name with a name length over the allowed maximum', ['%name' => $module_name]); try { $this->container->get('module_installer')->install([$module_name], FALSE); $this->fail($message); diff --git a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php index d1f6be457..0be9688a6 100644 --- a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php @@ -14,6 +14,11 @@ */ class InstallUninstallTest extends ModuleTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -36,7 +41,7 @@ public function testInstallUninstall() { $this->assertEqual($this->container->get('state')->get('system_test_preuninstall_module'), 'module_test'); $this->resetAll(); - $all_modules = system_rebuild_module_data(); + $all_modules = $this->container->get('extension.list.module')->getList(); // Test help on required modules, but do not test uninstalling. $required_modules = array_filter($all_modules, function ($module) { @@ -295,10 +300,10 @@ protected function assertInstallModuleUpdates($module) { $existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []); switch ($module) { case 'block': - $this->assertFalse(array_diff(['block_post_update_disable_blocks_with_missing_contexts'], $existing_updates)); + $this->assertEmpty(array_diff(['block_post_update_disable_blocks_with_missing_contexts'], $existing_updates)); break; case 'update_test_postupdate': - $this->assertFalse(array_diff(['update_test_postupdate_post_update_first', 'update_test_postupdate_post_update_second', 'update_test_postupdate_post_update_test1', 'update_test_postupdate_post_update_test0'], $existing_updates)); + $this->assertEmpty(array_diff(['update_test_postupdate_post_update_first', 'update_test_postupdate_post_update_second', 'update_test_postupdate_post_update_test1', 'update_test_postupdate_post_update_test0'], $existing_updates)); break; } } @@ -316,10 +321,10 @@ protected function assertUninstallModuleUpdates($module) { switch ($module) { case 'block': - $this->assertFalse(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $all_update_functions), 'Asserts that no pending post update functions are available.'); + $this->assertEmpty(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $all_update_functions), 'Asserts that no pending post update functions are available.'); $existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []); - $this->assertFalse(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $existing_updates), 'Asserts that no post update functions are stored in keyvalue store.'); + $this->assertEmpty(array_intersect(['block_post_update_disable_blocks_with_missing_contexts'], $existing_updates), 'Asserts that no post update functions are stored in keyvalue store.'); break; } } @@ -352,7 +357,7 @@ protected function preUninstallForum() { $query = \Drupal::entityQuery('taxonomy_term'); $query->condition('vid', 'forums'); $ids = $query->execute(); - $storage = \Drupal::entityManager()->getStorage('taxonomy_term'); + $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); $terms = $storage->loadMultiple($ids); $storage->delete($terms); } diff --git a/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php b/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php index 40ef29fcc..1e681859b 100644 --- a/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php +++ b/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Module; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Config\InstallStorage; use Drupal\Core\Database\Database; use Drupal\Core\Config\FileStorage; @@ -43,9 +44,9 @@ public function assertTableCount($base_table, $count = TRUE) { $tables = $connection->schema()->findTables($connection->prefixTables('{' . $base_table . '}') . '%'); if ($count) { - return $this->assertTrue($tables, format_string('Tables matching "@base_table" found.', ['@base_table' => $base_table])); + return $this->assertNotEmpty($tables, new FormattableMarkup('Tables matching "@base_table" found.', ['@base_table' => $base_table])); } - return $this->assertFalse($tables, format_string('Tables matching "@base_table" not found.', ['@base_table' => $base_table])); + return $this->assertEmpty($tables, new FormattableMarkup('Tables matching "@base_table" not found.', ['@base_table' => $base_table])); } /** @@ -63,7 +64,7 @@ public function assertModuleTablesExist($module) { $tables_exist = FALSE; } } - return $this->assertTrue($tables_exist, format_string('All database tables defined by the @module module exist.', ['@module' => $module])); + return $this->assertTrue($tables_exist, new FormattableMarkup('All database tables defined by the @module module exist.', ['@module' => $module])); } /** @@ -81,7 +82,7 @@ public function assertModuleTablesDoNotExist($module) { $tables_exist = TRUE; } } - return $this->assertFalse($tables_exist, format_string('None of the database tables defined by the @module module exist.', ['@module' => $module])); + return $this->assertFalse($tables_exist, new FormattableMarkup('None of the database tables defined by the @module module exist.', ['@module' => $module])); } /** @@ -110,7 +111,7 @@ public function assertModuleConfig($module) { // schema directory. return; } - $this->assertTrue($all_names); + $this->assertNotEmpty($all_names); $module_config_dependencies = \Drupal::service('config.manager')->findConfigEntityDependents('module', [$module]); // Look up each default configuration object name in the active @@ -127,7 +128,7 @@ public function assertModuleConfig($module) { } // Verify that all configuration has been installed (which means that $names // is empty). - return $this->assertFalse($names, format_string('All default configuration of @module module found.', ['@module' => $module])); + return $this->assertEmpty($names, new FormattableMarkup('All default configuration of @module module found.', ['@module' => $module])); } /** @@ -141,7 +142,7 @@ public function assertModuleConfig($module) { */ public function assertNoModuleConfig($module) { $names = \Drupal::configFactory()->listAll($module . '.'); - return $this->assertFalse($names, format_string('No configuration found for @module module.', ['@module' => $module])); + return $this->assertEmpty($names, new FormattableMarkup('No configuration found for @module module.', ['@module' => $module])); } /** @@ -161,7 +162,7 @@ public function assertModules(array $modules, $enabled) { else { $message = 'Module "@module" is not enabled.'; } - $this->assertEqual($this->container->get('module_handler')->moduleExists($module), $enabled, format_string($message, ['@module' => $module])); + $this->assertEqual($this->container->get('module_handler')->moduleExists($module), $enabled, new FormattableMarkup($message, ['@module' => $module])); } } @@ -195,7 +196,7 @@ public function assertLogMessage($type, $message, $variables = [], $severity = R ->countQuery() ->execute() ->fetchField(); - $this->assertTrue($count > 0, format_string('watchdog table contains @count rows for @message', ['@count' => $count, '@message' => format_string($message, $variables)])); + $this->assertTrue($count > 0, new FormattableMarkup('watchdog table contains @count rows for @message', ['@count' => $count, '@message' => new FormattableMarkup($message, $variables)])); } } diff --git a/core/modules/system/tests/src/Functional/Module/PrepareUninstallTest.php b/core/modules/system/tests/src/Functional/Module/PrepareUninstallTest.php index 5edaf42af..fdabecdbe 100644 --- a/core/modules/system/tests/src/Functional/Module/PrepareUninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/PrepareUninstallTest.php @@ -3,7 +3,7 @@ namespace Drupal\Tests\system\Functional\Module; use Drupal\Tests\BrowserTestBase; -use Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait; +use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait; /** * Tests that modules which provide entity types can be uninstalled. @@ -14,6 +14,11 @@ class PrepareUninstallTest extends BrowserTestBase { use TaxonomyTestTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An array of node objects. * @@ -158,7 +163,7 @@ public function testUninstall() { // Test an entity type without a label. /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ - $storage = $this->container->get('entity.manager') + $storage = $this->container->get('entity_type.manager') ->getStorage('entity_test_no_label'); $storage->create([ 'id' => mb_strtolower($this->randomMachineName()), diff --git a/core/modules/system/tests/src/Functional/Module/UninstallTest.php b/core/modules/system/tests/src/Functional/Module/UninstallTest.php index 98b96ee91..e65fe1704 100644 --- a/core/modules/system/tests/src/Functional/Module/UninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/UninstallTest.php @@ -23,6 +23,11 @@ class UninstallTest extends BrowserTestBase { */ public static $modules = ['module_test', 'user', 'views', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the hook_modules_uninstalled() of the user module. */ @@ -32,7 +37,7 @@ public function testUserPermsUninstalled() { $this->container->get('module_installer')->uninstall(['module_test']); // Are the perms defined by module_test removed? - $this->assertFalse(user_roles(FALSE, 'module_test perm'), 'Permissions were all removed.'); + $this->assertEmpty(user_roles(FALSE, 'module_test perm'), 'Permissions were all removed.'); } /** @@ -104,7 +109,7 @@ public function testUninstallPage() { } $entity_types = array_unique($entity_types); foreach ($entity_types as $entity_type_id) { - $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id); + $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id); // Add h3's since the entity type label is often repeated in the entity // labels. $this->assertRaw('

' . $entity_type->getLabel() . '

'); diff --git a/core/modules/system/tests/src/Functional/Module/VersionTest.php b/core/modules/system/tests/src/Functional/Module/VersionTest.php index 7b325fef8..2935edf2a 100644 --- a/core/modules/system/tests/src/Functional/Module/VersionTest.php +++ b/core/modules/system/tests/src/Functional/Module/VersionTest.php @@ -9,6 +9,11 @@ */ class VersionTest extends ModuleTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test version dependencies. */ diff --git a/core/modules/system/tests/src/Functional/Page/DefaultMetatagsTest.php b/core/modules/system/tests/src/Functional/Page/DefaultMetatagsTest.php index d020368c5..2493a3830 100644 --- a/core/modules/system/tests/src/Functional/Page/DefaultMetatagsTest.php +++ b/core/modules/system/tests/src/Functional/Page/DefaultMetatagsTest.php @@ -11,6 +11,11 @@ */ class DefaultMetatagsTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests meta tags. */ diff --git a/core/modules/system/tests/src/Functional/Pager/PagerTest.php b/core/modules/system/tests/src/Functional/Pager/PagerTest.php index e0d5c5434..4a6c9d028 100644 --- a/core/modules/system/tests/src/Functional/Pager/PagerTest.php +++ b/core/modules/system/tests/src/Functional/Pager/PagerTest.php @@ -20,7 +20,12 @@ class PagerTest extends BrowserTestBase { * * @var array */ - public static $modules = ['dblog', 'pager_test']; + public static $modules = ['dblog', 'image', 'pager_test']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; /** * A user with permission to access site reports. @@ -42,6 +47,7 @@ protected function setUp() { $this->adminUser = $this->drupalCreateUser([ 'access site reports', + 'administer image styles', ]); $this->drupalLogin($this->adminUser); } @@ -66,6 +72,10 @@ public function testActiveClass() { $current_page = (int) $matches[1]; $this->drupalGet($GLOBALS['base_root'] . parse_url($this->getUrl())['path'] . $elements[0]->getAttribute('href'), ['external' => TRUE]); $this->assertPagerItems($current_page); + + // Verify the pager does not render on a list without pagination. + $this->drupalGet('admin/config/media/image-styles'); + $this->assertElementNotPresent('.pager'); } /** @@ -211,7 +221,7 @@ public function testPagerEllipsis() { */ protected function assertPagerItems($current_page) { $elements = $this->xpath('//ul[contains(@class, :class)]/li', [':class' => 'pager__items']); - $this->assertTrue(!empty($elements), 'Pager found.'); + $this->assertNotEmpty($elements, 'Pager found.'); // Make current page 1-based. $current_page++; @@ -240,7 +250,7 @@ protected function assertPagerItems($current_page) { if ($current_page == $page) { $this->assertClass($element, 'is-active', 'Element for current page has .is-active class.'); $link = $element->find('css', 'a'); - $this->assertTrue($link, 'Element for current page has link.'); + $this->assertNotEmpty($link, 'Element for current page has link.'); $destination = $link->getAttribute('href'); // URL query string param is 0-indexed. $this->assertEqual($destination, '?page=' . ($page - 1)); @@ -249,29 +259,33 @@ protected function assertPagerItems($current_page) { $this->assertNoClass($element, 'is-active', "Element for page $page has no .is-active class."); $this->assertClass($element, 'pager__item', "Element for page $page has .pager__item class."); $link = $element->find('css', 'a'); - $this->assertTrue($link, "Link to page $page found."); + $this->assertNotEmpty($link, "Link to page $page found."); + // Pager link has an attribute set in pager_test_preprocess_pager(). + $this->assertEquals('yes', $link->getAttribute('pager-test')); $destination = $link->getAttribute('href'); $this->assertEqual($destination, '?page=' . ($page - 1)); } unset($elements[--$page]); } // Verify that no other items remain untested. - $this->assertTrue(empty($elements), 'All expected items found.'); + $this->assertEmpty($elements, 'All expected items found.'); // Verify first/previous and next/last items and links. if (isset($first)) { $this->assertClass($first, 'pager__item--first', 'Element for first page has .pager__item--first class.'); $link = $first->find('css', 'a'); - $this->assertTrue($link, 'Link to first page found.'); + $this->assertNotEmpty($link, 'Link to first page found.'); $this->assertNoClass($link, 'is-active', 'Link to first page is not active.'); + $this->assertEquals('first', $link->getAttribute('pager-test')); $destination = $link->getAttribute('href'); $this->assertEqual($destination, '?page=0'); } if (isset($previous)) { $this->assertClass($previous, 'pager__item--previous', 'Element for first page has .pager__item--previous class.'); $link = $previous->find('css', 'a'); - $this->assertTrue($link, 'Link to previous page found.'); + $this->assertNotEmpty($link, 'Link to previous page found.'); $this->assertNoClass($link, 'is-active', 'Link to previous page is not active.'); + $this->assertEquals('previous', $link->getAttribute('pager-test')); $destination = $link->getAttribute('href'); // URL query string param is 0-indexed, $current_page is 1-indexed. $this->assertEqual($destination, '?page=' . ($current_page - 2)); @@ -279,8 +293,9 @@ protected function assertPagerItems($current_page) { if (isset($next)) { $this->assertClass($next, 'pager__item--next', 'Element for next page has .pager__item--next class.'); $link = $next->find('css', 'a'); - $this->assertTrue($link, 'Link to next page found.'); + $this->assertNotEmpty($link, 'Link to next page found.'); $this->assertNoClass($link, 'is-active', 'Link to next page is not active.'); + $this->assertEquals('next', $link->getAttribute('pager-test')); $destination = $link->getAttribute('href'); // URL query string param is 0-indexed, $current_page is 1-indexed. $this->assertEqual($destination, '?page=' . $current_page); @@ -288,8 +303,9 @@ protected function assertPagerItems($current_page) { if (isset($last)) { $link = $last->find('css', 'a'); $this->assertClass($last, 'pager__item--last', 'Element for last page has .pager__item--last class.'); - $this->assertTrue($link, 'Link to last page found.'); + $this->assertNotEmpty($link, 'Link to last page found.'); $this->assertNoClass($link, 'is-active', 'Link to last page is not active.'); + $this->assertEquals('last', $link->getAttribute('pager-test')); $destination = $link->getAttribute('href'); // URL query string param is 0-indexed. $this->assertEqual($destination, '?page=' . ($total_pages - 1)); diff --git a/core/modules/system/tests/src/Functional/ParamConverter/UpcastingTest.php b/core/modules/system/tests/src/Functional/ParamConverter/UpcastingTest.php index faafe8460..a0f3caff8 100644 --- a/core/modules/system/tests/src/Functional/ParamConverter/UpcastingTest.php +++ b/core/modules/system/tests/src/Functional/ParamConverter/UpcastingTest.php @@ -14,6 +14,11 @@ class UpcastingTest extends BrowserTestBase { public static $modules = ['paramconverter_test', 'node', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Confirms that all parameters are converted as expected. * diff --git a/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php b/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php index 586b1c93b..cac7ce1be 100644 --- a/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php +++ b/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php @@ -18,6 +18,11 @@ class AjaxPageStateTest extends BrowserTestBase { */ public static $modules = ['node', 'views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * User account with all available permissions * diff --git a/core/modules/system/tests/src/Functional/Render/DisplayVariantTest.php b/core/modules/system/tests/src/Functional/Render/DisplayVariantTest.php index 2f5415ba7..c10866acd 100644 --- a/core/modules/system/tests/src/Functional/Render/DisplayVariantTest.php +++ b/core/modules/system/tests/src/Functional/Render/DisplayVariantTest.php @@ -18,6 +18,11 @@ class DisplayVariantTest extends BrowserTestBase { */ public static $modules = ['display_variant_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests selecting the variant and passing configuration. */ diff --git a/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php b/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php index 2bd6d289c..382a3442e 100644 --- a/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php +++ b/core/modules/system/tests/src/Functional/Render/HtmlResponseAttachmentsTest.php @@ -18,6 +18,11 @@ class HtmlResponseAttachmentsTest extends BrowserTestBase { */ public static $modules = ['render_attached_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test rendering of ['#attached']. */ diff --git a/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php b/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php index 185ca4985..97ca6d22d 100644 --- a/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php +++ b/core/modules/system/tests/src/Functional/Render/PlaceholderMessageTest.php @@ -19,6 +19,11 @@ class PlaceholderMessageTest extends BrowserTestBase { */ public static $modules = ['render_placeholder_message_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test rendering of message placeholder. */ diff --git a/core/modules/system/tests/src/Functional/Render/RenderArrayNonHtmlSubscriberTest.php b/core/modules/system/tests/src/Functional/Render/RenderArrayNonHtmlSubscriberTest.php index 08380dec0..4f02d1a7c 100644 --- a/core/modules/system/tests/src/Functional/Render/RenderArrayNonHtmlSubscriberTest.php +++ b/core/modules/system/tests/src/Functional/Render/RenderArrayNonHtmlSubscriberTest.php @@ -19,6 +19,11 @@ class RenderArrayNonHtmlSubscriberTest extends BrowserTestBase { */ public static $modules = ['render_array_non_html_subscriber_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests handling of responses by events subscriber. */ diff --git a/core/modules/system/tests/src/Functional/Render/UrlBubbleableMetadataBubblingTest.php b/core/modules/system/tests/src/Functional/Render/UrlBubbleableMetadataBubblingTest.php index 37d20ce3b..4c25fceeb 100644 --- a/core/modules/system/tests/src/Functional/Render/UrlBubbleableMetadataBubblingTest.php +++ b/core/modules/system/tests/src/Functional/Render/UrlBubbleableMetadataBubblingTest.php @@ -22,6 +22,11 @@ class UrlBubbleableMetadataBubblingTest extends BrowserTestBase { */ public static $modules = ['cache_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Rest/ActionJsonAnonTest.php b/core/modules/system/tests/src/Functional/Rest/ActionJsonAnonTest.php index 07a7a5b3d..a3da1f603 100644 --- a/core/modules/system/tests/src/Functional/Rest/ActionJsonAnonTest.php +++ b/core/modules/system/tests/src/Functional/Rest/ActionJsonAnonTest.php @@ -21,4 +21,9 @@ class ActionJsonAnonTest extends ActionResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Rest/ActionJsonBasicAuthTest.php b/core/modules/system/tests/src/Functional/Rest/ActionJsonBasicAuthTest.php index fdc516b9e..98c1d9a2a 100644 --- a/core/modules/system/tests/src/Functional/Rest/ActionJsonBasicAuthTest.php +++ b/core/modules/system/tests/src/Functional/Rest/ActionJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class ActionJsonBasicAuthTest extends ActionResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Rest/ActionJsonCookieTest.php b/core/modules/system/tests/src/Functional/Rest/ActionJsonCookieTest.php index 0766f03f0..82194e867 100644 --- a/core/modules/system/tests/src/Functional/Rest/ActionJsonCookieTest.php +++ b/core/modules/system/tests/src/Functional/Rest/ActionJsonCookieTest.php @@ -26,4 +26,9 @@ class ActionJsonCookieTest extends ActionResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Rest/ActionXmlAnonTest.php b/core/modules/system/tests/src/Functional/Rest/ActionXmlAnonTest.php index e45af980e..db0e28ab0 100644 --- a/core/modules/system/tests/src/Functional/Rest/ActionXmlAnonTest.php +++ b/core/modules/system/tests/src/Functional/Rest/ActionXmlAnonTest.php @@ -23,4 +23,9 @@ class ActionXmlAnonTest extends ActionResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Rest/ActionXmlBasicAuthTest.php b/core/modules/system/tests/src/Functional/Rest/ActionXmlBasicAuthTest.php index f11459bc6..0e90dbea3 100644 --- a/core/modules/system/tests/src/Functional/Rest/ActionXmlBasicAuthTest.php +++ b/core/modules/system/tests/src/Functional/Rest/ActionXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class ActionXmlBasicAuthTest extends ActionResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Rest/ActionXmlCookieTest.php b/core/modules/system/tests/src/Functional/Rest/ActionXmlCookieTest.php index dbee6fa1a..71c703c27 100644 --- a/core/modules/system/tests/src/Functional/Rest/ActionXmlCookieTest.php +++ b/core/modules/system/tests/src/Functional/Rest/ActionXmlCookieTest.php @@ -28,4 +28,9 @@ class ActionXmlCookieTest extends ActionResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Rest/MenuJsonAnonTest.php b/core/modules/system/tests/src/Functional/Rest/MenuJsonAnonTest.php index 468deb517..fbd61d7e8 100644 --- a/core/modules/system/tests/src/Functional/Rest/MenuJsonAnonTest.php +++ b/core/modules/system/tests/src/Functional/Rest/MenuJsonAnonTest.php @@ -21,4 +21,9 @@ class MenuJsonAnonTest extends MenuResourceTestBase { */ protected static $mimeType = 'application/json'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Rest/MenuJsonBasicAuthTest.php b/core/modules/system/tests/src/Functional/Rest/MenuJsonBasicAuthTest.php index 1d35d4216..5b9fe9e3b 100644 --- a/core/modules/system/tests/src/Functional/Rest/MenuJsonBasicAuthTest.php +++ b/core/modules/system/tests/src/Functional/Rest/MenuJsonBasicAuthTest.php @@ -16,6 +16,11 @@ class MenuJsonBasicAuthTest extends MenuResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Rest/MenuJsonCookieTest.php b/core/modules/system/tests/src/Functional/Rest/MenuJsonCookieTest.php index d923875e1..85c880028 100644 --- a/core/modules/system/tests/src/Functional/Rest/MenuJsonCookieTest.php +++ b/core/modules/system/tests/src/Functional/Rest/MenuJsonCookieTest.php @@ -26,4 +26,9 @@ class MenuJsonCookieTest extends MenuResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Rest/MenuXmlAnonTest.php b/core/modules/system/tests/src/Functional/Rest/MenuXmlAnonTest.php index e1c23b5f7..cf9d2101f 100644 --- a/core/modules/system/tests/src/Functional/Rest/MenuXmlAnonTest.php +++ b/core/modules/system/tests/src/Functional/Rest/MenuXmlAnonTest.php @@ -23,4 +23,9 @@ class MenuXmlAnonTest extends MenuResourceTestBase { */ protected static $mimeType = 'text/xml; charset=UTF-8'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Rest/MenuXmlBasicAuthTest.php b/core/modules/system/tests/src/Functional/Rest/MenuXmlBasicAuthTest.php index 091043004..9f786dd4d 100644 --- a/core/modules/system/tests/src/Functional/Rest/MenuXmlBasicAuthTest.php +++ b/core/modules/system/tests/src/Functional/Rest/MenuXmlBasicAuthTest.php @@ -18,6 +18,11 @@ class MenuXmlBasicAuthTest extends MenuResourceTestBase { */ public static $modules = ['basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Rest/MenuXmlCookieTest.php b/core/modules/system/tests/src/Functional/Rest/MenuXmlCookieTest.php index a229b6a3b..45f6a7969 100644 --- a/core/modules/system/tests/src/Functional/Rest/MenuXmlCookieTest.php +++ b/core/modules/system/tests/src/Functional/Rest/MenuXmlCookieTest.php @@ -28,4 +28,9 @@ class MenuXmlCookieTest extends MenuResourceTestBase { */ protected static $auth = 'cookie'; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + } diff --git a/core/modules/system/tests/src/Functional/Routing/DestinationTest.php b/core/modules/system/tests/src/Functional/Routing/DestinationTest.php index 109d80000..98ec29b47 100644 --- a/core/modules/system/tests/src/Functional/Routing/DestinationTest.php +++ b/core/modules/system/tests/src/Functional/Routing/DestinationTest.php @@ -22,6 +22,11 @@ class DestinationTest extends BrowserTestBase { */ public static $modules = ['system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that $_GET/$_REQUEST['destination'] only contain internal URLs. */ diff --git a/core/modules/system/tests/src/Functional/Routing/RouterPermissionTest.php b/core/modules/system/tests/src/Functional/Routing/RouterPermissionTest.php index 62a511f2e..1985725ef 100644 --- a/core/modules/system/tests/src/Functional/Routing/RouterPermissionTest.php +++ b/core/modules/system/tests/src/Functional/Routing/RouterPermissionTest.php @@ -18,6 +18,11 @@ class RouterPermissionTest extends BrowserTestBase { */ public static $modules = ['router_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests permission requirements on routes. */ diff --git a/core/modules/system/tests/src/Functional/Routing/RouterTest.php b/core/modules/system/tests/src/Functional/Routing/RouterTest.php index 53593670a..974afee86 100644 --- a/core/modules/system/tests/src/Functional/Routing/RouterTest.php +++ b/core/modules/system/tests/src/Functional/Routing/RouterTest.php @@ -23,6 +23,11 @@ class RouterTest extends BrowserTestBase { */ public static $modules = ['router_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Confirms that our FinishResponseSubscriber logic works properly. */ @@ -33,8 +38,9 @@ public function testFinishResponseSubscriber() { // Confirm that the router can get to a controller. $this->drupalGet('router_test/test1'); $this->assertRaw('test1', 'The correct string was returned because the route was successful.'); + $session = $this->getSession(); // Check expected headers from FinishResponseSubscriber. - $headers = $this->getSession()->getResponseHeaders(); + $headers = $session->getResponseHeaders(); $this->assertEquals($headers['X-UA-Compatible'], ['IE=edge']); $this->assertEquals($headers['Content-language'], ['en']); @@ -44,7 +50,7 @@ public function testFinishResponseSubscriber() { $this->drupalGet('router_test/test2'); $this->assertRaw('test2', 'The correct string was returned because the route was successful.'); // Check expected headers from FinishResponseSubscriber. - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', $expected_cache_contexts)]); $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous http_response rendered']); // Confirm that the page wrapping is being added, so we're not getting a @@ -58,46 +64,46 @@ public function testFinishResponseSubscriber() { // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags headers. // 1. controller result: render array, globally cacheable route access. $this->drupalGet('router_test/test18'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url']))]); $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous foo http_response rendered']); // 2. controller result: render array, per-role cacheable route access. $this->drupalGet('router_test/test19'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertEqual($headers['X-Drupal-Cache-Contexts'], [implode(' ', Cache::mergeContexts($renderer_required_cache_contexts, ['url', 'user.roles']))]); $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['config:user.role.anonymous foo http_response rendered']); // 3. controller result: Response object, globally cacheable route access. $this->drupalGet('router_test/test1'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertFalse(isset($headers['X-Drupal-Cache-Contexts'])); $this->assertFalse(isset($headers['X-Drupal-Cache-Tags'])); // 4. controller result: Response object, per-role cacheable route access. $this->drupalGet('router_test/test20'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertFalse(isset($headers['X-Drupal-Cache-Contexts'])); $this->assertFalse(isset($headers['X-Drupal-Cache-Tags'])); // 5. controller result: CacheableResponse object, globally cacheable route access. $this->drupalGet('router_test/test21'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertEqual($headers['X-Drupal-Cache-Contexts'], ['']); $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['http_response']); // 6. controller result: CacheableResponse object, per-role cacheable route access. $this->drupalGet('router_test/test22'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertEqual($headers['X-Drupal-Cache-Contexts'], ['user.roles']); $this->assertEqual($headers['X-Drupal-Cache-Tags'], ['http_response']); // Finally, verify that the X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags // headers are not sent when their container parameter is set to FALSE. $this->drupalGet('router_test/test18'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertTrue(isset($headers['X-Drupal-Cache-Contexts'])); $this->assertTrue(isset($headers['X-Drupal-Cache-Tags'])); $this->setContainerParameter('http.response.debug_cacheability_headers', FALSE); $this->rebuildContainer(); $this->resetAll(); $this->drupalGet('router_test/test18'); - $headers = $this->drupalGetHeaders(); + $headers = $session->getResponseHeaders(); $this->assertFalse(isset($headers['X-Drupal-Cache-Contexts'])); $this->assertFalse(isset($headers['X-Drupal-Cache-Tags'])); } diff --git a/core/modules/system/tests/src/Functional/ServiceProvider/ServiceProviderWebTest.php b/core/modules/system/tests/src/Functional/ServiceProvider/ServiceProviderWebTest.php index 5e86164f6..773c1795e 100644 --- a/core/modules/system/tests/src/Functional/ServiceProvider/ServiceProviderWebTest.php +++ b/core/modules/system/tests/src/Functional/ServiceProvider/ServiceProviderWebTest.php @@ -18,6 +18,11 @@ class ServiceProviderWebTest extends BrowserTestBase { */ public static $modules = ['file', 'service_provider_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that module service providers get registered to the DIC. * diff --git a/core/modules/system/tests/src/Functional/Session/SessionAuthenticationTest.php b/core/modules/system/tests/src/Functional/Session/SessionAuthenticationTest.php index 782587768..45b40fe42 100644 --- a/core/modules/system/tests/src/Functional/Session/SessionAuthenticationTest.php +++ b/core/modules/system/tests/src/Functional/Session/SessionAuthenticationTest.php @@ -27,6 +27,11 @@ class SessionAuthenticationTest extends BrowserTestBase { */ public static $modules = ['basic_auth', 'session_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -67,7 +72,7 @@ public function testSessionFromBasicAuthenticationDoesNotLeak() { // should no longer be logged in. $this->drupalGet($unprotected_url); $this->assertResponse(200, 'An unprotected route can be accessed without basic authentication.'); - $this->assertFalse(json_decode($session->getPage()->getContent())->user, 'The user is no longer authenticated after visiting a page without basic authentication.'); + $this->assertEquals(0, json_decode($session->getPage()->getContent())->user, 'The user is no longer authenticated after visiting a page without basic authentication.'); // If we access the protected page again without basic authentication we // should get 401 Unauthorized. diff --git a/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php b/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php index f746f9dbb..f5847645b 100644 --- a/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php +++ b/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php @@ -38,6 +38,11 @@ class SessionHttpsTest extends BrowserTestBase { */ public static $modules = ['session_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -252,7 +257,7 @@ protected function assertSessionIds($sid, $assertion_text) { $args = [ ':sid' => Crypt::hashBase64($sid), ]; - return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid', $args)->fetchField(), $assertion_text); + return $this->assertNotEmpty(\Drupal::database()->query('SELECT timestamp FROM {sessions} WHERE sid = :sid', $args)->fetchField(), $assertion_text); } /** diff --git a/core/modules/system/tests/src/Functional/Session/SessionTest.php b/core/modules/system/tests/src/Functional/Session/SessionTest.php index 0cee31f68..8092bf120 100644 --- a/core/modules/system/tests/src/Functional/Session/SessionTest.php +++ b/core/modules/system/tests/src/Functional/Session/SessionTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\system\Functional\Session; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Database\Database; use Drupal\Tests\BrowserTestBase; /** @@ -18,6 +20,11 @@ class SessionTest extends BrowserTestBase { */ public static $modules = ['session_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected $dumpHeaders = TRUE; /** @@ -44,7 +51,7 @@ public function testSessionSaveRegenerate() { // Start a new session by setting a message. $this->drupalGet('session-test/set-message'); $this->assertSessionCookie(TRUE); - $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as HttpOnly.'); + $this->assertRegExp('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE), 'Session cookie is set as HttpOnly.'); // Verify that the session is regenerated if a module calls exit // in hook_user_login(). @@ -64,7 +71,7 @@ public function testSessionSaveRegenerate() { ]; $this->drupalPostForm('user/login', $edit, t('Log in')); $this->drupalGet('user'); - $pass = $this->assertText($user->getAccountName(), format_string('Found name: %name', ['%name' => $user->getAccountName()]), 'User login'); + $pass = $this->assertText($user->getAccountName(), new FormattableMarkup('Found name: %name', ['%name' => $user->getAccountName()]), 'User login'); $this->_logged_in = $pass; $this->drupalGet('session-test/id'); @@ -185,16 +192,16 @@ public function testEmptyAnonymousSession() { // Start a new session by setting a message. $this->drupalGet('session-test/set-message'); $this->assertSessionCookie(TRUE); - $this->assertTrue($this->drupalGetHeader('Set-Cookie'), 'New session was started.'); + $this->assertNotEmpty($this->drupalGetHeader('Set-Cookie'), 'New session was started.'); // Display the message, during the same request the session is destroyed // and the session cookie is unset. $this->drupalGet(''); $this->assertSessionCookie(FALSE); $this->assertSessionEmpty(FALSE); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); $this->assertText(t('This is a dummy message.'), 'Message was displayed.'); - $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), 'Session cookie was deleted.'); + $this->assertRegExp('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie'), 'Session cookie was deleted.'); // Verify that session was destroyed. $this->drupalGet(''); @@ -203,7 +210,7 @@ public function testEmptyAnonymousSession() { // $this->assertSessionEmpty(TRUE); $this->assertNoText(t('This is a dummy message.'), 'Message was not cached.'); $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - $this->assertFalse($this->drupalGetHeader('Set-Cookie'), 'New session was not started.'); + $this->assertNull($this->drupalGetHeader('Set-Cookie'), 'New session was not started.'); // Verify that no session is created if drupal_save_session(FALSE) is called. $this->drupalGet('session-test/set-message-but-dont-save'); @@ -224,9 +231,10 @@ public function testEmptyAnonymousSession() { public function testSessionWrite() { $user = $this->drupalCreateUser([]); $this->drupalLogin($user); + $connection = Database::getConnection(); $sql = 'SELECT u.access, s.timestamp FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE u.uid = :uid'; - $times1 = db_query($sql, [':uid' => $user->id()])->fetchObject(); + $times1 = $connection->query($sql, [':uid' => $user->id()])->fetchObject(); // Before every request we sleep one second to make sure that if the session // is saved, its timestamp will change. @@ -234,21 +242,21 @@ public function testSessionWrite() { // Modify the session. sleep(1); $this->drupalGet('session-test/set/foo'); - $times2 = db_query($sql, [':uid' => $user->id()])->fetchObject(); + $times2 = $connection->query($sql, [':uid' => $user->id()])->fetchObject(); $this->assertEqual($times2->access, $times1->access, 'Users table was not updated.'); $this->assertNotEqual($times2->timestamp, $times1->timestamp, 'Sessions table was updated.'); // Write the same value again, i.e. do not modify the session. sleep(1); $this->drupalGet('session-test/set/foo'); - $times3 = db_query($sql, [':uid' => $user->id()])->fetchObject(); + $times3 = $connection->query($sql, [':uid' => $user->id()])->fetchObject(); $this->assertEqual($times3->access, $times1->access, 'Users table was not updated.'); $this->assertEqual($times3->timestamp, $times2->timestamp, 'Sessions table was not updated.'); // Do not change the session. sleep(1); $this->drupalGet(''); - $times4 = db_query($sql, [':uid' => $user->id()])->fetchObject(); + $times4 = $connection->query($sql, [':uid' => $user->id()])->fetchObject(); $this->assertEqual($times4->access, $times3->access, 'Users table was not updated.'); $this->assertEqual($times4->timestamp, $times3->timestamp, 'Sessions table was not updated.'); @@ -259,7 +267,7 @@ public function testSessionWrite() { ]; $this->writeSettings($settings); $this->drupalGet(''); - $times5 = db_query($sql, [':uid' => $user->id()])->fetchObject(); + $times5 = $connection->query($sql, [':uid' => $user->id()])->fetchObject(); $this->assertNotEqual($times5->access, $times4->access, 'Users table was updated.'); $this->assertNotEqual($times5->timestamp, $times4->timestamp, 'Sessions table was updated.'); } @@ -275,7 +283,7 @@ public function testEmptySessionID() { // Reset the sid in {sessions} to a blank string. This may exist in the // wild in some cases, although we normally prevent it from happening. - db_query("UPDATE {sessions} SET sid = '' WHERE uid = :uid", [':uid' => $user->id()]); + Database::getConnection()->query("UPDATE {sessions} SET sid = '' WHERE uid = :uid", [':uid' => $user->id()]); // Send a blank sid in the session cookie, and the session should no longer // be valid. Closing the curl handler will stop the previous session ID // from persisting. diff --git a/core/modules/system/tests/src/Functional/Session/StackSessionHandlerIntegrationTest.php b/core/modules/system/tests/src/Functional/Session/StackSessionHandlerIntegrationTest.php index d65ba4216..9b7143617 100644 --- a/core/modules/system/tests/src/Functional/Session/StackSessionHandlerIntegrationTest.php +++ b/core/modules/system/tests/src/Functional/Session/StackSessionHandlerIntegrationTest.php @@ -17,6 +17,11 @@ class StackSessionHandlerIntegrationTest extends BrowserTestBase { */ protected static $modules = ['session_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests a request. */ diff --git a/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php b/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php index c0749b490..4c4b0cb1c 100644 --- a/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php +++ b/core/modules/system/tests/src/Functional/System/AccessDeniedTest.php @@ -23,6 +23,11 @@ class AccessDeniedTest extends BrowserTestBase { */ public static $modules = ['block', 'node', 'system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected $adminUser; protected function setUp() { diff --git a/core/modules/system/tests/src/Functional/System/AdminMetaTagTest.php b/core/modules/system/tests/src/Functional/System/AdminMetaTagTest.php index 6fcc4b848..d17e537b6 100644 --- a/core/modules/system/tests/src/Functional/System/AdminMetaTagTest.php +++ b/core/modules/system/tests/src/Functional/System/AdminMetaTagTest.php @@ -11,6 +11,11 @@ */ class AdminMetaTagTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Verify that the meta tag HTML is generated correctly. */ diff --git a/core/modules/system/tests/src/Functional/System/AdminTest.php b/core/modules/system/tests/src/Functional/System/AdminTest.php index 7a0b2a3ae..2face4291 100644 --- a/core/modules/system/tests/src/Functional/System/AdminTest.php +++ b/core/modules/system/tests/src/Functional/System/AdminTest.php @@ -33,6 +33,11 @@ class AdminTest extends BrowserTestBase { */ public static $modules = ['locale']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { // testAdminPages() requires Locale module. parent::setUp(); @@ -154,11 +159,11 @@ public function testCompactMode() { $this->drupalGet('admin/compact/on'); $this->assertResponse(200, 'A valid page is returned after turning on compact mode.'); $this->assertUrl($frontpage_url, [], 'The user is redirected to the front page after turning on compact mode.'); - $this->assertTrue($session->getCookie('Drupal.visitor.admin_compact_mode'), 'Compact mode turns on.'); + $this->assertEquals('1', $session->getCookie('Drupal.visitor.admin_compact_mode'), 'Compact mode turns on.'); $this->drupalGet('admin/compact/on'); - $this->assertTrue($session->getCookie('Drupal.visitor.admin_compact_mode'), 'Compact mode remains on after a repeat call.'); + $this->assertEquals('1', $session->getCookie('Drupal.visitor.admin_compact_mode'), 'Compact mode remains on after a repeat call.'); $this->drupalGet(''); - $this->assertTrue($session->getCookie('Drupal.visitor.admin_compact_mode'), 'Compact mode persists on new requests.'); + $this->assertEquals('1', $session->getCookie('Drupal.visitor.admin_compact_mode'), 'Compact mode persists on new requests.'); $this->drupalGet('admin/compact/off'); $this->assertResponse(200, 'A valid page is returned after turning off compact mode.'); diff --git a/core/modules/system/tests/src/Functional/System/CronRunTest.php b/core/modules/system/tests/src/Functional/System/CronRunTest.php index 12b86c83e..eecd9db7f 100644 --- a/core/modules/system/tests/src/Functional/System/CronRunTest.php +++ b/core/modules/system/tests/src/Functional/System/CronRunTest.php @@ -21,6 +21,11 @@ class CronRunTest extends BrowserTestBase { */ public static $modules = ['common_test', 'common_test_cron_helper', 'automated_cron']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test cron runs. */ diff --git a/core/modules/system/tests/src/Functional/System/DateFormatsLockedTest.php b/core/modules/system/tests/src/Functional/System/DateFormatsLockedTest.php index faac2b762..d09d8d134 100644 --- a/core/modules/system/tests/src/Functional/System/DateFormatsLockedTest.php +++ b/core/modules/system/tests/src/Functional/System/DateFormatsLockedTest.php @@ -11,6 +11,11 @@ */ class DateFormatsLockedTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests attempts at listing, editing, and deleting locked date formats. */ diff --git a/core/modules/system/tests/src/Functional/System/DateFormatsMachineNameTest.php b/core/modules/system/tests/src/Functional/System/DateFormatsMachineNameTest.php index fd88bdc77..606a287a2 100644 --- a/core/modules/system/tests/src/Functional/System/DateFormatsMachineNameTest.php +++ b/core/modules/system/tests/src/Functional/System/DateFormatsMachineNameTest.php @@ -11,6 +11,11 @@ */ class DateFormatsMachineNameTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/System/DateTimeTest.php b/core/modules/system/tests/src/Functional/System/DateTimeTest.php index 15f360888..7ce8db7e0 100644 --- a/core/modules/system/tests/src/Functional/System/DateTimeTest.php +++ b/core/modules/system/tests/src/Functional/System/DateTimeTest.php @@ -21,6 +21,11 @@ class DateTimeTest extends BrowserTestBase { */ public static $modules = ['block', 'node', 'language', 'field', 'field_ui', 'datetime', 'options']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); @@ -64,7 +69,7 @@ public function testTimeZoneHandling() { // Set time zone to Los Angeles time. $config->set('timezone.default', 'America/Los_Angeles')->save(); - \Drupal::entityManager()->getViewBuilder('node')->resetCache([$node1, $node2]); + \Drupal::entityTypeManager()->getViewBuilder('node')->resetCache([$node1, $node2]); // Confirm date format and time zone. $this->drupalGet('node/' . $node1->id()); @@ -121,7 +126,7 @@ public function testDateFormatConfiguration() { // Make sure the date does not exist in config. $date_format = DateFormat::load($date_format_id); - $this->assertFalse($date_format); + $this->assertNull($date_format); // Add a new date format with an existing format. $date_format_id = strtolower($this->randomMachineName(8)); diff --git a/core/modules/system/tests/src/Functional/System/DefaultMobileMetaTagsTest.php b/core/modules/system/tests/src/Functional/System/DefaultMobileMetaTagsTest.php index 922839f99..a8f54bad6 100644 --- a/core/modules/system/tests/src/Functional/System/DefaultMobileMetaTagsTest.php +++ b/core/modules/system/tests/src/Functional/System/DefaultMobileMetaTagsTest.php @@ -19,6 +19,11 @@ class DefaultMobileMetaTagsTest extends BrowserTestBase { */ protected $defaultMetaTags; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $this->defaultMetaTags = [ diff --git a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php index 3ab502e86..f93468b65 100644 --- a/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php +++ b/core/modules/system/tests/src/Functional/System/ErrorHandlerTest.php @@ -19,6 +19,11 @@ class ErrorHandlerTest extends BrowserTestBase { */ public static $modules = ['error_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test the error handler. */ @@ -121,10 +126,10 @@ public function testExceptionHandler() { $this->assertSession()->statusCodeEquals(500); // We cannot use assertErrorMessage() since the exact error reported // varies from database to database. Check that the SQL string is displayed. - $this->assertText($error_pdo_exception['%type'], format_string('Found %type in error page.', $error_pdo_exception)); - $this->assertText($error_pdo_exception['@message'], format_string('Found @message in error page.', $error_pdo_exception)); - $error_details = format_string('in %function (line ', $error_pdo_exception); - $this->assertRaw($error_details, format_string("Found '@message' in error page.", ['@message' => $error_details])); + $this->assertText($error_pdo_exception['%type'], new FormattableMarkup('Found %type in error page.', $error_pdo_exception)); + $this->assertText($error_pdo_exception['@message'], new FormattableMarkup('Found @message in error page.', $error_pdo_exception)); + $error_details = new FormattableMarkup('in %function (line ', $error_pdo_exception); + $this->assertRaw($error_details, new FormattableMarkup("Found '@message' in error page.", ['@message' => $error_details])); $this->drupalGet('error-test/trigger-renderer-exception'); $this->assertSession()->statusCodeEquals(500); @@ -136,7 +141,7 @@ public function testExceptionHandler() { ->save(); $this->drupalGet('error-test/trigger-exception'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache')); + $this->assertNull($this->drupalGetHeader('X-Drupal-Cache')); $this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'Received expected HTTP status line.'); $this->assertSession()->statusCodeEquals(500); $this->assertNoErrorMessage($error_exception); @@ -147,7 +152,7 @@ public function testExceptionHandler() { */ public function assertErrorMessage(array $error) { $message = new FormattableMarkup('%type: @message in %function (line ', $error); - $this->assertRaw($message, format_string('Found error message: @message.', ['@message' => $message])); + $this->assertRaw($message, new FormattableMarkup('Found error message: @message.', ['@message' => $message])); } /** @@ -155,7 +160,7 @@ public function assertErrorMessage(array $error) { */ public function assertNoErrorMessage(array $error) { $message = new FormattableMarkup('%type: @message in %function (line ', $error); - $this->assertNoRaw($message, format_string('Did not find error message: @message.', ['@message' => $message])); + $this->assertNoRaw($message, new FormattableMarkup('Did not find error message: @message.', ['@message' => $message])); } /** @@ -165,7 +170,7 @@ public function assertNoErrorMessage(array $error) { * TRUE, if there are no messages. */ protected function assertNoMessages() { - return $this->assertFalse($this->xpath('//div[contains(@class, "messages")]'), 'Ensures that also no messages div exists, which proves that no messages were generated by the error handler, not even an empty one.'); + return $this->assertEmpty($this->xpath('//div[contains(@class, "messages")]'), 'Ensures that also no messages div exists, which proves that no messages were generated by the error handler, not even an empty one.'); } } diff --git a/core/modules/system/tests/src/Functional/System/FrontPageTest.php b/core/modules/system/tests/src/Functional/System/FrontPageTest.php index 7f9fc8c1f..98bab99ad 100644 --- a/core/modules/system/tests/src/Functional/System/FrontPageTest.php +++ b/core/modules/system/tests/src/Functional/System/FrontPageTest.php @@ -19,6 +19,11 @@ class FrontPageTest extends BrowserTestBase { */ public static $modules = ['node', 'system_test', 'views']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The path to a node that is created for testing. * diff --git a/core/modules/system/tests/src/Functional/System/HtaccessTest.php b/core/modules/system/tests/src/Functional/System/HtaccessTest.php index 79c4766c4..037a92a00 100644 --- a/core/modules/system/tests/src/Functional/System/HtaccessTest.php +++ b/core/modules/system/tests/src/Functional/System/HtaccessTest.php @@ -18,6 +18,11 @@ class HtaccessTest extends BrowserTestBase { */ public static $modules = ['node', 'path']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Get an array of file paths for access testing. * @@ -86,6 +91,10 @@ protected function getProtectedFiles() { $file_paths["$path/composer.json"] = 403; $file_paths["$path/composer.lock"] = 403; + // Ensure web server configuration files cannot be accessed. + $file_paths["$path/.htaccess"] = 403; + $file_paths["$path/web.config"] = 403; + return $file_paths; } diff --git a/core/modules/system/tests/src/Functional/System/IndexPhpTest.php b/core/modules/system/tests/src/Functional/System/IndexPhpTest.php index 4675fed0f..4146d0c65 100644 --- a/core/modules/system/tests/src/Functional/System/IndexPhpTest.php +++ b/core/modules/system/tests/src/Functional/System/IndexPhpTest.php @@ -11,6 +11,11 @@ */ class IndexPhpTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); } diff --git a/core/modules/system/tests/src/Functional/System/MainContentFallbackTest.php b/core/modules/system/tests/src/Functional/System/MainContentFallbackTest.php index 931b84ce2..acbb60aa8 100644 --- a/core/modules/system/tests/src/Functional/System/MainContentFallbackTest.php +++ b/core/modules/system/tests/src/Functional/System/MainContentFallbackTest.php @@ -18,6 +18,11 @@ class MainContentFallbackTest extends BrowserTestBase { */ public static $modules = ['block', 'system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected $adminUser; protected $webUser; diff --git a/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php b/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php index 060f53aa1..45522da9b 100644 --- a/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php +++ b/core/modules/system/tests/src/Functional/System/PageNotFoundTest.php @@ -23,6 +23,11 @@ class PageNotFoundTest extends BrowserTestBase { */ public static $modules = ['system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected $adminUser; protected function setUp() { diff --git a/core/modules/system/tests/src/Functional/System/PageTitleTest.php b/core/modules/system/tests/src/Functional/System/PageTitleTest.php index 8073827fb..bc2ed2018 100644 --- a/core/modules/system/tests/src/Functional/System/PageTitleTest.php +++ b/core/modules/system/tests/src/Functional/System/PageTitleTest.php @@ -21,6 +21,11 @@ class PageTitleTest extends BrowserTestBase { */ public static $modules = ['node', 'test_page_test', 'form_test', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected $contentUser; protected $savedTitle; diff --git a/core/modules/system/tests/src/Functional/System/ResponseGeneratorTest.php b/core/modules/system/tests/src/Functional/System/ResponseGeneratorTest.php index d0f2ab539..5c8f547c4 100644 --- a/core/modules/system/tests/src/Functional/System/ResponseGeneratorTest.php +++ b/core/modules/system/tests/src/Functional/System/ResponseGeneratorTest.php @@ -19,6 +19,11 @@ class ResponseGeneratorTest extends BrowserTestBase { */ public static $modules = ['hal', 'rest', 'node', 'basic_auth']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php b/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php index bcd8f7d3b..25db39966 100644 --- a/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php +++ b/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php @@ -11,6 +11,11 @@ */ class RetrieveFileTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Invokes system_retrieve_file() in several scenarios. */ diff --git a/core/modules/system/tests/src/Functional/System/ShutdownFunctionsTest.php b/core/modules/system/tests/src/Functional/System/ShutdownFunctionsTest.php index f5d55be78..c7e25beda 100644 --- a/core/modules/system/tests/src/Functional/System/ShutdownFunctionsTest.php +++ b/core/modules/system/tests/src/Functional/System/ShutdownFunctionsTest.php @@ -18,6 +18,11 @@ class ShutdownFunctionsTest extends BrowserTestBase { */ public static $modules = ['system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function tearDown() { // This test intentionally throws an exception in a PHP shutdown function. // Prevent it from being interpreted as an actual test failure. diff --git a/core/modules/system/tests/src/Functional/System/SiteMaintenanceTest.php b/core/modules/system/tests/src/Functional/System/SiteMaintenanceTest.php index d519c7351..ec3a4b695 100644 --- a/core/modules/system/tests/src/Functional/System/SiteMaintenanceTest.php +++ b/core/modules/system/tests/src/Functional/System/SiteMaintenanceTest.php @@ -24,6 +24,11 @@ class SiteMaintenanceTest extends BrowserTestBase { */ public static $modules = ['node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected $adminUser; protected function setUp() { @@ -136,7 +141,7 @@ public function testSiteMaintenance() { $this->assertText($user_message); // Regression test to check if title displays in Bartik on maintenance page. - \Drupal::service('theme_handler')->install(['bartik']); + \Drupal::service('theme_installer')->install(['bartik']); $this->config('system.theme')->set('default', 'bartik')->save(); // Logout and verify that offline message is displayed in Bartik. diff --git a/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php b/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php index f08a18d8b..ce3ac2a83 100644 --- a/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php +++ b/core/modules/system/tests/src/Functional/System/SitesDirectoryHardeningTest.php @@ -15,6 +15,11 @@ class SitesDirectoryHardeningTest extends BrowserTestBase { use StringTranslationTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the default behavior to restrict directory permissions is enforced. * diff --git a/core/modules/system/tests/src/Functional/System/StatusTest.php b/core/modules/system/tests/src/Functional/System/StatusTest.php index c902a3731..6d4cce4c7 100644 --- a/core/modules/system/tests/src/Functional/System/StatusTest.php +++ b/core/modules/system/tests/src/Functional/System/StatusTest.php @@ -4,7 +4,6 @@ use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; -use Drupal\system\SystemRequirements; use Symfony\Component\CssSelector\CssSelectorConverter; /** @@ -19,19 +18,21 @@ class StatusTest extends BrowserTestBase { */ public static $modules = ['update_test_postupdate']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - // Unset the sync directory in settings.php to trigger $config_directories - // error. - $settings['config_directories'] = [ - CONFIG_SYNC_DIRECTORY => (object) [ - 'value' => '', - 'required' => TRUE, - ], + // Unset the sync directory in settings.php to trigger the error. + $settings['settings']['config_sync_directory'] = (object) [ + 'value' => '', + 'required' => TRUE, ]; $this->writeSettings($settings); @@ -52,15 +53,6 @@ public function testStatusPage() { $phpversion = phpversion(); $this->assertText($phpversion, 'Php version is shown on the page.'); - // Checks if the suggestion to update to php 5.5.21 or 5.6.5 for disabling - // multiple statements is present when necessary. - if (\Drupal::database()->driver() === 'mysql' && !SystemRequirements::phpVersionWithPdoDisallowMultipleStatements($phpversion)) { - $this->assertText(t('PHP (multiple statement disabling)')); - } - else { - $this->assertNoText(t('PHP (multiple statement disabling)')); - } - if (function_exists('phpinfo')) { $this->assertLinkByHref(Url::fromRoute('system.php')->toString()); } @@ -72,7 +64,7 @@ public function testStatusPage() { $this->assertNoText(t('Out of date')); // The global $config_directories is not properly formed. - $this->assertRaw(t('Your %file file must define the $config_directories variable as an array containing the names of directories in which configuration files can be found. It must contain a %sync_key key.', ['%file' => $this->siteDirectory . '/settings.php', '%sync_key' => CONFIG_SYNC_DIRECTORY])); + $this->assertRaw(t("Your %file file must define the %setting setting", ['%file' => $this->siteDirectory . '/settings.php', '%setting' => "\$settings['config_sync_directory']"])); // Set the schema version of update_test_postupdate to a lower version, so // update_test_postupdate_update_8001() needs to be executed. diff --git a/core/modules/system/tests/src/Functional/System/SystemAuthorizeTest.php b/core/modules/system/tests/src/Functional/System/SystemAuthorizeTest.php index b3f9b310b..36f7208ea 100644 --- a/core/modules/system/tests/src/Functional/System/SystemAuthorizeTest.php +++ b/core/modules/system/tests/src/Functional/System/SystemAuthorizeTest.php @@ -18,6 +18,11 @@ class SystemAuthorizeTest extends BrowserTestBase { */ public static $modules = ['system_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); diff --git a/core/modules/system/tests/src/Functional/System/ThemeTest.php b/core/modules/system/tests/src/Functional/System/ThemeTest.php index 1c69d7a3b..2a7b5f898 100644 --- a/core/modules/system/tests/src/Functional/System/ThemeTest.php +++ b/core/modules/system/tests/src/Functional/System/ThemeTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\system\Functional\System; use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\Tests\BrowserTestBase; use Drupal\Tests\TestFileCreationTrait; @@ -32,6 +33,11 @@ class ThemeTest extends BrowserTestBase { */ public static $modules = ['node', 'block', 'file']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + protected function setUp() { parent::setUp(); @@ -64,12 +70,12 @@ public function testThemeSettings() { $supported_paths = [ // Raw stream wrapper URI. $file->uri => [ - 'form' => file_uri_target($file->uri), + 'form' => StreamWrapperManager::getTarget($file->uri), 'src' => file_url_transform_relative(file_create_url($file->uri)), ], // Relative path within the public filesystem. - file_uri_target($file->uri) => [ - 'form' => file_uri_target($file->uri), + StreamWrapperManager::getTarget($file->uri) => [ + 'form' => StreamWrapperManager::getTarget($file->uri), 'src' => file_url_transform_relative(file_create_url($file->uri)), ], // Relative path to a public file. @@ -107,17 +113,17 @@ public function testThemeSettings() { $explicit_file = 'public://logo.svg'; $local_file = $default_theme_path . '/logo.svg'; // Adjust for fully qualified stream wrapper URI in public filesystem. - if (file_uri_scheme($input) == 'public') { - $implicit_public_file = file_uri_target($input); + if (StreamWrapperManager::getScheme($input) == 'public') { + $implicit_public_file = StreamWrapperManager::getTarget($input); $explicit_file = $input; $local_file = strtr($input, ['public:/' => PublicStream::basePath()]); } // Adjust for fully qualified stream wrapper URI elsewhere. - elseif (file_uri_scheme($input) !== FALSE) { + elseif (StreamWrapperManager::getScheme($input) !== FALSE) { $explicit_file = $input; } // Adjust for relative path within public filesystem. - elseif ($input == file_uri_target($file->uri)) { + elseif ($input == StreamWrapperManager::getTarget($file->uri)) { $implicit_public_file = $input; $explicit_file = 'public://' . $input; $local_file = PublicStream::basePath() . '/' . $input; @@ -188,7 +194,7 @@ public function testThemeSettings() { ); $this->assertEqual($elements[0]->getAttribute('src'), file_url_transform_relative(file_create_url($uploaded_filename))); - $this->container->get('theme_handler')->install(['bartik']); + $this->container->get('theme_installer')->install(['bartik']); // Ensure only valid themes are listed in the local tasks. $this->drupalPlaceBlock('local_tasks_block', ['region' => 'header']); @@ -225,7 +231,7 @@ public function testThemeSettings() { */ public function testThemeSettingsLogo() { // Visit Bartik's theme settings page to replace the logo. - $this->container->get('theme_handler')->install(['bartik']); + $this->container->get('theme_installer')->install(['bartik']); $this->drupalGet('admin/appearance/settings/bartik'); $edit = [ 'default_logo' => FALSE, @@ -247,7 +253,7 @@ public function testThemeSettingsLogo() { * Tests the 'rendered' cache tag is cleared when saving theme settings. */ public function testThemeSettingsRenderCacheClear() { - $this->container->get('theme_handler')->install(['bartik']); + $this->container->get('theme_installer')->install(['bartik']); // Ensure the frontpage is cached for anonymous users. The render cache will // cleared by installing a theme. $this->drupalLogout(); @@ -269,7 +275,7 @@ public function testThemeSettingsRenderCacheClear() { * Test the administration theme functionality. */ public function testAdministrationTheme() { - $this->container->get('theme_handler')->install(['seven']); + $this->container->get('theme_installer')->install(['seven']); // Install an administration theme and show it on the node admin pages. $edit = [ @@ -312,7 +318,7 @@ public function testAdministrationTheme() { // Reset to the default theme settings. $edit = [ - 'admin_theme' => '0', + 'admin_theme' => '', 'use_admin_theme' => FALSE, ]; $this->drupalPostForm('admin/appearance', $edit, t('Save configuration')); @@ -328,14 +334,15 @@ public function testAdministrationTheme() { * Test switching the default theme. */ public function testSwitchDefaultTheme() { - /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */ - $theme_handler = \Drupal::service('theme_handler'); + /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */ + $theme_installer = \Drupal::service('theme_installer'); // First, install Stark and set it as the default theme programmatically. - $theme_handler->install(['stark']); + $theme_installer->install(['stark']); $this->config('system.theme')->set('default', 'stark')->save(); + $this->drupalPlaceBlock('local_tasks_block'); // Install Bartik and set it as the default theme. - $theme_handler->install(['bartik']); + $theme_installer->install(['bartik']); $this->drupalGet('admin/appearance'); $this->clickLink(t('Set as default')); $this->assertEqual($this->config('system.theme')->get('default'), 'bartik'); @@ -366,8 +373,11 @@ public function testInvalidTheme() { $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', ['@base_theme' => 'not_real_test_basetheme'])); $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', ['@base_theme' => 'test_invalid_basetheme'])); $this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', ['@theme_engine' => 'not_real_engine'])); - // Check for the error text of a theme with the wrong core version. - $this->assertText("This theme is not compatible with Drupal 8.x. Check that the .info.yml file contains the correct 'core' value."); + // Check for the error text of a theme with the wrong core version + // using 7.x and ^7. + $incompatible_core_message = 'This theme is not compatible with Drupal ' . \Drupal::VERSION . ". Check that the .info.yml file contains a compatible 'core' or 'core_version_requirement' value."; + $this->assertThemeIncompatibleText('Theme test with invalid core version', $incompatible_core_message); + $this->assertThemeIncompatibleText('Theme test with invalid semver core version', $incompatible_core_message); // Check for the error text of a theme without a content region. $this->assertText("This theme is missing a 'content' region."); } @@ -377,9 +387,9 @@ public function testInvalidTheme() { */ public function testUninstallingThemes() { // Install Bartik and set it as the default theme. - \Drupal::service('theme_handler')->install(['bartik']); + \Drupal::service('theme_installer')->install(['bartik']); // Set up seven as the admin theme. - \Drupal::service('theme_handler')->install(['seven']); + \Drupal::service('theme_installer')->install(['seven']); $edit = [ 'admin_theme' => 'seven', 'use_admin_theme' => TRUE, @@ -397,7 +407,7 @@ public function testUninstallingThemes() { $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.'); // Install Stark and set it as the default theme. - \Drupal::service('theme_handler')->install(['stark']); + \Drupal::service('theme_installer')->install(['stark']); $edit = [ 'admin_theme' => 'stark', @@ -436,24 +446,29 @@ public function testUninstallingThemes() { * Tests installing a theme and setting it as default. */ public function testInstallAndSetAsDefault() { - $this->drupalGet('admin/appearance'); - // Bartik is uninstalled in the test profile and has the third "Install and - // set as default" link. - $this->clickLink(t('Install and set as default'), 2); - // Test the confirmation message. - $this->assertText('Bartik is now the default theme.'); - // Make sure Bartik is now set as the default theme in config. - $this->assertEqual($this->config('system.theme')->get('default'), 'bartik'); - - // This checks for a regression. See https://www.drupal.org/node/2498691. - $this->assertNoText('The bartik theme was not found.'); - - $themes = \Drupal::service('theme_handler')->rebuildThemeData(); - $version = $themes['bartik']->info['version']; - - // Confirm Bartik is indicated as the default theme. - $out = $this->getSession()->getPage()->getContent(); - $this->assertTrue((bool) preg_match('/Bartik ' . preg_quote($version) . '\s{2,}\(default theme\)/', $out)); + $themes = [ + 'bartik' => 'Bartik', + 'test_core_semver' => 'Theme test with semver core version', + ]; + foreach ($themes as $theme_machine_name => $theme_name) { + $this->drupalGet('admin/appearance'); + $this->getSession()->getPage()->findLink("Install $theme_name as default theme")->click(); + // Test the confirmation message. + $this->assertText("$theme_name is now the default theme."); + // Make sure the theme is now set as the default theme in config. + $this->assertEqual($this->config('system.theme')->get('default'), $theme_machine_name); + + // This checks for a regression. See https://www.drupal.org/node/2498691. + $this->assertNoText("The $theme_machine_name theme was not found."); + + $themes = \Drupal::service('theme_handler')->rebuildThemeData(); + $version = $themes[$theme_machine_name]->info['version']; + + // Confirm the theme is indicated as the default theme and administration + // theme because the admin theme is the default theme. + $out = $this->getSession()->getPage()->getContent(); + $this->assertTrue((bool) preg_match("/$theme_name " . preg_quote($version) . '\s{2,}\(default theme, administration theme\)/', $out)); + } } /** @@ -461,7 +476,7 @@ public function testInstallAndSetAsDefault() { */ public function testThemeSettingsNoLogoNoFavicon() { // Install theme with no logo and no favicon feature. - $this->container->get('theme_handler')->install(['test_theme_settings_features']); + $this->container->get('theme_installer')->install(['test_theme_settings_features']); // Visit this theme's settings page. $this->drupalGet('admin/appearance/settings/test_theme_settings_features'); $edit = []; @@ -469,4 +484,16 @@ public function testThemeSettingsNoLogoNoFavicon() { $this->assertText('The configuration options have been saved.'); } + /** + * Asserts that expected incompatibility text is displayed for a theme. + * + * @param string $theme_name + * Theme name to select element on page. This can be a partial name. + * @param string $expected_text + * The expected incompatibility text. + */ + private function assertThemeIncompatibleText($theme_name, $expected_text) { + $this->assertSession()->elementExists('css', ".theme-info:contains(\"$theme_name\") .incompatible:contains(\"$expected_text\")"); + } + } diff --git a/core/modules/system/tests/src/Functional/System/TokenReplaceWebTest.php b/core/modules/system/tests/src/Functional/System/TokenReplaceWebTest.php index 450f50704..de6edc66c 100644 --- a/core/modules/system/tests/src/Functional/System/TokenReplaceWebTest.php +++ b/core/modules/system/tests/src/Functional/System/TokenReplaceWebTest.php @@ -20,6 +20,11 @@ class TokenReplaceWebTest extends BrowserTestBase { */ public static $modules = ['token_test', 'filter', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests a token replacement on an actual website. */ diff --git a/core/modules/system/tests/src/Functional/System/TokenScanTest.php b/core/modules/system/tests/src/Functional/System/TokenScanTest.php index f284aa978..77e28d6ed 100644 --- a/core/modules/system/tests/src/Functional/System/TokenScanTest.php +++ b/core/modules/system/tests/src/Functional/System/TokenScanTest.php @@ -11,6 +11,11 @@ */ class TokenScanTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Scans dummy text, then tests the output. */ diff --git a/core/modules/system/tests/src/Functional/System/TrustedHostsTest.php b/core/modules/system/tests/src/Functional/System/TrustedHostsTest.php index 7a65474c7..13f43b80b 100644 --- a/core/modules/system/tests/src/Functional/System/TrustedHostsTest.php +++ b/core/modules/system/tests/src/Functional/System/TrustedHostsTest.php @@ -11,6 +11,11 @@ */ class TrustedHostsTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -82,9 +87,9 @@ public function testShortcut() { $this->rebuildContainer(); $this->container->get('router.builder')->rebuild(); - /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ - $entity_manager = $this->container->get('entity.manager'); - $shortcut_storage = $entity_manager->getStorage('shortcut'); + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ + $entity_type_manager = $this->container->get('entity_type.manager'); + $shortcut_storage = $entity_type_manager->getStorage('shortcut'); $shortcut = $shortcut_storage->create([ 'title' => $this->randomString(), @@ -94,7 +99,7 @@ public function testShortcut() { $shortcut_storage->save($shortcut); // Grant the current user access to see the shortcuts. - $role_storage = $entity_manager->getStorage('user_role'); + $role_storage = $entity_type_manager->getStorage('user_role'); $roles = $this->loggedInUser->getRoles(TRUE); /** @var \Drupal\user\RoleInterface $role */ $role = $role_storage->load(reset($roles)); diff --git a/core/modules/system/tests/src/Functional/Theme/EngineNyanCatTest.php b/core/modules/system/tests/src/Functional/Theme/EngineNyanCatTest.php index 6dca1b38d..d2b5e3212 100644 --- a/core/modules/system/tests/src/Functional/Theme/EngineNyanCatTest.php +++ b/core/modules/system/tests/src/Functional/Theme/EngineNyanCatTest.php @@ -18,9 +18,14 @@ class EngineNyanCatTest extends BrowserTestBase { */ public static $modules = ['theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); - \Drupal::service('theme_handler')->install(['test_theme_nyan_cat_engine']); + \Drupal::service('theme_installer')->install(['test_theme_nyan_cat_engine']); } /** diff --git a/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php b/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php index 1e1cb5363..756e8076d 100644 --- a/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php +++ b/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php @@ -23,9 +23,14 @@ class EngineTwigTest extends BrowserTestBase { */ public static $modules = ['theme_test', 'twig_theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); - \Drupal::service('theme_handler')->install(['test_theme']); + \Drupal::service('theme_installer')->install(['test_theme']); } /** diff --git a/core/modules/system/tests/src/Functional/Theme/EntityFilteringThemeTest.php b/core/modules/system/tests/src/Functional/Theme/EntityFilteringThemeTest.php index 6e4bd0a0b..f79f990de 100644 --- a/core/modules/system/tests/src/Functional/Theme/EntityFilteringThemeTest.php +++ b/core/modules/system/tests/src/Functional/Theme/EntityFilteringThemeTest.php @@ -21,6 +21,11 @@ class EntityFilteringThemeTest extends BrowserTestBase { use CommentTestTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Use the standard profile. * @@ -82,7 +87,7 @@ protected function setUp() { // Install all available non-testing themes. $listing = new ExtensionDiscovery(\Drupal::root()); $this->themes = $listing->scan('theme', FALSE); - \Drupal::service('theme_handler')->install(array_keys($this->themes)); + \Drupal::service('theme_installer')->install(array_keys($this->themes)); // Create a test user. $this->user = $this->drupalCreateUser(['access content', 'access user profiles']); diff --git a/core/modules/system/tests/src/Functional/Theme/ExperimentalThemeTest.php b/core/modules/system/tests/src/Functional/Theme/ExperimentalThemeTest.php new file mode 100644 index 000000000..01409642f --- /dev/null +++ b/core/modules/system/tests/src/Functional/Theme/ExperimentalThemeTest.php @@ -0,0 +1,133 @@ +adminUser = $this->drupalCreateUser(['access administration pages', 'administer themes']); + $this->drupalLogin($this->adminUser); + } + + /** + * Tests installing experimental themes and dependencies in the UI. + */ + public function testExperimentalConfirmForm() { + // Only experimental themes should be marked as such with a parenthetical. + $this->drupalGet('admin/appearance'); + $this->assertText(sprintf('Experimental test %s (experimental theme)', \Drupal::VERSION)); + $this->assertText(sprintf('Experimental dependency test %s', \Drupal::VERSION)); + + // First, test installing a non-experimental theme with no dependencies. + // There should be no confirmation form and no experimental theme warning. + $this->drupalGet('admin/appearance'); + $this->cssSelect('a[title="Install Test theme theme"]')[0]->click(); + $this->assertText('The <strong>Test theme</strong> theme has been installed.'); + $this->assertNoText('Experimental modules are provided for testing purposes only.'); + + // Next, test installing an experimental theme with no dependencies. + // There should be a confirmation form with an experimental warning, but no + // list of dependencies. + $this->drupalGet('admin/appearance'); + $this->cssSelect('a[title="Install Experimental test theme"]')[0]->click(); + $this->assertText('Experimental themes are provided for testing purposes only. Use at your own risk.'); + + // The module should not be enabled and there should be a warning and a + // list of the experimental modules with only this one. + $this->assertNoText('The Experimental Test theme has been installed.'); + $this->assertText('Experimental themes are provided for testing purposes only.'); + + // There should be no message about enabling dependencies. + $this->assertNoText('You must enable'); + + // Enable the theme and confirm that it worked. + $this->drupalPostForm(NULL, [], 'Continue'); + $this->assertText('The Experimental test theme has been installed.'); + + // Setting it as the default should not ask for another confirmation. + $this->cssSelect('a[title="Set Experimental test as default theme"]')[0]->click(); + $this->assertNoText('Experimental themes are provided for testing purposes only. Use at your own risk.'); + $this->assertText('Experimental test is now the default theme.'); + $this->assertNoText(sprintf('Experimental test %s (experimental theme)', \Drupal::VERSION)); + $this->assertText(sprintf('Experimental test %s (default theme, administration theme, experimental theme)', \Drupal::VERSION)); + + // Uninstall the theme. + $this->config('system.theme')->set('default', 'test_theme')->save(); + \Drupal::service('theme_handler')->refreshInfo(); + \Drupal::service('theme_installer')->uninstall(['experimental_theme_test']); + + // Reinstall the same experimental theme, but this time immediately set it + // as the default. This should again trigger a confirmation form with an + // experimental warning. + $this->drupalGet('admin/appearance'); + $this->cssSelect('a[title="Install Experimental test as default theme"]')[0]->click(); + $this->assertText('Experimental themes are provided for testing purposes only. Use at your own risk.'); + + // Test enabling a theme that is not itself experimental, but that depends + // on an experimental module. + $this->drupalGet('admin/appearance'); + $this->cssSelect('a[title="Install Experimental dependency test theme"]')[0]->click(); + + // The theme should not be enabled and there should be a warning and a + // list of the experimental modules with only this one. + $this->assertNoText('The Experimental dependency test theme has been installed.'); + $this->assertText('Experimental themes are provided for testing purposes only. Use at your own risk.'); + $this->assertText('The following themes are experimental: Experimental test'); + + // Ensure the non-experimental theme is not listed as experimental. + $this->assertNoText('The following themes are experimental: Experimental test, Experimental dependency test'); + $this->assertNoText('The following themes are experimental: Experimental dependency test'); + + // There should be a message about enabling dependencies. + $this->assertText('You must enable the Experimental test theme to install Experimental dependency test'); + + // Enable the theme and confirm that it worked. + $this->drupalPostForm(NULL, [], 'Continue'); + $this->assertText('The Experimental dependency test theme has been installed.'); + $this->assertText(sprintf('Experimental test %s (experimental theme)', \Drupal::VERSION)); + $this->assertText(sprintf('Experimental dependency test %s', \Drupal::VERSION)); + + // Setting it as the default should not ask for another confirmation. + $this->cssSelect('a[title="Set Experimental dependency test as default theme"]')[0]->click(); + $this->assertNoText('Experimental themes are provided for testing purposes only. Use at your own risk.'); + $this->assertText('Experimental dependency test is now the default theme.'); + $this->assertText(sprintf('Experimental test %s (experimental theme)', \Drupal::VERSION)); + $this->assertText(sprintf('Experimental dependency test %s (default theme, administration theme)', \Drupal::VERSION)); + + // Uninstall the theme. + $this->config('system.theme')->set('default', 'test_theme')->save(); + \Drupal::service('theme_handler')->refreshInfo(); + \Drupal::service('theme_installer')->uninstall(['experimental_theme_test', 'experimental_theme_dependency_test']); + + // Reinstall the same theme, but this time immediately set it as the + // default. This should again trigger a confirmation form with an + // experimental warning for its dependency. + $this->drupalGet('admin/appearance'); + $this->cssSelect('a[title="Install Experimental dependency test as default theme"]')[0]->click(); + $this->assertText('Experimental themes are provided for testing purposes only. Use at your own risk.'); + } + +} diff --git a/core/modules/system/tests/src/Functional/Theme/FastTest.php b/core/modules/system/tests/src/Functional/Theme/FastTest.php index 977f54dfc..80012487b 100644 --- a/core/modules/system/tests/src/Functional/Theme/FastTest.php +++ b/core/modules/system/tests/src/Functional/Theme/FastTest.php @@ -18,6 +18,11 @@ class FastTest extends BrowserTestBase { */ public static $modules = ['theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); $this->account = $this->drupalCreateUser(['access user profiles']); diff --git a/core/modules/system/tests/src/Functional/Theme/HtmlAttributesTest.php b/core/modules/system/tests/src/Functional/Theme/HtmlAttributesTest.php index 5aa4be9cc..fccf2ff4d 100644 --- a/core/modules/system/tests/src/Functional/Theme/HtmlAttributesTest.php +++ b/core/modules/system/tests/src/Functional/Theme/HtmlAttributesTest.php @@ -18,6 +18,11 @@ class HtmlAttributesTest extends BrowserTestBase { */ public static $modules = ['theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that attributes in the 'html' and 'body' elements can be altered. */ diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeEarlyInitializationTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeEarlyInitializationTest.php index 040dfabfc..13d09e63e 100644 --- a/core/modules/system/tests/src/Functional/Theme/ThemeEarlyInitializationTest.php +++ b/core/modules/system/tests/src/Functional/Theme/ThemeEarlyInitializationTest.php @@ -19,6 +19,11 @@ class ThemeEarlyInitializationTest extends BrowserTestBase { */ public static $modules = ['theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Test that the theme system can generate output in a request listener. */ diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeInfoTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeInfoTest.php index e372bb262..05fbca78b 100644 --- a/core/modules/system/tests/src/Functional/Theme/ThemeInfoTest.php +++ b/core/modules/system/tests/src/Functional/Theme/ThemeInfoTest.php @@ -19,11 +19,16 @@ class ThemeInfoTest extends BrowserTestBase { public static $modules = ['theme_test']; /** - * The theme handler used in this test for enabling themes. + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * The theme installer used in this test for enabling themes. * - * @var \Drupal\Core\Extension\ThemeHandler + * @var \Drupal\Core\Extension\ThemeInstallerInterface */ - protected $themeHandler; + protected $themeInstaller; /** * The theme manager used in this test. @@ -45,7 +50,7 @@ class ThemeInfoTest extends BrowserTestBase { protected function setUp() { parent::setUp(); - $this->themeHandler = $this->container->get('theme_handler'); + $this->themeInstaller = $this->container->get('theme_installer'); $this->themeManager = $this->container->get('theme.manager'); $this->state = $this->container->get('state'); } @@ -54,7 +59,7 @@ protected function setUp() { * Tests stylesheets-remove. */ public function testStylesheets() { - $this->themeHandler->install(['test_basetheme', 'test_subtheme']); + $this->themeInstaller->install(['test_basetheme', 'test_subtheme']); $this->config('system.theme') ->set('default', 'test_subtheme') ->save(); @@ -83,7 +88,7 @@ public function testStylesheets() { * Tests that changes to the info file are picked up. */ public function testChanges() { - $this->themeHandler->install(['test_theme']); + $this->themeInstaller->install(['test_theme']); $this->config('system.theme')->set('default', 'test_theme')->save(); $this->themeManager->resetActiveTheme(); diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php index 36e8eb2d6..c521f58e5 100644 --- a/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php +++ b/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php @@ -19,9 +19,14 @@ class ThemeSuggestionsAlterTest extends BrowserTestBase { */ public static $modules = ['theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); - \Drupal::service('theme_handler')->install(['test_theme']); + \Drupal::service('theme_installer')->install(['test_theme']); } /** diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeTest.php index 545fc587a..fe4471a2f 100644 --- a/core/modules/system/tests/src/Functional/Theme/ThemeTest.php +++ b/core/modules/system/tests/src/Functional/Theme/ThemeTest.php @@ -20,12 +20,17 @@ class ThemeTest extends BrowserTestBase { */ public static $modules = ['theme_test', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); - \Drupal::service('theme_handler')->install(['test_theme']); + \Drupal::service('theme_installer')->install(['test_theme']); } /** diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeTokenTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeTokenTest.php index a399bed73..c1d8f37d9 100644 --- a/core/modules/system/tests/src/Functional/Theme/ThemeTokenTest.php +++ b/core/modules/system/tests/src/Functional/Theme/ThemeTokenTest.php @@ -18,6 +18,11 @@ class ThemeTokenTest extends BrowserTestBase { */ public static $modules = ['block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php b/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php index 9fe89fd58..90e5cf842 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php @@ -19,6 +19,11 @@ class TwigDebugMarkupTest extends BrowserTestBase { */ public static $modules = ['theme_test', 'node']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests debug markup added to Twig template output. */ @@ -26,7 +31,7 @@ public function testTwigDebugMarkup() { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); $extension = twig_extension(); - \Drupal::service('theme_handler')->install(['test_theme']); + \Drupal::service('theme_installer')->install(['test_theme']); $this->config('system.theme')->set('default', 'test_theme')->save(); $this->drupalCreateContentType(['type' => 'page']); // Enable debug, rebuild the service container, and clear all caches. @@ -43,7 +48,8 @@ public function testTwigDebugMarkup() { // Create a node and test different features of the debug markup. $node = $this->drupalCreateNode(); - $build = node_view($node); + $builder = \Drupal::entityTypeManager()->getViewBuilder('node'); + $build = $builder->view($node); $output = $renderer->renderRoot($build); $this->assertTrue(strpos($output, '') !== FALSE, 'Twig debug markup found in theme output when debug is enabled.'); $this->assertTrue(strpos($output, "THEME HOOK: 'node'") !== FALSE, 'Theme call information found.'); @@ -55,7 +61,7 @@ public function testTwigDebugMarkup() { // Create another node and make sure the template suggestions shown in the // debug markup are correct. $node2 = $this->drupalCreateNode(); - $build = node_view($node2); + $build = $builder->view($node2); $output = $renderer->renderRoot($build); $this->assertTrue(strpos($output, '* node--2--full' . $extension . PHP_EOL . ' * node--2' . $extension . PHP_EOL . ' * node--page--full' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node--full' . $extension . PHP_EOL . ' x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.'); @@ -63,7 +69,7 @@ public function testTwigDebugMarkup() { // debug markup are correct. $node3 = $this->drupalCreateNode(); $build = ['#theme' => 'node__foo__bar']; - $build += node_view($node3); + $build += $builder->view($node3); $output = $renderer->renderRoot($build); $this->assertTrue(strpos($output, "THEME HOOK: 'node__foo__bar'") !== FALSE, 'Theme call information found.'); $this->assertTrue(strpos($output, '* node--foo--bar' . $extension . PHP_EOL . ' * node--foo' . $extension . PHP_EOL . ' * node--<script type="text/javascript">alert('yo');</script>' . $extension . PHP_EOL . ' * node--3--full' . $extension . PHP_EOL . ' * node--3' . $extension . PHP_EOL . ' * node--page--full' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node--full' . $extension . PHP_EOL . ' x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.'); @@ -75,7 +81,7 @@ public function testTwigDebugMarkup() { $this->rebuildContainer(); $this->resetAll(); - $build = node_view($node); + $build = $builder->view($node); $output = $renderer->renderRoot($build); $this->assertFalse(strpos($output, '') !== FALSE, 'Twig debug markup not found in theme output when debug is disabled.'); } diff --git a/core/modules/system/tests/src/Functional/Theme/TwigEnvironmentTest.php b/core/modules/system/tests/src/Functional/Theme/TwigEnvironmentTest.php index 26f47e906..1043148fd 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigEnvironmentTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigEnvironmentTest.php @@ -16,6 +16,11 @@ class TwigEnvironmentTest extends BrowserTestBase { */ protected static $modules = ['twig_theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests template class loading with Twig embed. */ diff --git a/core/modules/system/tests/src/Functional/Theme/TwigExtensionTest.php b/core/modules/system/tests/src/Functional/Theme/TwigExtensionTest.php index 588b947e6..04e527e74 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigExtensionTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigExtensionTest.php @@ -19,9 +19,14 @@ class TwigExtensionTest extends BrowserTestBase { */ public static $modules = ['theme_test', 'twig_extension_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); - \Drupal::service('theme_handler')->install(['test_theme']); + \Drupal::service('theme_installer')->install(['test_theme']); } /** diff --git a/core/modules/system/tests/src/Functional/Theme/TwigLoaderTest.php b/core/modules/system/tests/src/Functional/Theme/TwigLoaderTest.php index d2b0b5465..1238f2074 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigLoaderTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigLoaderTest.php @@ -18,6 +18,11 @@ class TwigLoaderTest extends BrowserTestBase { */ public static $modules = ['twig_loader_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests adding an additional twig loader to the loader chain. */ diff --git a/core/modules/system/tests/src/Functional/Theme/TwigRegistryLoaderTest.php b/core/modules/system/tests/src/Functional/Theme/TwigRegistryLoaderTest.php index d6edd0064..c2c7642e2 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigRegistryLoaderTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigRegistryLoaderTest.php @@ -19,6 +19,11 @@ class TwigRegistryLoaderTest extends BrowserTestBase { */ public static $modules = ['twig_theme_test', 'block']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * @var \Drupal\Core\Template\TwigEnvironment */ @@ -26,7 +31,7 @@ class TwigRegistryLoaderTest extends BrowserTestBase { protected function setUp() { parent::setUp(); - \Drupal::service('theme_handler')->install(['test_theme_twig_registry_loader', 'test_theme_twig_registry_loader_theme', 'test_theme_twig_registry_loader_subtheme']); + \Drupal::service('theme_installer')->install(['test_theme_twig_registry_loader', 'test_theme_twig_registry_loader_theme', 'test_theme_twig_registry_loader_subtheme']); $this->twig = \Drupal::service('twig'); } diff --git a/core/modules/system/tests/src/Functional/Theme/TwigSettingsTest.php b/core/modules/system/tests/src/Functional/Theme/TwigSettingsTest.php index fd759502e..2cc8e24d0 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigSettingsTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigSettingsTest.php @@ -19,6 +19,11 @@ class TwigSettingsTest extends BrowserTestBase { */ public static $modules = ['theme_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Ensures Twig template auto reload setting can be overridden. */ @@ -76,8 +81,8 @@ public function testTwigDebugOverride() { */ public function testTwigCacheOverride() { $extension = twig_extension(); - $theme_handler = $this->container->get('theme_handler'); - $theme_handler->install(['test_theme']); + $theme_installer = $this->container->get('theme_installer'); + $theme_installer->install(['test_theme']); $this->config('system.theme')->set('default', 'test_theme')->save(); // The registry still works on theme globals, so set them here. diff --git a/core/modules/system/tests/src/Functional/Theme/TwigTransTest.php b/core/modules/system/tests/src/Functional/Theme/TwigTransTest.php index aeafe7aeb..9b93eb1d9 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigTransTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigTransTest.php @@ -24,6 +24,11 @@ class TwigTransTest extends BrowserTestBase { 'language', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * An administrative user for testing. * @@ -48,7 +53,7 @@ protected function setUp() { parent::setUp(); // Setup test_theme. - \Drupal::service('theme_handler')->install(['test_theme']); + \Drupal::service('theme_installer')->install(['test_theme']); $this->config('system.theme')->set('default', 'test_theme')->save(); // Create and log in as admin. @@ -108,7 +113,7 @@ public function testEmptyTwigTransTags() { $this->fail('{% trans %}{% endtrans %} did not throw an exception.'); } catch (\Twig_Error_Syntax $e) { - $this->assertTrue(strstr($e->getMessage(), '{% trans %} tag cannot be empty'), '{% trans %}{% endtrans %} threw the expected exception.'); + $this->assertContains('{% trans %} tag cannot be empty', $e->getMessage(), '{% trans %}{% endtrans %} threw the expected exception.'); } catch (\Exception $e) { $this->fail('{% trans %}{% endtrans %} threw an unexpected exception.'); @@ -192,6 +197,7 @@ protected function assertTwigTransTags() { * Helper function: install languages. */ protected function installLanguages() { + $file_system = \Drupal::service('file_system'); foreach ($this->languages as $langcode => $name) { // Generate custom .po contents for the language. $contents = $this->poFileContents($langcode); @@ -209,7 +215,7 @@ protected function installLanguages() { $this->assertRaw('"edit-languages-' . $langcode . '-weight"', 'Language code found.'); // Import the custom .po contents for the language. - $filename = \Drupal::service('file_system')->tempnam('temporary://', "po_") . '.po'; + $filename = $file_system->tempnam('temporary://', "po_") . '.po'; file_put_contents($filename, $contents); $options = [ 'files[file]' => $filename, @@ -217,7 +223,7 @@ protected function installLanguages() { 'customized' => TRUE, ]; $this->drupalPostForm('admin/config/regional/translate/import', $options, t('Import')); - drupal_unlink($filename); + $file_system->unlink($filename); } } $this->container->get('language_manager')->reset(); diff --git a/core/modules/system/tests/src/Functional/Update/AdminThemeUpdateTest.php b/core/modules/system/tests/src/Functional/Update/AdminThemeUpdateTest.php new file mode 100644 index 000000000..52bdbb254 --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/AdminThemeUpdateTest.php @@ -0,0 +1,39 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.6.0.bare.testing.php.gz', + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.admin_theme_0.php', + ]; + } + + /** + * Tests that system.theme:admin is updated as expected. + */ + public function testUpdateHookN() { + $this->assertSame('0', $this->config('system.theme')->get('admin')); + $this->runUpdates(); + $this->assertSame('', $this->config('system.theme')->get('admin')); + } + +} diff --git a/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithAutomatedCronTest.php b/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithAutomatedCronTest.php index e967f1701..3cd9542b0 100644 --- a/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithAutomatedCronTest.php +++ b/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithAutomatedCronTest.php @@ -12,6 +12,11 @@ */ class AutomatedCronUpdateWithAutomatedCronTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithoutAutomatedCronTest.php b/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithoutAutomatedCronTest.php index c131c7059..41243240b 100644 --- a/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithoutAutomatedCronTest.php +++ b/core/modules/system/tests/src/Functional/Update/AutomatedCronUpdateWithoutAutomatedCronTest.php @@ -12,6 +12,11 @@ */ class AutomatedCronUpdateWithoutAutomatedCronTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php b/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php index 2ad68d2ec..300676a2a 100644 --- a/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/BrokenCacheUpdateTest.php @@ -13,6 +13,11 @@ */ class BrokenCacheUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/ConfigOverridesUpdateTest.php b/core/modules/system/tests/src/Functional/Update/ConfigOverridesUpdateTest.php index e18195da1..8d565ac85 100644 --- a/core/modules/system/tests/src/Functional/Update/ConfigOverridesUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/ConfigOverridesUpdateTest.php @@ -14,6 +14,11 @@ */ class ConfigOverridesUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/DbUpdatesTrait.php b/core/modules/system/tests/src/Functional/Update/DbUpdatesTrait.php index 5fbc1ee18..288003b7b 100644 --- a/core/modules/system/tests/src/Functional/Update/DbUpdatesTrait.php +++ b/core/modules/system/tests/src/Functional/Update/DbUpdatesTrait.php @@ -42,18 +42,4 @@ protected function applyUpdates() { $this->checkForMetaRefresh(); } - /** - * Conditionally load Update API functions for the specified group. - * - * @param string $module - * The name of the module defining the update functions. - * @param string $group - * A name identifying the group of update functions to enable. - */ - public static function includeUpdates($module, $group) { - if ($index = \Drupal::state()->get($module . '.db_updates.' . $group)) { - module_load_include('inc', $module, 'update/' . $group . '_' . $index); - } - } - } diff --git a/core/modules/system/tests/src/Functional/Update/DependencyHookInvocationTest.php b/core/modules/system/tests/src/Functional/Update/DependencyHookInvocationTest.php index 203d89ef2..d967fa86f 100644 --- a/core/modules/system/tests/src/Functional/Update/DependencyHookInvocationTest.php +++ b/core/modules/system/tests/src/Functional/Update/DependencyHookInvocationTest.php @@ -19,6 +19,11 @@ class DependencyHookInvocationTest extends BrowserTestBase { */ public static $modules = ['update_test_0', 'update_test_1', 'update_test_2']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); require_once $this->root . '/core/includes/update.inc'; diff --git a/core/modules/system/tests/src/Functional/Update/DependencyMissingTest.php b/core/modules/system/tests/src/Functional/Update/DependencyMissingTest.php index e11999a46..b7fced809 100644 --- a/core/modules/system/tests/src/Functional/Update/DependencyMissingTest.php +++ b/core/modules/system/tests/src/Functional/Update/DependencyMissingTest.php @@ -18,6 +18,11 @@ class DependencyMissingTest extends BrowserTestBase { */ public static $modules = ['update_test_0', 'update_test_2']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { // Only install update_test_2.module, even though its updates have a // dependency on update_test_3.module. diff --git a/core/modules/system/tests/src/Functional/Update/DependencyOrderingTest.php b/core/modules/system/tests/src/Functional/Update/DependencyOrderingTest.php index 0f168aa78..649f9c93b 100644 --- a/core/modules/system/tests/src/Functional/Update/DependencyOrderingTest.php +++ b/core/modules/system/tests/src/Functional/Update/DependencyOrderingTest.php @@ -18,6 +18,11 @@ class DependencyOrderingTest extends BrowserTestBase { */ public static $modules = ['update_test_0', 'update_test_1', 'update_test_2', 'update_test_3']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + protected function setUp() { parent::setUp(); require_once $this->root . '/core/includes/update.inc'; diff --git a/core/modules/system/tests/src/Functional/Update/EntityReferenceAutocompleteWidgetMatchLimitUpdateTest.php b/core/modules/system/tests/src/Functional/Update/EntityReferenceAutocompleteWidgetMatchLimitUpdateTest.php new file mode 100644 index 000000000..fa196a88a --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/EntityReferenceAutocompleteWidgetMatchLimitUpdateTest.php @@ -0,0 +1,44 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Tests that the match_limit setting is added to the config. + * + * @expectedDeprecation Any entity_reference_autocomplete component of an entity_form_display must have a match_limit setting. The field_tags field on the node.article.default form display is missing it. This BC layer will be removed before 9.0.0. See https://www.drupal.org/node/2863188 + * @expectedDeprecation Any entity_reference_autocomplete component of an entity_form_display must have a match_limit setting. The uid field on the node.article.default form display is missing it. This BC layer will be removed before 9.0.0. See https://www.drupal.org/node/2863188 + */ + public function testViewsPostUpdateEntityLinkUrl() { + $display = EntityFormDisplay::load('node.article.default'); + $this->assertArrayNotHasKey('match_limit', $display->getComponent('field_tags')['settings']); + $this->assertArrayNotHasKey('match_limit', $display->getComponent('uid')['settings']); + + $this->runUpdates(); + + $display = EntityFormDisplay::load('node.article.default'); + $this->assertEquals(10, $display->getComponent('field_tags')['settings']['match_limit']); + $this->assertEquals(10, $display->getComponent('uid')['settings']['match_limit']); + } + +} diff --git a/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionDefaultTest.php b/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionDefaultTest.php index 26f5c2c9b..71375b0e3 100644 --- a/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionDefaultTest.php +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionDefaultTest.php @@ -19,11 +19,9 @@ class EntityUpdateAddRevisionDefaultTest extends UpdatePathTestBase { use DbUpdatesTrait; /** - * The entity manager service. - * - * @var \Drupal\Core\Entity\EntityManagerInterface + * {@inheritdoc} */ - protected $entityManager; + protected $defaultTheme = 'stark'; /** * The state service. @@ -39,7 +37,6 @@ protected function setUp() { parent::setUp(); // Do not use this property after calling ::runUpdates(). - $this->entityManager = \Drupal::entityManager(); $this->state = \Drupal::state(); } diff --git a/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionTranslationAffectedTest.php b/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionTranslationAffectedTest.php index e14820b9b..6e0d9985c 100644 --- a/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionTranslationAffectedTest.php +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateAddRevisionTranslationAffectedTest.php @@ -18,13 +18,6 @@ class EntityUpdateAddRevisionTranslationAffectedTest extends UpdatePathTestBase use EntityDefinitionTestTrait; use DbUpdatesTrait; - /** - * The entity manager service. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - /** * The state service. * @@ -37,6 +30,11 @@ class EntityUpdateAddRevisionTranslationAffectedTest extends UpdatePathTestBase */ protected static $modules = ['entity_test_update']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -44,7 +42,6 @@ protected function setUp() { parent::setUp(); // Do not use this property after calling ::runUpdates(). - $this->entityManager = \Drupal::entityManager(); $this->state = \Drupal::state(); } @@ -87,7 +84,7 @@ public function testAddingTheRevisionTranslationAffectedField() { // Check that the correct initial value was set when the field was // installed. $entity = \Drupal::entityTypeManager()->getStorage('entity_test_update')->load(1); - $this->assertTrue($entity->revision_translation_affected->value); + $this->assertNotEmpty($entity->revision_translation_affected->value); } /** diff --git a/core/modules/system/tests/src/Functional/Update/EntityUpdateInitialTest.php b/core/modules/system/tests/src/Functional/Update/EntityUpdateInitialTest.php index 7a85744b2..1d9a698b1 100644 --- a/core/modules/system/tests/src/Functional/Update/EntityUpdateInitialTest.php +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateInitialTest.php @@ -14,6 +14,11 @@ */ class EntityUpdateInitialTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/EntityUpdateToPublishableTest.php b/core/modules/system/tests/src/Functional/Update/EntityUpdateToPublishableTest.php index e887fd561..fd9a1d95e 100644 --- a/core/modules/system/tests/src/Functional/Update/EntityUpdateToPublishableTest.php +++ b/core/modules/system/tests/src/Functional/Update/EntityUpdateToPublishableTest.php @@ -17,6 +17,11 @@ class EntityUpdateToPublishableTest extends UpdatePathTestBase { use EntityDefinitionTestTrait; use DbUpdatesTrait; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The entity type manager service. * diff --git a/core/modules/system/tests/src/Functional/Update/FieldSchemaDataUninstallUpdateTest.php b/core/modules/system/tests/src/Functional/Update/FieldSchemaDataUninstallUpdateTest.php index 1beb62b73..e4c90ebe7 100644 --- a/core/modules/system/tests/src/Functional/Update/FieldSchemaDataUninstallUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/FieldSchemaDataUninstallUpdateTest.php @@ -14,6 +14,11 @@ */ class FieldSchemaDataUninstallUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/FilterHtmlUpdateTest.php b/core/modules/system/tests/src/Functional/Update/FilterHtmlUpdateTest.php index ea591fcbd..ebcf7f4c0 100644 --- a/core/modules/system/tests/src/Functional/Update/FilterHtmlUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/FilterHtmlUpdateTest.php @@ -13,6 +13,11 @@ */ class FilterHtmlUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/InstallProfileSystemInstall8300Test.php b/core/modules/system/tests/src/Functional/Update/InstallProfileSystemInstall8300Test.php index 0a1d03ff3..9d3131ff9 100644 --- a/core/modules/system/tests/src/Functional/Update/InstallProfileSystemInstall8300Test.php +++ b/core/modules/system/tests/src/Functional/Update/InstallProfileSystemInstall8300Test.php @@ -13,6 +13,11 @@ */ class InstallProfileSystemInstall8300Test extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -32,7 +37,7 @@ public function testUpdate() { // expected state before updating. $this->assertEqual('standard', \Drupal::installProfile()); $this->assertEqual('standard', Settings::get('install_profile'), 'The install profile has not been written to settings.php.'); - $this->assertFalse($this->config('core.extension')->get('profile'), 'The install profile is not present in core.extension configuration.'); + $this->assertNull($this->config('core.extension')->get('profile'), 'The install profile is not present in core.extension configuration.'); $this->runUpdates(); // Confirm that Drupal recognizes this distribution as the current profile. diff --git a/core/modules/system/tests/src/Functional/Update/InvalidUpdateHookTest.php b/core/modules/system/tests/src/Functional/Update/InvalidUpdateHookTest.php index 44fdf81ab..51fb4d280 100644 --- a/core/modules/system/tests/src/Functional/Update/InvalidUpdateHookTest.php +++ b/core/modules/system/tests/src/Functional/Update/InvalidUpdateHookTest.php @@ -22,6 +22,11 @@ class InvalidUpdateHookTest extends BrowserTestBase { */ public static $modules = ['update_test_invalid_hook', 'update_script_test', 'dblog']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * URL for the upgrade script. * diff --git a/core/modules/system/tests/src/Functional/Update/LocalActionsAndTasksConvertedIntoBlocksUpdateTest.php b/core/modules/system/tests/src/Functional/Update/LocalActionsAndTasksConvertedIntoBlocksUpdateTest.php index cfa822104..5b4e1bbd9 100644 --- a/core/modules/system/tests/src/Functional/Update/LocalActionsAndTasksConvertedIntoBlocksUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/LocalActionsAndTasksConvertedIntoBlocksUpdateTest.php @@ -15,6 +15,11 @@ */ class LocalActionsAndTasksConvertedIntoBlocksUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -42,7 +47,7 @@ public function testUpdateHookN() { $this->runUpdates(); /** @var \Drupal\block\BlockInterface $block_storage */ - $block_storage = \Drupal::entityManager()->getStorage('block'); + $block_storage = \Drupal::entityTypeManager()->getStorage('block'); /* @var \Drupal\block\BlockInterface[] $help_blocks */ $help_blocks = $block_storage->loadByProperties(['theme' => 'bartik', 'region' => 'help']); @@ -75,7 +80,7 @@ public function testUpdateHookN() { // Local actions are visible on the content listing page. $this->drupalGet('admin/content'); $action_link = $this->cssSelect('.action-links'); - $this->assertTrue($action_link); + $this->assertNotEmpty($action_link); $this->drupalGet('admin/structure/block/list/seven'); diff --git a/core/modules/system/tests/src/Functional/Update/MenuBlockPostUpdateTest.php b/core/modules/system/tests/src/Functional/Update/MenuBlockPostUpdateTest.php index 77ce66dcf..0e4d6c63b 100644 --- a/core/modules/system/tests/src/Functional/Update/MenuBlockPostUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/MenuBlockPostUpdateTest.php @@ -13,6 +13,11 @@ */ class MenuBlockPostUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleFilledTest.php b/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleFilledTest.php index 58429e3cb..6433bf4a6 100644 --- a/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleFilledTest.php +++ b/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleFilledTest.php @@ -10,6 +10,11 @@ */ class MenuTreeSerializationTitleFilledTest extends MenuTreeSerializationTitleTest { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleTest.php b/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleTest.php index 1a3fa84f4..d9572b06e 100644 --- a/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleTest.php +++ b/core/modules/system/tests/src/Functional/Update/MenuTreeSerializationTitleTest.php @@ -13,6 +13,11 @@ */ class MenuTreeSerializationTitleTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/MoveActionsToCoreTest.php b/core/modules/system/tests/src/Functional/Update/MoveActionsToCoreTest.php index 4d191994b..ad0e82593 100644 --- a/core/modules/system/tests/src/Functional/Update/MoveActionsToCoreTest.php +++ b/core/modules/system/tests/src/Functional/Update/MoveActionsToCoreTest.php @@ -13,12 +13,17 @@ */ class MoveActionsToCoreTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ protected function setDatabaseDumpFiles() { $this->databaseDumpFiles = [ - __DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', + __DIR__ . '/../../../../tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', __DIR__ . '/../../../../tests/fixtures/update/drupal-8.actions-2815379.php', ]; } diff --git a/core/modules/system/tests/src/Functional/Update/NoDependenciesUpdateTest.php b/core/modules/system/tests/src/Functional/Update/NoDependenciesUpdateTest.php index 152d66780..23c88142b 100644 --- a/core/modules/system/tests/src/Functional/Update/NoDependenciesUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/NoDependenciesUpdateTest.php @@ -12,6 +12,11 @@ */ class NoDependenciesUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/PageTitleConvertedIntoBlockUpdateTest.php b/core/modules/system/tests/src/Functional/Update/PageTitleConvertedIntoBlockUpdateTest.php index c9c264c96..6548db66f 100644 --- a/core/modules/system/tests/src/Functional/Update/PageTitleConvertedIntoBlockUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/PageTitleConvertedIntoBlockUpdateTest.php @@ -15,6 +15,11 @@ */ class PageTitleConvertedIntoBlockUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -43,7 +48,7 @@ public function testUpdateHookN() { $this->runUpdates(); /** @var \Drupal\block\BlockInterface $block_storage */ - $block_storage = \Drupal::entityManager()->getStorage('block'); + $block_storage = \Drupal::entityTypeManager()->getStorage('block'); $this->assertRaw('Because your site has custom theme(s) installed, we have placed the page title block in the content region. Please manually review the block configuration and remove the page title variables from your page templates.'); diff --git a/core/modules/system/tests/src/Functional/Update/PathAliasToEntityUpdateTest.php b/core/modules/system/tests/src/Functional/Update/PathAliasToEntityUpdateTest.php new file mode 100644 index 000000000..7a860906e --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/PathAliasToEntityUpdateTest.php @@ -0,0 +1,131 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../tests/fixtures/update/drupal-8.filled.standard.php.gz', + __DIR__ . '/../../../../tests/fixtures/update/drupal-8.convert-path-aliases-to-entities-2336597.php', + ]; + } + + /** + * Tests the conversion of path aliases to entities. + * + * @see system_update_8803() + * @see system_update_8804() + */ + public function testConversionToEntities() { + $database = \Drupal::database(); + $schema = $database->schema(); + $this->assertTrue($schema->tableExists('url_alias')); + + $query = $database->select('url_alias'); + $query->addField('url_alias', 'pid', 'id'); + $query->addField('url_alias', 'source', 'path'); + $query->addField('url_alias', 'alias'); + $query->addField('url_alias', 'langcode'); + + // Path aliases did not have a 'status' value before the conversion to + // entities, but we're adding it here to ensure that the field was installed + // and populated correctly. + $query->addExpression('1', 'status'); + $original_records = $query->execute()->fetchAllAssoc('id'); + + // drupal-8.filled.standard.php.gz contains one URL alias and + // drupal-8.convert-path-aliases-to-entities-2336597.php adds another four. + $url_alias_count = 5; + $this->assertCount($url_alias_count, $original_records); + + $this->runUpdates(); + + /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ + $module_handler = $this->container->get('module_handler'); + $this->assertTrue($module_handler->moduleExists('path_alias')); + + $entity_type = \Drupal::entityDefinitionUpdateManager()->getEntityType('path_alias'); + $this->assertEquals('path_alias', $entity_type->getProvider()); + $this->assertEquals(PathAlias::class, $entity_type->getClass()); + + $field_storage_definitions = \Drupal::service('entity.last_installed_schema.repository')->getLastInstalledFieldStorageDefinitions('path_alias'); + $this->assertEquals('path_alias', $field_storage_definitions['id']->getProvider()); + $this->assertEquals('path_alias', $field_storage_definitions['revision_id']->getProvider()); + $this->assertEquals('path_alias', $field_storage_definitions['langcode']->getProvider()); + $this->assertEquals('path_alias', $field_storage_definitions['uuid']->getProvider()); + $this->assertEquals('path_alias', $field_storage_definitions['status']->getProvider()); + $this->assertEquals('path_alias', $field_storage_definitions['path']->getProvider()); + $this->assertEquals('path_alias', $field_storage_definitions['alias']->getProvider()); + + // Check that the 'path_alias' entity tables have been created and the + // 'url_alias' table has been deleted. + $this->assertTrue($schema->tableExists('path_alias')); + $this->assertTrue($schema->tableExists('path_alias_revision')); + $this->assertFalse($schema->tableExists('url_alias')); + + // Check that we have a backup of the old table. + $this->assertCount(1, $schema->findTables('old_%_url_alias')); + + $path_alias_count = \Drupal::entityTypeManager()->getStorage('path_alias')->loadMultiple(); + $this->assertCount($url_alias_count, $path_alias_count); + + // Make sure that existing aliases still work. + $assert_session = $this->assertSession(); + $this->drupalGet('test-article'); + $assert_session->responseContains('/node/1'); + $assert_session->pageTextContains('Test Article - New title'); + + $this->drupalGet('test-article-new-alias'); + $assert_session->responseContains('/node/1'); + $assert_session->pageTextContains('Test Article - New title'); + + $this->drupalGet('test-alias-for-any-language'); + $assert_session->responseContains('/node/8'); + $assert_session->pageTextContains('Test title'); + + $this->drupalGet('test-alias-in-english'); + $assert_session->responseContains('/node/8'); + $assert_session->pageTextContains('Test title'); + + $spanish = \Drupal::languageManager()->getLanguage('es'); + + $this->drupalGet('test-alias-for-any-language', ['language' => $spanish]); + $assert_session->responseContains('/es/node/8'); + $assert_session->pageTextContains('Test title Spanish'); + + $this->drupalGet('test-alias-in-spanish', ['language' => $spanish]); + $assert_session->responseContains('/es/node/8'); + $assert_session->pageTextContains('Test title Spanish'); + + // Check that correct data was written in both the base and the revision + // tables. + $base_table_records = $database->select('path_alias') + ->fields('path_alias', ['id', 'path', 'alias', 'langcode', 'status']) + ->execute()->fetchAllAssoc('id'); + $this->assertEquals($original_records, $base_table_records); + + $revision_table_records = $database->select('path_alias_revision') + ->fields('path_alias_revision', ['id', 'path', 'alias', 'langcode', 'status']) + ->execute()->fetchAllAssoc('id'); + $this->assertEquals($original_records, $revision_table_records); + } + +} diff --git a/core/modules/system/tests/src/Functional/Update/RebuildScriptTest.php b/core/modules/system/tests/src/Functional/Update/RebuildScriptTest.php index a684b037f..da036ad6e 100644 --- a/core/modules/system/tests/src/Functional/Update/RebuildScriptTest.php +++ b/core/modules/system/tests/src/Functional/Update/RebuildScriptTest.php @@ -12,6 +12,11 @@ */ class RebuildScriptTest extends BrowserTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test redirect in rebuild.php. */ @@ -21,7 +26,7 @@ public function testRebuild() { $cache->set('rebuild_test', TRUE); $this->drupalGet(Url::fromUri('base:core/rebuild.php')); $this->assertUrl(new Url('')); - $this->assertTrue($cache->get('rebuild_test')); + $this->assertInstanceOf(\stdClass::class, $cache->get('rebuild_test')); $settings['settings']['rebuild_access'] = (object) [ 'value' => TRUE, diff --git a/core/modules/system/tests/src/Functional/Update/RecalculatedDependencyTest.php b/core/modules/system/tests/src/Functional/Update/RecalculatedDependencyTest.php index 2cda26e9a..e2f262a8e 100644 --- a/core/modules/system/tests/src/Functional/Update/RecalculatedDependencyTest.php +++ b/core/modules/system/tests/src/Functional/Update/RecalculatedDependencyTest.php @@ -12,6 +12,11 @@ */ class RecalculatedDependencyTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/RemoveResponseGzipFromSystemPerformanceConfigurationTest.php b/core/modules/system/tests/src/Functional/Update/RemoveResponseGzipFromSystemPerformanceConfigurationTest.php index f7f772b13..9ab66fd26 100644 --- a/core/modules/system/tests/src/Functional/Update/RemoveResponseGzipFromSystemPerformanceConfigurationTest.php +++ b/core/modules/system/tests/src/Functional/Update/RemoveResponseGzipFromSystemPerformanceConfigurationTest.php @@ -12,6 +12,11 @@ */ class RemoveResponseGzipFromSystemPerformanceConfigurationTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationFilledTest.php b/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationFilledTest.php index 93c9c8e23..e7454c031 100644 --- a/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationFilledTest.php +++ b/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationFilledTest.php @@ -10,6 +10,11 @@ */ class RouterIndexOptimizationFilledTest extends RouterIndexOptimizationTest { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationTest.php b/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationTest.php index f4672b4cd..884a8dc26 100644 --- a/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationTest.php +++ b/core/modules/system/tests/src/Functional/Update/RouterIndexOptimizationTest.php @@ -12,6 +12,11 @@ */ class RouterIndexOptimizationTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/SevenSecondaryLocalTasksConvertedIntoBlockUpdateTest.php b/core/modules/system/tests/src/Functional/Update/SevenSecondaryLocalTasksConvertedIntoBlockUpdateTest.php index 172897497..a5637564b 100644 --- a/core/modules/system/tests/src/Functional/Update/SevenSecondaryLocalTasksConvertedIntoBlockUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/SevenSecondaryLocalTasksConvertedIntoBlockUpdateTest.php @@ -14,6 +14,11 @@ */ class SevenSecondaryLocalTasksConvertedIntoBlockUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -41,7 +46,7 @@ public function testUpdateHookN() { $this->runUpdates(); /** @var \Drupal\block\BlockInterface $block_storage */ - $block_storage = \Drupal::entityManager()->getStorage('block'); + $block_storage = \Drupal::entityTypeManager()->getStorage('block'); // Disable maintenance mode. // @todo Can be removed once maintenance mode is automatically turned off @@ -54,7 +59,7 @@ public function testUpdateHookN() { // Local actions are visible on the content listing page. $this->drupalGet('admin/structure/block'); $action_link = $this->cssSelect('#secondary-tabs-title'); - $this->assertTrue($action_link); + $this->assertNotEmpty($action_link); } } diff --git a/core/modules/system/tests/src/Functional/Update/SiteBrandingConvertedIntoBlockUpdateTest.php b/core/modules/system/tests/src/Functional/Update/SiteBrandingConvertedIntoBlockUpdateTest.php index 72f3a2782..4470398b2 100644 --- a/core/modules/system/tests/src/Functional/Update/SiteBrandingConvertedIntoBlockUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/SiteBrandingConvertedIntoBlockUpdateTest.php @@ -12,6 +12,11 @@ */ class SiteBrandingConvertedIntoBlockUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -39,7 +44,7 @@ public function testUpdateHookN() { $this->runUpdates(); /** @var \Drupal\block\BlockInterface $block_storage */ - $block_storage = \Drupal::entityManager()->getStorage('block'); + $block_storage = \Drupal::entityTypeManager()->getStorage('block'); $this->assertRaw('Because your site has custom theme(s) installed, we had to set the branding block into the content region. Please manually review the block configuration and remove the site name, slogan, and logo variables from your templates.'); diff --git a/core/modules/system/tests/src/Functional/Update/StableBaseThemeUpdateTest.php b/core/modules/system/tests/src/Functional/Update/StableBaseThemeUpdateTest.php index 2bfc59d21..5675cb99b 100644 --- a/core/modules/system/tests/src/Functional/Update/StableBaseThemeUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/StableBaseThemeUpdateTest.php @@ -2,7 +2,19 @@ namespace Drupal\Tests\system\Functional\Update; +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\DependencyInjection\ServiceProviderInterface; +use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\Extension\InfoParser; +use Drupal\Core\Extension\InfoParserInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Extension\ThemeEngineExtensionList; +use Drupal\Core\Extension\ThemeExtensionList; +use Drupal\Core\State\StateInterface; use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use org\bovigo\vfs\vfsStream; /** * Tests the upgrade path for introducing the Stable base theme. @@ -12,7 +24,7 @@ * @group system * @group legacy */ -class StableBaseThemeUpdateTest extends UpdatePathTestBase { +class StableBaseThemeUpdateTest extends UpdatePathTestBase implements ServiceProviderInterface { /** * The theme handler. @@ -21,6 +33,11 @@ class StableBaseThemeUpdateTest extends UpdatePathTestBase { */ protected $themeHandler; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -31,6 +48,22 @@ protected function setDatabaseDumpFiles() { ]; } + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container) { + $container->getDefinition('extension.list.theme') + ->setClass(VfsThemeExtensionList::class); + } + + /** + * {@inheritdoc} + */ + protected function prepareEnvironment() { + parent::prepareEnvironment(); + $GLOBALS['conf']['container_service_providers']['test'] = $this; + } + /** * {@inheritdoc} */ @@ -38,10 +71,26 @@ protected function setUp() { parent::setUp(); $this->themeHandler = $this->container->get('theme_handler'); $this->themeHandler->refreshInfo(); + + $vfs_root = vfsStream::setup('core'); + vfsStream::create([ + 'themes' => [ + 'test_stable' => [ + 'test_stable.info.yml' => file_get_contents(DRUPAL_ROOT . '/core/tests/fixtures/test_stable/test_stable.info.yml'), + 'test_stable.theme' => file_get_contents(DRUPAL_ROOT . '/core/tests/fixtures/test_stable/test_stable.theme'), + ], + 'stable' => [ + 'stable.info.yml' => file_get_contents(DRUPAL_ROOT . '/core/themes/stable/stable.info.yml'), + 'stable.theme' => file_get_contents(DRUPAL_ROOT . '/core/themes/stable/stable.theme'), + ], + ], + ], $vfs_root); } /** * Tests that the Stable base theme is installed if necessary. + * + * @expectedDeprecation There is no `base theme` property specified in the test_stable.info.yml file. The optionality of the `base theme` property is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. All Drupal 8 themes must add `base theme: stable` to their *.info.yml file for them to continue to work as-is in future versions of Drupal. Drupal 9 requires the `base theme` property to be specified. See https://www.drupal.org/node/3066038 */ public function testUpdateHookN() { $this->assertTrue($this->themeHandler->themeExists('test_stable')); @@ -55,3 +104,41 @@ public function testUpdateHookN() { } } + +class VfsThemeExtensionList extends ThemeExtensionList { + + /** + * The extension discovery for this extension list. + * + * @var \Drupal\Core\Extension\ExtensionDiscovery + */ + protected $extensionDiscovery; + + /** + * {@inheritdoc} + */ + public function __construct(string $root, string $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, ConfigFactoryInterface $config_factory, ThemeEngineExtensionList $engine_list, $install_profile) { + parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $config_factory, $engine_list, $install_profile); + $this->extensionDiscovery = new ExtensionDiscovery('vfs://core'); + $this->infoParser = new VfsInfoParser('vfs:/'); + } + + /** + * {@inheritdoc} + */ + public function getExtensionDiscovery() { + return $this->extensionDiscovery; + } + +} + +class VfsInfoParser extends InfoParser { + + /** + * {@inheritdoc} + */ + public function parse($filename) { + return parent::parse("vfs://core/$filename"); + } + +} diff --git a/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php index 764c92c5f..db5928ef1 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php @@ -13,6 +13,11 @@ */ class UpdateActionsWithEntityPluginsTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php b/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php new file mode 100644 index 000000000..0dc56602c --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php @@ -0,0 +1,49 @@ +set('will_not_exist_after_update', TRUE); + // The site might be broken at the time so logging in using the UI might + // not work, so we use the API itself. + $this->writeSettings([ + 'settings' => [ + 'update_free_access' => (object) [ + 'value' => TRUE, + 'required' => TRUE, + ], + ], + ]); + + // Clicking continue should clear the caches. + $this->drupalGet(Url::fromRoute('system.db_update', [], ['path_processing' => FALSE])); + $this->updateRequirementsProblem(); + $this->clickLink(t('Continue')); + + $this->assertFalse(\Drupal::cache()->get('will_not_exist_after_update', FALSE)); + } + +} diff --git a/core/modules/system/tests/src/Functional/Update/UpdateEntityDisplayTest.php b/core/modules/system/tests/src/Functional/Update/UpdateEntityDisplayTest.php index 187c7ac91..5599462eb 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdateEntityDisplayTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdateEntityDisplayTest.php @@ -14,6 +14,11 @@ */ class UpdateEntityDisplayTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathNewDependencyTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathNewDependencyTest.php new file mode 100644 index 000000000..8f10ef76c --- /dev/null +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathNewDependencyTest.php @@ -0,0 +1,95 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../tests/fixtures/update/drupal-8.6.0.bare.testing.php.gz', + ]; + } + + /** + * Test that a module can add services that depend on new modules. + */ + public function testUpdateNewDependency() { + // The new_dependency_test before the update is just an empty info.yml file. + // The code of the new_dependency_test module is after the update and + // contains the dependency on the new_dependency_test_with_service module. + $extension_config = $this->container->get('config.factory')->getEditable('core.extension'); + $extension_config + ->set('module.new_dependency_test', 0) + ->set('module', module_config_sort($extension_config->get('module'))) + ->save(TRUE); + drupal_set_installed_schema_version('new_dependency_test', \Drupal::CORE_MINIMUM_SCHEMA_VERSION); + + // Rebuild the container and test that the service with the optional unmet + // dependency is still available while the ones that fail are not. + try { + $this->rebuildContainer(); + $this->fail('The container has services with unmet dependencies and should have failed to rebuild.'); + } + catch (ServiceNotFoundException $exception) { + $this->assertEquals('The service "new_dependency_test.dependent" has a dependency on a non-existent service "new_dependency_test_with_service.service".', $exception->getMessage()); + } + + // Running the updates enables the dependency. + $this->runUpdates(); + + $this->assertTrue(array_key_exists('new_dependency_test', $this->container->get('config.factory')->get('core.extension')->get('module'))); + $this->assertTrue(array_key_exists('new_dependency_test_with_service', $this->container->get('config.factory')->get('core.extension')->get('module'))); + + // Tests that the new services are available and working as expected. + $this->assertEquals('Hello', $this->container->get('new_dependency_test_with_service.service')->greet()); + $this->assertEquals('Hello', $this->container->get('new_dependency_test.dependent')->greet()); + $this->assertEquals('Hello', $this->container->get('new_dependency_test.alias')->greet()); + $this->assertEquals('Hello World', $this->container->get('new_dependency_test.hard_dependency')->greet()); + $this->assertEquals('Hello World', $this->container->get('new_dependency_test.optional_dependency')->greet()); + + // Tests that existing decorated services work as expected during update. + $this->assertTrue(\Drupal::state()->get('new_dependency_test_update_8001.decorated_service'), 'The new_dependency_test.another_service service is decorated'); + $this->assertTrue(\Drupal::state()->get('new_dependency_test_update_8001.decorated_service_custom_inner'), 'The new_dependency_test.another_service_two service is decorated'); + + // Tests that services are available as expected. + $before_install = \Drupal::state()->get('new_dependency_test_update_8001.has_before_install', []); + $this->assertSame([ + 'new_dependency_test.hard_dependency' => FALSE, + 'new_dependency_test.optional_dependency' => TRUE, + 'new_dependency_test.recursion' => FALSE, + 'new_dependency_test.alias' => FALSE, + 'new_dependency_test.alias_dependency' => FALSE, + 'new_dependency_test.alias2' => FALSE, + 'new_dependency_test.alias_dependency2' => FALSE, + ], $before_install); + + $after_install = \Drupal::state()->get('new_dependency_test_update_8001.has_after_install', []); + $this->assertSame([ + 'new_dependency_test.hard_dependency' => TRUE, + 'new_dependency_test.optional_dependency' => TRUE, + 'new_dependency_test.recursion' => TRUE, + 'new_dependency_test.alias' => TRUE, + 'new_dependency_test.alias_dependency' => TRUE, + 'new_dependency_test.alias2' => TRUE, + 'new_dependency_test.alias_dependency2' => TRUE, + ], $after_install); + } + +} diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseFilledTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseFilledTest.php index 45c3d8477..0b59c4b6f 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseFilledTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseFilledTest.php @@ -14,6 +14,11 @@ */ class UpdatePathRC1TestBaseFilledTest extends UpdatePathRC1TestBaseTest { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -379,7 +384,6 @@ public function testUpdatedSite() { 'search', 'serialization', 'shortcut', - 'simpletest', 'statistics', 'syslog', 'system', diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php index da53ba6e6..c3e0cdb05 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathRC1TestBaseTest.php @@ -18,6 +18,11 @@ class UpdatePathRC1TestBaseTest extends UpdatePathTestBase { */ protected static $modules = ['update_test_schema']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -62,7 +67,7 @@ public function testDatabaseLoaded() { $this->assertText('Site-Install'); $extensions = \Drupal::service('config.storage')->read('core.extension'); $this->assertTrue(isset($extensions['theme']['stable']), 'Stable is installed after updating.'); - $blocks = \Drupal::entityManager()->getStorage('block')->loadByProperties(['theme' => 'stable']); + $blocks = \Drupal::entityTypeManager()->getStorage('block')->loadByProperties(['theme' => 'stable']); $this->assertTrue(empty($blocks), 'No blocks have been placed for Stable.'); } diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathTestBaseFilledTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathTestBaseFilledTest.php index 5cc23b007..e938bbc6b 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathTestBaseFilledTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathTestBaseFilledTest.php @@ -16,12 +16,17 @@ */ class UpdatePathTestBaseFilledTest extends UpdatePathTestBaseTest { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ protected function setDatabaseDumpFiles() { parent::setDatabaseDumpFiles(); - $this->databaseDumpFiles[0] = __DIR__ . '/../../../../tests/fixtures/update/drupal-8.filled.standard.php.gz'; + $this->databaseDumpFiles[0] = __DIR__ . '/../../../../tests/fixtures/update/drupal-8.8.0.filled.standard.php.gz'; } /** @@ -381,7 +386,6 @@ public function testUpdatedSite() { 'search', 'serialization', 'shortcut', - 'simpletest', 'statistics', 'syslog', 'system', diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php index d5bcb1682..473c392aa 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathTestJavaScriptTest.php @@ -12,6 +12,11 @@ */ class UpdatePathTestJavaScriptTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php index 4eaec54e3..6be4f333c 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingFilledTest.php @@ -10,6 +10,11 @@ */ class UpdatePathWithBrokenRoutingFilledTest extends UpdatePathWithBrokenRoutingTest { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php index c54804712..17a70c021 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePathWithBrokenRoutingTest.php @@ -12,6 +12,11 @@ */ class UpdatePathWithBrokenRoutingTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php index a01476534..5c6b223d6 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateFailingTest.php @@ -12,6 +12,11 @@ */ class UpdatePostUpdateFailingTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php index 3e2c46a99..1015e7a13 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatePostUpdateTest.php @@ -13,6 +13,11 @@ */ class UpdatePostUpdateTest extends UpdatePathTestBase { + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php b/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php index aa28be020..193704b2f 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdateSchemaTest.php @@ -21,6 +21,11 @@ class UpdateSchemaTest extends BrowserTestBase { */ public static $modules = ['update_test_schema']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * @var \Drupal\user\UserInterface */ diff --git a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php index 162cbf482..3e484f231 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php @@ -23,6 +23,11 @@ class UpdateScriptTest extends BrowserTestBase { */ public static $modules = ['update_script_test', 'dblog', 'language']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * {@inheritdoc} */ @@ -208,7 +213,7 @@ public function testNoUpdateFunctionality() { */ public function testSuccessfulUpdateFunctionality() { $initial_maintenance_mode = $this->container->get('state')->get('system.maintenance_mode'); - $this->assertFalse($initial_maintenance_mode, 'Site is not in maintenance mode.'); + $this->assertNull($initial_maintenance_mode, 'Site is not in maintenance mode.'); $this->runUpdates($initial_maintenance_mode); $final_maintenance_mode = $this->container->get('state')->get('system.maintenance_mode'); $this->assertEqual($final_maintenance_mode, $initial_maintenance_mode, 'Maintenance mode should not have changed after database updates.'); diff --git a/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php b/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php index 7d3a5ef3e..6b08e685e 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdatesWith7xTest.php @@ -22,8 +22,15 @@ class UpdatesWith7xTest extends BrowserTestBase { */ public static $modules = ['update_test_with_7x']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * The URL for the update page. + * + * @var string */ private $updateUrl; diff --git a/core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php b/core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php index b565bfd74..560ee6c8b 100644 --- a/core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/Form/ElementsTableSelectTest.php @@ -16,6 +16,11 @@ class ElementsTableSelectTest extends WebDriverTestBase { */ protected static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Test the presence of ajax functionality for all options. */ diff --git a/core/modules/system/tests/src/FunctionalJavascript/Form/RebuildTest.php b/core/modules/system/tests/src/FunctionalJavascript/Form/RebuildTest.php index caf52094d..9b38a1625 100644 --- a/core/modules/system/tests/src/FunctionalJavascript/Form/RebuildTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/Form/RebuildTest.php @@ -21,6 +21,11 @@ class RebuildTest extends WebDriverTestBase { */ protected static $modules = ['node', 'form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * A user for testing. * @@ -78,7 +83,7 @@ public function testPreserveFormActionAfterAJAX() { 'required' => TRUE, ])->save(); - entity_get_form_display('node', 'page', 'default') + \Drupal::service('entity_display.repository')->getFormDisplay('node', 'page', 'default') ->setComponent($field_name, ['type' => 'text_textfield']) ->setComponent($field_file_name, ['type' => 'file_generic']) ->save(); diff --git a/core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php b/core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php index 41733ec46..3a3840d2d 100644 --- a/core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/Form/TriggeringElementTest.php @@ -16,6 +16,11 @@ class TriggeringElementTest extends WebDriverTestBase { */ protected static $modules = ['form_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests the the triggering element when no button information is included. * diff --git a/core/modules/system/tests/src/FunctionalJavascript/FrameworkTest.php b/core/modules/system/tests/src/FunctionalJavascript/FrameworkTest.php index 60f10b35f..f2ff799f8 100644 --- a/core/modules/system/tests/src/FunctionalJavascript/FrameworkTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/FrameworkTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\FunctionalJavascript; +use Drupal\Component\Render\FormattableMarkup; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; /** @@ -16,6 +17,11 @@ class FrameworkTest extends WebDriverTestBase { */ protected static $modules = ['node', 'ajax_test', 'ajax_forms_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'classy'; + /** * Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request. */ @@ -37,9 +43,9 @@ public function testLazyLoad() { // Verify that the base page doesn't have the settings and files that are to // be lazy loaded as part of the next requests. - $this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', ['%setting' => $expected['setting_name']])); - $this->assertTrue(!in_array($expected['library_1'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_1']])); - $this->assertTrue(!in_array($expected['library_2'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_2']])); + $this->assertTrue(!isset($original_settings[$expected['setting_name']]), new FormattableMarkup('Page originally lacks the %setting, as expected.', ['%setting' => $expected['setting_name']])); + $this->assertTrue(!in_array($expected['library_1'], $original_libraries), new FormattableMarkup('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_1']])); + $this->assertTrue(!in_array($expected['library_2'], $original_libraries), new FormattableMarkup('Page originally lacks the %library library, as expected.', ['%library' => $expected['library_2']])); // Submit the AJAX request without triggering files getting added. $page->pressButton('Submit'); @@ -48,9 +54,9 @@ public function testLazyLoad() { $new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']); // Verify the setting was not added when not expected. - $this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', ['%setting' => $expected['setting_name']])); - $this->assertTrue(!in_array($expected['library_1'], $new_libraries), format_string('Page still lacks the %library library, as expected.', ['%library' => $expected['library_1']])); - $this->assertTrue(!in_array($expected['library_2'], $new_libraries), format_string('Page still lacks the %library library, as expected.', ['%library' => $expected['library_2']])); + $this->assertTrue(!isset($new_settings[$expected['setting_name']]), new FormattableMarkup('Page still lacks the %setting, as expected.', ['%setting' => $expected['setting_name']])); + $this->assertTrue(!in_array($expected['library_1'], $new_libraries), new FormattableMarkup('Page still lacks the %library library, as expected.', ['%library' => $expected['library_1']])); + $this->assertTrue(!in_array($expected['library_2'], $new_libraries), new FormattableMarkup('Page still lacks the %library library, as expected.', ['%library' => $expected['library_2']])); // Submit the AJAX request and trigger adding files. $page->checkField('add_files'); @@ -61,11 +67,11 @@ public function testLazyLoad() { // Verify the expected setting was added, both to drupalSettings, and as // the first AJAX command. - $this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', ['%setting' => $expected['setting_name']])); + $this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], new FormattableMarkup('Page now has the %setting.', ['%setting' => $expected['setting_name']])); // Verify the expected CSS file was added, both to drupalSettings, and as // the second AJAX command for inclusion into the HTML. - $this->assertTrue(in_array($expected['library_1'], $new_libraries), format_string('Page state now has the %library library.', ['%library' => $expected['library_1']])); + $this->assertTrue(in_array($expected['library_1'], $new_libraries), new FormattableMarkup('Page state now has the %library library.', ['%library' => $expected['library_1']])); // Verify the expected JS file was added, both to drupalSettings, and as // the third AJAX command for inclusion into the HTML. By testing for an @@ -73,7 +79,7 @@ public function testLazyLoad() { // unexpected JavaScript code, such as a jQuery.extend() that would // potentially clobber rather than properly merge settings, didn't // accidentally get added. - $this->assertTrue(in_array($expected['library_2'], $new_libraries), format_string('Page state now has the %library library.', ['%library' => $expected['library_2']])); + $this->assertTrue(in_array($expected['library_2'], $new_libraries), new FormattableMarkup('Page state now has the %library library.', ['%library' => $expected['library_2']])); } /** @@ -97,7 +103,7 @@ public function testCurrentPathChange() { public function testLazyLoadOverriddenCSS() { // The test theme overrides js.module.css without an implementation, // thereby removing it. - \Drupal::service('theme_handler')->install(['test_theme']); + \Drupal::service('theme_installer')->install(['test_theme']); $this->config('system.theme') ->set('default', 'test_theme') ->save(); diff --git a/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php b/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php index 3597ba46d..e7ce0429e 100644 --- a/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php @@ -16,6 +16,11 @@ class ModalRendererTest extends WebDriverTestBase { */ public static $modules = ['system', 'dialog_renderer_test']; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that links respect 'data-dialog-renderer' attribute. */ diff --git a/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTest.php b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTest.php index c42d777c1..f2d44592c 100644 --- a/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTest.php @@ -25,6 +25,11 @@ class OffCanvasTest extends OffCanvasTestBase { 'off_canvas_test', ]; + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + /** * Tests that non-contextual links will work with the off-canvas dialog. * @@ -156,7 +161,7 @@ protected function assertOffCanvasDialog($link_index, $position) { // Click the first test like that should open the page. $page->clickLink($link_text); if ($this->lastDialogClass) { - $this->waitForNoElement('.' . $this->lastDialogClass); + $web_assert->assertNoElementAfterWait('css', '.' . $this->lastDialogClass); } $this->waitForOffCanvasToOpen($position); $this->lastDialogClass = "$position-$link_index"; diff --git a/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTestBase.php b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTestBase.php index b2e6c2b65..3907768b9 100644 --- a/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTestBase.php +++ b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTestBase.php @@ -41,7 +41,7 @@ protected function assertPageLoadComplete() { * @todo Move this function to https://www.drupal.org/node/2821724. */ protected function assertAllContextualLinksLoaded() { - $this->waitForNoElement('[data-contextual-id]:empty'); + $this->assertSession()->assertNoElementAfterWait('css', '[data-contextual-id]:empty'); } /** @@ -81,7 +81,7 @@ protected function waitForOffCanvasToOpen($position = 'side') { * Waits for off-canvas dialog to close. */ protected function waitForOffCanvasToClose() { - $this->waitForNoElement('#drupal-off-canvas'); + $this->assertSession()->assertNoElementAfterWait('css', '#drupal-off-canvas'); } /** @@ -103,25 +103,12 @@ protected function getOffCanvasDialog() { * @param int $timeout * (optional) Timeout in milliseconds, defaults to 10000. * - * @todo Remove in https://www.drupal.org/node/2892440. + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * Drupal\FunctionalJavascriptTests\JSWebAssert::assertNoElementAfterWait() */ protected function waitForNoElement($selector, $timeout = 10000) { - - $start = microtime(TRUE); - $end = $start + ($timeout / 1000); - $page = $this->getSession()->getPage(); - - do { - $result = $page->find('css', $selector); - - if (empty($result)) { - return; - } - - usleep(100000); - } while (microtime(TRUE) < $end); - - $this->assertEmpty($result, 'Element was not on the page after wait.'); + @trigger_error('::waitForNoElement is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Use \Drupal\FunctionalJavascriptTests\JSWebAssert::assertNoElementAfterWait() instead.', E_USER_DEPRECATED); + $this->assertSession()->assertNoElementAfterWait('css', $selector, $timeout); } /** diff --git a/core/modules/system/tests/src/FunctionalJavascript/System/DateFormatTest.php b/core/modules/system/tests/src/FunctionalJavascript/System/DateFormatTest.php new file mode 100644 index 000000000..3460f15b3 --- /dev/null +++ b/core/modules/system/tests/src/FunctionalJavascript/System/DateFormatTest.php @@ -0,0 +1,68 @@ +drupalLogin($this->drupalCreateUser(['administer site configuration'])); + $this->drupalPlaceBlock('local_actions_block'); + } + + /** + * Tests XSS via date format configuration. + */ + public function testDateFormatXss() { + $page = $this->getSession()->getPage(); + $assert = $this->assertSession(); + + $date_format = DateFormat::create([ + 'id' => 'xss_short', + 'label' => 'XSS format', + 'pattern' => '\<\s\c\r\i\p\t\>\a\l\e\r\t\(\"\X\S\S\")\;\<\/\s\c\r\i\p\t\>', + ]); + $date_format->save(); + $this->drupalGet('admin/config/regional/date-time'); + $assert->assertEscaped('', 'The date format was properly escaped'); + $this->drupalGet('admin/config/regional/date-time/formats/manage/xss_short'); + $assert->assertEscaped('', 'The date format was properly escaped'); + + // Add a new date format with HTML in it. + $this->drupalGet('admin/config/regional/date-time/formats/add'); + $date_format = '& \<\e\m\>Y\<\/\e\m\>'; + $page->fillField('date_format_pattern', $date_format); + $assert->waitForText('Displayed as'); + $assert->assertEscaped('' . date("Y") . ''); + $page->fillField('label', 'date_html_pattern'); + // Wait for the machine name ID to be completed. + $assert->waitForLink('Edit'); + $page->pressButton('Add format'); + $assert->pageTextContains('Custom date format added.'); + $assert->assertEscaped('' . date("Y") . ''); + } + +} diff --git a/core/modules/system/tests/src/FunctionalJavascript/ThemeFormSettingsTest.php b/core/modules/system/tests/src/FunctionalJavascript/ThemeFormSettingsTest.php deleted file mode 100644 index 4f3e9f1d4..000000000 --- a/core/modules/system/tests/src/FunctionalJavascript/ThemeFormSettingsTest.php +++ /dev/null @@ -1,78 +0,0 @@ -drupalCreateUser(['administer themes']); - $this->drupalLogin($admin); - } - - /** - * Tests that submission handler works correctly. - * - * @dataProvider providerTestFormSettingsSubmissionHandler - */ - public function testFormSettingsSubmissionHandler($theme) { - - \Drupal::service('theme_handler')->install([$theme]); - - $page = $this->getSession()->getPage(); - $assert_session = $this->assertSession(); - - $this->drupalGet("admin/appearance/settings/$theme"); - - // Add a new managed file. - $file = current($this->getTestFiles('image')); - $image_file_path = \Drupal::service('file_system')->realpath($file->uri); - $page->attachFileToField('files[custom_logo]', $image_file_path); - $assert_session->waitForButton('custom_logo_remove_button'); - - // Assert the new file is uploaded as temporary. This file should not be - // saved as permanent if settings are not submited. - $image_field = $this->xpath('//input[@name="custom_logo[fids]"]')[0]; - $file = File::load($image_field->getValue()); - $this->assertFalse($file->isPermanent()); - - $page->pressButton('Save configuration'); - \Drupal::entityTypeManager()->getStorage('file')->resetCache(); - - // Assert the uploaded file is saved as permanent. - $image_field = $this->xpath('//input[@name="custom_logo[fids]"]')[0]; - $file = File::load($image_field->getValue()); - $this->assertTrue($file->isPermanent()); - } - - /** - * Provides test data for ::testFormSettingsSubmissionHandler(). - */ - public function providerTestFormSettingsSubmissionHandler() { - return [ - 'test theme.theme' => ['test_theme_theme'], - 'test theme-settings.php' => ['test_theme_settings'], - ]; - } - -} diff --git a/core/modules/system/tests/src/FunctionalJavascript/ThemeSettingsFormTest.php b/core/modules/system/tests/src/FunctionalJavascript/ThemeSettingsFormTest.php new file mode 100644 index 000000000..f80ae96fa --- /dev/null +++ b/core/modules/system/tests/src/FunctionalJavascript/ThemeSettingsFormTest.php @@ -0,0 +1,83 @@ +drupalCreateUser(['administer themes']); + $this->drupalLogin($admin); + } + + /** + * Tests that submission handler works correctly. + * + * @dataProvider providerTestFormSettingsSubmissionHandler + */ + public function testFormSettingsSubmissionHandler($theme) { + + \Drupal::service('theme_installer')->install([$theme]); + + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + $this->drupalGet("admin/appearance/settings/$theme"); + + // Add a new managed file. + $file = current($this->getTestFiles('image')); + $image_file_path = \Drupal::service('file_system')->realpath($file->uri); + $page->attachFileToField('files[custom_logo]', $image_file_path); + $assert_session->waitForButton('custom_logo_remove_button'); + + // Assert the new file is uploaded as temporary. This file should not be + // saved as permanent if settings are not submited. + $image_field = $this->xpath('//input[@name="custom_logo[fids]"]')[0]; + $file = File::load($image_field->getValue()); + $this->assertFalse($file->isPermanent()); + + $page->pressButton('Save configuration'); + \Drupal::entityTypeManager()->getStorage('file')->resetCache(); + + // Assert the uploaded file is saved as permanent. + $image_field = $this->xpath('//input[@name="custom_logo[fids]"]')[0]; + $file = File::load($image_field->getValue()); + $this->assertTrue($file->isPermanent()); + } + + /** + * Provides test data for ::testFormSettingsSubmissionHandler(). + */ + public function providerTestFormSettingsSubmissionHandler() { + return [ + 'test theme.theme' => ['test_theme_theme'], + 'test theme-settings.php' => ['test_theme_settings'], + ]; + } + +} diff --git a/core/modules/system/tests/src/Kernel/Action/ActionTest.php b/core/modules/system/tests/src/Kernel/Action/ActionTest.php index 6c381d7c8..dad380079 100644 --- a/core/modules/system/tests/src/Kernel/Action/ActionTest.php +++ b/core/modules/system/tests/src/Kernel/Action/ActionTest.php @@ -58,7 +58,7 @@ public function testOperations() { // Create a new unsaved user. $name = $this->randomMachineName(); - $user_storage = $this->container->get('entity.manager')->getStorage('user'); + $user_storage = $this->container->get('entity_type.manager')->getStorage('user'); $account = $user_storage->create(['name' => $name, 'bundle' => 'user']); $loaded_accounts = $user_storage->loadMultiple(); $this->assertEqual(count($loaded_accounts), 0); diff --git a/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php b/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php index 8915815e2..8d0ef50df 100644 --- a/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php +++ b/core/modules/system/tests/src/Kernel/Block/SystemMenuBlockTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Kernel\Block; +use Drupal\Component\Render\FormattableMarkup; use Drupal\KernelTests\KernelTestBase; use Drupal\system\Entity\Menu; use Drupal\block\Entity\Block; @@ -227,7 +228,7 @@ public function testConfigLevelDepth() { foreach ($blocks as $id => $block) { $block_build = $block->build(); $items = isset($block_build['#items']) ? $block_build['#items'] : []; - $this->assertIdentical($no_active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with no active trail renders the expected tree.', ['%id' => $id])); + $this->assertIdentical($no_active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), new FormattableMarkup('Menu block %id with no active trail renders the expected tree.', ['%id' => $id])); } // Scenario 2: test all block instances when there's an active trail. @@ -279,7 +280,7 @@ public function testConfigLevelDepth() { foreach ($blocks as $id => $block) { $block_build = $block->build(); $items = isset($block_build['#items']) ? $block_build['#items'] : []; - $this->assertIdentical($active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with an active trail renders the expected tree.', ['%id' => $id])); + $this->assertIdentical($active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), new FormattableMarkup('Menu block %id with an active trail renders the expected tree.', ['%id' => $id])); } } diff --git a/core/modules/system/tests/src/Kernel/Common/AddFeedTest.php b/core/modules/system/tests/src/Kernel/Common/AddFeedTest.php index 1060b30e4..b7d34222f 100644 --- a/core/modules/system/tests/src/Kernel/Common/AddFeedTest.php +++ b/core/modules/system/tests/src/Kernel/Common/AddFeedTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Kernel\Common; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; use Drupal\KernelTests\KernelTestBase; @@ -68,7 +69,7 @@ public function testBasicFeedAddNoTitle() { $this->setRawContent($response->getContent()); // Assert that the content contains the RSS links we specified. foreach ($urls as $description => $feed_info) { - $this->assertPattern($this->urlToRSSLinkPattern($feed_info['url'], $feed_info['title']), format_string('Found correct feed header for %description', ['%description' => $description])); + $this->assertPattern($this->urlToRSSLinkPattern($feed_info['url'], $feed_info['title']), new FormattableMarkup('Found correct feed header for %description', ['%description' => $description])); } } diff --git a/core/modules/system/tests/src/Kernel/Common/FormElementsRenderTest.php b/core/modules/system/tests/src/Kernel/Common/FormElementsRenderTest.php index 956847057..9874efa9a 100644 --- a/core/modules/system/tests/src/Kernel/Common/FormElementsRenderTest.php +++ b/core/modules/system/tests/src/Kernel/Common/FormElementsRenderTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Kernel\Common; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; use Drupal\KernelTests\KernelTestBase; @@ -145,7 +146,7 @@ protected function assertRenderedElement(array $element, $xpath, array $xpath_ar // @see \Drupal\simpletest\WebTestBase::xpath() $xpath = $this->buildXPathQuery($xpath, $xpath_args); $element += ['#value' => NULL]; - $this->assertFieldByXPath($xpath, $element['#value'], format_string('#type @type was properly rendered.', [ + $this->assertFieldByXPath($xpath, $element['#value'], new FormattableMarkup('#type @type was properly rendered.', [ '@type' => var_export($element['#type'], TRUE), ])); } diff --git a/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php b/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php index 1a3f373f3..1ff6a8455 100644 --- a/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php +++ b/core/modules/system/tests/src/Kernel/Common/SystemListingTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Kernel\Common; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\KernelTests\KernelTestBase; @@ -33,7 +34,7 @@ public function testDirectoryPrecedence() { foreach ($expected_directories as $module => $directories) { foreach ($directories as $directory) { $filename = "$directory/$module/$module.info.yml"; - $this->assertTrue(file_exists($this->root . '/' . $filename), format_string('@filename exists.', ['@filename' => $filename])); + $this->assertTrue(file_exists($this->root . '/' . $filename), new FormattableMarkup('@filename exists.', ['@filename' => $filename])); } } @@ -45,7 +46,7 @@ public function testDirectoryPrecedence() { foreach ($expected_directories as $module => $directories) { $expected_directory = array_shift($directories); $expected_uri = "$expected_directory/$module/$module.info.yml"; - $this->assertEqual($files[$module]->getPathname(), $expected_uri, format_string('Module @actual was found at @expected.', [ + $this->assertEqual($files[$module]->getPathname(), $expected_uri, new FormattableMarkup('Module @actual was found at @expected.', [ '@actual' => $files[$module]->getPathname(), '@expected' => $expected_uri, ])); diff --git a/core/modules/system/tests/src/Kernel/DeprecatedPathHooksTest.php b/core/modules/system/tests/src/Kernel/DeprecatedPathHooksTest.php new file mode 100644 index 000000000..b89691378 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/DeprecatedPathHooksTest.php @@ -0,0 +1,76 @@ +installEntitySchema('path_alias'); + } + + /** + * @covers ::save + * + * @expectedDeprecation The deprecated hook hook_path_insert() is implemented in these functions: path_deprecated_test_path_insert(). It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_insert() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865. + */ + public function testInsert() { + $source = '/' . $this->randomMachineName(); + $alias = '/' . $this->randomMachineName(); + + $alias_storage = \Drupal::service('path.alias_storage'); + $alias_storage->save($source, $alias); + } + + /** + * @covers ::save + * + * @expectedDeprecation The deprecated hook hook_path_update() is implemented in these functions: path_deprecated_test_path_update(). It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_update() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865. + */ + public function testUpdate() { + $source = '/' . $this->randomMachineName(); + $alias = '/' . $this->randomMachineName(); + + $alias_storage = \Drupal::service('path.alias_storage'); + $alias_storage->save($source, $alias); + + $new_source = '/' . $this->randomMachineName(); + $path = $alias_storage->load(['source' => $source]); + $alias_storage->save($new_source, $alias, LanguageInterface::LANGCODE_NOT_SPECIFIED, $path['pid']); + } + + /** + * @covers ::delete + * + * @expectedDeprecation The deprecated hook hook_path_delete() is implemented in these functions: path_deprecated_test_path_delete(). It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_delete() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865. + */ + public function testDelete() { + $source = '/' . $this->randomMachineName(); + $alias = '/' . $this->randomMachineName(); + + $alias_storage = \Drupal::service('path.alias_storage'); + $alias_storage->save($source, $alias); + + $path = $alias_storage->load(['source' => $source]); + $alias_storage->delete(['pid' => $path['pid']]); + } + +} diff --git a/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php index 0a33a88c6..54de7bf0b 100644 --- a/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php +++ b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php @@ -53,7 +53,7 @@ protected function setUp() { $this->installEntitySchema('node'); /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ - $storage = $this->container->get('entity.manager') + $storage = $this->container->get('entity_type.manager') ->getStorage('entity_test_no_label'); // Create a new node-type. diff --git a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php index 976032e05..fca57c4e7 100644 --- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php +++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Kernel\Extension; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Extension\MissingDependencyException; use Drupal\Core\Extension\ModuleUninstallValidatorException; use Drupal\entity_test\Entity\EntityTest; @@ -23,7 +24,7 @@ class ModuleHandlerTest extends KernelTestBase { * The basic functionality of retrieving enabled modules. */ public function testModuleList() { - $module_list = ['system']; + $module_list = ['path_alias', 'system']; $this->assertModuleList($module_list, 'Initial'); @@ -61,7 +62,7 @@ public function testModuleList() { protected function assertModuleList(array $expected_values, $condition) { $expected_values = array_values(array_unique($expected_values)); $enabled_modules = array_keys($this->container->get('module_handler')->getModuleList()); - $this->assertEqual($expected_values, $enabled_modules, format_string('@condition: extension handler returns correct results', ['@condition' => $condition])); + $this->assertEqual($expected_values, $enabled_modules, new FormattableMarkup('@condition: extension handler returns correct results', ['@condition' => $condition])); } /** @@ -89,14 +90,13 @@ public function testDependencyResolution() { // Color will depend on Config, which depends on a non-existing module Foo. // Nothing should be installed. \Drupal::state()->set('module_test.dependency', 'missing dependency'); - \Drupal::service('extension.list.module')->reset(); try { $result = $this->moduleInstaller()->install(['color']); - $this->fail(t('ModuleInstaller::install() throws an exception if dependencies are missing.')); + $this->fail('ModuleInstaller::install() throws an exception if dependencies are missing.'); } catch (MissingDependencyException $e) { - $this->pass(t('ModuleInstaller::install() throws an exception if dependencies are missing.')); + $this->pass('ModuleInstaller::install() throws an exception if dependencies are missing.'); } $this->assertFalse($this->moduleHandler()->moduleExists('color'), 'ModuleInstaller::install() aborts if dependencies are missing.'); @@ -104,7 +104,6 @@ public function testDependencyResolution() { // Fix the missing dependency. // Color module depends on Config. Config depends on Help module. \Drupal::state()->set('module_test.dependency', 'dependency'); - \Drupal::service('extension.list.module')->reset(); $result = $this->moduleInstaller()->install(['color']); $this->assertTrue($result, 'ModuleInstaller::install() returns the correct value.'); @@ -136,7 +135,6 @@ public function testDependencyResolution() { // dependency on a specific version of Help module in its info file. Make // sure that Drupal\Core\Extension\ModuleInstaller::install() still works. \Drupal::state()->set('module_test.dependency', 'version dependency'); - \Drupal::service('extension.list.module')->reset(); $result = $this->moduleInstaller()->install(['color']); $this->assertTrue($result, 'ModuleInstaller::install() returns the correct value.'); @@ -171,7 +169,7 @@ public function testUninstallProfileDependencyBC() { drupal_get_filename('profile', $profile, 'core/profiles/' . $profile . '/' . $profile . '.info.yml'); $this->enableModules(['module_test', $profile]); - $data = \Drupal::service('extension.list.module')->reset()->getList(); + $data = \Drupal::service('extension.list.module')->getList(); $this->assertFalse(isset($data[$profile]->requires[$dependency])); $this->assertContains($dependency, $data[$profile]->info['install']); @@ -225,7 +223,8 @@ public function testUninstallProfileDependency() { $this->assertNotContains($profile, $uninstalled_modules, 'The installation profile is not in the list of uninstalled modules.'); // Try uninstalling the required module. - $this->setExpectedException(ModuleUninstallValidatorException::class, 'The following reasons prevent the modules from being uninstalled: The Testing install profile dependencies module is required'); + $this->expectException(ModuleUninstallValidatorException::class); + $this->expectExceptionMessage('The following reasons prevent the modules from being uninstalled: The Testing install profile dependencies module is required'); $this->moduleInstaller()->uninstall([$dependency]); } @@ -256,7 +255,8 @@ public function testProfileAllDependencies() { } // Try uninstalling the dependencies. - $this->setExpectedException(ModuleUninstallValidatorException::class, 'The following reasons prevent the modules from being uninstalled: The Testing install profile all dependencies module is required'); + $this->expectException(ModuleUninstallValidatorException::class); + $this->expectExceptionMessage('The following reasons prevent the modules from being uninstalled: The Testing install profile all dependencies module is required'); $this->moduleInstaller()->uninstall($dependencies); } @@ -269,7 +269,7 @@ public function testUninstallContentDependency() { $this->assertTrue($this->moduleHandler()->moduleExists('module_test'), 'Test module is enabled.'); $this->installSchema('user', 'users_data'); - $entity_types = \Drupal::entityManager()->getDefinitions(); + $entity_types = \Drupal::entityTypeManager()->getDefinitions(); foreach ($entity_types as $entity_type) { if ('entity_test' == $entity_type->getProvider()) { $this->installEntitySchema($entity_type->id()); @@ -280,7 +280,6 @@ public function testUninstallContentDependency() { // entity_test will depend on help. This way help can not be uninstalled // when there is test content preventing entity_test from being uninstalled. \Drupal::state()->set('module_test.dependency', 'dependency'); - \Drupal::service('extension.list.module')->reset(); // Create an entity so that the modules can not be disabled. $entity = EntityTest::create(['name' => $this->randomString()]); @@ -319,7 +318,7 @@ public function testUninstallContentDependency() { */ public function testModuleMetaData() { // Generate the list of available modules. - $modules = system_rebuild_module_data(); + $modules = $this->container->get('extension.list.module')->getList(); // Check that the mtime field exists for the system module. $this->assertTrue(!empty($modules['system']->info['mtime']), 'The system.info.yml file modification time field is present.'); // Use 0 if mtime isn't present, to avoid an array index notice. diff --git a/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php b/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php index 28d056cec..456b506e7 100644 --- a/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php +++ b/core/modules/system/tests/src/Kernel/Form/ProgrammaticTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Kernel\Form; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Form\FormState; use Drupal\KernelTests\KernelTestBase; @@ -77,14 +78,14 @@ protected function doSubmitForm($values, $valid_input) { '%values' => print_r($values, TRUE), '%errors' => $valid_form ? t('None') : implode(' ', $errors), ]; - $this->assertTrue($valid_input == $valid_form, format_string('Input values: %values
Validation handler errors: %errors', $args)); + $this->assertTrue($valid_input == $valid_form, new FormattableMarkup('Input values: %values
Validation handler errors: %errors', $args)); // We check submitted values only if we have a valid input. if ($valid_input) { // Fetching the values that were set in the submission handler. $stored_values = $form_state->get('programmatic_form_submit'); foreach ($values as $key => $value) { - $this->assertEqual($stored_values[$key], $value, format_string('Submission handler correctly executed: %stored_key is %stored_value', ['%stored_key' => $key, '%stored_value' => print_r($value, TRUE)])); + $this->assertEqual($stored_values[$key], $value, new FormattableMarkup('Submission handler correctly executed: %stored_key is %stored_value', ['%stored_key' => $key, '%stored_value' => print_r($value, TRUE)])); } } } diff --git a/core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php b/core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php similarity index 83% rename from core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php rename to core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php index 0dd7510e8..b541ed8cb 100644 --- a/core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php +++ b/core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php @@ -1,16 +1,21 @@ fileTranslation = new FileTranslation('filename'); + $this->fileTranslation = new FileTranslation('filename', $this->container->get('file_system')); $method = new \ReflectionMethod('\Drupal\Core\StringTranslation\Translator\FileTranslation', 'getTranslationFilesPattern'); $method->setAccessible(TRUE); $this->filePatternMethod = $method; diff --git a/core/modules/system/tests/src/Kernel/Mail/MailTest.php b/core/modules/system/tests/src/Kernel/Mail/MailTest.php new file mode 100644 index 000000000..7e3cf5dc3 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Mail/MailTest.php @@ -0,0 +1,381 @@ +installEntitySchema('user'); + $this->installEntitySchema('file'); + } + + /** + * Assert that the pluggable mail system is functional. + */ + public function testPluggableFramework() { + // Switch mail backends. + $this->configureDefaultMailInterface('test_php_mail_failure'); + + // Get the default MailInterface class instance. + $mail_backend = \Drupal::service('plugin.manager.mail')->getInstance(['module' => 'default', 'key' => 'default']); + + // Assert whether the default mail backend is an instance of the expected + // class. + // Default mail interface can be swapped. + $this->assertInstanceOf(TestPhpMailFailure::class, $mail_backend); + + // Add a module-specific mail backend. + $this->config('system.mail')->set('interface.mymodule_testkey', 'test_mail_collector')->save(); + + // Get the added MailInterface class instance. + $mail_backend = \Drupal::service('plugin.manager.mail')->getInstance(['module' => 'mymodule', 'key' => 'testkey']); + + // Assert whether the added mail backend is an instance of the expected + // class. + // Additional mail interfaces can be added. + $this->assertInstanceOf(TestMailCollector::class, $mail_backend); + } + + /** + * Assert that the pluggable mail system is functional. + */ + public function testErrorMessageDisplay() { + // Switch mail backends. + $this->configureDefaultMailInterface('test_php_mail_failure'); + + // Test with errors displayed to users. + \Drupal::service('plugin.manager.mail')->mail('default', 'default', 'test@example.com', 'en'); + $messages = \Drupal::messenger()->messagesByType(MessengerInterface::TYPE_ERROR); + $this->assertEquals('Unable to send email. Contact the site administrator if the problem persists.', $messages[0]); + \Drupal::messenger()->deleteAll(); + + // Test without errors displayed to users. + \Drupal::service('plugin.manager.mail')->mail('default', 'default', 'test@example.com', 'en', ['_error_message' => '']); + $this->assertEmpty(\Drupal::messenger()->messagesByType(MessengerInterface::TYPE_ERROR)); + } + + /** + * Test that message sending may be canceled. + * + * @see mail_cancel_test_mail_alter() + */ + public function testCancelMessage() { + $language_interface = \Drupal::languageManager()->getCurrentLanguage(); + + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', []); + + // Send a test message that mail_cancel_test_alter should cancel. + \Drupal::service('plugin.manager.mail')->mail('mail_cancel_test', 'cancel_test', 'cancel@example.com', $language_interface->getId()); + // Retrieve sent message. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + + // Assert that the message was not actually sent. + // Message was canceled. + $this->assertFalse($sent_message); + } + + /** + * Checks the From: and Reply-to: headers. + */ + public function testFromAndReplyToHeader() { + $language = \Drupal::languageManager()->getCurrentLanguage(); + + // Set required site configuruation. + $this->config('system.site') + ->set('mail', 'mailtest@example.com') + ->set('name', 'Drupal') + ->save(); + + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', []); + // Send an email with a reply-to address specified. + $from_email = 'Drupal '; + $reply_email = 'someone_else@example.com'; + \Drupal::service('plugin.manager.mail')->mail('mail_cancel_test', 'from_test', 'from_test@example.com', $language, [], $reply_email); + // Test that the reply-to email is just the email and not the site name + // and default sender email. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + // Message is sent from the site email account. + $this->assertEquals($from_email, $sent_message['headers']['From']); + // Message reply-to headers are set. + $this->assertEquals($reply_email, $sent_message['headers']['Reply-to']); + // Errors-to header must not be set, it is deprecated. + $this->assertFalse(isset($sent_message['headers']['Errors-To'])); + + // Test that long site names containing characters that need MIME encoding + // works as expected. + $this->config('system.site')->set('name', 'Drépal this is a very long test sentence to test what happens with very long site names')->save(); + // Send an email and check that the From-header contains the site name. + \Drupal::service('plugin.manager.mail')->mail('mail_cancel_test', 'from_test', 'from_test@example.com', $language); + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + // From header is correctly encoded. + $this->assertEquals('=?UTF-8?B?RHLDqXBhbCB0aGlzIGlzIGEgdmVyeSBsb25nIHRlc3Qgc2VudGVuY2UgdG8gdGU=?= ', $sent_message['headers']['From']); + // From header is correctly encoded. + $this->assertEquals('Drépal this is a very long test sentence to te ', Unicode::mimeHeaderDecode($sent_message['headers']['From'])); + $this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.'); + // Errors-to header must not be set, it is deprecated. + $this->assertFalse(isset($sent_message['headers']['Errors-To'])); + + // Test RFC-2822 rules are respected for 'display-name' component of + // 'From:' header. Specials characters are not allowed, so randomly add one + // of them to the site name and check the string is wrapped in quotes. Also + // hardcode some double-quotes and backslash to validate these are escaped + // properly too. + $specials = '()<>[]:;@\,."'; + $site_name = 'Drupal' . $specials[rand(0, strlen($specials) - 1)] . ' "si\te"'; + $this->config('system.site')->set('name', $site_name)->save(); + // Send an email and check that the From-header contains the site name + // within double-quotes. Also make sure double-quotes and "\" are escaped. + \Drupal::service('plugin.manager.mail')->mail('mail_cancel_test', 'from_test', 'from_test@example.com', $language); + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + $escaped_site_name = str_replace(['\\', '"'], ['\\\\', '\\"'], $site_name); + // From header is correctly quoted. + $this->assertEquals('"' . $escaped_site_name . '" ', $sent_message['headers']['From']); + + // Make sure display-name is not quoted nor escaped if part on an encoding. + $site_name = 'Drépal, "si\te"'; + $this->config('system.site')->set('name', $site_name)->save(); + // Send an email and check that the From-header contains the site name. + \Drupal::service('plugin.manager.mail')->mail('mail_cancel_test', 'from_test', 'from_test@example.com', $language); + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + // From header is correctly encoded. + $this->assertEquals('=?UTF-8?B?RHLDqXBhbCwgInNpXHRlIg==?= ', $sent_message['headers']['From']); + // From header is correctly encoded. + $this->assertEquals($site_name . ' ', Unicode::mimeHeaderDecode($sent_message['headers']['From'])); + } + + /** + * Checks that relative paths in mails are converted into absolute URLs. + */ + public function testConvertRelativeUrlsIntoAbsolute() { + $language_interface = \Drupal::languageManager()->getCurrentLanguage(); + + $this->configureDefaultMailInterface('test_html_mail_collector'); + + // Fetch the hostname and port for matching against. + $http_host = \Drupal::request()->getSchemeAndHttpHost(); + + // Random generator. + $random = new Random(); + + // One random tag name. + $tag_name = strtolower($random->name(8, TRUE)); + + // Test root relative urls. + foreach (['href', 'src'] as $attribute) { + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', []); + + $html = "<$tag_name $attribute=\"/root-relative\">root relative url in mail test"; + $expected_html = "<$tag_name $attribute=\"{$http_host}/root-relative\">root relative url in mail test"; + + // Prepare render array. + $render = ['#markup' => Markup::create($html)]; + + // Send a test message that mail_cancel_test_mail_alter should cancel. + \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); + // Retrieve sent message. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + + // Wrap the expected HTML and assert. + $expected_html = MailFormatHelper::wrapMail($expected_html); + $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails."); + } + + // Test protocol relative urls. + foreach (['href', 'src'] as $attribute) { + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', []); + + $html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test"; + $expected_html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test"; + + // Prepare render array. + $render = ['#markup' => Markup::create($html)]; + + // Send a test message that mail_cancel_test_mail_alter should cancel. + \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); + // Retrieve sent message. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + + // Wrap the expected HTML and assert. + $expected_html = MailFormatHelper::wrapMail($expected_html); + $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails."); + } + + // Test absolute urls. + foreach (['href', 'src'] as $attribute) { + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', []); + + $html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test"; + $expected_html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test"; + + // Prepare render array. + $render = ['#markup' => Markup::create($html)]; + + // Send a test message that mail_cancel_test_mail_alter should cancel. + \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); + // Retrieve sent message. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + + // Wrap the expected HTML and assert. + $expected_html = MailFormatHelper::wrapMail($expected_html); + $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails."); + } + } + + /** + * Checks that mails built from render arrays contain absolute paths. + * + * By default Drupal uses relative paths for images and links. When sending + * emails, absolute paths should be used instead. + */ + public function testRenderedElementsUseAbsolutePaths() { + $language_interface = \Drupal::languageManager()->getCurrentLanguage(); + + $this->configureDefaultMailInterface('test_html_mail_collector'); + + // Fetch the hostname and port for matching against. + $http_host = \Drupal::request()->getSchemeAndHttpHost(); + + // Random generator. + $random = new Random(); + $image_name = $random->name(); + + $test_base_url = 'http://localhost'; + $this->setSetting('file_public_base_url', $test_base_url); + $filepath = \Drupal::service('file_system')->createFilename("{$image_name}.png", ''); + $directory_uri = 'public://' . dirname($filepath); + \Drupal::service('file_system')->prepareDirectory($directory_uri, FileSystemInterface::CREATE_DIRECTORY); + + // Create an image file. + $file = File::create(['uri' => "public://{$image_name}.png", 'filename' => "{$image_name}.png"]); + $file->save(); + + $base_path = base_path(); + + $path_pairs = [ + 'root relative' => [$file->getFileUri(), "{$http_host}{$base_path}{$image_name}.png"], + 'protocol relative' => ['//example.com/image.png', '//example.com/image.png'], + 'absolute' => ['http://example.com/image.png', 'http://example.com/image.png'], + ]; + + // Test images. + foreach ($path_pairs as $test_type => $paths) { + list($input_path, $expected_path) = $paths; + + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', []); + + // Build the render array. + $render = [ + '#theme' => 'image', + '#uri' => $input_path, + ]; + $expected_html = "\"\""; + + // Send a test message that mail_cancel_test_mail_alter should cancel. + \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); + // Retrieve sent message. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + + // Wrap the expected HTML and assert. + $expected_html = MailFormatHelper::wrapMail($expected_html); + $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$test_type} paths are converted properly."); + } + + // Test links. + $path_pairs = [ + 'root relative' => [Url::fromUserInput('/path/to/something'), "{$http_host}{$base_path}path/to/something"], + 'protocol relative' => [Url::fromUri('//example.com/image.png'), '//example.com/image.png'], + 'absolute' => [Url::fromUri('http://example.com/image.png'), 'http://example.com/image.png'], + ]; + + foreach ($path_pairs as $paths) { + list($input_path, $expected_path) = $paths; + + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', []); + + // Build the render array. + $render = [ + '#title' => 'Link', + '#type' => 'link', + '#url' => $input_path, + ]; + $expected_html = "Link"; + + // Send a test message that mail_cancel_test_mail_alter should cancel. + \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]); + // Retrieve sent message. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + + // Wrap the expected HTML and assert. + $expected_html = MailFormatHelper::wrapMail($expected_html); + $this->assertSame($expected_html, $sent_message['body']); + } + } + + /** + * Configures the default mail interface. + * + * KernelTestBase enforces the usage of 'test_mail_collector' plugin to + * collect mail. Since we need to test this functionality itself, we + * manually configure the default mail interface. + * + * @todo Refactor in https://www.drupal.org/project/drupal/issues/3076715 + * + * @param string $mail_interface + * The mail interface to configure. + */ + protected function configureDefaultMailInterface($mail_interface) { + $GLOBALS['config']['system.mail']['interface']['default'] = $mail_interface; + } + +} diff --git a/core/modules/system/tests/src/Kernel/Migrate/d6/MigrateSystemConfigurationTest.php b/core/modules/system/tests/src/Kernel/Migrate/d6/MigrateSystemConfigurationTest.php index 33c963de3..f1c62a481 100644 --- a/core/modules/system/tests/src/Kernel/Migrate/d6/MigrateSystemConfigurationTest.php +++ b/core/modules/system/tests/src/Kernel/Migrate/d6/MigrateSystemConfigurationTest.php @@ -47,9 +47,6 @@ class MigrateSystemConfigurationTest extends MigrateDrupal6TestBase { 'allow_insecure_uploads' => TRUE, // default_scheme is not handled by the migration. 'default_scheme' => 'public', - 'path' => [ - 'temporary' => 'files/temp', - ], // temporary_maximum_age is not handled by the migration. 'temporary_maximum_age' => 21600, ], diff --git a/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php index 0216e2dd3..8fef991b4 100644 --- a/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php +++ b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateSystemConfigurationTest.php @@ -45,9 +45,6 @@ class MigrateSystemConfigurationTest extends MigrateDrupal7TestBase { 'allow_insecure_uploads' => TRUE, // default_scheme is not handled by the migration. 'default_scheme' => 'public', - 'path' => [ - 'temporary' => '/tmp', - ], // temporary_maximum_age is not handled by the migration. 'temporary_maximum_age' => 21600, ], diff --git a/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateThemeSettingsTest.php b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateThemeSettingsTest.php index 08f70cff7..333795e76 100644 --- a/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateThemeSettingsTest.php +++ b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateThemeSettingsTest.php @@ -17,10 +17,8 @@ class MigrateThemeSettingsTest extends MigrateDrupal7TestBase { protected function setUp() { parent::setUp(); - // Install bartik theme. - \Drupal::service('theme_handler')->install(['bartik']); - // Install seven theme. - \Drupal::service('theme_handler')->install(['seven']); + // Install bartik and seven themes. + \Drupal::service('theme_installer')->install(['bartik', 'seven']); $this->executeMigration('d7_theme_settings'); } @@ -36,7 +34,7 @@ public function testMigrateThemeSettings() { $this->assertTrue($config->get('features.comment_user_verification')); $this->assertTrue($config->get('features.favicon')); $this->assertTrue($config->get('features.node_user_picture')); - $this->assertFalse($config->get('features.logo')); + $this->assertNull($config->get('features.logo')); $this->assertTrue($config->get('features.name')); $this->assertTrue($config->get('features.slogan')); $this->assertSame('public://gnu.png', $config->get('logo.path')); @@ -49,7 +47,7 @@ public function testMigrateThemeSettings() { $this->assertTrue($config->get('features.comment_user_verification')); $this->assertTrue($config->get('features.favicon')); $this->assertTrue($config->get('features.node_user_picture')); - $this->assertFalse($config->get('features.logo')); + $this->assertNull($config->get('features.logo')); $this->assertTrue($config->get('features.name')); $this->assertTrue($config->get('features.slogan')); $this->assertSame('', $config->get('logo.path')); diff --git a/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php b/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php new file mode 100644 index 000000000..245c965b1 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php @@ -0,0 +1,43 @@ +findPage() instead. See https://www.drupal.org/node/2779457 + */ + public function testFindPage() { + $this->assertInternalType('int', pager_find_page()); + } + + /** + * @expectedDeprecation pager_default_initialize is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->createPager() instead. See https://www.drupal.org/node/2779457 + */ + public function testDefaultInitialize() { + $this->assertInternalType('int', pager_default_initialize(1, 1)); + } + + /** + * @expectedDeprecation pager_get_query_parameters is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead. See https://www.drupal.org/node/2779457 + */ + public function testGetQueryParameters() { + $this->assertInternalType('array', pager_get_query_parameters()); + } + + /** + * @expectedDeprecation pager_query_add_page is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->getUpdatedParameters() instead. See https://www.drupal.org/node/2779457 + */ + public function testQueryAddPage() { + $this->assertArrayHasKey('page', pager_query_add_page([], 1, 1)); + } + +} diff --git a/core/modules/system/tests/src/Kernel/PathHooksTest.php b/core/modules/system/tests/src/Kernel/PathHooksTest.php deleted file mode 100644 index 892c7d1ab..000000000 --- a/core/modules/system/tests/src/Kernel/PathHooksTest.php +++ /dev/null @@ -1,55 +0,0 @@ -randomMachineName(); - $alias = '/' . $this->randomMachineName(); - - // Check system_path_insert(); - $alias_manager = $this->prophesize(AliasManagerInterface::class); - $alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1); - $alias_manager->cacheClear($source)->shouldBeCalledTimes(1); - \Drupal::getContainer()->set('path.alias_manager', $alias_manager->reveal()); - $alias_storage = \Drupal::service('path.alias_storage'); - $alias_storage->save($source, $alias); - - $new_source = '/' . $this->randomMachineName(); - $path = $alias_storage->load(['source' => $source]); - - // Check system_path_update(); - $alias_manager = $this->prophesize(AliasManagerInterface::class); - $alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(2); - $alias_manager->cacheClear($source)->shouldBeCalledTimes(1); - $alias_manager->cacheClear($new_source)->shouldBeCalledTimes(1); - \Drupal::getContainer()->set('path.alias_manager', $alias_manager->reveal()); - $alias_storage->save($new_source, $alias, LanguageInterface::LANGCODE_NOT_SPECIFIED, $path['pid']); - - // Check system_path_delete(); - $alias_manager = $this->prophesize(AliasManagerInterface::class); - $alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1); - $alias_manager->cacheClear($new_source)->shouldBeCalledTimes(1); - \Drupal::getContainer()->set('path.alias_manager', $alias_manager->reveal()); - $alias_storage->delete(['pid' => $path['pid']]); - - } - -} diff --git a/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php index 3bee38d1a..0b31ec6db 100644 --- a/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php +++ b/core/modules/system/tests/src/Kernel/Scripts/DbCommandBaseTest.php @@ -50,7 +50,7 @@ public function testSpecifyDatabaseDoesNotExist() { $command_tester->execute([ '--database' => 'dne', ]); - $this->setExpectedException(ConnectionNotDefinedException::class); + $this->expectException(ConnectionNotDefinedException::class); $command->getDatabaseConnection($command_tester->getInput()); } @@ -129,6 +129,7 @@ public function getDatabaseConnection(InputInterface $input) { */ protected function execute(InputInterface $input, OutputInterface $output) { // Empty implementation for testing. + return 0; } } diff --git a/core/modules/system/tests/src/Kernel/System/CronQueueTest.php b/core/modules/system/tests/src/Kernel/System/CronQueueTest.php index 649a2c752..81f037589 100644 --- a/core/modules/system/tests/src/Kernel/System/CronQueueTest.php +++ b/core/modules/system/tests/src/Kernel/System/CronQueueTest.php @@ -105,8 +105,8 @@ public function testExceptions() { $queue->createItem([]); $this->cron->run(); - $this->assertEqual(\Drupal::state()->get('cron_queue_test_requeue_exception'), 2); - $this->assertFalse($queue->numberOfItems()); + $this->assertEquals(2, \Drupal::state()->get('cron_queue_test_requeue_exception')); + $this->assertEquals(0, $queue->numberOfItems()); } } diff --git a/core/modules/system/tests/src/Kernel/System/InfoAlterTest.php b/core/modules/system/tests/src/Kernel/System/InfoAlterTest.php index ebdac3ce0..ee5fbf6ad 100644 --- a/core/modules/system/tests/src/Kernel/System/InfoAlterTest.php +++ b/core/modules/system/tests/src/Kernel/System/InfoAlterTest.php @@ -22,14 +22,14 @@ class InfoAlterTest extends KernelTestBase { */ public function testSystemInfoAlter() { \Drupal::state()->set('module_required_test.hook_system_info_alter', TRUE); - $info = system_rebuild_module_data(); + $info = \Drupal::service('extension.list.module')->getList(); $this->assertFalse(isset($info['node']->info['required']), 'Before the module_required_test is installed the node module is not required.'); // Enable the test module. \Drupal::service('module_installer')->install(['module_required_test'], FALSE); $this->assertTrue(\Drupal::moduleHandler()->moduleExists('module_required_test'), 'Test required module is enabled.'); - $info = system_rebuild_module_data(); + $info = \Drupal::service('extension.list.module')->getList(); $this->assertTrue($info['node']->info['required'], 'After the module_required_test is installed the node module is required.'); } diff --git a/core/modules/system/tests/src/Kernel/System/SystemGetInfoTest.php b/core/modules/system/tests/src/Kernel/System/SystemGetInfoTest.php index cd42fa487..fe4e1fe49 100644 --- a/core/modules/system/tests/src/Kernel/System/SystemGetInfoTest.php +++ b/core/modules/system/tests/src/Kernel/System/SystemGetInfoTest.php @@ -8,6 +8,7 @@ * Tests system_get_info(). * * @group system + * @group legacy */ class SystemGetInfoTest extends KernelTestBase { @@ -15,11 +16,14 @@ class SystemGetInfoTest extends KernelTestBase { /** * Tests system_get_info(). + * + * @expectedDeprecation system_get_info() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal::service('extension.list.module')->getExtensionInfo() or \Drupal::service('extension.list.module')->getAllInstalledInfo() instead. See https://www.drupal.org/node/2709919 + * @expectedDeprecation system_get_info() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal::service('extension.list.theme')->getExtensionInfo() or \Drupal::service('extension.list.theme')->getAllInstalledInfo() instead. See https://www.drupal.org/node/2709919 */ public function testSystemGetInfo() { $system_module_info = system_get_info('module', 'system'); $this->assertSame('System', $system_module_info['name']); - $this->assertSame(['system' => $system_module_info], system_get_info('module')); + $this->assertSame(['path_alias' => system_get_info('module', 'path_alias'), 'system' => $system_module_info], system_get_info('module')); // The User module is not installed so system_get_info() should return // an empty array. @@ -30,7 +34,7 @@ public function testSystemGetInfo() { $this->container->get('module_installer')->install(['user']); $user_module_info = system_get_info('module', 'user'); $this->assertSame('User', $user_module_info['name']); - $this->assertSame(['system' => $system_module_info, 'user' => $user_module_info], system_get_info('module')); + $this->assertSame(['path_alias' => system_get_info('module', 'path_alias'), 'system' => $system_module_info, 'user' => $user_module_info], system_get_info('module')); // Test theme info. There are no themes installed yet. $this->assertSame([], system_get_info('theme', 'stable')); diff --git a/core/modules/system/tests/src/Kernel/SystemFunctionsLegacyTest.php b/core/modules/system/tests/src/Kernel/SystemFunctionsLegacyTest.php new file mode 100644 index 000000000..c047dab26 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/SystemFunctionsLegacyTest.php @@ -0,0 +1,30 @@ +getList(). See https://www.drupal.org/node/2709919 + * @see system_rebuild_module_data() + */ + public function testSystemRebuildModuleDataDeprecation() { + $list = system_rebuild_module_data(); + $this->assertInstanceOf(Extension::class, $list['system']); + } + +} diff --git a/core/modules/system/tests/src/Kernel/Theme/FunctionsTest.php b/core/modules/system/tests/src/Kernel/Theme/FunctionsTest.php index 9b93dff3f..0e33530e7 100644 --- a/core/modules/system/tests/src/Kernel/Theme/FunctionsTest.php +++ b/core/modules/system/tests/src/Kernel/Theme/FunctionsTest.php @@ -5,6 +5,7 @@ use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Html; use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Render\Element\Link; use Drupal\Core\Session\UserSession; use Drupal\Core\Url; use Drupal\KernelTests\KernelTestBase; @@ -30,7 +31,7 @@ protected function setUp() { parent::setUp(); // Enable the Classy theme. - $this->container->get('theme_handler')->install(['classy']); + $this->container->get('theme_installer')->install(['classy']); $this->config('system.theme')->set('default', 'classy')->save(); } @@ -417,9 +418,114 @@ public function testIndexedKeyedLinks() { } /** - * Test the use of drupal_pre_render_links() on a nested array of links. + * Test the use of Link::preRenderLinks() on a nested array of links. + * + * @see \Drupal\Core\Render\Element\Link::preRenderLinks() */ public function testDrupalPreRenderLinks() { + // Define the base array to be rendered, containing a variety of different + // kinds of links. + $base_array = [ + '#theme' => 'links', + '#pre_render' => [[Link::class, 'preRenderLinks']], + '#links' => [ + 'parent_link' => [ + 'title' => 'Parent link original', + 'url' => Url::fromRoute('router_test.1'), + ], + ], + 'first_child' => [ + '#theme' => 'links', + '#links' => [ + // This should be rendered if 'first_child' is rendered separately, + // but ignored if the parent is being rendered (since it duplicates + // one of the parent's links). + 'parent_link' => [ + 'title' => 'Parent link copy', + 'url' => Url::fromRoute('router_test.6'), + ], + // This should always be rendered. + 'first_child_link' => [ + 'title' => 'First child link', + 'url' => Url::fromRoute('router_test.7'), + ], + ], + ], + // This should always be rendered as part of the parent. + 'second_child' => [ + '#theme' => 'links', + '#links' => [ + 'second_child_link' => [ + 'title' => 'Second child link', + 'url' => Url::fromRoute('router_test.8'), + ], + ], + ], + // This should never be rendered, since the user does not have access to + // it. + 'third_child' => [ + '#theme' => 'links', + '#links' => [ + 'third_child_link' => [ + 'title' => 'Third child link', + 'url' => Url::fromRoute('router_test.9'), + ], + ], + '#access' => FALSE, + ], + ]; + + // Start with a fresh copy of the base array, and try rendering the entire + // thing. We expect a single
diff --git a/core/themes/claro/templates/datetime-wrapper.html.twig b/core/themes/claro/templates/datetime-wrapper.html.twig new file mode 100644 index 000000000..4215b28ff --- /dev/null +++ b/core/themes/claro/templates/datetime-wrapper.html.twig @@ -0,0 +1,34 @@ +{# +/** + * @file + * Theme override of a datetime form wrapper. + * + * @todo Refactor when https://www.drupal.org/node/3010558 is fixed. + * + * @see template_preprocess_form_element() + */ +#} +{% + set title_classes = [ + 'form-item__label', + required ? 'js-form-required', + required ? 'form-required', + errors ? 'has-error', + ] +%} +
+{% if title %} + {{ title }} +{% endif %} +{{ content }} +{% if errors %} +
+ {{ errors }} +
+{% endif %} +{% if description %} + + {{ description }} +
+{% endif %} +
diff --git a/core/themes/claro/templates/details.html.twig b/core/themes/claro/templates/details.html.twig new file mode 100644 index 000000000..88f72ee13 --- /dev/null +++ b/core/themes/claro/templates/details.html.twig @@ -0,0 +1,91 @@ +{# +/** + * @file + * Theme override for a details element. + * + * Available variables + * - attributes: A list of HTML attributes for the details element. + * - errors: (optional) Any errors for this details element, may not be set. + * - title: (optional) The title of the element, may not be set. + * - description: (optional) The description of the element, may not be set. + * - children: (optional) The children of the element, may not be set. + * - value: (optional) The value of the element, may not be set. + * - accordion: whether the details element should look as an accordion. + * - accordion_item: whether the details element is an item of an accordion + * list. + * - disabled: whether the details is disabled. + * + * @see template_preprocess_details() + * @see claro_preprocess_details() + */ +#} +{# + Prefix 'details' class to avoid collision with Modernizr. + + @todo Remove prefix after https://www.drupal.org/node/2981732 has been solved. +#} +{% + set classes = [ + 'claro-details', + accordion ? 'claro-details--accordion', + accordion_item ? 'claro-details--accordion-item', + ] +%} +{% + set content_wrapper_classes = [ + 'claro-details__wrapper', + 'details-wrapper', + accordion ? 'claro-details__wrapper--accordion', + accordion_item ? 'claro-details__wrapper--accordion-item', + ] +%} +{% + set inner_wrapper_classes = [ + 'claro-details__content', + accordion ? 'claro-details__content--accordion', + accordion_item ? 'claro-details__content--accordion-item', + ] +%} + + {%- if title -%} + {% + set summary_classes = [ + 'claro-details__summary', + required ? 'js-form-required', + required ? 'form-required', + accordion ? 'claro-details__summary--accordion', + accordion_item ? 'claro-details__summary--accordion-item', + ] + %} + + {{- title -}} + {%- if required -%} + + {%- endif -%} + + {%- endif -%} + + {% if accordion or accordion_item %} + + {% endif %} + + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {%- if description -%} +
{{ description }}
+ {%- endif -%} + {%- if children -%} + {{ children }} + {%- endif -%} + {%- if value -%} + {{ value }} + {%- endif -%} + + {% if accordion or accordion_item %} +
+ {% endif %} +
+ diff --git a/core/themes/claro/templates/entity-add-list.html.twig b/core/themes/claro/templates/entity-add-list.html.twig new file mode 100644 index 000000000..98683b0bb --- /dev/null +++ b/core/themes/claro/templates/entity-add-list.html.twig @@ -0,0 +1,49 @@ +{# +/** + * @file + * Theme override to to present a list of available bundles. + * + * Available variables: + * - bundles: A list of bundles, each with the following properties: + * - label: Bundle label. + * - description: Bundle description. + * - add_link: \Drupal\Core\Link link instance to create an entity of this + * bundle. + * - add_bundle_message: The message shown when there are no bundles. Only + * available if the entity type uses bundle entities. + * + * @see template_preprocess_entity_add_list() + */ +#} +{% + set item_classes = [ + 'admin-item', + ] +%} +{% if bundles is not empty %} + + {% for bundle in bundles %} + {# + Add 'admin-item__link' class to the link attributes. + This is needed for keeping the original attributes of the link's url. + #} + {% set bundle_attributes = bundle.add_link.url.getOption('attributes') ?: {} %} + {% set link_attributes = create_attribute(bundle_attributes).addClass('admin-item__link') %} + +
+ + {{ bundle.add_link.text }} + +
+ {# Don't print empty description wrapper if there is no description #} + {% if bundle.description %} +
{{ bundle.description }}
+ {% endif %} +
+ {% endfor %} + +{% elseif add_bundle_message is not empty %} +

+ {{ add_bundle_message }} +

+{% endif %} diff --git a/core/themes/claro/templates/field/file-link.html.twig b/core/themes/claro/templates/field/file-link.html.twig new file mode 100644 index 000000000..891c7a124 --- /dev/null +++ b/core/themes/claro/templates/field/file-link.html.twig @@ -0,0 +1,18 @@ +{# +/** + * @file + * Theme override for a link to a file. + * + * Available variables: + * - attributes: The HTML attributes for the containing element. + * - link: A link to the file. + * - icon: The icon image representing the file type. This seems to be always + * empty. + * - file_size: The size of the file. + * + * @see template_preprocess_file_link() + * @see stable_preprocess_image_widget() + */ +#} +{{ attach_library('classy/file') }} +{{ link }} ({{ file_size }}) diff --git a/core/themes/claro/templates/fieldset.html.twig b/core/themes/claro/templates/fieldset.html.twig new file mode 100644 index 000000000..3609aba98 --- /dev/null +++ b/core/themes/claro/templates/fieldset.html.twig @@ -0,0 +1,81 @@ +{# +/** + * @file + * Theme override for a fieldset element and its children. + * + * @see template_preprocess_fieldset() + * @see claro_preprocess_fieldset() + */ +#} +{% + set classes = [ + 'fieldset', + attributes.hasClass('fieldgroup') ? 'fieldset--group', + 'js-form-item', + 'form-item', + 'js-form-wrapper', + 'form-wrapper', + ] +%} +{% + set wrapper_classes = [ + 'fieldset__wrapper', + attributes.hasClass('fieldgroup') ? 'fieldset__wrapper--group', + ] +%} +{% + set legend_span_classes = [ + 'fieldset__label', + attributes.hasClass('fieldgroup') ? 'fieldset__label--group', + required ? 'js-form-required', + required ? 'form-required', + ] +%} +{% + set legend_classes = [ + 'fieldset__legend', + attributes.hasClass('fieldgroup') and not attributes.hasClass('form-composite') ? 'fieldset__legend--group', + attributes.hasClass('form-composite') ? 'fieldset__legend--composite', + title_display == 'invisible' ? 'fieldset__legend--invisible' : 'fieldset__legend--visible', + ] +%} +{% + set description_classes = [ + 'fieldset__description', + ] +%} + + + {# Always wrap fieldset legends in a for CSS positioning. #} + {% if legend.title %} + + {{ legend.title }} + + {% endif %} + + + {% if inline_items %} +
+ {% endif %} + + {% if prefix %} + {{ prefix }} + {% endif %} + {{ children }} + {% if suffix %} + {{ suffix }} + {% endif %} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {% if description.content %} + {{ description.content }}
+ {% endif %} + + {% if inline_items %} +
+ {% endif %} +
+ diff --git a/core/themes/claro/templates/filter/filter-guidelines.html.twig b/core/themes/claro/templates/filter/filter-guidelines.html.twig new file mode 100644 index 000000000..e7d7ab461 --- /dev/null +++ b/core/themes/claro/templates/filter/filter-guidelines.html.twig @@ -0,0 +1,29 @@ +{# +/** + * @file + * Theme override for guidelines for a text format. + * + * Available variables: + * - format: Contains information about the current text format, including the + * following: + * - name: The name of the text format, potentially unsafe and needs to be + * escaped. + * - format: The machine name of the text format, e.g. 'basic_html'. + * - attributes: HTML attributes for the containing element. + * - tips: Descriptions and a CSS ID in the form of 'module-name/filter-id' + * (only used when 'long' is TRUE) for each filter in one or more text + * formats. + * + * @see template_preprocess_filter_tips() + */ +#} +{% + set classes = [ + 'filter-guidelines__item', + 'filter-guidelines__item--' ~ format.id|clean_class, + ] +%} + +

{{ format.label }}

+ {{ tips }} +
diff --git a/core/themes/claro/templates/filter/filter-tips.html.twig b/core/themes/claro/templates/filter/filter-tips.html.twig new file mode 100644 index 000000000..665b56798 --- /dev/null +++ b/core/themes/claro/templates/filter/filter-tips.html.twig @@ -0,0 +1,66 @@ +{# +/** + * @file + * Theme override for a set of filter tips. + * + * Available variables: + * - tips: Descriptions and a CSS ID in the form of 'module-name/filter-id' + * (only used when 'long' is TRUE) for each filter in one or more text + * formats. + * - long: A flag indicating whether the passed-in filter tips contain extended + * explanations, i.e. intended to be output on the path 'filter/tips' + * (TRUE), or are in a short format, i.e. suitable to be displayed below a + * form element. Defaults to FALSE. + * - multiple: A flag indicating there is more than one filter tip. + * + * @see template_preprocess_filter_tips() + * @see claro_preprocess_filter_tips() + */ +#} +{% if multiple %} +

{{ 'Text Formats'|t }}

+{% endif %} + +{% if tips|length %} + {% if multiple %} +
+ {% endif %} + + {% for name, tip in tips %} + {% if multiple %} + {% + set tip_classes = [ + 'compose-tips__item', + 'compose-tips__item--name-' ~ name|clean_class, + ] + %} + + {% endif %} + {% if multiple or long %} +

{{ tip.name }}

+ {% endif %} + + {% if tip.list|length %} +
    + {% for item in tip.list %} + {% + set item_classes = [ + 'filter-tips__item', + long ? 'filter-tips__item--long' : 'filter-tips__item--short', + long ? 'filter-tips__item--id-' ~ item.id|clean_class, + ] + %} + {{ item.tip }} + {% endfor %} +
+ {% endif %} + + {% if multiple %} +
+ {% endif %} + {% endfor %} + + {% if multiple %} +
+ {% endif %} +{% endif %} diff --git a/core/themes/claro/templates/form-element-label.html.twig b/core/themes/claro/templates/form-element-label.html.twig new file mode 100644 index 000000000..f05010d68 --- /dev/null +++ b/core/themes/claro/templates/form-element-label.html.twig @@ -0,0 +1,12 @@ +{# +/** + * @file + * Template override for a form element label. + */ +#} + +{% extends '@classy/form/form-element-label.html.twig' %} + +{% + set attributes = attributes.addClass('form-item__label') +%} diff --git a/core/themes/claro/templates/form-element.html.twig b/core/themes/claro/templates/form-element.html.twig new file mode 100644 index 000000000..b474619df --- /dev/null +++ b/core/themes/claro/templates/form-element.html.twig @@ -0,0 +1,64 @@ +{# +/** + * @file + * Theme override for a form element. + * + * @todo Remove when https://www.drupal.org/node/3010558 is fixed. + * + * @see template_preprocess_form_element() + */ +#} +{# + Most of core-provided js assumes that the CSS class pattern js-form-item-[something] or + js-form-type-[something] exists on form items. We have to keep them. +#} +{% + set classes = [ + 'js-form-item', + 'form-item', + 'js-form-type-' ~ type|clean_class, + 'form-type--' ~ type|clean_class, + type in ['checkbox', 'radio'] ? 'form-type--boolean', + 'js-form-item-' ~ name|clean_class, + 'form-item--' ~ name|clean_class, + title_display not in ['after', 'before'] ? 'form-item--no-label', + disabled == 'disabled' ? 'form-item--disabled', + errors ? 'form-item--error', + ] +%} +{% + set description_classes = [ + 'form-item__description', + description_display == 'invisible' ? 'visually-hidden', + ] +%} + + {% if label_display in ['before', 'invisible'] %} + {{ label }} + {% endif %} + {% if prefix is not empty %} + {{ prefix }} + {% endif %} + {% if description_display == 'before' and description.content %} + + {{ description.content }} +
+ {% endif %} + {{ children }} + {% if suffix is not empty %} + {{ suffix }} + {% endif %} + {% if label_display == 'after' %} + {{ label }} + {% endif %} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {% if description_display in ['after', 'invisible'] and description.content %} + + {{ description.content }} +
+ {% endif %} +
diff --git a/core/themes/claro/templates/form/field-multiple-value-form.html.twig b/core/themes/claro/templates/form/field-multiple-value-form.html.twig new file mode 100644 index 000000000..c8d8a9b81 --- /dev/null +++ b/core/themes/claro/templates/form/field-multiple-value-form.html.twig @@ -0,0 +1,52 @@ +{# +/** + * @file + * Theme override for multiple value form element. + * + * Available variables for all fields: + * - multiple: Whether there are multiple instances of the field. + * - disabled: Whether the inpur is disabled. + * + * Available variables for single cardinality fields: + * - elements: Form elements to be rendered. + * + * Available variables when there are multiple fields. + * - table: Table of field items. + * - description: The description element containing the following properties: + * - content: The description content of the form element. + * - attributes: HTML attributes to apply to the description container. + * - button: "Add another item" button. + * + * @see template_preprocess_field_multiple_value_form() + * @see claro_preprocess_field_multiple_value_form() + */ +#} +{% if multiple %} + {% + set classes = [ + 'js-form-item', + 'form-item', + 'form-item--multiple', + disabled ? 'form-item--disabled', + ] + %} + {% + set description_classes = [ + 'form-item__description', + disabled ? 'is-disabled', + ] + %} + + {{ table }} + {% if description.content %} + {{ description.content }}
+ {% endif %} + {% if button %} +
{{ button }}
+ {% endif %} +
+{% else %} + {% for element in elements %} + {{ element }} + {% endfor %} +{% endif %} diff --git a/core/themes/claro/templates/form/input.html.twig b/core/themes/claro/templates/form/input.html.twig new file mode 100644 index 000000000..f8a6cab61 --- /dev/null +++ b/core/themes/claro/templates/form/input.html.twig @@ -0,0 +1,24 @@ +{# +/** + * @file + * Theme override for an 'input' #type form element. + * + * Available variables: + * - attributes: A list of HTML attributes for the input element. + * - children: Optional additional rendered elements. + * + * @see template_preprocess_input() + * @see claro_preprocess_input() + */ +#} +{% spaceless %} +{% if autocomplete_message %} +
+ + +
+ {{ children }} +{% else %} + {{ children }} +{% endif %} +{% endspaceless %} diff --git a/core/themes/claro/templates/install-page.html.twig b/core/themes/claro/templates/install-page.html.twig new file mode 100644 index 000000000..c51fe6369 --- /dev/null +++ b/core/themes/claro/templates/install-page.html.twig @@ -0,0 +1,51 @@ +{# +/** + * @file + * Claro theme implementation to display a Drupal installation page. + * + * All available variables are mirrored in page.html.twig. + * Some may be blank but they are provided for consistency. + * + * @see template_preprocess_install_page() + */ +#} +
+ +
+ {% if site_name %} +

+ {{ site_name }} + {% if site_version %} + {{ site_version }} + {% endif %} +

+ {% endif %} +
+ + {% if page.sidebar_first %} + {# /.layout-sidebar-first #} + {% endif %} + +
+ {% if title %} +

{{ title }}

+ {% endif %} + {{ page.highlighted }} + {{ page.content }} +
+ + {% if page.sidebar_second %} + {# /.layout-sidebar-second #} + {% endif %} + + {% if page.page_bottom %} +
+ {{ page.page_bottom }} +
+ {% endif %} + +
{# /.layout-container #} diff --git a/core/themes/claro/templates/maintenance-page.html.twig b/core/themes/claro/templates/maintenance-page.html.twig new file mode 100644 index 000000000..4e569e1e6 --- /dev/null +++ b/core/themes/claro/templates/maintenance-page.html.twig @@ -0,0 +1,40 @@ +{# +/** + * @file + * Claro's theme implementation to display a single Drupal page while offline. + * + * All available variables are mirrored in page.html.twig. + * Some may be blank but they are provided for consistency. + * + * @see template_preprocess_maintenance_page() + */ +#} +
+ +
+ {% if site_name %} +

{{ site_name }}

+ {% endif %} +
+ + {% if page.sidebar_first %} + {# /.layout-sidebar-first #} + {% endif %} + +
+ {% if title %} +

{{ title }}

+ {% endif %} + {{ page.highlighted }} + {{ page.content }} +
+ + {% if page.page_bottom %} +
+ {{ page.page_bottom }} +
+ {% endif %} + +
{# /.layout-container #} diff --git a/core/themes/claro/templates/menu-local-tasks.html.twig b/core/themes/claro/templates/menu-local-tasks.html.twig new file mode 100644 index 000000000..f07015060 --- /dev/null +++ b/core/themes/claro/templates/menu-local-tasks.html.twig @@ -0,0 +1,27 @@ +{# +/** + * @file + * Claro theme implementation to display primary and secondary local tasks. + * + * Available variables: + * - primary: HTML list items representing primary tasks. + * - secondary: HTML list items representing primary tasks. + * + * Each item in these variables (primary and secondary) can be individually + * themed in menu-local-task.html.twig. + * + * @ingroup themeable + */ +#} +{% if primary %} +

{{ 'Primary tabs'|t }}

+ +{% endif %} +{% if secondary %} +

{{ 'Secondary tabs'|t }}

+ +{% endif %} diff --git a/core/themes/claro/templates/misc/status-messages.html.twig b/core/themes/claro/templates/misc/status-messages.html.twig new file mode 100644 index 000000000..91207a54c --- /dev/null +++ b/core/themes/claro/templates/misc/status-messages.html.twig @@ -0,0 +1,72 @@ +{# +/** + * @file + * Theme override for status messages. + * + * Displays status, error, and warning messages, grouped by type. + * + * An invisible heading identifies the messages for assistive technology. + * Sighted users see a colored box. See http://www.w3.org/TR/WCAG-TECHS/H69.html + * for info. + * + * Add an ARIA label to the contentinfo area so that assistive technology + * user agents will better describe this landmark. + * + * Available variables: + * - message_list: List of messages to be displayed, grouped by type. + * - status_headings: List of all status types. + * - attributes: HTML attributes for the element, including: + * - class: HTML classes. + * - title_ids: A list of unique ids keyed by message type. + * + * @see claro_preprocess_status_messages(). + */ +#} +
+{% for type, messages in message_list %} + {% + set classes = [ + 'messages-list__item', + 'messages', + 'messages--' ~ type, + ] + %} + {% + set is_message_with_title = status_headings[type] + %} + {% + set is_message_with_icon = type in ['error', 'status', 'warning'] + %} + +
+ {% if type == 'error' %} +
+ {% endif %} + {% if is_message_with_title or is_message_with_icon %} +
+ {% if is_message_with_title %} +

+ {{ status_headings[type] }} +

+ {% endif %} +
+ {% endif %} +
+ {% if messages|length > 1 %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% else %} + {{ messages|first }} + {% endif %} +
+ {% if type == 'error' %} +
+ {% endif %} +
+ {# Remove type specific classes. #} + {% set attributes = attributes.removeClass(classes) %} +{% endfor %} +
diff --git a/core/themes/claro/templates/navigation/details--vertical-tabs.html.twig b/core/themes/claro/templates/navigation/details--vertical-tabs.html.twig new file mode 100644 index 000000000..ba0d82325 --- /dev/null +++ b/core/themes/claro/templates/navigation/details--vertical-tabs.html.twig @@ -0,0 +1,79 @@ +{# +/** + * @file + * Theme override for a details element. + * + * This variation is used for theming the details of a Vertical Tabs element. + * + * Available variables + * - attributes: A list of HTML attributes for the details element. + * - errors: (optional) Any errors for this details element, may not be set. + * - title: (optional) The title of the element, may not be set. + * - description: (optional) The description of the element, may not be set. + * - children: (optional) The children of the element, may not be set. + * - value: (optional) The value of the element, may not be set. + * - accordion: whether the details element should look as an accordion. + * - accordion_item: whether the details element is an item of an accordion + * list. + * + * @see template_preprocess_details() + * @see claro_preprocess_details() + */ +#} +{# + Prefix 'details' class to avoid collision with Modernizr. + + @todo Remove prefix after https://www.drupal.org/node/2981732 has been solved. +#} +{% + set classes = [ + 'claro-details', + 'claro-details--vertical-tabs-item', + ] +%} +{% + set content_wrapper_classes = [ + 'claro-details__wrapper', + 'details-wrapper', + 'claro-details__wrapper--vertical-tabs-item', + ] +%} +{% + set inner_wrapper_classes = [ + 'claro-details__content', + 'claro-details__content--vertical-tabs-item', + ] +%} + + {%- if title -%} + {% + set summary_classes = [ + 'claro-details__summary', + 'claro-details__summary--vertical-tabs-item', + required ? 'js-form-required', + required ? 'form-required', + ] + %} + + {{- title -}} + + {%- endif -%} + + + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {%- if description -%} +
{{ description }}
+ {%- endif -%} + {%- if children -%} + {{ children }} + {%- endif -%} + {%- if value -%} + {{ value }} + {%- endif -%} +
+
+ diff --git a/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig b/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig new file mode 100644 index 000000000..be73cf7bb --- /dev/null +++ b/core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig @@ -0,0 +1,19 @@ +{# +/** + * @file + * Theme override for a local task link. + * + * Available variables: + * - attributes: HTML attributes for the wrapper element. + * - is_active: Whether the task item is an active tab. + * - link: A rendered link element. + * + * Note: This template renders the content for each task item in + * menu-local-tasks.html.twig. + * + * @see template_preprocess_menu_local_task() + * + * @todo remove this after https://www.drupal.org/node/3051605 has been solved. + */ +#} +{{ link }} diff --git a/core/themes/claro/templates/navigation/menu-local-task.html.twig b/core/themes/claro/templates/navigation/menu-local-task.html.twig new file mode 100644 index 000000000..c2b19cd1f --- /dev/null +++ b/core/themes/claro/templates/navigation/menu-local-task.html.twig @@ -0,0 +1,25 @@ +{# +/** + * @file + * Theme override for a local task link. + * + * Available variables: + * - attributes: HTML attributes for the wrapper element. + * - is_active: Whether the task item is an active tab. + * - link: A rendered link element. + * - level: The menu level where the tab is rendered. + * + * Note: This template renders the content for each task item in + * menu-local-tasks.html.twig. + * + * @see template_preprocess_menu_local_task() + */ +#} + + {{ link }} + {% if is_active and level == 'primary' %} + + {% endif %} + diff --git a/core/themes/claro/templates/node-add-list.html.twig b/core/themes/claro/templates/node-add-list.html.twig new file mode 100644 index 000000000..6024608b4 --- /dev/null +++ b/core/themes/claro/templates/node-add-list.html.twig @@ -0,0 +1,25 @@ +{# +/** + * @file + * Claro's theme implementation to list node types available for adding content. + * + * Available variables: + * - bundles: A list of content types, each with the following properties: + * - label: Content type label. + * - add_link: \Drupal\Core\Link link instance to create an entity of this + * content type. This is a GeneratedLink originally and is switched by + * claro_preprocess_node_add_list(). + * - description: Description of this type of content. + * + * @todo Revisit after https://www.drupal.org/node/3026221 has been solved. + * + * @see template_preprocess_node_add_list() + * @see claro_preprocess_node_add_list() + */ +#} +{% extends '@claro/entity-add-list.html.twig' %} + +{% set create_content = path('node.type_add') %} +{% + set add_bundle_message = 'You have not created any content types yet. Go to the content type creation page to add a new content type.'|t({ '@create-content': create_content }) +%} diff --git a/core/themes/claro/templates/node-edit-form.html.twig b/core/themes/claro/templates/node-edit-form.html.twig new file mode 100644 index 000000000..d0e7d86d2 --- /dev/null +++ b/core/themes/claro/templates/node-edit-form.html.twig @@ -0,0 +1,32 @@ +{# +/** + * @file + * Theme override for a node edit form. + * + * Two column template for the node add/edit form. + * + * This template will be used when a node edit form specifies 'node_edit_form' + * as its #theme callback. Otherwise, by default, node add/edit forms will be + * themed by form.html.twig. + * + * Available variables: + * - form: The node add/edit form. + * + * @see claro_form_node_form_alter() + */ +#} +
+
+ {{ form|without('advanced', 'footer', 'actions') }} +
+
+ {{ form.advanced }} +
+ +
diff --git a/core/themes/claro/templates/off-canvas-page-wrapper.html.twig b/core/themes/claro/templates/off-canvas-page-wrapper.html.twig new file mode 100644 index 000000000..906f01ed7 --- /dev/null +++ b/core/themes/claro/templates/off-canvas-page-wrapper.html.twig @@ -0,0 +1,26 @@ +{# +/** + * @file + * Default theme implementation for a page wrapper. + * + * For consistent wrapping to {{ page }} render in all themes. The + * "data-off-canvas-main-canvas" attribute is required by the off-canvas dialog. + * This is used by the core/drupal.dialog.off_canvas library to select the + * "main canvas" page element as opposed to the "off canvas" which is the dialog + * itself. The "main canvas" element must be resized according to the width of + * the "off canvas" dialog so that no portion of the "main canvas" is obstructed + * by the off-canvas dialog. The off-canvas dialog can vary in width when opened + * and can be resized by the user. The "data-off-canvas-main-canvas" attribute + * cannot be removed without breaking the off-canvas dialog functionality. + * + * Available variables: + * - children: Contains the child elements of the page. + * + * @ingroup themeable + */ +#} +{% if children %} +
+ {{ children }} +
+{% endif %} diff --git a/core/themes/claro/templates/page.html.twig b/core/themes/claro/templates/page.html.twig new file mode 100644 index 000000000..2842da219 --- /dev/null +++ b/core/themes/claro/templates/page.html.twig @@ -0,0 +1,62 @@ +{# +/** + * @file + * Claro's theme implementation to display a single Drupal page. + * + * The doctype, html, head, and body tags are not in this template. Instead + * they can be found in the html.html.twig template normally located in the + * core/modules/system directory. + * + * Available variables: + * + * General utility variables: + * - base_path: The base URL path of the Drupal installation. Will usually be + * "/" unless you have installed Drupal in a sub-directory. + * - is_front: A flag indicating if the current page is the front page. + * - logged_in: A flag indicating if the user is registered and signed in. + * - is_admin: A flag indicating if the user has permission to access + * administration pages. + * + * Site identity: + * - front_page: The URL of the front page. Use this instead of base_path when + * linking to the front page. This includes the language domain or prefix. + * + * Page content (in order of occurrence in the default page.html.twig): + * - node: Fully loaded node, if there is an automatically-loaded node + * associated with the page and the node ID is the second argument in the + * page's path (e.g. node/12345 and node/12345/revisions, but not + * comment/reply/12345). + * + * Regions: + * - page.header: Items for the header region. + * - page.pre_content: Items for the pre-content region. + * - page.breadcrumb: Items for the breadcrumb region. + * - page.highlighted: Items for the highlighted region. + * - page.help: Dynamic help text, mostly for admin pages. + * - page.content: The main content of the current page. + * + * @see template_preprocess_page() + * @see html.html.twig + */ +#} +
+
+ {{ page.breadcrumb }} + {{ page.header }} +
+
+ +
+ {{ page.pre_content }} +
+
+ {{ page.highlighted }} + {% if page.help %} +
+ {{ page.help }} +
+ {% endif %} + {{ page.content }} +
+ +
diff --git a/core/themes/claro/templates/pager.html.twig b/core/themes/claro/templates/pager.html.twig new file mode 100644 index 000000000..e59cdb360 --- /dev/null +++ b/core/themes/claro/templates/pager.html.twig @@ -0,0 +1,127 @@ +{# +/** + * @file + * Theme override to display a pager. + * + * Available variables: + * - heading_id: Pagination heading ID. + * - items: List of pager items. + * The list is keyed by the following elements: + * - first: Item for the first page; not present on the first page of results. + * - previous: Item for the previous page; not present on the first page + * of results. + * - next: Item for the next page; not present on the last page of results. + * - last: Item for the last page; not present on the last page of results. + * - pages: List of pages, keyed by page number. + * Sub-sub elements: + * items.first, items.previous, items.next, items.last, and each item inside + * items.pages contain the following elements: + * - href: URL with appropriate query parameters for the item. + * - attributes: A keyed list of HTML attributes for the item. + * - text: The visible text used for the item link, such as "‹ Previous" + * or "Next ›". + * - current: The page number of the current page. + * - ellipses: If there are more pages than the quantity allows, then an + * ellipsis before or after the listed pages may be present. + * - previous: Present if the currently visible list of pages does not start + * at the first page. + * - next: Present if the visible list of pages ends before the last page. + * + * @see template_preprocess_pager() + * + * @todo review all uses of the replace() filter below in + * https://www.drupal.org/node/3053707 as the behavior it addresses will + * likely change when that issue is completed. + */ +#} +{% if items %} + +{% endif %} diff --git a/core/themes/claro/templates/region--breadcrumb.html.twig b/core/themes/claro/templates/region--breadcrumb.html.twig new file mode 100644 index 000000000..a66f43131 --- /dev/null +++ b/core/themes/claro/templates/region--breadcrumb.html.twig @@ -0,0 +1,23 @@ +{# +/** + * @file + * Theme override to display a breadcrumb region. + * + * Available variables: + * - content: The content for this region, typically blocks. + * - attributes: HTML attributes for the region
. + * - region: The name of the region variable as defined in the theme's + * .info.yml file. + * + * @see template_preprocess_region() + */ +#} +{% + set classes = [ + 'region', + 'region-' ~ region|clean_class, + ] +%} + + {{ content }} +
diff --git a/core/themes/claro/templates/status-report-counter.html.twig b/core/themes/claro/templates/status-report-counter.html.twig new file mode 100644 index 000000000..13ab8a84d --- /dev/null +++ b/core/themes/claro/templates/status-report-counter.html.twig @@ -0,0 +1,26 @@ +{# +/** + * @file + * Theme override for status report counter. + * + * Available variables: + * - amount: The number shown on counter. + * - text: The text shown on counter. + * - severity: The severity of the counter. + * + * @ingroup themable + */ +#} +{% + set classes = [ + 'system-status-counter', + 'system-status-counter--' ~ severity, + ] +%} + + + + {{ amount }} {{ text }} + {{ text }} Details + + diff --git a/core/themes/claro/templates/status-report-general-info.html.twig b/core/themes/claro/templates/status-report-general-info.html.twig new file mode 100644 index 000000000..a5d6ce7ba --- /dev/null +++ b/core/themes/claro/templates/status-report-general-info.html.twig @@ -0,0 +1,99 @@ +{# +/** + * @file + * Theme override for status report general info. + * + * Available variables: + * - drupal: The status of Drupal installation: + * - value: The current status of Drupal installation. + * - description: The description for current status of Drupal installation. + * - cron: The status of cron: + * - value: The current status of cron. + * - description: The description for current status of cron. + * - cron.run_cron: An array to render a button for running cron. + * - database_system: The status of database system: + * - value: The current status of database sytem. + * - description: The description for current status of cron. + * - database_system_version: The info about current database version: + * - value: The current version of database. + * - description: The description for current version of database. + * - php: The current version of PHP: + * - value: The status of currently installed PHP version. + * - description: The description for current installed PHP version. + * - php_memory_limit: The info about current PHP memory limit: + * - value: The status of currently set PHP memory limit. + * - description: The description for currently set PHP memory limit. + * - webserver: The info about currently installed web server: + * - value: The status of currently installed web server. + * - description: The description for the status of currently installed web + * server. + */ +#} +
+

{{ 'General System Information'|t }}

+
+
+ +
+

{{ 'Drupal Version'|t }}

+ {{ drupal.value }} + {% if drupal.description %} +
{{ drupal.description }}
+ {% endif %} +
+
+
+ +
+

{{ 'Last Cron Run'|t }}

+ {{ cron.value }} + {% if cron.run_cron %} +
{{ cron.run_cron }}
+ {% endif %} + {% if cron.description %} +
{{ cron.description }}
+ {% endif %} +
+
+
+ +
+

{{ 'Web Server'|t }}

+ {{ webserver.value }} + {% if webserver.description %} +
{{ webserver.description }}
+ {% endif %} +
+
+
+ +
+

{{ 'PHP'|t }}

+

{{ 'Version'|t }}

{{ php.value }} + {% if php.description %} +
{{ php.description }}
+ {% endif %} + +

{{ 'Memory limit'|t }}

{{ php_memory_limit.value }} + {% if php_memory_limit.description %} +
{{ php_memory_limit.description }}
+ {% endif %} +
+
+
+ +
+

{{ 'Database'|t }}

+

{{ 'Version'|t }}

{{ database_system_version.value }} + {% if database_system_version.description %} +
{{ database_system_version.description }}
+ {% endif %} + +

{{ 'System'|t }}

{{ database_system.value }} + {% if database_system.description %} +
{{ database_system.description }}
+ {% endif %} +
+
+
+
diff --git a/core/themes/claro/templates/status-report-grouped.html.twig b/core/themes/claro/templates/status-report-grouped.html.twig new file mode 100644 index 000000000..64572fefd --- /dev/null +++ b/core/themes/claro/templates/status-report-grouped.html.twig @@ -0,0 +1,55 @@ +{# +/** + * @file + * Theme override to display status report. + * + * - grouped_requirements: Contains grouped requirements. + * Each group contains: + * - title: The title of the group. + * - type: The severity of the group. + * - items: The requirement instances. + * Each requirement item contains: + * - title: The title of the requirement. + * - value: (optional) The requirement's status. + * - description: (optional) The requirement's description. + * - severity_title: The title of the severity. + * - severity_status: Indicates the severity status. + */ +#} +{{ attach_library('core/drupal.collapse') }} +{{ attach_library('claro/drupal.responsive-detail') }} + +
+ {% for group in grouped_requirements %} +
+

{{ group.title }}

+ {% for requirement in group.items %} +
+
+ {% + set summary_classes = [ + 'system-status-report__status-title', + group.type in ['warning', 'error'] ? 'system-status-report__status-icon system-status-report__status-icon--' ~ group.type + ] + %} + + {% if requirement.severity_title %} + {{ requirement.severity_title }} + {% endif %} + {{ requirement.title }} + +
+ {{ requirement.value }} + {% if requirement.description %} +
{{ requirement.description }}
+ {% endif %} +
+
+ + {% if loop.last %} +
+ {% endif %} + {% endfor %} +
+ {% endfor %} +
diff --git a/core/themes/claro/templates/status-report-page.html.twig b/core/themes/claro/templates/status-report-page.html.twig new file mode 100644 index 000000000..27e0d1576 --- /dev/null +++ b/core/themes/claro/templates/status-report-page.html.twig @@ -0,0 +1,28 @@ +{# +/** + * @file + * Theme override for the status report page. + * + * Available variables: + * - counters: The list of counter elements. + * - general_info: A render array to create general info element. + * - requirements: A render array to create requirements table. + * + * @see template_preprocess_status_report() + */ +#} +{% if counters|length == 3 %} + {% set element_width_class = ' system-status-report-counters__item--third-width' %} +{% elseif counters|length == 2 %} + {% set element_width_class = ' system-status-report-counters__item--half-width' %} +{% endif %} +
+ {% for counter in counters %} +
+ {{ counter }} +
+ {% endfor %} +
+ +{{ general_info }} +{{ requirements }} diff --git a/core/themes/claro/templates/system-themes-page.html.twig b/core/themes/claro/templates/system-themes-page.html.twig new file mode 100644 index 000000000..1108c7f2f --- /dev/null +++ b/core/themes/claro/templates/system-themes-page.html.twig @@ -0,0 +1,113 @@ +{# +/** + * @file + * Theme override for the Appearance page. + * + * Available variables: + * - attributes: HTML attributes for the main container. + * - theme_groups: A list of theme groups. Each theme group contains: + * - attributes: HTML attributes specific to this theme group. + * - title: Title for the theme group. + * - state: State of the theme group, e.g. installed or uninstalled. + * - themes: A list of themes within the theme group. Each theme contains: + * - attributes: HTML attributes specific to this theme. + * - screenshot: A screenshot representing the theme. + * - description: Description of the theme. + * - name: Theme name. + * - version: The theme's version number. + * - is_default: Boolean indicating whether the theme is the default theme + * or not. + * - is_admin: Boolean indicating whether the theme is the admin theme or + * not. + * - notes: Identifies what context this theme is being used in, e.g., + * default theme, admin theme. + * - incompatible: Text describing any compatibility issues. + * - operations: A list of operation links, e.g., Settings, Enable, Disable, + * etc. these links should only be displayed if the theme is compatible. + * - title_id: The unique id of the theme label. + * - description_id: The unique id of the theme description. This is + * undefined if the description is casted to an empty string. + * + * @see template_preprocess_system_themes_page() + * @see claro_preprocess_system_themes_page() + */ +#} + + {% for theme_group in theme_groups %} + {% + set theme_group_classes = [ + 'system-themes-list', + 'system-themes-list--' ~ theme_group.state, + 'clearfix', + ] + %} + + {% + set card_alignment = theme_group.state == 'installed' ? 'horizontal' : 'vertical' + %} + + {% if not loop.first %} +
+ {% endif %} + + +

{{ theme_group.title }}

+
+ {% for theme in theme_group.themes %} + {% + set theme_classes = [ + theme.is_default ? 'theme-default', + theme.is_admin ? 'theme-admin', + 'card', + 'card--' ~ card_alignment, + 'card-list__item', + ] + %} + {% + set theme_title_classes = [ + 'card__content-item', + 'heading-f', + ] + %} + {% + set theme_description_classes = [ + 'card__content-item', + ] + %} + + {% if theme.screenshot %} +
+ {{ theme.screenshot }} +
+ {% endif %} +
+
+ + {{- theme.name }} {{ theme.version -}} + {% if theme.notes %} + ({{ theme.notes|safe_join(', ') }}) + {%- endif -%} + + + {% if theme.description and theme.description_id %} + + {{ theme.description }} +
+ {%- endif -%} +
+ + +
+
+ {% endfor %} + + + {% endfor %} + diff --git a/core/themes/claro/templates/text-format-wrapper.html.twig b/core/themes/claro/templates/text-format-wrapper.html.twig new file mode 100644 index 000000000..6a660f13c --- /dev/null +++ b/core/themes/claro/templates/text-format-wrapper.html.twig @@ -0,0 +1,41 @@ +{# +/** + * @file + * Theme override for a text format-enabled form element. + * + * @todo Remove when https://www.drupal.org/node/3016346 and + * https://www.drupal.org/node/3016343 are fixed. + * + * Available variables: + * - children: Text format element children. + * - description: Text format element description. + * - attributes: HTML attributes for the containing element. + * - aria_description: Flag for whether or not an ARIA description has been + * added to the description container. + * - description_attributes: attributes for the description. + * @see https://www.drupal.org/node/3016343 + * - disabled: An indicator for whether the associated form element is disabled, + * added by this theme. + * @see https://www.drupal.org/node/3016346 + * + * @see template_preprocess_text_format_wrapper() + */ +#} +{% + set classes = [ + 'js-form-item', + 'form-item', + ] +%} + + {{ children }} + {% if description %} + {% + set description_classes = [ + aria_description ? 'form-item__description', + disabled ? 'is-disabled', + ] + %} + {{ description }} + {% endif %} + diff --git a/core/themes/claro/templates/views-exposed-form.html.twig b/core/themes/claro/templates/views-exposed-form.html.twig new file mode 100644 index 000000000..edccd0d5b --- /dev/null +++ b/core/themes/claro/templates/views-exposed-form.html.twig @@ -0,0 +1,19 @@ +{# +/** + * @file + * Theme override for a views exposed form. + * + * Available variables: + * - form: A render element representing the form. + * + * @see template_preprocess_views_exposed_form() + */ +#} +{% if q is not empty %} + {# + This ensures that, if clean URLs are off, the 'q' is added first, + as a hidden form element, so that it shows up first in the POST URL. + #} +{{ q }} +{% endif %} +{{ form }} diff --git a/core/themes/claro/templates/views/views-mini-pager.html.twig b/core/themes/claro/templates/views/views-mini-pager.html.twig new file mode 100644 index 000000000..7f429be4c --- /dev/null +++ b/core/themes/claro/templates/views/views-mini-pager.html.twig @@ -0,0 +1,54 @@ +{# +/** + * @file + * Theme override for a views mini-pager. + * + * Available variables: + * - heading_id: Pagination heading ID. + * - items: List of pager items. + * + * @see template_preprocess_views_mini_pager() + */ +#} +{% + set pager_action_classes = [ + 'pager__link', + 'pager__link--mini', + 'pager__link--action-link' + ] +%} +{% if items.previous or items.next %} + + {{ 'Pagination'|t }} + + {% if items.previous %} + {% spaceless %} +
  • + + {{ 'Previous page'|t }} + +
  • + {% endspaceless %} + {% endif %} + + {% if items.current %} +
  • + + {{ 'Current page'|t }} + + {{ items.current }} +
  • + {% endif %} + + {% if items.next %} + {% spaceless %} +
  • + + {{ 'Next page'|t }} + +
  • + {% endspaceless %} + {% endif %} + + +{% endif %} diff --git a/core/themes/classy/classy.info.yml b/core/themes/classy/classy.info.yml index 66f34cc9c..5f46a6eaa 100644 --- a/core/themes/classy/classy.info.yml +++ b/core/themes/classy/classy.info.yml @@ -1,9 +1,10 @@ name: Classy type: theme +base theme: stable description: 'A base theme with sensible default CSS classes added. Learn how to use Classy as a base theme in the Drupal 8 Theming Guide.' package: Core -# version: VERSION -# core: 8.x +version: VERSION +core: 8.x hidden: true libraries: @@ -22,9 +23,12 @@ libraries-extend: - classy/file core/drupal.progress: - classy/progress + media/media_embed_ckeditor_theme: + - classy/media_embed_ckeditor_theme + media_library/view: + - classy/media_library + media_library/widget: + - classy/media_library -# Information added by Drupal.org packaging script on 2019-05-23 -version: '8.7.2' -core: '8.x' -project: 'drupal' -datestamp: 1558597129 +ckeditor_stylesheets: + - css/components/media-embed-error.css diff --git a/core/themes/classy/classy.libraries.yml b/core/themes/classy/classy.libraries.yml index f970f86b1..872b9738e 100644 --- a/core/themes/classy/classy.libraries.yml +++ b/core/themes/classy/classy.libraries.yml @@ -68,6 +68,12 @@ indented: component: css/components/indented.css: {} +media_library: + version: VERSION + css: + layout: + css/layout/media-library.css: {} + messages: version: VERSION css: @@ -97,3 +103,14 @@ user: css: component: css/components/user.css: { weight: -10 } + +media_embed_error: + version: VERSION + css: + component: + css/components/media-embed-error.css: {} + +media_embed_ckeditor_theme: + version: VERSION + js: + js/media_embed_ckeditor.theme.js: {} diff --git a/core/themes/classy/classy.theme b/core/themes/classy/classy.theme new file mode 100644 index 000000000..c9611e44a --- /dev/null +++ b/core/themes/classy/classy.theme @@ -0,0 +1,44 @@ +getFormObject(); + if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) { + $form['#attributes']['class'][] = 'media-library-views-form'; + } +} + +/** + * Implements hook_preprocess_image_widget(). + */ +function classy_preprocess_image_widget(array &$variables) { + $data = &$variables['data']; + if (isset($data['preview']['#access']) && $data['preview']['#access'] === FALSE) { + unset($data['preview']); + } +} diff --git a/core/themes/classy/css/components/media-embed-error.css b/core/themes/classy/css/components/media-embed-error.css new file mode 100644 index 000000000..f3486bcc8 --- /dev/null +++ b/core/themes/classy/css/components/media-embed-error.css @@ -0,0 +1,20 @@ +/** + * @file + * Media Embed filter: default styling for media embed errors. + */ + +/** + * The caption filter's styling overrides ours, so add a more specific selector + * to account for that. + */ +.media-embed-error, +.caption > .media-embed-error { + max-width: 200px; + padding: 100px 20px 20px; + text-align: center; + background-color: #ebebeb; + background-image: url(../../../../modules/media/images/icons/no-thumbnail.png); + background-repeat: no-repeat; + background-position: center top; + background-size: 100px 100px; +} diff --git a/core/themes/classy/css/layout/media-library.css b/core/themes/classy/css/layout/media-library.css new file mode 100644 index 000000000..84dee10da --- /dev/null +++ b/core/themes/classy/css/layout/media-library.css @@ -0,0 +1,28 @@ +/** + * @file + * Contains minimal layout styling for the media library. + */ + +.media-library-wrapper { + display: flex; +} + +.media-library-menu { + flex-basis: 20%; + flex-shrink: 0; +} + +.media-library-content { + flex-grow: 1; +} + +.media-library-views-form { + display: flex; + flex-wrap: wrap; +} + +.media-library-views-form .media-library-item { + justify-content: space-between; + max-width: 23%; + margin: 1%; +} diff --git a/core/themes/classy/js/media_embed_ckeditor.theme.es6.js b/core/themes/classy/js/media_embed_ckeditor.theme.es6.js new file mode 100644 index 000000000..10193f7ba --- /dev/null +++ b/core/themes/classy/js/media_embed_ckeditor.theme.es6.js @@ -0,0 +1,22 @@ +/** + * @file + * Classy theme overrides for the Media Embed CKEditor plugin. + */ + +(Drupal => { + /** + * Themes the error displayed when the media embed preview fails. + * + * @param {string} error + * The error message to display + * + * @return {string} + * A string representing a DOM fragment. + * + * @see media-embed-error.html.twig + */ + Drupal.theme.mediaEmbedPreviewError = () => + `
    ${Drupal.t( + 'An error occurred while trying to preview the media. Please save your work and reload this page.', + )}
    `; +})(Drupal); diff --git a/core/themes/classy/js/media_embed_ckeditor.theme.js b/core/themes/classy/js/media_embed_ckeditor.theme.js new file mode 100644 index 000000000..6614288cb --- /dev/null +++ b/core/themes/classy/js/media_embed_ckeditor.theme.js @@ -0,0 +1,12 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function (Drupal) { + Drupal.theme.mediaEmbedPreviewError = function () { + return '
    ' + Drupal.t('An error occurred while trying to preview the media. Please save your work and reload this page.') + '
    '; + }; +})(Drupal); \ No newline at end of file diff --git a/core/themes/classy/templates/block/block--system-branding-block.html.twig b/core/themes/classy/templates/block/block--system-branding-block.html.twig index 7c3b7dea1..57e9570e5 100644 --- a/core/themes/classy/templates/block/block--system-branding-block.html.twig +++ b/core/themes/classy/templates/block/block--system-branding-block.html.twig @@ -15,7 +15,7 @@ #} {% block content %} {% if site_logo %} - {% endif %} diff --git a/core/themes/classy/templates/content/aggregator-item.html.twig b/core/themes/classy/templates/content/aggregator-item.html.twig index 83ef5d019..16f4428a0 100644 --- a/core/themes/classy/templates/content/aggregator-item.html.twig +++ b/core/themes/classy/templates/content/aggregator-item.html.twig @@ -5,7 +5,7 @@ * * Available variables: * - url: URL to the originating feed item. - * - title: Title of the feed item. + * - title: (optional) Title of the feed item. * - content: All field items. Use {{ content }} to print them all, * or print a subset such as {{ content.field_example }}. Use * {{ content|without('field_example') }} to temporarily suppress the printing @@ -21,9 +21,11 @@ #} {{ title_prefix }} -

    - {{ title }} -

    + {% if title %} +

    + {{ title }} +

    + {% endif %} {{ title_suffix }} {{ content }} diff --git a/core/themes/classy/templates/content/media-embed-error.html.twig b/core/themes/classy/templates/content/media-embed-error.html.twig new file mode 100644 index 000000000..7dbef0038 --- /dev/null +++ b/core/themes/classy/templates/content/media-embed-error.html.twig @@ -0,0 +1,21 @@ +{# +/** + * @file + * Theme override for a missing media error. + * + * Available variables + * - message: The message text. + * - attributes: HTML attributes for the containing element. + * + * When a response from the back end can't be returned, a related error message + * is displayed from JavaScript. + * + * @see Drupal.theme.mediaEmbedPreviewError + * + * @ingroup themeable + */ +#} +{{ attach_library('classy/media_embed_error') }} + + {{ message }} + diff --git a/core/themes/classy/templates/content/taxonomy-term.html.twig b/core/themes/classy/templates/content/taxonomy-term.html.twig index 7e0446e01..6478b507d 100644 --- a/core/themes/classy/templates/content/taxonomy-term.html.twig +++ b/core/themes/classy/templates/content/taxonomy-term.html.twig @@ -5,7 +5,7 @@ * * Available variables: * - url: URL of the current term. - * - name: Name of the current term. + * - name: (optional) Name of the current term. * - content: Items for the content of the term (fields and description). * Use 'content' to print them all, or print a subset such as * 'content.description'. Use the following code to exclude the @@ -31,7 +31,7 @@ %} {{ title_prefix }} - {% if not page %} + {% if name and not page %}

    {{ name }}

    {% endif %} {{ title_suffix }} diff --git a/core/themes/classy/templates/dataset/aggregator-feed.html.twig b/core/themes/classy/templates/dataset/aggregator-feed.html.twig index 9b1229a3b..9eacccb60 100644 --- a/core/themes/classy/templates/dataset/aggregator-feed.html.twig +++ b/core/themes/classy/templates/dataset/aggregator-feed.html.twig @@ -7,7 +7,7 @@ * For example, "example.com/aggregator/sources/1". * * Available variables: - * - title: Title of the feed item. + * - title: (optional) Title of the feed item. * - content: All field items. Use {{ content }} to print them all, * or print a subset such as {{ content.field_example }}. Use * {{ content|without('field_example') }} to temporarily suppress the printing @@ -26,7 +26,7 @@ {{ title_prefix }} - {% if not full %} + {% if title and not full %} {{ title }} {% endif %} {{ title_suffix }} diff --git a/core/themes/classy/templates/field/file-link.html.twig b/core/themes/classy/templates/field/file-link.html.twig index 11170c0c0..78384e653 100644 --- a/core/themes/classy/templates/field/file-link.html.twig +++ b/core/themes/classy/templates/field/file-link.html.twig @@ -7,8 +7,10 @@ * - attributes: The HTML attributes for the containing element. * - link: A link to the file. * - icon: The icon image representing the file type. + * - file_size: The size of the file. * * @see template_preprocess_file_link() + * @see stable_preprocess_image_widget() */ #} {{ attach_library('classy/file') }} diff --git a/core/themes/classy/templates/field/image-formatter.html.twig b/core/themes/classy/templates/field/image-formatter.html.twig index 04a98a355..465118eec 100644 --- a/core/themes/classy/templates/field/image-formatter.html.twig +++ b/core/themes/classy/templates/field/image-formatter.html.twig @@ -13,7 +13,7 @@ */ #} {% if url %} - {{ image }} + {{ link(image, url) }} {% else %} {{ image }} {% endif %} diff --git a/core/themes/classy/templates/form/details.html.twig b/core/themes/classy/templates/form/details.html.twig index 24366c3d9..c554096da 100644 --- a/core/themes/classy/templates/form/details.html.twig +++ b/core/themes/classy/templates/form/details.html.twig @@ -7,6 +7,7 @@ * - attributes: A list of HTML attributes for the details element. * - errors: (optional) Any errors for this details element, may not be set. * - title: (optional) The title of the element, may not be set. + * - summary_attributes: A list of HTML attributes for the summary element. * - description: (optional) The description of the element, may not be set. * - children: (optional) The children of the element, may not be set. * - value: (optional) The value of the element, may not be set. diff --git a/core/themes/classy/templates/media-library/container--media-library-content.html.twig b/core/themes/classy/templates/media-library/container--media-library-content.html.twig new file mode 100644 index 000000000..7c930e2c7 --- /dev/null +++ b/core/themes/classy/templates/media-library/container--media-library-content.html.twig @@ -0,0 +1,28 @@ +{# +/** + * @file + * Theme implementation the content area of the modal media library dialog. + * + * The content area is everything that is not the menu of available media + * types. This includes the form to add new media items, if available, and + * the view of available media to select. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - children: The rendered child elements of the container. + * - has_parent: A flag to indicate that the container has one or more parent + containers. + * + * @see template_preprocess_container() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + has_parent ? 'js-form-wrapper', + has_parent ? 'form-wrapper', + 'media-library-content', + ] +%} +{{ children }} diff --git a/core/themes/classy/templates/media-library/container--media-library-widget-selection.html.twig b/core/themes/classy/templates/media-library/container--media-library-widget-selection.html.twig new file mode 100644 index 000000000..7c0af4430 --- /dev/null +++ b/core/themes/classy/templates/media-library/container--media-library-widget-selection.html.twig @@ -0,0 +1,28 @@ +{# +/** + * @file + * Theme implementation of a wrapper for selected media items. + * + * This is used to wrap around the set of media items that are currently + * selected in the media library widget (not the modal dialog), which may + * be used for entity reference fields that target media. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - children: The rendered child elements of the container. + * - has_parent: A flag to indicate that the container has one or more parent + containers. + * + * @see template_preprocess_container() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + has_parent ? 'js-form-wrapper', + has_parent ? 'form-wrapper', + 'media-library-selection', + ] +%} +{{ children }} diff --git a/core/themes/classy/templates/media-library/links--media-library-menu.html.twig b/core/themes/classy/templates/media-library/links--media-library-menu.html.twig new file mode 100644 index 000000000..dfc80f50f --- /dev/null +++ b/core/themes/classy/templates/media-library/links--media-library-menu.html.twig @@ -0,0 +1,36 @@ +{% extends "links.html.twig" %} +{# +/** + * @file + * Theme implementation of the media type menu in the media library dialog. + * + * Available variables: + * - attributes: Attributes for the UL containing the list of links. + * - links: Links to be output. + * Each link will have the following elements: + * - title: The link text. + * - href: The link URL. If omitted, the 'title' is shown as a plain text + * item in the links list. If 'href' is supplied, the entire link is passed + * to l() as its $options parameter. + * - attributes: (optional) HTML attributes for the anchor, or for the + * tag if no 'href' is supplied. + * - heading: (optional) A heading to precede the links. + * - text: The heading text. + * - level: The heading level (e.g. 'h2', 'h3'). + * - attributes: (optional) A keyed list of attributes for the heading. + * If the heading is a string, it will be used as the text of the heading and + * the level will default to 'h2'. + * + * Headings should be used on navigation menus and any list of links that + * consistently appears on multiple pages. To make the heading invisible use + * the 'visually-hidden' CSS class. Do not use 'display:none', which + * removes it from screen readers and assistive technology. Headings allow + * screen reader and keyboard only users to navigate to or skip the links. + * See http://juicystudio.com/article/screen-readers-display-none.php and + * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. + * + * @see classy_preprocess_links__media_library_menu() + * @see template_preprocess_links() + */ +#} +{% set attributes = attributes.addClass('media-library-menu') %} diff --git a/core/themes/classy/templates/media-library/media--media-library.html.twig b/core/themes/classy/templates/media-library/media--media-library.html.twig new file mode 100644 index 000000000..e88635424 --- /dev/null +++ b/core/themes/classy/templates/media-library/media--media-library.html.twig @@ -0,0 +1,55 @@ +{# +/** + * @file + * Theme override of a media item in the media library. + * + * This is used for media that the user can select from the grid of media + * items. It is not used for items that have already been selected in the + * corresponding field widget, or for items that have been previously selected + * before adding new media to the library. + * + * Available variables: + * - media: The entity with limited access to object properties and methods. + * Only method names starting with "get", "has", or "is" and a few common + * methods such as "id", "label", and "bundle" are available. For example: + * - entity.getEntityTypeId() will return the entity type ID. + * - entity.hasField('field_example') returns TRUE if the entity includes + * field_example. (This does not indicate the presence of a value in this + * field.) + * Calling other methods, such as entity.delete(), will result in an exception. + * See \Drupal\Core\Entity\EntityInterface for a full list of methods. + * - name: Name of the media. + * - content: Media content. + * - title_prefix: Additional output populated by modules, intended to be + * displayed in front of the main title tag that appears in the template. + * - title_suffix: Additional output populated by modules, intended to be + * displayed after the main title tag that appears in the template. + * - view_mode: View mode; for example, "teaser" or "full". + * - attributes: HTML attributes for the containing element. + * - title_attributes: Same as attributes, except applied to the main title + * tag that appears in the template. + * - url: Direct URL of the media. + * - preview_attributes: HTML attributes for the preview wrapper. + * - metadata_attributes: HTML attributes for the expandable metadata area. + * - status: Whether or not the Media is published. + * + * @see template_preprocess_media() + * + * @ingroup themeable + */ +#} + + {% if content %} + + {{ content|without('name') }} + + {% if not status %} +
    {{ "unpublished" | t }}
    + {% endif %} + +
    + {{ name }} +
    + + {% endif %} + diff --git a/core/themes/classy/templates/media-library/media-library-item--small.html.twig b/core/themes/classy/templates/media-library/media-library-item--small.html.twig new file mode 100644 index 000000000..ba03858b7 --- /dev/null +++ b/core/themes/classy/templates/media-library/media-library-item--small.html.twig @@ -0,0 +1,31 @@ +{# +/** + * @file + * Default theme implementation of a media library item. + * + * This is used when displaying selected media items, either in the field + * widget or in the "Additional selected media" area when adding new + * media items in the media library modal dialog. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - content: The content of the media library item, plus any additional + * fields or elements surrounding it. + * + * @see seven_preprocess_media_library_item__small() + * @see seven_preprocess_media_library_item__widget() + * @see template_preprocess_media_library_item() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + 'media-library-item', + 'media-library-item--grid', + 'media-library-item--small', + ] +%} + + {{ content }} + diff --git a/core/themes/classy/templates/media-library/media-library-item.html.twig b/core/themes/classy/templates/media-library/media-library-item.html.twig new file mode 100644 index 000000000..297780e0f --- /dev/null +++ b/core/themes/classy/templates/media-library/media-library-item.html.twig @@ -0,0 +1,28 @@ +{# +/** + * @file + * Default theme implementation of a media library item. + * + * This is used when displaying selected media items, either in the field + * widget or in the "Additional selected media" area when adding new + * media items in the media library modal dialog. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - content: The content of the media library item, plus any additional + * fields or elements surrounding it. + * + * @see template_preprocess_media_library_item() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + 'media-library-item', + 'media-library-item--grid', + ] +%} + + {{ content }} + diff --git a/core/themes/classy/templates/media-library/media-library-wrapper.html.twig b/core/themes/classy/templates/media-library/media-library-wrapper.html.twig new file mode 100644 index 000000000..850f63aa1 --- /dev/null +++ b/core/themes/classy/templates/media-library/media-library-wrapper.html.twig @@ -0,0 +1,21 @@ +{# +/** + * @file + * Theme override of a container used to wrap the media library's modal dialog + * interface. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - menu: The menu of availble media types to choose from. + * - content: The form to add new media items, followed by the grid or table of + * existing media items to choose from. + * + * @see template_preprocess_media_library_wrapper() + * + * @ingroup themeable + */ +#} + + {{ menu }} + {{ content }} + diff --git a/core/themes/classy/templates/media-library/views-view-unformatted--media-library.html.twig b/core/themes/classy/templates/media-library/views-view-unformatted--media-library.html.twig new file mode 100644 index 000000000..a94d4e2b6 --- /dev/null +++ b/core/themes/classy/templates/media-library/views-view-unformatted--media-library.html.twig @@ -0,0 +1,35 @@ +{# +/** + * @file + * Theme override of the media library view. + * + * This is used to display a grid of media items, in both the administrative + * interface and in the modal media library dialog's grid layout. + * + * Available variables: + * - title: The title of this group of rows. May be empty. + * - rows: A list of the view's row items. + * - attributes: The row's HTML attributes. + * - content: The row's content. + * - view: The view object. + * - default_row_class: A flag indicating whether default classes should be + * used on rows. + * + * @see template_preprocess_views_view_unformatted() + */ +#} +{% if title %} +

    {{ title }}

    +{% endif %} +{% for row in rows %} + {% + set row_classes = [ + default_row_class ? 'views-row', + 'media-library-item', + 'media-library-item--grid', + ] + %} + + {{- row.content -}} + +{% endfor %} diff --git a/core/themes/classy/templates/views/views-mini-pager.html.twig b/core/themes/classy/templates/views/views-mini-pager.html.twig index 124700b47..4b46f2bb1 100644 --- a/core/themes/classy/templates/views/views-mini-pager.html.twig +++ b/core/themes/classy/templates/views/views-mini-pager.html.twig @@ -4,14 +4,15 @@ * Theme override for a views mini-pager. * * Available variables: + * - heading_id: Pagination heading ID. * - items: List of pager items. * * @see template_preprocess_views_mini_pager() */ #} {% if items.previous or items.next %} -