From 38ba7c357d01619665cd2697f5fabc3a7836071a Mon Sep 17 00:00:00 2001 From: Pantheon Automation Date: Thu, 7 Jul 2016 09:44:38 -0700 Subject: [PATCH] Update to Drupal 8.1.5. For more information, see https://www.drupal.org/project/drupal/releases/8.1.5 --- composer.lock | 12 +- core/INSTALL.txt | 2 +- core/MAINTAINERS.txt | 8 +- core/composer.json | 2 + core/includes/entity.inc | 79 +- core/includes/file.inc | 139 +++- core/includes/install.core.inc | 2 +- core/includes/install.inc | 10 +- core/includes/update.inc | 2 +- core/lib/Drupal.php | 2 +- .../Drupal/Component/Annotation/Plugin.php | 2 +- .../Component/Datetime/DateTimePlus.php | 2 +- .../DependencyInjection/Container.php | 2 +- .../Dumper/OptimizedPhpArrayDumper.php | 4 + .../DependencyInjection/composer.json | 4 +- .../Component/EventDispatcher/composer.json | 4 +- .../Component/HttpFoundation/composer.json | 2 +- .../Plugin/Discovery/DiscoveryTrait.php | 4 +- .../lib/Drupal/Component/Plugin/composer.json | 2 +- .../Render/OutputStrategyInterface.php | 2 +- .../Component/Serialization/composer.json | 2 +- core/lib/Drupal/Core/Cache/Cache.php | 2 +- .../Cache/CacheableDependencyInterface.php | 2 +- .../Drupal/Core/Cache/ChainedFastBackend.php | 8 + .../Cache/Context/QueryArgsCacheContext.php | 18 +- core/lib/Drupal/Core/Config/ConfigManager.php | 9 +- .../Config/Entity/ConfigDependencyManager.php | 63 +- .../Drupal/Core/Config/StorageInterface.php | 12 +- core/lib/Drupal/Core/Database/Connection.php | 11 +- .../Database/Driver/pgsql/Install/Tasks.php | 4 + .../Database/Driver/sqlite/Install/Tasks.php | 2 +- .../Core/Database/Driver/sqlite/Schema.php | 8 +- .../Database/Query/ExtendableInterface.php | 19 +- .../lib/Drupal/Core/Database/Query/Select.php | 21 +- .../Drupal/Core/Datetime/Element/Datelist.php | 1 + .../DependencyInjection/YamlFileLoader.php | 2 - core/lib/Drupal/Core/Entity/Entity.php | 2 +- .../Drupal/Core/Entity/EntityInterface.php | 9 +- .../Entity/EntityTypeBundleInfoInterface.php | 10 +- .../Core/Entity/Query/QueryInterface.php | 37 +- .../Drupal/Core/Entity/Query/Sql/Tables.php | 61 +- .../Core/Entity/Query/Sql/TablesInterface.php | 6 +- .../Core/Entity/Sql/DefaultTableMapping.php | 7 +- .../Entity/Sql/SqlContentEntityStorage.php | 2 +- .../Sql/SqlContentEntityStorageSchema.php | 18 + .../Core/Entity/Sql/TableMappingInterface.php | 5 + .../MaintenanceModeSubscriber.php | 31 +- .../Core/Extension/ModuleHandlerInterface.php | 2 +- core/lib/Drupal/Core/Extension/module.api.php | 4 +- .../Plugin/Field/FieldType/PasswordItem.php | 9 +- core/lib/Drupal/Core/Form/FormBase.php | 22 + .../Drupal/Core/Form/FormBuilderInterface.php | 6 +- .../Core/Plugin/ContextAwarePluginBase.php | 6 +- .../Drupal/Core/Render/Element/Checkboxes.php | 2 +- .../Core/Render/Element/StatusMessages.php | 2 +- .../Core/Render/RenderCacheInterface.php | 2 +- .../Core/Routing/RouteBuilderInterface.php | 21 +- core/lib/Drupal/Core/Routing/UrlGenerator.php | 8 +- .../Core/Routing/UrlGeneratorInterface.php | 2 +- .../PhpStreamWrapperInterface.php | 2 +- .../Translator/FileTranslation.php | 2 +- core/lib/Drupal/Core/Url.php | 6 +- .../lib/Drupal/Core/Utility/LinkGenerator.php | 1 + core/misc/autocomplete.js | 2 +- .../src/Functional}/ActionUninstallTest.php | 6 +- .../src/Functional}/BulkFormTest.php | 10 +- .../src/Functional}/ConfigurationTest.php | 6 +- .../Plugin/migrate/source/AggregatorItem.php | 2 +- .../src/Functional}/IpAddressBlockingTest.php | 8 +- core/modules/big_pipe/src/Render/BigPipe.php | 29 +- .../big_pipe/src/Tests/BigPipeTest.php | 36 + .../big_pipe_test/big_pipe_test.routing.yml | 9 + .../src/BigPipeTestController.php | 51 ++ .../BigPipeRegressionTest.php | 117 +++ .../big_pipe_test_theme.info.yml | 5 + .../templates/field--comment.html.twig | 13 + .../block/migration_templates/d7_block.yml | 3 + core/modules/block/src/Entity/Block.php | 1 - .../block/src/Plugin/migrate/source/Block.php | 17 +- .../Kernel/Migrate/d7/MigrateBlockTest.php | 2 +- .../Unit/Plugin/migrate/source/BlockTest.php | 56 +- .../optional/views.view.block_content.yml | 2 +- .../migration_templates/d6_custom_block.yml | 4 +- .../migration_templates/d7_custom_block.yml | 4 +- .../src/Plugin/migrate/source/d6/Box.php | 2 +- .../src/Tests/BlockContentListViewsTest.php | 4 + .../modules/book/src/BookManagerInterface.php | 4 +- .../src/Plugin/migrate/source/d6/Book.php | 2 +- .../Plugin/migrate/source/d7/CommentType.php | 2 +- .../d6_i18n_system_maintenance.yml | 14 + .../d6_i18n_system_site.yml | 38 + .../migration_templates/d6_i18n_user_mail.yml | 68 ++ .../d6_i18n_user_settings.yml | 29 + .../d6/MigrateI18nSystemMaintenanceTest.php | 32 + .../Migrate/d6/MigrateI18nSystemSiteTest.php | 47 ++ .../Migrate/d6/MigrateI18nUserConfigsTest.php | 74 ++ .../contact/src/ContactFormInterface.php | 2 +- .../Plugin/migrate/source/ContactCategory.php | 2 +- .../Plugin/migrate/source/ContactSettings.php | 2 +- .../ContentTranslationController.php | 4 +- .../datetime/src/Tests/DateTimeFieldTest.php | 6 + .../DynamicPageCacheSubscriber.php | 6 +- .../field/migration_templates/d7_field.yml | 6 +- .../d7_field_formatter_settings.yml | 2 + .../migration_templates/d7_field_instance.yml | 2 + .../d7_field_instance_widget_settings.yml | 2 + .../migrate/process/{d6 => }/FieldType.php | 25 +- .../source/d6/FieldInstancePerFormDisplay.php | 2 +- .../source/d6/FieldInstancePerViewMode.php | 2 +- .../migrate/source/d7/FieldInstance.php | 4 +- .../modules/field/src/Tests/FieldTestBase.php | 2 +- .../field/src/Tests/FieldUnitTestBase.php | 4 +- .../field/src/Tests/Views/FieldUITest.php | 10 + .../tests/src/Kernel/FieldKernelTestBase.php | 4 +- .../migrate/source/d7/FieldInstanceTest.php | 6 + .../Plugin/migrate/source/d7/FieldTest.php | 44 +- .../src/Form/FieldStorageConfigEditForm.php | 17 + .../field_ui/src/Tests/ManageFieldsTest.php | 39 + core/modules/file/file.module | 4 +- .../Plugin/migrate/cckfield/d6/FileField.php | 3 +- .../Plugin/migrate/cckfield/d7/FileField.php | 1 + .../Plugin/migrate/cckfield/d7/ImageField.php | 3 +- .../Plugin/migrate/destination/EntityFile.php | 10 +- .../src/Plugin/migrate/source/d6/File.php | 2 +- .../migrate/source/d6/UploadInstance.php | 2 +- .../src/Plugin/migrate/source/d7/File.php | 2 +- .../file/src/Tests/FileListingTest.php | 54 +- .../migration_templates/d7_filter_format.yml | 2 + .../Migrate/d7/MigrateFilterFormatTest.php | 20 +- .../migrate/source/d6/FilterFormatTest.php | 9 + .../migrate/source/d7/FilterFormatTest.php | 3 + ...m_display.taxonomy_term.forums.default.yml | 1 + ...w_display.taxonomy_term.forums.default.yml | 1 + .../src/Functional}/ExperimentalHelpTest.php | 6 +- .../src/Functional}/HelpBlockTest.php | 6 +- .../src/Functional}/HelpTest.php | 10 +- .../src/Functional}/NoHelpTest.php | 6 +- .../Field/FieldFormatter/ImageFormatter.php | 8 +- .../src/Plugin/Field/FieldType/ImageItem.php | 2 +- .../tests/src/Kernel/ImageFormatterTest.php | 102 +++ .../image/tests/src/Kernel/ImageItemTest.php | 4 + .../image/tests/src/Unit/ImageStyleTest.php | 2 +- .../d6_language_content_settings.yml | 44 ++ .../d7_language_content_settings.yml | 44 ++ .../language/src/DefaultLanguageItem.php | 2 +- .../source/d6/LanguageContentSettings.php | 57 ++ .../source/d7/LanguageContentSettings.php | 63 ++ .../d6/MigrateLanguageContentSettingsTest.php | 59 ++ .../d7/MigrateLanguageContentSettingsTest.php | 50 ++ .../src/Plugin/migrate/cckfield/LinkField.php | 6 +- .../src/Plugin/migrate/process/d6/CckLink.php | 28 +- .../Plugin/migrate/process/d6/CckLinkTest.php | 56 ++ .../migration_templates/menu_links.yml | 4 +- core/modules/migrate/migrate.api.php | 81 +- core/modules/migrate/src/Plugin/Migration.php | 6 +- .../src/Plugin/migrate/destination/Entity.php | 20 + .../migrate/source/SourcePluginBase.php | 2 +- .../migrate.migration.node_template.yml | 10 - .../migrate.migration.other_template.yml | 10 - .../migrate.migration.url_template.yml | 10 - .../template_test/template_test.info.yml | 5 - .../tests/src/Kernel/MigrateBundleTest.php | 155 ++++ .../tests/src/Kernel/Plugin/MigrationTest.php | 37 + .../tests/src/Unit/MigrateSourceTest.php | 19 + .../tests/src/Unit/MigrateTestCase.php | 6 +- .../migrate_drupal.services.yml | 2 +- .../src/Annotation/MigrateCckField.php | 25 +- .../src/MigrationConfigurationTrait.php | 170 ++++ .../src/MigrationCreationTrait.php | 166 +--- .../Plugin/MigrateCckFieldPluginManager.php | 55 ++ .../migrate/cckfield/CckFieldPluginBase.php | 2 +- .../migrate_drupal/tests/fixtures/drupal6.php | 30 +- .../migrate_drupal/tests/fixtures/drupal7.php | 6 +- ...rate_cckfield_plugin_manager_test.info.yml | 6 + .../Plugin/migrate/cckfield/D6FileField.php | 29 + .../cckfield/D6NoCoreVersionSpecified.php | 25 + .../MigrateCckFieldPluginManagerTest.php | 58 ++ .../src/Kernel/d6/MigrateDrupal6TestBase.php | 9 +- .../src/Kernel/d7/MigrateDrupal7TestBase.php | 9 +- .../src/Form/MigrateUpgradeForm.php | 4 +- .../src/Tests/MigrateUpgradeTestBase.php | 4 +- .../src/Tests/d6/MigrateUpgrade6Test.php | 13 + .../src/Tests/d7/MigrateUpgrade7Test.php | 13 + .../node/migration_templates/d6_node.yml | 1 - .../migration_templates/d6_node_revision.yml | 1 - .../node/migration_templates/d7_node.yml | 1 - .../migration_templates/d7_node_revision.yml | 1 - core/modules/node/node.api.php | 4 +- core/modules/node/node.module | 10 +- .../src/Controller/NodePreviewController.php | 20 - .../node/src/Plugin/migrate/D6NodeDeriver.php | 3 +- .../node/src/Plugin/migrate/D7NodeDeriver.php | 3 +- .../src/Plugin/migrate/source/d6/Node.php | 2 +- .../src/Plugin/migrate/source/d7/Node.php | 2 +- .../node/src/Tests/PagePreviewTest.php | 18 + .../src/Kernel/Migrate/d6/MigrateNodeTest.php | 26 +- .../Plugin/migrate/source/d6/NodeTest.php | 5 +- .../responsive_image.post_update.php | 32 + .../ResponsiveImageFormatter.php | 15 + .../Update/ResponsiveImageUpdateTest.php | 77 ++ .../Kernel/ResponsiveImageIntegrationTest.php | 72 ++ .../src/Tests/Views/StyleSerializerTest.php | 16 + .../search/src/SearchPageInterface.php | 2 +- .../src/Plugin/migrate/source/d7/Shortcut.php | 2 +- core/modules/simpletest/simpletest.module | 100 ++- .../simpletest/src/AssertContentTrait.php | 18 + .../simpletest/src/BrowserTestBase.php | 2 +- .../src/Form/SimpletestResultsForm.php | 2 +- .../src/Form/SimpletestTestForm.php | 15 +- core/modules/simpletest/src/TestBase.php | 3 +- .../Tests/MissingDependentModuleUnitTest.php | 4 +- .../src/Tests/SimpleTestBrowserTest.php | 2 +- core/modules/simpletest/src/WebTestBase.php | 2 +- .../src/Functional/BrowserTestBaseTest.php | 3 + .../tests/src/Unit/TestBaseTest.php | 1 + .../tests/src/Unit/WebTestBaseTest.php | 1 + .../system/src/Controller/AdminController.php | 2 +- .../src/Plugin/views/field/BulkForm.php | 2 +- .../src/Tests/Cache/ApcuBackendUnitTest.php | 2 +- .../Tests/Entity/EntityCacheTagsTestBase.php | 2 +- .../src/Tests/System/SiteMaintenanceTest.php | 18 +- .../Tests/Theme/StableLibraryOverrideTest.php | 6 +- core/modules/system/system.install | 2 +- core/modules/system/templates/html.html.twig | 4 + .../Entity/EntityTestMultiValueBasefield.php | 39 + .../migration_templates/d6_taxonomy_term.yml | 15 +- .../migration_templates/d7_taxonomy_term.yml | 18 +- .../cckfield/TaxonomyTermReference.php | 6 +- .../src/Plugin/migrate/source/Term.php | 4 +- core/modules/taxonomy/taxonomy.module | 8 +- .../tests/src/Kernel/ForwardRevisionTest.php | 121 +++ .../Migrate/d6/MigrateTaxonomyTermTest.php | 15 + .../Migrate/d7/MigrateTaxonomyTermTest.php | 53 +- .../src/Plugin/migrate/cckfield/TextField.php | 9 +- core/modules/toolbar/src/Element/Toolbar.php | 2 +- core/modules/tracker/tracker.module | 2 +- core/modules/user/src/MigratePassword.php | 89 --- .../Plugin/migrate/destination/EntityUser.php | 38 +- .../src/Plugin/migrate/source/d6/Role.php | 2 +- .../src/Plugin/migrate/source/d6/User.php | 2 +- .../Plugin/migrate/source/d6/UserPicture.php | 2 +- .../migrate/source/d6/UserPictureFile.php | 2 +- .../src/Plugin/migrate/source/d7/User.php | 2 +- core/modules/user/src/SharedTempStore.php | 2 +- core/modules/user/src/UserDataInterface.php | 4 - core/modules/user/src/UserServiceProvider.php | 25 - .../src/Kernel/Migrate/d7/MigrateUserTest.php | 22 +- core/modules/user/user.services.yml | 3 - .../config/schema/views.display.schema.yml | 6 + .../src/Controller/ViewAjaxController.php | 2 +- core/modules/views/src/EntityViewsData.php | 39 + .../views/src/Plugin/Menu/ViewsMenuLink.php | 7 +- .../views/src/Plugin/ViewsHandlerManager.php | 2 +- .../views/src/Plugin/views/HandlerBase.php | 15 + .../views/src/Plugin/views/PluginBase.php | 5 +- .../views/display/DisplayPluginInterface.php | 2 +- .../views/src/Plugin/views/display/Page.php | 1 + .../Plugin/views/display/PathPluginBase.php | 2 + .../src/Plugin/views/field/EntityLabel.php | 14 +- .../views/src/Plugin/views/field/Field.php | 27 +- .../views/field/FieldHandlerInterface.php | 4 +- .../Plugin/views/filter/BooleanOperator.php | 5 + .../src/Plugin/views/join/JoinPluginBase.php | 23 +- .../views/src/Plugin/views/query/Sql.php | 130 +++- .../src/Tests/Plugin/DisplayPageWebTest.php | 2 +- .../Tests/Plugin/NumericFormatPluralTest.php | 2 +- core/modules/views/src/ViewExecutable.php | 2 +- core/modules/views/src/Views.php | 4 +- .../views.view.base_and_revision.yml | 346 ++++++++ ....view.test_entity_multivalue_basefield.yml | 45 ++ .../test_views/views.view.test_field_body.yml | 188 +++++ .../Plugin/views/Handler/FieldTest.php | 105 +++ ...EntityViewsWithMultivalueBasefieldTest.php | 54 ++ .../Plugin/Display/ViewsMenuLinkTest.php | 98 +++ .../tests/src/Kernel/Plugin/JoinTest.php | 7 +- .../src/Kernel}/Plugin/PluginBaseTest.php | 14 +- .../Kernel/Plugin/SqlEntityLoadingTest.php | 83 ++ .../tests/src/Kernel/ViewsKernelTestBase.php | 2 +- .../tests/src/Unit/EntityViewsDataTest.php | 131 +++- .../tests/src/Unit/Plugin/query/SqlTest.php | 438 ++++++++++- core/modules/views/views.install | 29 + .../views_ui/src/Form/Ajax/ConfigHandler.php | 2 +- .../src/Tests/FilterBooleanWebTest.php | 4 + .../FunctionalJavascript/ViewsWizardTest.php | 61 ++ core/phpcs.xml.dist | 3 - core/phpunit.xml.dist | 40 +- .../src/Functional}/StandardTest.php | 6 +- core/scripts/run-tests.sh | 31 +- .../FunctionalJavascriptTests/JSWebAssert.php | 31 + .../JavascriptTestBase.php | 25 + .../FunctionalTests/AssertLegacyTrait.php | 427 ++++++++++ .../Core/Config/ConfigDependencyTest.php | 115 ++- .../Core/Database/ConnectionTest.php | 2 + .../Core/Database/SelectComplexTest.php | 46 ++ .../Core}/DrupalKernel/DrupalKernelTest.php | 39 +- .../Core/Entity/EntityAutocompleteTest.php | 2 +- .../Core/Entity/EntityQueryTest.php | 83 ++ .../Drupal/KernelTests/KernelTestBase.php | 5 +- core/tests/Drupal/Tests/BrowserTestBase.php | 385 +++++++-- .../DependencyInjection/ContainerTest.php | 11 + .../Dumper/OptimizedPhpArrayDumperTest.php | 54 ++ .../Tests/Component/Utility/ImageTest.php | 9 +- .../Tests/Component/Utility/UnicodeTest.php | 4 +- .../Drupal/Tests/Component/Uuid/UuidTest.php | 2 +- .../Drupal/Tests/ComposerIntegrationTest.php | 1 + .../Core/Cache/ChainedFastBackendTest.php | 15 + .../Context/QueryArgsCacheContextTest.php | 9 +- .../Config/ConfigDependencyManagerTest.php | 120 +++ .../Tests/Core/Config/StorageComparerTest.php | 6 +- .../Tests/Core/Database/ConnectionTest.php | 8 +- .../Tests/Core/Database/Stub/Select.php | 9 + .../Tests/Core/Entity/EntityUrlTest.php | 736 ++++++++++-------- .../Sql/SqlContentEntityStorageSchemaTest.php | 36 +- .../Drupal/Tests/Core/Image/ImageTest.php | 1 + .../Tests/Core/Render/RendererTestBase.php | 2 +- .../Tests/Core/Routing/UrlGeneratorTest.php | 6 +- .../Drupal/Tests/Core/UnroutedUrlTest.php | 3 +- core/tests/Drupal/Tests/Core/UrlTest.php | 12 + .../Tests/Core/Utility/LinkGeneratorTest.php | 31 +- .../Tests/TestSuites/TestSuiteBaseTest.php | 134 ++++ core/tests/Drupal/Tests/WebAssert.php | 358 +++++++++ .../FunctionalJavascriptTestSuite.php | 27 + core/tests/TestSuites/FunctionalTestSuite.php | 27 + core/tests/TestSuites/KernelTestSuite.php | 27 + core/tests/TestSuites/TestSuiteBase.php | 60 ++ core/tests/TestSuites/UnitTestSuite.php | 27 + core/tests/bootstrap.php | 13 +- .../classy/templates/layout/html.html.twig | 4 + .../stable/templates/layout/html.html.twig | 4 + vendor/composer/installed.json | 14 +- vendor/masterminds/html5/.scrutinizer.yml | 41 + vendor/masterminds/html5/.travis.yml | 11 +- vendor/masterminds/html5/README.md | 21 +- vendor/masterminds/html5/RELEASE.md | 19 +- vendor/masterminds/html5/composer.json | 2 +- vendor/masterminds/html5/phpunit.xml.dist | 13 - .../masterminds/html5/src/HTML5/Elements.php | 8 +- .../html5/src/HTML5/Parser/EventHandler.php | 2 +- .../html5/src/HTML5/Parser/Tokenizer.php | 2 +- .../html5/src/HTML5/Parser/UTF8Utils.php | 12 +- .../src/HTML5/Serializer/OutputRules.php | 6 +- .../html5/src/HTML5/Serializer/Traverser.php | 1 - 342 files changed, 7814 insertions(+), 1534 deletions(-) rename core/modules/action/{src/Tests => tests/src/Functional}/ActionUninstallTest.php (87%) rename core/modules/action/{src/Tests => tests/src/Functional}/BulkFormTest.php (95%) rename core/modules/action/{src/Tests => tests/src/Functional}/ConfigurationTest.php (96%) rename core/modules/ban/{src/Tests => tests/src/Functional}/IpAddressBlockingTest.php (94%) create mode 100644 core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php create mode 100644 core/modules/big_pipe/tests/themes/big_pipe_test_theme/big_pipe_test_theme.info.yml create mode 100644 core/modules/big_pipe/tests/themes/big_pipe_test_theme/templates/field--comment.html.twig create mode 100644 core/modules/config_translation/migration_templates/d6_i18n_system_maintenance.yml create mode 100644 core/modules/config_translation/migration_templates/d6_i18n_system_site.yml create mode 100644 core/modules/config_translation/migration_templates/d6_i18n_user_mail.yml create mode 100644 core/modules/config_translation/migration_templates/d6_i18n_user_settings.yml create mode 100644 core/modules/config_translation/tests/src/Kernel/Migrate/d6/MigrateI18nSystemMaintenanceTest.php create mode 100644 core/modules/config_translation/tests/src/Kernel/Migrate/d6/MigrateI18nSystemSiteTest.php create mode 100644 core/modules/config_translation/tests/src/Kernel/Migrate/d6/MigrateI18nUserConfigsTest.php rename core/modules/field/src/Plugin/migrate/process/{d6 => }/FieldType.php (71%) rename core/modules/help/{src/Tests => tests/src/Functional}/ExperimentalHelpTest.php (90%) rename core/modules/help/{src/Tests => tests/src/Functional}/HelpBlockTest.php (90%) rename core/modules/help/{src/Tests => tests/src/Functional}/HelpTest.php (95%) rename core/modules/help/{src/Tests => tests/src/Functional}/NoHelpTest.php (91%) create mode 100644 core/modules/image/tests/src/Kernel/ImageFormatterTest.php create mode 100644 core/modules/language/migration_templates/d6_language_content_settings.yml create mode 100644 core/modules/language/migration_templates/d7_language_content_settings.yml create mode 100644 core/modules/language/src/Plugin/migrate/source/d6/LanguageContentSettings.php create mode 100644 core/modules/language/src/Plugin/migrate/source/d7/LanguageContentSettings.php create mode 100644 core/modules/language/tests/src/Kernel/Migrate/d6/MigrateLanguageContentSettingsTest.php create mode 100644 core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentSettingsTest.php create mode 100644 core/modules/link/tests/src/Unit/Plugin/migrate/process/d6/CckLinkTest.php delete mode 100644 core/modules/migrate/tests/modules/template_test/migration_templates/migrate.migration.node_template.yml delete mode 100644 core/modules/migrate/tests/modules/template_test/migration_templates/migrate.migration.other_template.yml delete mode 100644 core/modules/migrate/tests/modules/template_test/migration_templates/migrate.migration.url_template.yml delete mode 100644 core/modules/migrate/tests/modules/template_test/template_test.info.yml create mode 100644 core/modules/migrate/tests/src/Kernel/MigrateBundleTest.php create mode 100644 core/modules/migrate_drupal/src/MigrationConfigurationTrait.php create mode 100644 core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php create mode 100644 core/modules/migrate_drupal/tests/modules/migrate_cckfield_plugin_manager_test/migrate_cckfield_plugin_manager_test.info.yml create mode 100644 core/modules/migrate_drupal/tests/modules/migrate_cckfield_plugin_manager_test/src/Plugin/migrate/cckfield/D6FileField.php create mode 100644 core/modules/migrate_drupal/tests/modules/migrate_cckfield_plugin_manager_test/src/Plugin/migrate/cckfield/D6NoCoreVersionSpecified.php create mode 100644 core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php create mode 100644 core/modules/responsive_image/responsive_image.post_update.php create mode 100644 core/modules/responsive_image/src/Tests/Update/ResponsiveImageUpdateTest.php create mode 100644 core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php create mode 100644 core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMultiValueBasefield.php create mode 100644 core/modules/taxonomy/tests/src/Kernel/ForwardRevisionTest.php delete mode 100644 core/modules/user/src/MigratePassword.php delete mode 100644 core/modules/user/src/UserServiceProvider.php create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.base_and_revision.yml create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_body.yml create mode 100644 core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/FieldTest.php create mode 100644 core/modules/views/tests/src/Kernel/Entity/EntityViewsWithMultivalueBasefieldTest.php create mode 100644 core/modules/views/tests/src/Kernel/Plugin/Display/ViewsMenuLinkTest.php rename core/modules/views/{src/Tests => tests/src/Kernel}/Plugin/PluginBaseTest.php (79%) create mode 100644 core/modules/views/tests/src/Kernel/Plugin/SqlEntityLoadingTest.php create mode 100644 core/modules/views_ui/tests/src/FunctionalJavascript/ViewsWizardTest.php rename core/profiles/standard/{src/Tests => tests/src/Functional}/StandardTest.php (98%) create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php create mode 100644 core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php rename core/{modules/system/src/Tests => tests/Drupal/KernelTests/Core}/DrupalKernel/DrupalKernelTest.php (86%) create mode 100644 core/tests/Drupal/Tests/Core/Config/ConfigDependencyManagerTest.php create mode 100644 core/tests/Drupal/Tests/Core/Database/Stub/Select.php create mode 100644 core/tests/Drupal/Tests/TestSuites/TestSuiteBaseTest.php create mode 100644 core/tests/TestSuites/FunctionalJavascriptTestSuite.php create mode 100644 core/tests/TestSuites/FunctionalTestSuite.php create mode 100644 core/tests/TestSuites/KernelTestSuite.php create mode 100644 core/tests/TestSuites/TestSuiteBase.php create mode 100644 core/tests/TestSuites/UnitTestSuite.php create mode 100644 vendor/masterminds/html5/.scrutinizer.yml diff --git a/composer.lock b/composer.lock index 86881211a..f2c6c183a 100644 --- a/composer.lock +++ b/composer.lock @@ -891,16 +891,16 @@ }, { "name": "masterminds/html5", - "version": "2.1.2", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "8f782e0f01a6e33a319bdc8f6de9cfd6569979a4" + "reference": "170aa5cb35b29fccafbf5ea63487c013f396fdc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/8f782e0f01a6e33a319bdc8f6de9cfd6569979a4", - "reference": "8f782e0f01a6e33a319bdc8f6de9cfd6569979a4", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/170aa5cb35b29fccafbf5ea63487c013f396fdc9", + "reference": "170aa5cb35b29fccafbf5ea63487c013f396fdc9", "shasum": "" }, "require": { @@ -910,7 +910,7 @@ "require-dev": { "phpunit/phpunit": "4.*", "sami/sami": "~2.0", - "satooshi/php-coveralls": "0.6.*" + "satooshi/php-coveralls": "1.0.*" }, "type": "library", "extra": { @@ -952,7 +952,7 @@ "serializer", "xml" ], - "time": "2015-06-07 08:43:18" + "time": "2016-05-10 14:11:45" }, { "name": "paragonie/random_compat", diff --git a/core/INSTALL.txt b/core/INSTALL.txt index ff35ee334..8c5ee8723 100644 --- a/core/INSTALL.txt +++ b/core/INSTALL.txt @@ -27,7 +27,7 @@ Drupal requires: - Percona Server 5.5.8 (or greater) (http://www.percona.com/). Percona Server is a backwards-compatible replacement for MySQL. - PostgreSQL 9.1.2 (or greater) (http://www.postgresql.org/). - - SQLite 3.6.8 (or greater) (http://www.sqlite.org/). + - SQLite 3.7.11 (or greater) (http://www.sqlite.org/). For more detailed information about Drupal requirements, including a list of PHP extensions and configurations that are required, see "System requirements" diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 44b4d7a01..10ee48cf7 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -28,8 +28,12 @@ Provisional membership: - Scott Reeves 'Cottser' https://www.drupal.org/u/cottser Drupal 7 +- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx + (Framework Manager) - David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein (Release Manager, Framework Manager, Product Manager) +- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0 + (Release Manager, Product Manager) Provisional membership: None at this time. @@ -200,7 +204,6 @@ Transliteration system - Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu - Damien Tournoud 'damien-tournoud' https://www.drupal.org/u/damien-tournoud - Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun -- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon Typed data system - Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago @@ -213,7 +216,7 @@ Accessibility - Jesse Renée Beach 'jessebeach' https://www.drupal.org/u/jessebeach Documentation -- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon +- ? Performance - Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch @@ -413,7 +416,6 @@ Responsive Image module - Jelle Sebreghts 'Jelle_S' https://www.drupal.org/u/jelle_s Search module -- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon - Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin Serialization module diff --git a/core/composer.json b/core/composer.json index 1266df391..a8ee73e4c 100644 --- a/core/composer.json +++ b/core/composer.json @@ -64,8 +64,10 @@ "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-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", diff --git a/core/includes/entity.inc b/core/includes/entity.inc index d48aa4eb2..cdf3120b9 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -32,12 +32,13 @@ function entity_render_cache_clear() { * The bundle info for a specific entity type, or all entity types. * * @deprecated in Drupal 8.x-dev and will be removed before Drupal 9.0.0. Use - * \Drupal\Core\Entity\EntityManagerInterface::getBundleInfo() for a single - * bundle, or \Drupal\Core\Entity\EntityManagerInterface::getAllBundleInfo() - * for all bundles. + * \Drupal\Core\Entity\EntityTypeBundleInfoInterface::getBundleInfo() for a + * single bundle, or + * \Drupal\Core\Entity\EntityTypeBundleInfoInterface::getAllBundleInfo() for + * all bundles. * - * @see \Drupal\Core\Entity\EntityManagerInterface::getBundleInfo() - * @see \Drupal\Core\Entity\EntityManagerInterface::getAllBundleInfo() + * @see \Drupal\Core\Entity\EntityTypeBundleInfoInterface::getBundleInfo() + * @see \Drupal\Core\Entity\EntityTypeBundleInfoInterface::getAllBundleInfo() */ function entity_get_bundles($entity_type = NULL) { if (isset($entity_type)) { @@ -67,11 +68,11 @@ function entity_get_bundles($entity_type = NULL) { * entity type is variable, use the entity manager service to load the entity * from the entity storage: * @code - * \Drupal::entityManager()->getStorage($entity_type)->load($id) + * \Drupal::entityTypeManager()->getStorage($entity_type)->load($id); * @endcode * * @see \Drupal\Core\Entity\EntityInterface::load() - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::load() * @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage * @see \Drupal\Core\Entity\Query\QueryInterface @@ -100,10 +101,12 @@ function entity_load($entity_type, $id, $reset = FALSE) { * the entity storage's loadRevision() method to load a specific entity * revision: * @code - * \Drupal::entityManager()->getStorage($entity_type)->loadRevision($revision_id); + * \Drupal::entityTypeManager() + * ->getStorage($entity_type) + * ->loadRevision($revision_id); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::loadRevision() * @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage */ @@ -125,10 +128,12 @@ function entity_revision_load($entity_type, $revision_id) { * the entity storage's deleteRevision() method to delete a specific entity * revision: * @code - * \Drupal::entityManager()->getStorage($entity_type)>deleteRevision($revision_id); + * \Drupal::entityTypeManager() + * ->getStorage($entity_type) + * ->deleteRevision($revision_id); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::deleteRevision() */ function entity_revision_delete($entity_type, $revision_id) { @@ -171,11 +176,11 @@ function entity_revision_delete($entity_type, $revision_id) { * the entity type is variable, use the entity manager service to load the * entity from the entity storage: * @code - * \Drupal::entityManager()->getStorage($entity_type)->loadMultiple($id) + * \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple($id); * @endcode * * @see \Drupal\Core\Entity\EntityInterface::loadMultiple() - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::loadMultiple() * @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage * @see \Drupal\Core\Entity\Query\QueryInterface @@ -205,10 +210,12 @@ function entity_load_multiple($entity_type, array $ids = NULL, $reset = FALSE) { * the entity storage's loadByProperties() method to load an entity by their * property values: * @code - * \Drupal::entityManager()->getStorage($entity_type)->loadByProperties($values); + * \Drupal::entityTypeManager() + * ->getStorage($entity_type) + * ->loadByProperties($values); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::loadByProperties() */ function entity_load_multiple_by_properties($entity_type, array $values) { @@ -236,10 +243,10 @@ function entity_load_multiple_by_properties($entity_type, array $values) { * @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use * the entity storage's loadUnchanged() method to load an unchanged entity: * @code - * \Drupal::entityManager()->getStorage($entity_type)->loadUnchanged($id). + * \Drupal::entityTypeManager()->getStorage($entity_type)->loadUnchanged($id); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::loadUnchanged() */ function entity_load_unchanged($entity_type, $id) { @@ -259,12 +266,12 @@ function entity_load_unchanged($entity_type, $id) { * @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use * the entity storage's delete() method to delete multiple entities: * @code - * $storage_handler = \Drupal::entityManager()->getStorage($entity_type); + * $storage_handler = \Drupal::entityTypeManager()->getStorage($entity_type); * $entities = $storage_handler->loadMultiple($ids); * $storage_handler->delete($entities); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::loadMultiple() * @see \Drupal\Core\Entity\EntityStorageInterface::delete() */ @@ -292,10 +299,10 @@ function entity_delete_multiple($entity_type, array $ids) { * entity type is variable, use the entity storage's create() method to * construct a new entity: * @code - * \Drupal::entityManager()->getStorage($entity_type)->create($values) + * \Drupal::entityTypeManager()->getStorage($entity_type)->create($values); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getStorage() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage() * @see \Drupal\Core\Entity\EntityStorageInterface::create() */ function entity_create($entity_type, array $values = array()) { @@ -349,11 +356,12 @@ function entity_page_label(EntityInterface $entity, $langcode = NULL) { * @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. * Use the entity view builder's view() method for creating a render array: * @code - * $view_builder = \Drupal::entityManager()->getViewBuilder($entity->getEntityTypeId()); + * $view_builder = \Drupal::entityTypeManager() + * ->getViewBuilder($entity->getEntityTypeId()); * return $view_builder->view($entity, $view_mode, $langcode); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getViewBuilder() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getViewBuilder() * @see \Drupal\Core\Entity\EntityViewBuilderInterface::view() */ function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL, $reset = FALSE) { @@ -386,11 +394,12 @@ function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL, $res * Use the entity view builder's viewMultiple() method for creating a render * array for the provided entities: * @code - * $view_builder = \Drupal::entityManager()->getViewBuilder($entity->getEntityTypeId()); + * $view_builder = \Drupal::entityTypeManager() + * ->getViewBuilder($entity->getEntityTypeId()); * return $view_builder->viewMultiple($entities, $view_mode, $langcode); * @endcode * - * @see \Drupal\Core\Entity\EntityManagerInterface::getViewBuilder() + * @see \Drupal\Core\Entity\EntityTypeManagerInterface::getViewBuilder() * @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewMultiple() */ function entity_view_multiple(array $entities, $view_mode, $langcode = NULL, $reset = FALSE) { @@ -443,7 +452,9 @@ function entity_view_multiple(array $entities, $view_mode, $langcode = NULL, $re * @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. * If the display is available in configuration use: * @code - * \Drupal::entityManager()->getStorage('entity_view_display')->load($entity_type . '.' . $bundle . '.' . $view_mode); + * \Drupal::entityTypeManager() + * ->getStorage('entity_view_display') + * ->load($entity_type . '.' . $bundle . '.' . $view_mode); * @endcode * When the display is not available in configuration, you can create a new * EntityViewDisplay object using: @@ -454,7 +465,9 @@ function entity_view_multiple(array $entities, $view_mode, $langcode = NULL, $re * 'mode' => $view_mode, * 'status' => TRUE, * )); - * \Drupal::entityManager()->getStorage('entity_view_display')->create($values); + * \Drupal::entityTypeManager() + * ->getStorage('entity_view_display') + * ->create($values); * @endcode * * @see \Drupal\Core\Entity\EntityStorageInterface::create() @@ -519,10 +532,12 @@ function entity_get_display($entity_type, $bundle, $view_mode) { * @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. * If the entity form display is available in configuration use: * @code - * \Drupal::entityManager()->getStorage('entity_form_display')->load($entity_type . '.' . $bundle . '.' . $form_mode); + * \Drupal::entityTypeManager() + * ->getStorage('entity_form_display') + * ->load($entity_type . '.' . $bundle . '.' . $form_mode); * @endcode - * When the entity form display is not available in configuration, you can create a new - * EntityFormDisplay object using: + * When the entity form display is not available in configuration, you can + * create a new EntityFormDisplay object using: * @code * $values = ('entity_form_display', array( * 'targetEntityType' => $entity_type, @@ -530,7 +545,9 @@ function entity_get_display($entity_type, $bundle, $view_mode) { * 'mode' => $form_mode, * 'status' => TRUE, * )); - * \Drupal::entityManager()->getStorage('entity_form_display')->create($values); + * \Drupal::entityTypeManager() + * ->getStorage('entity_form_display') + * ->create($values); * @endcode * * @see \Drupal\Core\Entity\EntityStorageInterface::create() diff --git a/core/includes/file.inc b/core/includes/file.inc index 3cd943982..528d00bc4 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -397,7 +397,7 @@ function file_valid_uri($uri) { } /** - * Copies a file to a new location without invoking the file API. + * Copies a file to a new location without database changes or hook invocation. * * This is a powerful function that in many ways performs like an advanced * version of copy(). @@ -407,10 +407,9 @@ function file_valid_uri($uri) { * - If the $source and $destination are equal, the behavior depends on the * $replace parameter. FILE_EXISTS_REPLACE will error out. FILE_EXISTS_RENAME * will rename the file until the $destination is unique. - * - Provides a fallback using realpaths if the move fails using stream - * wrappers. This can occur because PHP's copy() function does not properly - * support streams if open_basedir is enabled. See - * https://bugs.php.net/bug.php?id=60456 + * - Works around a PHP bug where copy() does not properly support streams if + * safe_mode or open_basedir are enabled. + * @see https://bugs.php.net/bug.php?id=60456 * * @param $source * A string specifying the filepath or URI of the source file. @@ -431,18 +430,66 @@ function file_valid_uri($uri) { * @see file_copy() */ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { + if (!file_unmanaged_prepare($source, $destination, $replace)) { + return FALSE; + } + // Attempt to resolve the URIs. This is necessary in certain configurations + // (see above). + $real_source = drupal_realpath($source) ?: $source; + $real_destination = drupal_realpath($destination) ?: $destination; + // Perform the copy operation. + if (!@copy($real_source, $real_destination)) { + \Drupal::logger('file')->error('The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination)); + return FALSE; + } + // Set the permissions on the new file. + drupal_chmod($destination); + return $destination; +} + +/** + * Internal function that prepares the destination for a file_unmanaged_copy or + * file_unmanaged_move operation. + * + * - Checks if $source and $destination are valid and readable/writable. + * - Checks that $source is not equal to $destination; if they are an error + * is reported. + * - If file already exists in $destination either the call will error out, + * replace the file or rename the file based on the $replace parameter. + * + * @param $source + * A string specifying the filepath or URI of the source file. + * @param $destination + * A URI containing the destination that $source should be moved/copied to. + * The URI may be a bare filepath (without a scheme) and in that case the + * default scheme (file://) will be used. If this value is omitted, Drupal's + * default files scheme will be used, usually "public://". + * @param $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. + * + * @return + * TRUE, or FALSE in the event of an error. + * + * @see file_unmanaged_copy() + * @see file_unmanaged_move() + */ +function file_unmanaged_prepare($source, &$destination = NULL, $replace = FILE_EXISTS_RENAME) { $original_source = $source; $logger = \Drupal::logger('file'); // Assert that the source file actually exists. if (!file_exists($source)) { // @todo Replace drupal_set_message() calls with exceptions instead. - drupal_set_message(t('The specified file %file could not be copied because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error'); + drupal_set_message(t('The specified file %file could not be moved/copied because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error'); if (($realpath = drupal_realpath($original_source)) !== FALSE) { - $logger->notice('File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath)); + $logger->notice('File %file (%realpath) could not be moved/copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath)); } else { - $logger->notice('File %file could not be copied because it does not exist.', array('%file' => $original_source)); + $logger->notice('File %file could not be moved/copied because it does not exist.', array('%file' => $original_source)); } return FALSE; } @@ -463,8 +510,8 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST $dirname = drupal_dirname($destination); if (!file_prepare_directory($dirname)) { // The destination is not valid. - $logger->notice('File %file could not be copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname)); - drupal_set_message(t('The specified file %file could not be copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error'); + $logger->notice('File %file could not be moved/copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname)); + drupal_set_message(t('The specified file %file could not be moved/copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error'); return FALSE; } } @@ -472,8 +519,8 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST // Determine whether we can perform this operation based on overwrite rules. $destination = file_destination($destination, $replace); if ($destination === FALSE) { - drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error'); - $logger->notice('File %file could not be copied because a file by that name already exists in the destination directory (%destination)', array('%file' => $original_source, '%destination' => $destination)); + drupal_set_message(t('The file %file could not be moved/copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error'); + $logger->notice('File %file could not be moved/copied because a file by that name already exists in the destination directory (%destination)', array('%file' => $original_source, '%destination' => $destination)); return FALSE; } @@ -481,26 +528,13 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST $real_source = drupal_realpath($source); $real_destination = drupal_realpath($destination); if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) { - drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error'); - $logger->notice('File %file could not be copied because it would overwrite itself.', array('%file' => $source)); + drupal_set_message(t('The specified file %file was not moved/copied because it would overwrite itself.', array('%file' => $source)), 'error'); + $logger->notice('File %file could not be moved/copied because it would overwrite itself.', array('%file' => $source)); return FALSE; } // Make sure the .htaccess files are present. file_ensure_htaccess(); - // Perform the copy operation. - if (!@copy($source, $destination)) { - // If the copy failed and realpaths exist, retry the operation using them - // instead. - if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) { - $logger->error('The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination)); - return FALSE; - } - } - - // Set the permissions on the new file. - drupal_chmod($destination); - - return $destination; + return TRUE; } /** @@ -551,12 +585,24 @@ function file_destination($destination, $replace) { /** * Moves a file to a new location without database changes or hook invocation. * + * This is a powerful function that in many ways performs like an advanced + * version of rename(). + * - Checks if $source and $destination are valid and readable/writable. + * - Checks that $source is not equal to $destination; if they are an error + * is reported. + * - If file already exists in $destination either the call will error out, + * replace the file or rename the file based on the $replace parameter. + * - Works around a PHP bug where rename() does not properly support streams if + * safe_mode or open_basedir are enabled. + * @see https://bugs.php.net/bug.php?id=60456 + * * @param $source - * A string specifying the filepath or URI of the original file. + * A string specifying the filepath or URI of the source file. * @param $destination - * A string containing the destination that $source should be moved to. - * This must be a stream wrapper URI. If this value is omitted, Drupal's - * default files scheme will be used, usually "public://". + * A URI containing the destination that $source should be moved to. The + * URI may be a bare filepath (without a scheme) and in that case the default + * scheme (file://) will be used. If this value is omitted, Drupal's default + * files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. @@ -565,16 +611,37 @@ function file_destination($destination, $replace) { * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return - * The URI of the moved file, or FALSE in the event of an error. + * The path to the new file, or FALSE in the event of an error. * * @see file_move() */ function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - $filepath = file_unmanaged_copy($source, $destination, $replace); - if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) { + if (!file_unmanaged_prepare($source, $destination, $replace)) { return FALSE; } - return $filepath; + // Ensure compatibility with Windows. + // @see drupal_unlink() + if ((substr(PHP_OS, 0, 3) == 'WIN') && (!file_stream_wrapper_valid_scheme(file_uri_scheme($source)))) { + chmod($source, 0600); + } + // Attempt to resolve the URIs. This is necessary in certain configurations + // (see above) and can also permit fast moves across local schemes. + $real_source = drupal_realpath($source) ?: $source; + $real_destination = drupal_realpath($destination) ?: $destination; + // Perform the move operation. + if (!@rename($real_source, $real_destination)) { + // Fall back to slow copy and unlink procedure. This is necessary for + // renames across schemes that are not local, or where rename() has not been + // implemented. It's not necessary to use drupal_unlink() as the Windows + // issue has already been resolved above. + if (!@copy($real_source, $real_destination) || !@unlink($real_source)) { + \Drupal::logger('file')->error('The specified file %file could not be moved to %destination.', array('%file' => $source, '%destination' => $destination)); + return FALSE; + } + } + // Set the permissions on the new file. + drupal_chmod($destination); + return $destination; } /** diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 8dd191cb1..1b3b255b8 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1084,7 +1084,7 @@ function install_verify_completed_task() { * The site path. * * @return bool - * TRUE if there are no database errors. + * TRUE if there are no database errors. */ function install_verify_database_settings($site_path) { if ($database = Database::getConnectionInfo()) { diff --git a/core/includes/install.inc b/core/includes/install.inc index 88ca1e2ad..f8008b3ed 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -115,8 +115,8 @@ function drupal_install_profile_distribution_name() { * Loads the installation profile, extracting its defined version. * * @return string Distribution version defined in the profile's .info.yml file. - * Defaults to \Drupal::VERSION if no version is explicitly provided - * by the installation profile. + * Defaults to \Drupal::VERSION if no version is explicitly provided + * by the installation profile. * * @see install_profile_info() */ @@ -139,7 +139,7 @@ function drupal_install_profile_distribution_version() { * Detects all supported databases that are compiled into PHP. * * @return - * An array of database types compiled into PHP. + * An array of database types compiled into PHP. */ function drupal_detect_database_types() { $databases = drupal_get_database_types(); @@ -724,7 +724,7 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { * (optional) Whether to output messages. Defaults to TRUE. * * @return - * TRUE/FALSE whether or not the directory was successfully created. + * TRUE/FALSE whether or not the directory was successfully created. */ function drupal_install_mkdir($file, $mask, $message = TRUE) { $mod = 0; @@ -773,7 +773,7 @@ function drupal_install_mkdir($file, $mask, $message = TRUE) { * (optional) Whether to output messages. Defaults to TRUE. * * @return - * TRUE/FALSE whether or not we were able to fix the file's permissions. + * TRUE/FALSE whether or not we were able to fix the file's permissions. */ function drupal_install_fix_file($file, $mask, $message = TRUE) { // If $file does not exist, fileperms() issues a PHP warning. diff --git a/core/includes/update.inc b/core/includes/update.inc index a07595e63..2c020db6b 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -65,7 +65,7 @@ function update_check_incompatibility($name, $type = 'module') { * Returns whether the minimum schema requirement has been satisfied. * * @return array - * A requirements info array. + * A requirements info array. */ function update_system_schema_requirements() { $requirements = array(); diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 7b367069b..aa4fc0aee 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -81,7 +81,7 @@ class Drupal { /** * The current system version. */ - const VERSION = '8.1.3'; + const VERSION = '8.1.5'; /** * Core API compatibility. diff --git a/core/lib/Drupal/Component/Annotation/Plugin.php b/core/lib/Drupal/Component/Annotation/Plugin.php index 9ec9b9022..29f36759c 100644 --- a/core/lib/Drupal/Component/Annotation/Plugin.php +++ b/core/lib/Drupal/Component/Annotation/Plugin.php @@ -49,7 +49,7 @@ class Plugin implements AnnotationInterface { * The annotation array. * * @return array - * The parsed annotation as a definition. + * The parsed annotation as a definition. */ protected function parse(array $values) { $definitions = array(); diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php index 377bb27b5..56bd92f23 100644 --- a/core/lib/Drupal/Component/Datetime/DateTimePlus.php +++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php @@ -455,7 +455,7 @@ class DateTimePlus { /** * Detects if there were errors in the processing of this date. * - * @return boolean + * @return bool * TRUE if there were errors in the processing of this date, FALSE * otherwise. */ diff --git a/core/lib/Drupal/Component/DependencyInjection/Container.php b/core/lib/Drupal/Component/DependencyInjection/Container.php index 733490ca3..452a2c84d 100644 --- a/core/lib/Drupal/Component/DependencyInjection/Container.php +++ b/core/lib/Drupal/Component/DependencyInjection/Container.php @@ -373,7 +373,7 @@ class Container implements IntrospectableContainerInterface, ResettableContainer * {@inheritdoc} */ public function has($id) { - return isset($this->services[$id]) || isset($this->serviceDefinitions[$id]); + return isset($this->aliases[$id]) || isset($this->services[$id]) || isset($this->serviceDefinitions[$id]); } /** diff --git a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php index d52fb89a0..317f2c49d 100644 --- a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php @@ -60,6 +60,7 @@ class OptimizedPhpArrayDumper extends Dumper { */ public function getArray() { $definition = array(); + $this->aliases = $this->getAliases(); $definition['aliases'] = $this->getAliases(); $definition['parameters'] = $this->getParameters(); $definition['services'] = $this->getServiceDefinitions(); @@ -454,6 +455,9 @@ class OptimizedPhpArrayDumper extends Dumper { } // Private shared service. + if (isset($this->aliases[$id])) { + $id = $this->aliases[$id]; + } $definition = $this->container->getDefinition($id); if (!$definition->isPublic()) { // The ContainerBuilder does not share a private service, but this means a diff --git a/core/lib/Drupal/Component/DependencyInjection/composer.json b/core/lib/Drupal/Component/DependencyInjection/composer.json index 28dc0312f..8dda45fbe 100644 --- a/core/lib/Drupal/Component/DependencyInjection/composer.json +++ b/core/lib/Drupal/Component/DependencyInjection/composer.json @@ -12,8 +12,8 @@ }, "require": { "php": ">=5.5.9", - "symfony/dependency-injection": "2.7.*", - "symfony/expression-language": "2.7.*" + "symfony/dependency-injection": "~2.8", + "symfony/expression-language": "~2.7" }, "autoload": { "psr-4": { diff --git a/core/lib/Drupal/Component/EventDispatcher/composer.json b/core/lib/Drupal/Component/EventDispatcher/composer.json index 213284103..be6623283 100644 --- a/core/lib/Drupal/Component/EventDispatcher/composer.json +++ b/core/lib/Drupal/Component/EventDispatcher/composer.json @@ -6,8 +6,8 @@ "license": "GPL-2.0+", "require": { "php": ">=5.5.9", - "symfony/dependency-injection": "2.7.*", - "symfony/event-dispatcher": "2.7.*" + "symfony/dependency-injection": "~2.8", + "symfony/event-dispatcher": "~2.7" }, "autoload": { "psr-4": { diff --git a/core/lib/Drupal/Component/HttpFoundation/composer.json b/core/lib/Drupal/Component/HttpFoundation/composer.json index 57c929d18..f82c0679a 100644 --- a/core/lib/Drupal/Component/HttpFoundation/composer.json +++ b/core/lib/Drupal/Component/HttpFoundation/composer.json @@ -6,7 +6,7 @@ "license": "GPL-2.0+", "require": { "php": ">=5.5.9", - "symfony/http-foundation": "2.7.*" + "symfony/http-foundation": "~2.7" }, "autoload": { "psr-4": { diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php index b065de959..7d076fa75 100644 --- a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php +++ b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php @@ -30,8 +30,8 @@ trait DiscoveryTrait { * @param string $plugin_id * A plugin id. * @param bool $exception_on_invalid - * (optional) If TRUE, an invalid plugin ID will throw an exception. - * Defaults to FALSE. + * If TRUE, an invalid plugin ID will cause an exception to be thrown; if + * FALSE, NULL will be returned. * * @return array|null * A plugin definition, or NULL if the plugin ID is invalid and diff --git a/core/lib/Drupal/Component/Plugin/composer.json b/core/lib/Drupal/Component/Plugin/composer.json index 1acc8c5e5..4ffeeed3a 100644 --- a/core/lib/Drupal/Component/Plugin/composer.json +++ b/core/lib/Drupal/Component/Plugin/composer.json @@ -6,7 +6,7 @@ "license": "GPL-2.0+", "require": { "php": ">=5.5.9", - "symfony/validator": "2.7.*" + "symfony/validator": "~2.7" }, "autoload": { "psr-4": { diff --git a/core/lib/Drupal/Component/Render/OutputStrategyInterface.php b/core/lib/Drupal/Component/Render/OutputStrategyInterface.php index 3406e8098..3ed9725b8 100644 --- a/core/lib/Drupal/Component/Render/OutputStrategyInterface.php +++ b/core/lib/Drupal/Component/Render/OutputStrategyInterface.php @@ -7,7 +7,7 @@ namespace Drupal\Component\Render; * * Output strategies assist in transforming HTML strings into strings that are * appropriate for a given context (e.g. plain-text), through performing the - * relevant formatting. No santization is applied. + * relevant formatting. No sanitization is applied. */ interface OutputStrategyInterface { diff --git a/core/lib/Drupal/Component/Serialization/composer.json b/core/lib/Drupal/Component/Serialization/composer.json index ff62221bc..5d629cfc1 100644 --- a/core/lib/Drupal/Component/Serialization/composer.json +++ b/core/lib/Drupal/Component/Serialization/composer.json @@ -6,7 +6,7 @@ "license": "GPL-2.0+", "require": { "php": ">=5.5.9", - "symfony/yaml": "2.7.*" + "symfony/yaml": "~2.7" }, "autoload": { "psr-4": { diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php index 06b4ac2f2..2d0c22075 100644 --- a/core/lib/Drupal/Core/Cache/Cache.php +++ b/core/lib/Drupal/Core/Cache/Cache.php @@ -149,7 +149,7 @@ class Cache { * Gets all cache bin services. * * @return array - * An array of cache backend objects keyed by cache bins. + * An array of cache backend objects keyed by cache bins. */ public static function getBins() { $bins = array(); diff --git a/core/lib/Drupal/Core/Cache/CacheableDependencyInterface.php b/core/lib/Drupal/Core/Cache/CacheableDependencyInterface.php index 2bb89ba90..cbce6aac6 100644 --- a/core/lib/Drupal/Core/Cache/CacheableDependencyInterface.php +++ b/core/lib/Drupal/Core/Cache/CacheableDependencyInterface.php @@ -37,7 +37,7 @@ interface CacheableDependencyInterface { * When this object is modified, these cache tags will be invalidated. * * @return string[] - * A set of cache tags. + * A set of cache tags. */ public function getCacheTags(); diff --git a/core/lib/Drupal/Core/Cache/ChainedFastBackend.php b/core/lib/Drupal/Core/Cache/ChainedFastBackend.php index 62839c2b0..5c0750d20 100644 --- a/core/lib/Drupal/Core/Cache/ChainedFastBackend.php +++ b/core/lib/Drupal/Core/Cache/ChainedFastBackend.php @@ -87,8 +87,16 @@ class ChainedFastBackend implements CacheBackendInterface, CacheTagsInvalidatorI * The fast cache backend. * @param string $bin * The cache bin for which the object is created. + * + * @throws \Exception + * When the consistent cache backend and the fast cache backend are the same + * service. */ public function __construct(CacheBackendInterface $consistent_backend, CacheBackendInterface $fast_backend, $bin) { + if ($consistent_backend == $fast_backend) { + // @todo: should throw a proper exception. See https://www.drupal.org/node/2751847. + trigger_error('Consistent cache backend and fast cache backend cannot use the same service.', E_USER_ERROR); + } $this->consistentBackend = $consistent_backend; $this->fastBackend = $fast_backend; $this->bin = 'cache_' . $bin; diff --git a/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php index 0926ba60b..a48664468 100644 --- a/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php @@ -25,20 +25,22 @@ class QueryArgsCacheContext extends RequestStackCacheContextBase implements Calc */ public function getContext($query_arg = NULL) { if ($query_arg === NULL) { - return $this->requestStack->getCurrentRequest()->getQueryString(); + // All arguments requested. Use normalized query string to minimize + // variations. + $value = $this->requestStack->getCurrentRequest()->getQueryString(); + return ($value !== NULL) ? $value : ''; } elseif ($this->requestStack->getCurrentRequest()->query->has($query_arg)) { $value = $this->requestStack->getCurrentRequest()->query->get($query_arg); - if ($value !== '') { + if (is_array($value)) { + return http_build_query($value); + } + elseif ($value !== '') { return $value; } - else { - return '?valueless?'; - } - } - else { - return NULL; + return '?valueless?'; } + return ''; } /** diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index 1ab9fdb55..11d7c8439 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -307,8 +307,9 @@ class ConfigManager implements ConfigManagerInterface { // Try to fix any dependencies and find out what will happen to the // dependency graph. Entities are processed in the order of most dependent - // first. For example, this ensures that fields are removed before - // field storages. + // first. For example, this ensures that Menu UI third party dependencies on + // node types are fixed before processing the node type's other + // dependencies. while ($dependent = array_pop($dependents)) { /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent */ if ($dry_run) { @@ -346,7 +347,9 @@ class ConfigManager implements ConfigManagerInterface { // If the entity cannot be fixed then it has to be deleted. if (!$fixed) { $delete_uuids[] = $dependent->uuid(); - $return['delete'][] = $dependent; + // Deletes should occur in the order of the least dependent first. For + // example, this ensures that fields are removed before field storages. + array_unshift($return['delete'], $dependent); } } // Use the lists of UUIDs to filter the original list to work out which diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php index 377f7c4d0..c274f28a1 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php @@ -173,7 +173,9 @@ class ConfigDependencyManager { // dependent is at the top. For example, this ensures that fields are // always after field storages. This is because field storages need to be // created before a field. - return array_reverse(array_intersect_key($this->graph, $dependencies)); + $graph = $this->getGraph(); + uasort($graph, array($this, 'sortGraph')); + return array_replace(array_intersect_key($graph, $dependencies), $dependencies); } /** @@ -185,10 +187,32 @@ class ConfigDependencyManager { */ public function sortAll() { $graph = $this->getGraph(); - // Sort by reverse weight and alphabetically. The most dependent entities + // Sort by weight and alphabetically. The most dependent entities // are last and entities with the same weight are alphabetically ordered. - uasort($graph, array($this, 'sortGraph')); - return array_keys($graph); + uasort($graph, array($this, 'sortGraphByWeight')); + // Use array_intersect_key() to exclude modules and themes from the list. + return array_keys(array_intersect_key($graph, $this->data)); + } + + /** + * Sorts the dependency graph by weight and alphabetically. + * + * @param array $a + * First item for comparison. The compared items should be associative + * arrays that include a 'weight' and a 'name' key. + * @param array $b + * Second item for comparison. + * + * @return int + * The comparison result for uasort(). + */ + protected function sortGraphByWeight(array $a, array $b) { + $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight'); + + if ($weight_cmp === 0) { + return SortArray::sortByKeyString($a, $b, 'name'); + } + return $weight_cmp; } /** @@ -196,7 +220,7 @@ class ConfigDependencyManager { * * @param array $a * First item for comparison. The compared items should be associative - * arrays that include a 'weight' and a 'component' key. + * arrays that include a 'weight' and a 'name' key. * @param array $b * Second item for comparison. * @@ -207,7 +231,7 @@ class ConfigDependencyManager { $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1; if ($weight_cmp === 0) { - return SortArray::sortByKeyString($a, $b, 'component'); + return SortArray::sortByKeyString($a, $b, 'name'); } return $weight_cmp; } @@ -228,9 +252,11 @@ class ConfigDependencyManager { $graph = $this->getGraph(); foreach ($entities_to_check as $entity) { - if (isset($graph[$entity]) && !empty($graph[$entity]['reverse_paths'])) { - foreach ($graph[$entity]['reverse_paths'] as $dependency => $value) { - $dependent_entities[$dependency] = $this->data[$dependency]; + if (isset($graph[$entity]) && !empty($graph[$entity]['paths'])) { + foreach ($graph[$entity]['paths'] as $dependency => $value) { + if (isset($this->data[$dependency])) { + $dependent_entities[$dependency] = $this->data[$dependency]; + } } } } @@ -248,14 +274,21 @@ class ConfigDependencyManager { $graph = array(); foreach ($this->data as $entity) { $graph_key = $entity->getConfigDependencyName(); - $graph[$graph_key]['edges'] = array(); - $dependencies = $entity->getDependencies('config'); - if (!empty($dependencies)) { - foreach ($dependencies as $dependency) { - $graph[$graph_key]['edges'][$dependency] = TRUE; - } + if (!isset($graph[$graph_key])) { + $graph[$graph_key] = [ + 'edges' => [], + 'name' => $graph_key, + ]; + } + // Include all dependencies in the graph so that topographical sorting + // works. + foreach (array_merge($entity->getDependencies('config'), $entity->getDependencies('module'), $entity->getDependencies('theme')) as $dependency) { + $graph[$dependency]['edges'][$graph_key] = TRUE; + $graph[$dependency]['name'] = $dependency; } } + // Ensure that order of the graph is consistent. + krsort($graph); $graph_object = new Graph($graph); $this->graph = $graph_object->searchAndSort(); } diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index 49bc91676..18a712633 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -93,28 +93,28 @@ interface StorageInterface { /** * Encodes configuration data into the storage-specific format. * + * This is a publicly accessible static method to allow for alternative + * usages in data conversion scripts and also tests. + * * @param array $data * The configuration data to encode. * * @return string * The encoded configuration data. - * - * This is a publicly accessible static method to allow for alternative - * usages in data conversion scripts and also tests. */ public function encode($data); /** * Decodes configuration data from the storage-specific format. * + * This is a publicly accessible static method to allow for alternative + * usages in data conversion scripts and also tests. + * * @param string $raw * The raw configuration data string to decode. * * @return array * The decoded configuration data as an associative array. - * - * This is a publicly accessible static method to allow for alternative - * usages in data conversion scripts and also tests. */ public function decode($raw); diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 2002e9ebc..791bf7a01 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -753,14 +753,11 @@ abstract class Connection { */ public function getDriverClass($class) { if (empty($this->driverClasses[$class])) { - $driver = $this->driver(); - if (!empty($this->connectionOptions['namespace'])) { - $driver_class = $this->connectionOptions['namespace'] . '\\' . $class; - } - else { - // Fallback for Drupal 7 settings.php. - $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\{$class}"; + if (empty($this->connectionOptions['namespace'])) { + // Fallback for Drupal 7 settings.php and the test runner script. + $this->connectionOptions['namespace'] = (new \ReflectionObject($this))->getNamespaceName(); } + $driver_class = $this->connectionOptions['namespace'] . '\\' . $class; $this->driverClasses[$class] = class_exists($driver_class) ? $driver_class : $class; } return $this->driverClasses[$class]; diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php index a91b24056..95ff52716 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php @@ -246,6 +246,9 @@ class Tasks extends InstallTasks { // concurrency issues, when both try to update at the same time. try { $connection = Database::getConnection(); + // When testing, two installs might try to run the CREATE FUNCTION queries + // at the same time. Do not let that happen. + $connection->query('SELECT pg_advisory_lock(1)'); // Don't use {} around pg_proc table. if (!$connection->query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) { $connection->query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS @@ -264,6 +267,7 @@ class Tasks extends InstallTasks { [ 'allow_delimiter_in_query' => TRUE ] ); } + $connection->query('SELECT pg_advisory_unlock(1)'); $this->pass(t('PostgreSQL has initialized itself.')); } diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php index 3a5b97091..f56b26ce4 100644 --- a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php +++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php @@ -28,7 +28,7 @@ class Tasks extends InstallTasks { * {@inheritdoc} */ public function minimumVersion() { - return '3.6.8'; + return '3.7.11'; } /** diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php index f97a6ec67..a3c001cc8 100644 --- a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php +++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php @@ -420,8 +420,12 @@ class Schema extends DatabaseSchema { * * @param $table * Name of the table. + * * @return * An array representing the schema, from drupal_get_schema(). + * + * @throws \Exception + * If a column of the table could not be parsed. */ protected function introspectSchema($table) { $mapped_fields = array_flip($this->getFieldTypeMap()); @@ -459,7 +463,7 @@ class Schema extends DatabaseSchema { } } else { - new \Exception("Unable to parse the column type " . $row->type); + throw new \Exception("Unable to parse the column type " . $row->type); } } $indexes = array(); @@ -710,7 +714,7 @@ class Schema extends DatabaseSchema { // Can't use query placeholders for the schema because the query would // have to be :prefixsqlite_master, which does not work. We also need to // ignore the internal SQLite tables. - $result = db_query("SELECT name FROM " . $schema . ".sqlite_master WHERE type = :type AND name LIKE :table_name AND name NOT LIKE :pattern", array( + $result = $this->connection->query("SELECT name FROM " . $schema . ".sqlite_master WHERE type = :type AND name LIKE :table_name AND name NOT LIKE :pattern", array( ':type' => 'table', ':table_name' => $table_expression, ':pattern' => 'sqlite_%', diff --git a/core/lib/Drupal/Core/Database/Query/ExtendableInterface.php b/core/lib/Drupal/Core/Database/Query/ExtendableInterface.php index b0d6f762d..d512bd611 100644 --- a/core/lib/Drupal/Core/Database/Query/ExtendableInterface.php +++ b/core/lib/Drupal/Core/Database/Query/ExtendableInterface.php @@ -6,11 +6,11 @@ namespace Drupal\Core\Database\Query; * Interface for extendable query objects. * * "Extenders" follow the "Decorator" OOP design pattern. That is, they wrap - * and "decorate" another object. In our case, they implement the same interface - * as select queries and wrap a select query, to which they delegate almost all - * operations. Subclasses of this class may implement additional methods or - * override existing methods as appropriate. Extenders may also wrap other - * extender objects, allowing for arbitrarily complex "enhanced" queries. + * and "decorate" another object. In our case, they implement the same + * interface as select queries and wrap a select query, to which they delegate + * almost all operations. Subclasses of this class may implement additional + * methods or override existing methods as appropriate. Extenders may also wrap + * other extender objects, allowing for arbitrarily complex "enhanced" queries. */ interface ExtendableInterface { @@ -18,9 +18,12 @@ interface ExtendableInterface { * Enhance this object by wrapping it in an extender object. * * @param $extender_name - * The base name of the extending class. The base name will be checked - * against the current database connection to allow driver-specific subclasses - * as well, using the same logic as the query objects themselves. + * The fully-qualified name of the extender class, without the leading '\' + * (for example, Drupal\my_module\myExtenderClass). The extender name will + * be checked against the current database connection to allow + * driver-specific subclasses as well, using the same logic as the query + * objects themselves. + * * @return \Drupal\Core\Database\Query\ExtendableInterface * The extender object, which now contains a reference to this object. */ diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php index a7e340bfc..b269410b8 100644 --- a/core/lib/Drupal/Core/Database/Query/Select.php +++ b/core/lib/Drupal/Core/Database/Query/Select.php @@ -39,7 +39,7 @@ class Select extends Query implements SelectInterface { * 'type' => $join_type (one of INNER, LEFT OUTER, RIGHT OUTER), * 'table' => $table, * 'alias' => $alias_of_the_table, - * 'condition' => $condition_clause_on_which_to_join, + * 'condition' => $join_condition (string or Condition object), * 'arguments' => $array_of_arguments_for_placeholders_in_the condition. * 'all_fields' => TRUE to SELECT $alias.*, FALSE or NULL otherwise. * ) @@ -47,6 +47,10 @@ class Select extends Query implements SelectInterface { * If $table is a string, it is taken as the name of a table. If it is * a Select query object, it is taken as a subquery. * + * If $join_condition is a Condition object, any arguments should be + * incorporated into the object; a separate array of arguments does not + * need to be provided. + * * @var array */ protected $tables = array(); @@ -196,6 +200,10 @@ class Select extends Query implements SelectInterface { if ($table['table'] instanceof SelectInterface) { $args += $table['table']->arguments(); } + // If the join condition is an object, grab its arguments recursively. + if (!empty($table['condition']) && $table['condition'] instanceof ConditionInterface) { + $args += $table['condition']->arguments(); + } } foreach ($this->expressions as $expression) { @@ -225,6 +233,10 @@ class Select extends Query implements SelectInterface { if ($table['table'] instanceof SelectInterface) { $table['table']->compile($connection, $queryPlaceholder); } + // Make sure join conditions are also compiled. + if (!empty($table['condition']) && $table['condition'] instanceof ConditionInterface) { + $table['condition']->compile($connection, $queryPlaceholder); + } } // If there are any dependent queries to UNION, compile it recursively. @@ -248,6 +260,11 @@ class Select extends Query implements SelectInterface { return FALSE; } } + if (!empty($table['condition']) && $table['condition'] instanceof ConditionInterface) { + if (!$table['condition']->compiled()) { + return FALSE; + } + } } foreach ($this->union as $union) { @@ -822,7 +839,7 @@ class Select extends Query implements SelectInterface { $query .= $table_string . ' ' . $this->connection->escapeTable($table['alias']); if (!empty($table['condition'])) { - $query .= ' ON ' . $table['condition']; + $query .= ' ON ' . (string) $table['condition']; } } diff --git a/core/lib/Drupal/Core/Datetime/Element/Datelist.php b/core/lib/Drupal/Core/Datetime/Element/Datelist.php index 76f61bb04..f39d0a290 100644 --- a/core/lib/Drupal/Core/Datetime/Element/Datelist.php +++ b/core/lib/Drupal/Core/Datetime/Element/Datelist.php @@ -268,6 +268,7 @@ class Datelist extends DateElementBase { '#options' => $options, '#required' => $element['#required'], '#error_no_message' => FALSE, + '#empty_option' => $title, ); } diff --git a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php index c0a874749..aa49cf737 100644 --- a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php +++ b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php @@ -58,8 +58,6 @@ class YamlFileLoader public function load($file) { // Load from the file cache, fall back to loading the file. - // @todo Refactor this to cache parsed definition objects in - // https://www.drupal.org/node/2464053 $content = $this->fileCache->get($file); if (!$content) { $content = $this->loadFile($file); diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 6e0b4d847..e89ce3164 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -306,7 +306,7 @@ abstract class Entity implements EntityInterface { // The entity ID is needed as a route parameter. $uri_route_parameters[$this->getEntityTypeId()] = $this->id(); } - if ($rel === 'revision') { + if ($rel === 'revision' && $this instanceof RevisionableInterface) { $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); } diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index ae436bbaa..3a38e072d 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -339,10 +339,17 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf public static function preCreate(EntityStorageInterface $storage, array &$values); /** - * Acts on an entity after it is created but before hooks are invoked. + * Acts on a created entity before hooks are invoked. + * + * Used after the entity is created, but before saving the entity and before + * any of the presave hooks are invoked. + * + * See the @link entity_crud Entity CRUD topic @endlink for more information. * * @param \Drupal\Core\Entity\EntityStorageInterface $storage * The entity storage object. + * + * @see \Drupal\Core\Entity\EntityInterface::create() */ public function postCreate(EntityStorageInterface $storage); diff --git a/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php index 4425d2872..882f98b3d 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php @@ -11,7 +11,10 @@ interface EntityTypeBundleInfoInterface { * Get the bundle info of all entity types. * * @return array - * An array of all bundle information. + * An array of bundle information where the outer array is keyed by entity + * type. The next level is keyed by the bundle name. The inner arrays are + * associative arrays of bundle information, such as the label for the + * bundle. */ public function getAllBundleInfo(); @@ -22,7 +25,10 @@ interface EntityTypeBundleInfoInterface { * The entity type. * * @return array - * Returns the bundle information for the specified entity type. + * An array of bundle information where the outer array is keyed by the + * bundle name, or the entity type name if the entity does not have bundles. + * The inner arrays are associative arrays of bundle information, such as + * the label for the bundle. */ public function getBundleInfo($entity_type); diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php index e673db1cf..17b266ffc 100644 --- a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php @@ -28,24 +28,47 @@ interface QueryInterface extends AlterableInterface { * and the Polish 'siema' within a 'greetings' text field: * @code * $entity_ids = \Drupal::entityQuery($entity_type) - * ->condition('greetings', 'merhaba', '=', 'tr'); - * ->condition('greetings.value', 'siema', '=', 'pl'); + * ->condition('greetings', 'merhaba', '=', 'tr') + * ->condition('greetings.value', 'siema', '=', 'pl') * ->execute(); * $entity_ids = $query->execute(); * @endcode * * @param $field - * Name of the field being queried. It must contain a field name, - * optionally followed by a column name. The column can be "entity" for - * reference fields and that can be followed similarly by a field name - * and so on. Some examples: + * Name of the field being queried. It must contain a field name, optionally + * followed by a column name. The column can be "entity" for reference + * fields and that can be followed similarly by a field name and so on. Some + * examples: * - nid * - tags.value * - tags * - uid.entity.name * "tags" "is the same as "tags.value" as value is the default column. * If two or more conditions have the same field names they apply to the - * same delta within that field. + * same delta within that field. In order to limit the condition to a + * specific item a numeric delta should be added between the field name and + * the column name. + * @code + * ->condition('tags.5.value', 'news') + * @endcode + * This will require condition to be satisfied on a specific delta of the + * field. The condition above will require the 6th value of the field to + * match the provided value. Further, it's possible to create a condition on + * the delta itself by using '%delta'. For example, + * @code + * ->condition('tags.%delta', 5) + * @endcode + * will find only entities which have at least six tags. Finally, the + * condition on the delta itself accompanied with a condition on the value + * will require the value to appear in the specific delta range. For + * example, + * @code + * ->condition('tags.%delta', 0, '>')) + * ->condition('tags.%delta.value', 'news')) + * @endcode + * will only find the "news" tag if it is not the first value. It should be + * noted that conditions on specific deltas and delta ranges are only + * supported when querying content entities. * @param $value * The value for $field. In most cases, this is a scalar and it's treated as * case-insensitive. For more complex operators, it is an array. The meaning diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php index ca25fb97a..c8bf59d15 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php @@ -5,6 +5,7 @@ namespace Drupal\Core\Entity\Query\Sql; use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Entity\Query\QueryException; use Drupal\Core\Entity\Sql\SqlEntityStorageInterface; +use Drupal\Core\Entity\Sql\TableMappingInterface; /** * Adds tables and fields to the SQL entity query. @@ -112,11 +113,37 @@ class Tables implements TablesInterface { // Check whether this field is stored in a dedicated table. if ($field_storage && $table_mapping->requiresDedicatedTableStorage($field_storage)) { + $delta = NULL; // Find the field column. $column = $field_storage->getMainPropertyName(); if ($key < $count) { $next = $specifiers[$key + 1]; + // If this is a numeric specifier we're adding a condition on the + // specific delta. + if (is_numeric($next)) { + $delta = $next; + $index_prefix .= ".$delta"; + // Do not process it again. + $key++; + $next = $specifiers[$key + 1]; + } + // If this specifier is the reserved keyword "%delta" we're adding a + // condition on a delta range. + elseif ($next == TableMappingInterface::DELTA) { + $index_prefix .= TableMappingInterface::DELTA; + // Do not process it again. + $key++; + // If there are more specifiers to work with then continue + // processing. If this is the last specifier then use the reserved + // keyword as a column name. + if ($key < $count) { + $next = $specifiers[$key + 1]; + } + else { + $column = TableMappingInterface::DELTA; + } + } // Is this a field column? $columns = $field_storage->getColumns(); if (isset($columns[$next]) || in_array($next, $table_mapping->getReservedColumns())) { @@ -140,7 +167,7 @@ class Tables implements TablesInterface { $next_index_prefix = "$relationship_specifier.$column"; } } - $table = $this->ensureFieldTable($index_prefix, $field_storage, $type, $langcode, $base_table, $entity_id_field, $field_id_field); + $table = $this->ensureFieldTable($index_prefix, $field_storage, $type, $langcode, $base_table, $entity_id_field, $field_id_field, $delta); $sql_column = $table_mapping->getFieldColumnName($field_storage, $column); $property_definitions = $field_storage->getPropertyDefinitions(); if (isset($property_definitions[$column])) { @@ -173,6 +200,27 @@ class Tables implements TablesInterface { // next one is a column of this field. if ($key < $count) { $next = $specifiers[$key + 1]; + // If this specifier is the reserved keyword "%delta" we're adding a + // condition on a delta range. + if ($next == TableMappingInterface::DELTA) { + $key++; + if ($key < $count) { + $next = $specifiers[$key + 1]; + } + else { + return 0; + } + } + // If this is a numeric specifier we're adding a condition on the + // specific delta. Since we know that this is a single value base + // field no other value than 0 makes sense. + if (is_numeric($next)) { + if ($next > 0) { + $this->sqlQuery->condition('1 <> 1'); + } + $key++; + $next = $specifiers[$key + 1]; + } // Is this a field column? $columns = $field_storage->getColumns(); if (isset($columns[$next]) || in_array($next, $table_mapping->getReservedColumns())) { @@ -264,7 +312,7 @@ class Tables implements TablesInterface { * @return string * @throws \Drupal\Core\Entity\Query\QueryException */ - protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $base_table, $entity_id_field, $field_id_field) { + protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $base_table, $entity_id_field, $field_id_field, $delta) { $field_name = $field->getName(); if (!isset($this->fieldTables[$index_prefix . $field_name])) { $entity_type_id = $this->sqlQuery->getMetaData('entity_type'); @@ -274,12 +322,12 @@ class Tables implements TablesInterface { if ($field->getCardinality() != 1) { $this->sqlQuery->addMetaData('simple_query', FALSE); } - $this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = $base_table.$entity_id_field", $langcode); + $this->fieldTables[$index_prefix . $field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = $base_table.$entity_id_field", $langcode, $delta); } return $this->fieldTables[$index_prefix . $field_name]; } - protected function addJoin($type, $table, $join_condition, $langcode) { + protected function addJoin($type, $table, $join_condition, $langcode, $delta = NULL) { $arguments = array(); if ($langcode) { $entity_type_id = $this->sqlQuery->getMetaData('entity_type'); @@ -291,6 +339,11 @@ class Tables implements TablesInterface { $join_condition .= ' AND %alias.' . $langcode_key . ' = ' . $placeholder; $arguments[$placeholder] = $langcode; } + if (isset($delta)) { + $placeholder = ':delta' . $this->sqlQuery->nextPlaceholder(); + $join_condition .= ' AND %alias.delta = ' . $placeholder; + $arguments[$placeholder] = $delta; + } return $this->sqlQuery->addJoin($type, $table, NULL, $join_condition, $arguments); } diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php b/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php index 214e130fd..bd8e0de36 100644 --- a/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/Sql/TablesInterface.php @@ -11,8 +11,10 @@ interface TablesInterface { * Adds a field to a database query. * * @param string $field - * If it contains a dot, then field name dot field column. If it doesn't - * then entity property name. + * If it doesn't contain a dot, then an entity base field name. If it + * contains a dot, then either field name dot field column or field name dot + * delta dot field column. Delta can be a numeric value or a "%delta" for + * any value. * @param string $type * Join type, can either be INNER or LEFT. * @param string $langcode diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php index e425fde35..5f20c3f08 100644 --- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php +++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php @@ -204,7 +204,12 @@ class DefaultTableMapping implements TableMappingInterface { $column_name = count($storage_definition->getColumns()) == 1 ? $field_name : $field_name . '__' . $property_name; } elseif ($this->requiresDedicatedTableStorage($storage_definition)) { - $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name; + if ($property_name == TableMappingInterface::DELTA) { + $column_name = 'delta'; + } + else { + $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name; + } } else { throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field."); diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index d58445477..391e3444a 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -150,7 +150,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt * The database connection to be used. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend to be used. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index d76c1329e..ec3ebaf00 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -1837,6 +1837,24 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage } } + // Add unique keys. + foreach ($schema['unique keys'] as $index_name => $columns) { + $real_name = $this->getFieldIndexName($storage_definition, $index_name); + foreach ($columns as $column_name) { + // Unique keys can be specified as either a column name or an array with + // column name and length. Allow for either case. + if (is_array($column_name)) { + $data_schema['unique keys'][$real_name][] = array( + $table_mapping->getFieldColumnName($storage_definition, $column_name[0]), + $column_name[1], + ); + } + else { + $data_schema['unique keys'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name); + } + } + } + // Add foreign keys. foreach ($schema['foreign keys'] as $specifier => $specification) { $real_name = $this->getFieldIndexName($storage_definition, $specifier); diff --git a/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php b/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php index f21c2700d..3c553bf00 100644 --- a/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php +++ b/core/lib/Drupal/Core/Entity/Sql/TableMappingInterface.php @@ -19,6 +19,11 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; */ interface TableMappingInterface { + /** + * A property that represents delta used in entity query conditions. + */ + const DELTA = '%delta'; + /** * Gets a list of table names for this mapping. * diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php index 873996b02..57d43ec0b 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -3,7 +3,6 @@ namespace Drupal\Core\EventSubscriber; use Drupal\Component\Utility\SafeMarkup; -use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Routing\RouteMatch; @@ -13,6 +12,7 @@ use Drupal\Core\Site\MaintenanceModeInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -90,18 +90,25 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { * The event to process. */ public function onKernelRequestMaintenance(GetResponseEvent $event) { - $route_match = RouteMatch::createFromRequest($event->getRequest()); + $request = $event->getRequest(); + $route_match = RouteMatch::createFromRequest($request); if ($this->maintenanceMode->applies($route_match)) { // Don't cache maintenance mode pages. \Drupal::service('page_cache_kill_switch')->trigger(); + if (!$this->maintenanceMode->exempt($this->account)) { // Deliver the 503 page if the site is in maintenance mode and the // logged in user is not allowed to bypass it. + + // If the request format is not 'html' then show default maintenance + // mode page else show a text/plain page with maintenance message. + if ($request->getRequestFormat() !== 'html') { + $response = new Response($this->getSiteMaintenanceMessage(), 503, array('Content-Type' => 'text/plain')); + $event->setResponse($response); + return; + } drupal_maintenance_theme(); - $content = Xss::filterAdmin(SafeMarkup::format($this->config->get('system.maintenance')->get('message'), array( - '@site' => $this->config->get('system.site')->get('name'), - ))); - $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Site under maintenance'), 'maintenance_page'); + $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $this->getSiteMaintenanceMessage()], $this->t('Site under maintenance'), 'maintenance_page'); $response->setStatusCode(503); $event->setResponse($response); } @@ -121,6 +128,18 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { } } + /** + * Gets the site maintenance message. + * + * @return \Drupal\Component\Render\MarkupInterface + * The formatted site maintenance message. + */ + protected function getSiteMaintenanceMessage() { + return SafeMarkup::format($this->config->get('system.maintenance')->get('message'), array( + '@site' => $this->config->get('system.site')->get('name'), + )); + } + /** * Wraps the drupal_set_message function. */ diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index 124efa2ed..e0336dd0a 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -214,7 +214,7 @@ interface ModuleHandlerInterface { * The name of the module (without the .module extension). * @param string $hook * The name of the hook to invoke. - * @param ... + * @param array $args * Arguments to pass to the hook implementation. * * @return mixed diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php index 37f8b7759..b73238763 100644 --- a/core/lib/Drupal/Core/Extension/module.api.php +++ b/core/lib/Drupal/Core/Extension/module.api.php @@ -72,6 +72,8 @@ use Drupal\Core\Utility\UpdateException; * frequently called should be left in the main module file so that they are * always available. * + * See system_hook_info() for all hook groups defined by Drupal core. + * * @return * An associative array whose keys are hook names and whose values are an * associative array containing: @@ -79,8 +81,6 @@ use Drupal\Core\Utility\UpdateException; * system will determine whether a file with the name $module.$group.inc * exists, and automatically load it when required. * - * See system_hook_info() for all hook groups defined by Drupal core. - * * @see hook_hook_info_alter() */ function hook_hook_info() { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php index 1df00cb4f..f86eac7e5 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/PasswordItem.php @@ -28,6 +28,8 @@ class PasswordItem extends StringItem { ->setSetting('case_sensitive', TRUE); $properties['existing'] = DataDefinition::create('string') ->setLabel(new TranslatableMarkup('Existing password')); + $properties['pre_hashed'] = DataDefinition::create('boolean') + ->setLabel(new TranslatableMarkup('Determines if a password needs hashing')); return $properties; } @@ -40,8 +42,11 @@ class PasswordItem extends StringItem { $entity = $this->getEntity(); - // Update the user password if it has changed. - if ($entity->isNew() || (strlen(trim($this->value)) > 0 && $this->value != $entity->original->{$this->getFieldDefinition()->getName()}->value)) { + if ($this->pre_hashed) { + // Reset the pre_hashed value since it has now been used. + $this->pre_hashed = FALSE; + } + elseif ($entity->isNew() || (strlen(trim($this->value)) > 0 && $this->value != $entity->original->{$this->getFieldDefinition()->getName()}->value)) { // Allow alternate password hashing schemes. $this->value = \Drupal::service('password')->hash(trim($this->value)); // Abort if the hashing failed and returned FALSE. diff --git a/core/lib/Drupal/Core/Form/FormBase.php b/core/lib/Drupal/Core/Form/FormBase.php index c33f2a167..4a020dc2a 100644 --- a/core/lib/Drupal/Core/Form/FormBase.php +++ b/core/lib/Drupal/Core/Form/FormBase.php @@ -15,7 +15,29 @@ use Symfony\Component\HttpFoundation\RequestStack; /** * Provides a base class for forms. * + * This class exists as a mid-point between dependency injection through + * ContainerInjectionInterface, and a less-structured use of traits which + * default to using the \Drupal accessor for service discovery. + * + * To properly inject services, override create() and use the setters provided + * by the traits to inject the needed services. + * + * @code + * public static function create($container) { + * $form = new static(); + * // In this example we only need string translation so we use the + * // setStringTranslation() method provided by StringTranslationTrait. + * $form->setStringTranslation($container->get('string_translation')); + * return $form; + * } + * @endcode + * + * Alternately, do not use FormBase. A class can implement FormInterface, use + * the traits it needs, and inject services from the container as required. + * * @ingroup form_api + * + * @see \Drupal\Core\DependencyInjection\ContainerInjectionInterface */ abstract class FormBase implements FormInterface, ContainerInjectionInterface { diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php index 334e10be4..ca794379d 100644 --- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php +++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php @@ -137,9 +137,9 @@ interface FormBuilderInterface { * by calling $form_state->getErrors(). * * @param \Drupal\Core\Form\FormInterface|string $form_arg - * A form object to use to build the form, or the unique string identifying - * the desired form. If $form_arg is a string and a function with that - * name exists, it is called to build the form array. + * The value must be one of the following: + * - The name of a class that implements \Drupal\Core\Form\FormInterface. + * - An instance of a class that implements \Drupal\Core\Form\FormInterface. * @param $form_state * The current state of the form. Most important is the * $form_state->getValues() collection, a tree of data used to simulate the diff --git a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php index ab7359826..05ba8a83d 100644 --- a/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php +++ b/core/lib/Drupal/Core/Plugin/ContextAwarePluginBase.php @@ -41,11 +41,11 @@ abstract class ContextAwarePluginBase extends ComponentContextAwarePluginBase im /** * {@inheritdoc} * - * @return \Drupal\Core\Plugin\Context\ContextInterface - * The context object. - * * This code is identical to the Component in order to pick up a different * Context class. + * + * @return \Drupal\Core\Plugin\Context\ContextInterface + * The context object. */ public function getContext($name) { // Check for a valid context value. diff --git a/core/lib/Drupal/Core/Render/Element/Checkboxes.php b/core/lib/Drupal/Core/Render/Element/Checkboxes.php index 8487a0960..1ca1aea5d 100644 --- a/core/lib/Drupal/Core/Render/Element/Checkboxes.php +++ b/core/lib/Drupal/Core/Render/Element/Checkboxes.php @@ -17,7 +17,7 @@ use Drupal\Core\Form\FormStateInterface; * @code * $form['high_school']['tests_taken'] = array( * '#type' => 'checkboxes', - * '#options' => array('SAT' => $this->t('SAT'), 'ACT' => $this->t('ACT'))), + * '#options' => array('SAT' => $this->t('SAT'), 'ACT' => $this->t('ACT')), * '#title' => $this->t('What standardized tests did you take?'), * ... * ); diff --git a/core/lib/Drupal/Core/Render/Element/StatusMessages.php b/core/lib/Drupal/Core/Render/Element/StatusMessages.php index 55990e6b9..f026aa5c5 100644 --- a/core/lib/Drupal/Core/Render/Element/StatusMessages.php +++ b/core/lib/Drupal/Core/Render/Element/StatusMessages.php @@ -12,7 +12,7 @@ namespace Drupal\Core\Render\Element; * $build['status_messages'] = [ * '#type' => 'status_messages', * ]; - * @end + * @endcode * * @RenderElement("status_messages") */ diff --git a/core/lib/Drupal/Core/Render/RenderCacheInterface.php b/core/lib/Drupal/Core/Render/RenderCacheInterface.php index f31b33037..86f38d358 100644 --- a/core/lib/Drupal/Core/Render/RenderCacheInterface.php +++ b/core/lib/Drupal/Core/Render/RenderCacheInterface.php @@ -64,7 +64,7 @@ interface RenderCacheInterface { * this array. * * @return bool|null - * Returns FALSE if no cache item could be created, NULL otherwise. + * Returns FALSE if no cache item could be created, NULL otherwise. * * @see ::get() */ diff --git a/core/lib/Drupal/Core/Routing/RouteBuilderInterface.php b/core/lib/Drupal/Core/Routing/RouteBuilderInterface.php index 83b1d3ed1..d191cb9a6 100644 --- a/core/lib/Drupal/Core/Routing/RouteBuilderInterface.php +++ b/core/lib/Drupal/Core/Routing/RouteBuilderInterface.php @@ -2,10 +2,27 @@ namespace Drupal\Core\Routing; +/** + * Rebuilds the route information and dumps it. + * + * Rebuilding the route information is the process of gathering all routing data + * from .routing.yml files, creating a + * \Symfony\Component\Routing\RouteCollection object out of it, and dispatching + * that object as a \Drupal\Core\Routing\RouteBuildEvent to all registered + * listeners. After that, the \Symfony\Component\Routing\RouteCollection object + * is used to dump the data. Examples of a dump include filling up the routing + * table, auto-generating Apache mod_rewrite rules, or auto-generating a PHP + * matcher class. + * + * @see \Drupal\Core\Routing\MatcherDumperInterface + * @see \Drupal\Core\Routing\RouteProviderInterface + * + * @ingroup routing + */ interface RouteBuilderInterface { /** - * Rebuilds the route info and dumps to dumper. + * Rebuilds the route information and dumps it. * * @return bool * Returns TRUE if the rebuild succeeds, FALSE otherwise. @@ -13,7 +30,7 @@ interface RouteBuilderInterface { public function rebuild(); /** - * Rebuilds the route info and dumps to dumper if necessary. + * Rebuilds the route information if necessary, and dumps it. * * @return bool * Returns TRUE if the rebuild occurs, FALSE otherwise. diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php index 9291c40fd..ccf2b60d8 100644 --- a/core/lib/Drupal/Core/Routing/UrlGenerator.php +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php @@ -229,10 +229,8 @@ class UrlGenerator implements UrlGeneratorInterface { // Add a query string if needed, including extra parameters. $query_params += array_diff_key($parameters, $variables, $defaults); - if ($query_params && $query = http_build_query($query_params, '', '&')) { - // "/" and "?" can be left decoded for better user experience, see - // http://tools.ietf.org/html/rfc3986#section-3.4 - $url .= '?' . strtr($query, array('%2F' => '/')); + if ($query_params && $query = UrlHelper::buildQuery($query_params)) { + $url .= '?' . $query; } return $url; @@ -253,7 +251,7 @@ class UrlGenerator implements UrlGeneratorInterface { * $parameters merged in. * * @return string - * The url path corresponding to the route, without the base path. + * The url path corresponding to the route, without the base path. */ protected function getInternalPathFromRoute($name, SymfonyRoute $route, $parameters = array(), $query_params = array()) { // The Route has a cache of its own and is not recompiled as long as it does diff --git a/core/lib/Drupal/Core/Routing/UrlGeneratorInterface.php b/core/lib/Drupal/Core/Routing/UrlGeneratorInterface.php index f5174f1d2..70829cd68 100644 --- a/core/lib/Drupal/Core/Routing/UrlGeneratorInterface.php +++ b/core/lib/Drupal/Core/Routing/UrlGeneratorInterface.php @@ -21,7 +21,7 @@ interface UrlGeneratorInterface extends VersatileGeneratorInterface { * \Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(). * * @return string - * The internal Drupal path corresponding to the route. + * The internal Drupal path corresponding to the route. */ public function getPathFromRoute($name, $parameters = array()); diff --git a/core/lib/Drupal/Core/StreamWrapper/PhpStreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/PhpStreamWrapperInterface.php index ed8db6465..4c11c8d66 100644 --- a/core/lib/Drupal/Core/StreamWrapper/PhpStreamWrapperInterface.php +++ b/core/lib/Drupal/Core/StreamWrapper/PhpStreamWrapperInterface.php @@ -64,7 +64,7 @@ interface PhpStreamWrapperInterface { public function stream_cast($cast_as); /** - * @return void + * Closes stream. */ public function stream_close(); diff --git a/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php b/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php index d4c46e3ef..abe5e9a7b 100644 --- a/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php +++ b/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php @@ -82,7 +82,7 @@ class FileTranslation extends StaticTranslation { * want to find translation files. * * @return string - * String file pattern. + * String file pattern. */ protected function getTranslationFilesPattern($langcode = NULL) { // The file name matches: drupal-[release version].[language code].po diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index f29e14697..6a58319e0 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -272,7 +272,11 @@ class Url { if ($uri_parts === FALSE) { throw new \InvalidArgumentException("The URI '$uri' is malformed."); } - if (empty($uri_parts['scheme'])) { + // We support protocol-relative URLs. + if (strpos($uri, '//') === 0) { + $uri_parts['scheme'] = ''; + } + elseif (empty($uri_parts['scheme'])) { throw new \InvalidArgumentException("The URI '$uri' is invalid. You must use a valid URI scheme."); } $uri_parts += ['path' => '']; diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index 7acb83815..496100133 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -140,6 +140,7 @@ class LinkGenerator implements LinkGeneratorInterface { // Allow other modules to modify the structure of the link. $this->moduleHandler->alter('link', $variables); + $url = $variables['url']; // Move attributes out of options since generateFromRoute() doesn't need // them. Include a placeholder for the href. diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js index 032a15ca3..254e7e509 100644 --- a/core/misc/autocomplete.js +++ b/core/misc/autocomplete.js @@ -222,7 +222,7 @@ }); // Use jQuery UI Autocomplete on the textfield. $autocomplete.autocomplete(autocomplete.options) - .each(function() { + .each(function () { $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem; }); } diff --git a/core/modules/action/src/Tests/ActionUninstallTest.php b/core/modules/action/tests/src/Functional/ActionUninstallTest.php similarity index 87% rename from core/modules/action/src/Tests/ActionUninstallTest.php rename to core/modules/action/tests/src/Functional/ActionUninstallTest.php index fd8faa5c9..25dd564a8 100644 --- a/core/modules/action/src/Tests/ActionUninstallTest.php +++ b/core/modules/action/tests/src/Functional/ActionUninstallTest.php @@ -1,8 +1,8 @@ drupalGet('test_bulk_form'); $result = $this->xpath('//label[@for="edit-action"]'); - $this->assertEqual('With selection', (string) $result[0]); + $this->assertEqual('With selection', $result[0]->getText()); // Setup up a different bulk form title. $view = Views::getView('test_bulk_form'); @@ -133,7 +133,7 @@ class BulkFormTest extends WebTestBase { $this->drupalGet('test_bulk_form'); $result = $this->xpath('//label[@for="edit-action"]'); - $this->assertEqual('Test title', (string) $result[0]); + $this->assertEqual('Test title', $result[0]->getText()); $this->drupalGet('test_bulk_form'); // Call the node delete action. diff --git a/core/modules/action/src/Tests/ConfigurationTest.php b/core/modules/action/tests/src/Functional/ConfigurationTest.php similarity index 96% rename from core/modules/action/src/Tests/ConfigurationTest.php rename to core/modules/action/tests/src/Functional/ConfigurationTest.php index a6b20502f..17d913de6 100644 --- a/core/modules/action/src/Tests/ConfigurationTest.php +++ b/core/modules/action/tests/src/Functional/ConfigurationTest.php @@ -1,9 +1,9 @@ select('aggregator_item', 'ai') ->fields('ai') - ->orderBy('iid'); + ->orderBy('ai.iid'); } /** diff --git a/core/modules/ban/src/Tests/IpAddressBlockingTest.php b/core/modules/ban/tests/src/Functional/IpAddressBlockingTest.php similarity index 94% rename from core/modules/ban/src/Tests/IpAddressBlockingTest.php rename to core/modules/ban/tests/src/Functional/IpAddressBlockingTest.php index 9fb9ff850..738bddfb5 100644 --- a/core/modules/ban/src/Tests/IpAddressBlockingTest.php +++ b/core/modules/ban/tests/src/Functional/IpAddressBlockingTest.php @@ -1,15 +1,15 @@ drupalPostForm('admin/config/people/ban/' . $submit_ip, NULL, t('Add')); + $this->drupalPostForm('admin/config/people/ban/' . $submit_ip, array(), t('Add')); $ip = db_query("SELECT iid from {ban_ip} WHERE ip = :ip", array(':ip' => $submit_ip))->fetchField(); $this->assertTrue($ip, 'IP address found in database'); $this->assertRaw(t('The IP address %ip has been banned.', array('%ip' => $submit_ip)), 'IP address was banned.'); diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php index 3e67737f5..5e3cc1203 100644 --- a/core/modules/big_pipe/src/Render/BigPipe.php +++ b/core/modules/big_pipe/src/Render/BigPipe.php @@ -226,6 +226,13 @@ class BigPipe implements BigPipeInterface { $preg_placeholder_strings = array_map($prepare_for_preg_split, array_keys($no_js_placeholders)); $fragments = preg_split('/' . implode('|', $preg_placeholder_strings) . '/', $html, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + // Determine how many occurrences there are of each no-JS placeholder. + $placeholder_occurrences = array_count_values(array_intersect($fragments, array_keys($no_js_placeholders))); + + // Set up a variable to store the content of placeholders that have multiple + // occurrences. + $multi_occurrence_placeholders_content = []; + foreach ($fragments as $fragment) { // If the fragment isn't one of the no-JS placeholders, it is the HTML in // between placeholders and it must be printed & flushed immediately. The @@ -236,6 +243,15 @@ class BigPipe implements BigPipeInterface { continue; } + // If there are multiple occurrences of this particular placeholder, and + // this is the second occurrence, we can skip all calculations and just + // send the same content. + if ($placeholder_occurrences[$fragment] > 1 && isset($multi_occurrence_placeholders_content[$fragment])) { + print $multi_occurrence_placeholders_content[$fragment]; + flush(); + continue; + } + $placeholder = $fragment; assert('isset($no_js_placeholders[$placeholder])'); $token = Crypt::randomBytesBase64(55); @@ -310,6 +326,13 @@ class BigPipe implements BigPipeInterface { // they can be sent in ::sendPreBody(). $cumulative_assets->setAlreadyLoadedLibraries(array_merge($cumulative_assets->getAlreadyLoadedLibraries(), $html_response->getAttachments()['library'])); $cumulative_assets->setSettings($html_response->getAttachments()['drupalSettings']); + + // If there are multiple occurrences of this particular placeholder, track + // the content that was sent, so we can skip all calculations for the next + // occurrence. + if ($placeholder_occurrences[$fragment] > 1) { + $multi_occurrence_placeholders_content[$fragment] = $html_response->getContent(); + } } } @@ -508,7 +531,9 @@ EOF; * * @return array * Indexed array; the order in which the BigPipe placeholders must be sent. - * Values are the BigPipe placeholder IDs. + * Values are the BigPipe placeholder IDs. Note that only unique + * placeholders are kept: if the same placeholder occurs multiple times, we + * only keep the first occurrence. */ protected function getPlaceholderOrder($html) { $fragments = explode('
'; + $this->assertRaw('The count is 1.'); + $this->assertNoRaw('The count is 2.'); + $this->assertNoRaw('The count is 3.'); + $raw_content = $this->getRawContent(); + $this->assertTrue(substr_count($raw_content, $expected_placeholder_replacement) == 1, 'Only one placeholder replacement was found for the duplicate #lazy_builder arrays.'); + + // By calling performMetaRefresh() here, we simulate JavaScript being + // disabled, because as far as the BigPipe module is concerned, it is + // enabled in the browser when the BigPipe no-JS cookie is set. + // @see setUp() + // @see performMetaRefresh() + $this->performMetaRefresh(); + $this->assertBigPipeNoJsCookieExists(TRUE); + $this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence')); + $this->assertRaw('The count is 1.'); + $this->assertNoRaw('The count is 2.'); + $this->assertNoRaw('The count is 3.'); + } + protected function assertBigPipeResponseHeadersPresent() { $this->pass('Verifying BigPipe response headers…', 'Debug'); $this->assertTrue(FALSE !== strpos($this->drupalGetHeader('Cache-Control'), 'private'), 'Cache-Control header set to "private".'); diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml b/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml index 4979406d5..710506063 100644 --- a/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml +++ b/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml @@ -15,3 +15,12 @@ no_big_pipe: _no_big_pipe: TRUE requirements: _access: 'TRUE' + +big_pipe_test_multi_occurrence: + path: '/big_pipe_test_multi_occurrence' + defaults: + _controller: '\Drupal\big_pipe_test\BigPipeTestController::multiOccurrence' + _title: 'BigPipe test multiple occurrences of the same placeholder' + requirements: + _access: 'TRUE' + diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php index 450a464bd..30594a555 100644 --- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php +++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php @@ -52,6 +52,30 @@ class BigPipeTestController { return ['#markup' => '

Nope.

']; } + /** + * A page with multiple occurrences of the same placeholder. + * + * @see \Drupal\big_pipe\Tests\BigPipeTest::testBigPipeMultipleOccurrencePlaceholders() + * + * @return array + */ + public function multiOccurrence() { + return [ + 'item1' => [ + '#lazy_builder' => [static::class . '::counter', []], + '#create_placeholder' => TRUE, + ], + 'item2' => [ + '#lazy_builder' => [static::class . '::counter', []], + '#create_placeholder' => TRUE, + ], + 'item3' => [ + '#lazy_builder' => [static::class . '::counter', []], + '#create_placeholder' => TRUE, + ], + ]; + } + /** * #lazy_builder callback; builds