summaryrefslogtreecommitdiffstats
path: root/core/lib
diff options
context:
space:
mode:
Diffstat (limited to 'core/lib')
-rw-r--r--core/lib/Drupal.php36
-rw-r--r--core/lib/Drupal/Component/Annotation/composer.json4
-rw-r--r--core/lib/Drupal/Component/Assertion/Inspector.php16
-rw-r--r--core/lib/Drupal/Component/Assertion/composer.json2
-rw-r--r--core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php31
-rw-r--r--core/lib/Drupal/Component/Bridge/composer.json2
-rw-r--r--core/lib/Drupal/Component/ClassFinder/composer.json4
-rw-r--r--core/lib/Drupal/Component/Datetime/DateTimePlus.php31
-rw-r--r--core/lib/Drupal/Component/Datetime/composer.json2
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php14
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php2
-rw-r--r--core/lib/Drupal/Component/DependencyInjection/composer.json4
-rw-r--r--core/lib/Drupal/Component/Diff/Engine/DiffEngine.php62
-rw-r--r--core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php4
-rw-r--r--core/lib/Drupal/Component/Diff/WordLevelDiff.php3
-rw-r--r--core/lib/Drupal/Component/Diff/composer.json4
-rw-r--r--core/lib/Drupal/Component/Discovery/composer.json2
-rw-r--r--core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php65
-rw-r--r--core/lib/Drupal/Component/EventDispatcher/composer.json6
-rw-r--r--core/lib/Drupal/Component/FileCache/composer.json2
-rw-r--r--core/lib/Drupal/Component/FileSystem/composer.json2
-rw-r--r--core/lib/Drupal/Component/Gettext/PoHeader.php48
-rw-r--r--core/lib/Drupal/Component/Gettext/PoItem.php75
-rw-r--r--core/lib/Drupal/Component/Gettext/PoMemoryWriter.php8
-rw-r--r--core/lib/Drupal/Component/Gettext/PoStreamReader.php220
-rw-r--r--core/lib/Drupal/Component/Gettext/PoStreamWriter.php51
-rw-r--r--core/lib/Drupal/Component/Gettext/composer.json2
-rw-r--r--core/lib/Drupal/Component/Graph/composer.json2
-rw-r--r--core/lib/Drupal/Component/HttpFoundation/SecuredRedirectResponse.php2
-rw-r--r--core/lib/Drupal/Component/HttpFoundation/composer.json4
-rw-r--r--core/lib/Drupal/Component/PhpStorage/FileStorage.php45
-rw-r--r--core/lib/Drupal/Component/PhpStorage/composer.json2
-rw-r--r--core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionInterface.php71
-rw-r--r--core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionTrait.php60
-rw-r--r--core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php2
-rw-r--r--core/lib/Drupal/Component/Plugin/Exception/MissingValueContextException.php21
-rw-r--r--core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php2
-rw-r--r--core/lib/Drupal/Component/Plugin/PluginBase.php2
-rw-r--r--core/lib/Drupal/Component/Plugin/PluginManagerBase.php24
-rw-r--r--core/lib/Drupal/Component/Plugin/composer.json4
-rw-r--r--core/lib/Drupal/Component/ProxyBuilder/composer.json2
-rw-r--r--core/lib/Drupal/Component/Render/FormattableMarkup.php10
-rw-r--r--core/lib/Drupal/Component/Render/HtmlEscapedText.php3
-rw-r--r--core/lib/Drupal/Component/Render/MarkupTrait.php4
-rw-r--r--core/lib/Drupal/Component/Render/composer.json2
-rw-r--r--core/lib/Drupal/Component/Serialization/LICENSE.txt339
-rw-r--r--core/lib/Drupal/Component/Serialization/YamlPecl.php2
-rw-r--r--core/lib/Drupal/Component/Serialization/composer.json4
-rw-r--r--core/lib/Drupal/Component/Transliteration/LICENSE.txt339
-rw-r--r--core/lib/Drupal/Component/Transliteration/composer.json2
-rw-r--r--core/lib/Drupal/Component/Transliteration/data/x03.php10
-rw-r--r--core/lib/Drupal/Component/Transliteration/data/x04.php12
-rw-r--r--core/lib/Drupal/Component/Utility/Color.php26
-rw-r--r--core/lib/Drupal/Component/Utility/Html.php19
-rw-r--r--core/lib/Drupal/Component/Utility/Random.php1
-rw-r--r--core/lib/Drupal/Component/Utility/SafeMarkup.php3
-rw-r--r--core/lib/Drupal/Component/Utility/Unicode.php237
-rw-r--r--core/lib/Drupal/Component/Utility/UrlHelper.php12
-rw-r--r--core/lib/Drupal/Component/Utility/composer.json6
-rw-r--r--core/lib/Drupal/Component/Uuid/Com.php1
-rw-r--r--core/lib/Drupal/Component/Uuid/composer.json4
-rw-r--r--core/lib/Drupal/Core/Access/AccessManagerInterface.php2
-rw-r--r--core/lib/Drupal/Core/Access/AccessResult.php27
-rw-r--r--core/lib/Drupal/Core/Access/AccessResultForbidden.php1
-rw-r--r--core/lib/Drupal/Core/Access/CheckProvider.php1
-rw-r--r--core/lib/Drupal/Core/Access/CheckProviderInterface.php1
-rw-r--r--core/lib/Drupal/Core/Access/CustomAccessCheck.php9
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php99
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php99
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityChangedActionDeriver.php22
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php40
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityPublishedActionDeriver.php23
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/EntityActionBase.php62
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/PublishAction.php38
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php79
-rw-r--r--core/lib/Drupal/Core/Action/Plugin/Action/UnpublishAction.php38
-rw-r--r--core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php56
-rw-r--r--core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php39
-rw-r--r--core/lib/Drupal/Core/Ajax/OpenDialogCommand.php2
-rw-r--r--core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php1
-rw-r--r--core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php7
-rw-r--r--core/lib/Drupal/Core/Annotation/ContextDefinition.php28
-rw-r--r--core/lib/Drupal/Core/Archiver/ArchiveTar.php8
-rw-r--r--core/lib/Drupal/Core/Asset/CssOptimizer.php4
-rw-r--r--core/lib/Drupal/Core/Asset/JsOptimizer.php2
-rw-r--r--core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php2
-rw-r--r--core/lib/Drupal/Core/Asset/LibraryDiscovery.php2
-rw-r--r--core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php6
-rw-r--r--core/lib/Drupal/Core/Batch/BatchBuilder.php340
-rw-r--r--core/lib/Drupal/Core/Block/BlockBase.php5
-rw-r--r--core/lib/Drupal/Core/Block/BlockManager.php34
-rw-r--r--core/lib/Drupal/Core/Block/BlockManagerInterface.php3
-rw-r--r--core/lib/Drupal/Core/Block/MessagesBlockPluginInterface.php3
-rw-r--r--core/lib/Drupal/Core/Block/Plugin/Block/Broken.php2
-rw-r--r--core/lib/Drupal/Core/Block/Plugin/Block/PageTitleBlock.php3
-rw-r--r--core/lib/Drupal/Core/Cache/ApcuBackend.php4
-rw-r--r--core/lib/Drupal/Core/Cache/Cache.php7
-rw-r--r--core/lib/Drupal/Core/Cache/CacheBackendInterface.php2
-rw-r--r--core/lib/Drupal/Core/Cache/CacheCollector.php3
-rw-r--r--core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php3
-rw-r--r--core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php2
-rw-r--r--core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php2
-rw-r--r--core/lib/Drupal/Core/Cache/Context/SiteCacheContext.php2
-rw-r--r--core/lib/Drupal/Core/Cache/DatabaseBackend.php3
-rw-r--r--core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php2
-rw-r--r--core/lib/Drupal/Core/Cache/MemoryBackend.php4
-rw-r--r--core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php63
-rw-r--r--core/lib/Drupal/Core/Cache/MemoryCache/MemoryCacheInterface.php18
-rw-r--r--core/lib/Drupal/Core/Cache/PhpBackend.php4
-rw-r--r--core/lib/Drupal/Core/Command/DbDumpCommand.php8
-rw-r--r--core/lib/Drupal/Core/Command/InstallCommand.php337
-rw-r--r--core/lib/Drupal/Core/Command/QuickStartCommand.php76
-rw-r--r--core/lib/Drupal/Core/Command/ServerCommand.php278
-rw-r--r--core/lib/Drupal/Core/Composer/Composer.php50
-rw-r--r--core/lib/Drupal/Core/Condition/ConditionManager.php14
-rw-r--r--core/lib/Drupal/Core/Config/Config.php45
-rw-r--r--core/lib/Drupal/Core/Config/ConfigImporter.php16
-rw-r--r--core/lib/Drupal/Core/Config/ConfigInstaller.php44
-rw-r--r--core/lib/Drupal/Core/Config/ConfigInstallerInterface.php3
-rw-r--r--core/lib/Drupal/Core/Config/ConfigManager.php81
-rw-r--r--core/lib/Drupal/Core/Config/DatabaseStorage.php1
-rw-r--r--core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php4
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php15
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php2
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php77
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php50
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php11
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityUpdater.php119
-rw-r--r--core/lib/Drupal/Core/Config/Entity/DraggableListBuilder.php2
-rw-r--r--core/lib/Drupal/Core/Config/Entity/Query/Condition.php7
-rw-r--r--core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php4
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php1
-rw-r--r--core/lib/Drupal/Core/Config/FileStorage.php2
-rw-r--r--core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php77
-rw-r--r--core/lib/Drupal/Core/Config/PreExistingConfigException.php6
-rw-r--r--core/lib/Drupal/Core/Config/UnmetDependenciesException.php2
-rw-r--r--core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php47
-rw-r--r--core/lib/Drupal/Core/Controller/ArgumentResolver/RawParameterValueResolver.php28
-rw-r--r--core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php30
-rw-r--r--core/lib/Drupal/Core/Controller/ControllerBase.php6
-rw-r--r--core/lib/Drupal/Core/Controller/ControllerResolver.php5
-rw-r--r--core/lib/Drupal/Core/Controller/FormController.php29
-rw-r--r--core/lib/Drupal/Core/Controller/HtmlFormController.php11
-rw-r--r--core/lib/Drupal/Core/Controller/TitleResolver.php15
-rw-r--r--core/lib/Drupal/Core/Database/Connection.php127
-rw-r--r--core/lib/Drupal/Core/Database/Database.php96
-rw-r--r--core/lib/Drupal/Core/Database/Driver/mysql/Connection.php328
-rw-r--r--core/lib/Drupal/Core/Database/Driver/mysql/Insert.php4
-rw-r--r--core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php14
-rw-r--r--core/lib/Drupal/Core/Database/Driver/mysql/Schema.php66
-rw-r--r--core/lib/Drupal/Core/Database/Driver/mysql/Upsert.php3
-rw-r--r--core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php2
-rw-r--r--core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php2
-rw-r--r--core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php64
-rw-r--r--core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php47
-rw-r--r--core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php2
-rw-r--r--core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php92
-rw-r--r--core/lib/Drupal/Core/Database/Driver/sqlite/Select.php1
-rw-r--r--core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php2
-rw-r--r--core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php1
-rw-r--r--core/lib/Drupal/Core/Database/Install/Tasks.php2
-rw-r--r--core/lib/Drupal/Core/Database/Query/Condition.php3
-rw-r--r--core/lib/Drupal/Core/Database/Query/Merge.php2
-rw-r--r--core/lib/Drupal/Core/Database/Query/Query.php2
-rw-r--r--core/lib/Drupal/Core/Database/Query/Select.php11
-rw-r--r--core/lib/Drupal/Core/Database/Query/SelectExtender.php2
-rw-r--r--core/lib/Drupal/Core/Database/Query/SelectInterface.php8
-rw-r--r--core/lib/Drupal/Core/Database/Schema.php47
-rw-r--r--core/lib/Drupal/Core/Database/StatementInterface.php2
-rw-r--r--core/lib/Drupal/Core/Database/StatementPrefetch.php16
-rw-r--r--core/lib/Drupal/Core/Database/database.api.php106
-rw-r--r--core/lib/Drupal/Core/Datetime/DateHelper.php1
-rw-r--r--core/lib/Drupal/Core/Datetime/DrupalDateTime.php2
-rw-r--r--core/lib/Drupal/Core/Datetime/Element/Datetime.php14
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php2
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php4
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php40
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php32
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php4
-rw-r--r--core/lib/Drupal/Core/Diff/DiffFormatter.php8
-rw-r--r--core/lib/Drupal/Core/DrupalKernel.php68
-rw-r--r--core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Annotation/EntityType.php2
-rw-r--r--core/lib/Drupal/Core/Entity/BundleEntityFormBase.php2
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityBase.php204
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php5
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityForm.php33
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityInterface.php66
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php7
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php357
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php19
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityType.php33
-rw-r--r--core/lib/Drupal/Core/Entity/Controller/EntityListController.php3
-rw-r--r--core/lib/Drupal/Core/Entity/Controller/EntityViewController.php3
-rw-r--r--core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php25
-rw-r--r--core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php6
-rw-r--r--core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php1
-rw-r--r--core/lib/Drupal/Core/Entity/Entity.php7
-rw-r--r--core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php4
-rw-r--r--core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php8
-rw-r--r--core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php14
-rw-r--r--core/lib/Drupal/Core/Entity/EntityBundleListener.php1
-rw-r--r--core/lib/Drupal/Core/Entity/EntityChangedInterface.php5
-rw-r--r--core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php43
-rw-r--r--core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php2
-rw-r--r--core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php41
-rw-r--r--core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php12
-rw-r--r--core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php2
-rw-r--r--core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php86
-rw-r--r--core/lib/Drupal/Core/Entity/EntityDisplayBase.php18
-rw-r--r--core/lib/Drupal/Core/Entity/EntityFieldManager.php21
-rw-r--r--core/lib/Drupal/Core/Entity/EntityForm.php11
-rw-r--r--core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepository.php22
-rw-r--r--core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepositoryInterface.php28
-rw-r--r--core/lib/Drupal/Core/Entity/EntityListBuilder.php4
-rw-r--r--core/lib/Drupal/Core/Entity/EntityListBuilderInterface.php3
-rw-r--r--core/lib/Drupal/Core/Entity/EntityManager.php6
-rw-r--r--core/lib/Drupal/Core/Entity/EntityPublishedTrait.php3
-rw-r--r--core/lib/Drupal/Core/Entity/EntityRepository.php4
-rw-r--r--core/lib/Drupal/Core/Entity/EntityStorageBase.php65
-rw-r--r--core/lib/Drupal/Core/Entity/EntityStorageInterface.php12
-rw-r--r--core/lib/Drupal/Core/Entity/EntityType.php41
-rw-r--r--core/lib/Drupal/Core/Entity/EntityTypeInterface.php43
-rw-r--r--core/lib/Drupal/Core/Entity/EntityTypeManager.php1
-rw-r--r--core/lib/Drupal/Core/Entity/EntityTypeManagerInterface.php3
-rw-r--r--core/lib/Drupal/Core/Entity/EntityViewBuilder.php45
-rw-r--r--core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php26
-rw-r--r--core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php323
-rw-r--r--core/lib/Drupal/Core/Entity/HtmlEntityFormController.php10
-rw-r--r--core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php37
-rw-r--r--core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php10
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/DataType/ConfigEntityAdapter.php103
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php9
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php8
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/PhpSelection.php9
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php21
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityHasFieldConstraint.php53
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityHasFieldConstraintValidator.php35
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php21
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php123
-rw-r--r--core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Query/ConditionFundamentals.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Query/ConditionInterface.php5
-rw-r--r--core/lib/Drupal/Core/Entity/Query/QueryBase.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Query/QueryFactory.php7
-rw-r--r--core/lib/Drupal/Core/Entity/Query/Sql/Condition.php10
-rw-r--r--core/lib/Drupal/Core/Entity/Query/Sql/Query.php1
-rw-r--r--core/lib/Drupal/Core/Entity/Query/Sql/QueryAggregate.php2
-rw-r--r--core/lib/Drupal/Core/Entity/Query/Sql/Tables.php31
-rw-r--r--core/lib/Drupal/Core/Entity/RevisionableInterface.php52
-rw-r--r--core/lib/Drupal/Core/Entity/RevisionableStorageInterface.php68
-rw-r--r--core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php10
-rw-r--r--core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php26
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php247
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php339
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php300
-rw-r--r--core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchemaConverter.php26
-rw-r--r--core/lib/Drupal/Core/Entity/TranslatableInterface.php20
-rw-r--r--core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php83
-rw-r--r--core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php46
-rw-r--r--core/lib/Drupal/Core/Entity/TranslatableStorageInterface.php30
-rw-r--r--core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php43
-rw-r--r--core/lib/Drupal/Core/Entity/entity.api.php214
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/ActiveLinkResponseFilter.php4
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php38
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/ConfigSnapshotSubscriber.php4
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php18
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/EnforcedFormResponseSubscriber.php1
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/Fast404ExceptionHtmlSubscriber.php1
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/FinalExceptionSubscriber.php12
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php4
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php8
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php32
-rw-r--r--core/lib/Drupal/Core/Executable/ExecutablePluginBase.php10
-rw-r--r--core/lib/Drupal/Core/Extension/Exception/UninstalledExtensionException.php8
-rw-r--r--core/lib/Drupal/Core/Extension/Exception/UnknownExtensionException.php8
-rw-r--r--core/lib/Drupal/Core/Extension/ExtensionDiscovery.php2
-rw-r--r--core/lib/Drupal/Core/Extension/ExtensionList.php544
-rw-r--r--core/lib/Drupal/Core/Extension/InfoParserDynamic.php18
-rw-r--r--core/lib/Drupal/Core/Extension/InfoParserInterface.php5
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleExtensionList.php228
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandler.php65
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php91
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleInstaller.php55
-rw-r--r--core/lib/Drupal/Core/Extension/ProfileExtensionList.php29
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeHandler.php8
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php10
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeInstaller.php9
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php8
-rw-r--r--core/lib/Drupal/Core/Extension/module.api.php11
-rw-r--r--core/lib/Drupal/Core/Field/BaseFieldDefinition.php105
-rw-r--r--core/lib/Drupal/Core/Field/BaseFieldOverrideStorage.php10
-rw-r--r--core/lib/Drupal/Core/Field/ChangedFieldItemList.php12
-rw-r--r--core/lib/Drupal/Core/Field/DeletedFieldsRepository.php97
-rw-r--r--core/lib/Drupal/Core/Field/DeletedFieldsRepositoryInterface.php73
-rw-r--r--core/lib/Drupal/Core/Field/Entity/BaseFieldOverride.php7
-rw-r--r--core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php2
-rw-r--r--core/lib/Drupal/Core/Field/FieldConfigBase.php17
-rw-r--r--core/lib/Drupal/Core/Field/FieldDefinitionInterface.php7
-rw-r--r--core/lib/Drupal/Core/Field/FieldInputValueNormalizerTrait.php42
-rw-r--r--core/lib/Drupal/Core/Field/FieldItemInterface.php4
-rw-r--r--core/lib/Drupal/Core/Field/FieldItemList.php29
-rw-r--r--core/lib/Drupal/Core/Field/FieldItemListInterface.php25
-rw-r--r--core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php66
-rw-r--r--core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php16
-rw-r--r--core/lib/Drupal/Core/Field/FieldStorageDefinitionListener.php31
-rw-r--r--core/lib/Drupal/Core/Field/FieldTypePluginManager.php15
-rw-r--r--core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php20
-rw-r--r--core/lib/Drupal/Core/Field/MapFieldItemList.php34
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php4
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LanguageFormatter.php14
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php23
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TimestampAgoFormatter.php30
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php6
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EmailItem.php2
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php69
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php68
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/MapItem.php3
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php4
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TimestampItem.php1
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UriItem.php3
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/BooleanCheckboxWidget.php2
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php4
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/migrate/field/Email.php10
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/migrate/field/d7/EntityReference.php15
-rw-r--r--core/lib/Drupal/Core/Field/PluginSettingsBase.php2
-rw-r--r--core/lib/Drupal/Core/Field/PreconfiguredFieldUiOptionsInterface.php6
-rw-r--r--core/lib/Drupal/Core/Field/WidgetBase.php37
-rw-r--r--core/lib/Drupal/Core/Field/WidgetPluginManager.php1
-rw-r--r--core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php2
-rw-r--r--core/lib/Drupal/Core/FileTransfer/FileTransfer.php3
-rw-r--r--core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php2
-rw-r--r--core/lib/Drupal/Core/Form/ConfigFormBase.php2
-rw-r--r--core/lib/Drupal/Core/Form/ConfirmFormInterface.php8
-rw-r--r--core/lib/Drupal/Core/Form/EnforcedResponse.php2
-rw-r--r--core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php35
-rw-r--r--core/lib/Drupal/Core/Form/FormBase.php2
-rw-r--r--core/lib/Drupal/Core/Form/FormBuilder.php12
-rw-r--r--core/lib/Drupal/Core/Form/FormErrorHandler.php17
-rw-r--r--core/lib/Drupal/Core/Form/FormInterface.php4
-rw-r--r--core/lib/Drupal/Core/Form/FormState.php10
-rw-r--r--core/lib/Drupal/Core/Form/FormStateInterface.php5
-rw-r--r--core/lib/Drupal/Core/Form/FormValidator.php15
-rw-r--r--core/lib/Drupal/Core/Form/form.api.php6
-rw-r--r--core/lib/Drupal/Core/GeneratedLink.php5
-rw-r--r--core/lib/Drupal/Core/GeneratedUrl.php2
-rw-r--r--core/lib/Drupal/Core/Http/ClientFactory.php2
-rw-r--r--core/lib/Drupal/Core/Http/HandlerStackConfigurator.php2
-rw-r--r--core/lib/Drupal/Core/Http/TrustedHostsRequestFactory.php2
-rw-r--r--core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php1
-rw-r--r--core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationManager.php4
-rw-r--r--core/lib/Drupal/Core/Installer/ConfigOverride.php62
-rw-r--r--core/lib/Drupal/Core/Installer/Exception/InstallProfileMismatchException.php4
-rw-r--r--core/lib/Drupal/Core/Installer/Form/SelectProfileForm.php82
-rw-r--r--core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php37
-rw-r--r--core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php7
-rw-r--r--core/lib/Drupal/Core/Installer/InstallerModuleExtensionList.php58
-rw-r--r--core/lib/Drupal/Core/Installer/InstallerServiceProvider.php3
-rw-r--r--core/lib/Drupal/Core/Installer/NormalInstallerServiceProvider.php21
-rw-r--r--core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php2
-rw-r--r--core/lib/Drupal/Core/KeyValueStore/KeyValueFactory.php2
-rw-r--r--core/lib/Drupal/Core/Language/LanguageManager.php7
-rw-r--r--core/lib/Drupal/Core/Layout/LayoutPluginManager.php15
-rw-r--r--core/lib/Drupal/Core/Layout/LayoutPluginManagerInterface.php3
-rw-r--r--core/lib/Drupal/Core/Lock/DatabaseLockBackend.php6
-rw-r--r--core/lib/Drupal/Core/Logger/LogMessageParser.php4
-rw-r--r--core/lib/Drupal/Core/Logger/LogMessageParserInterface.php7
-rw-r--r--core/lib/Drupal/Core/Logger/RfcLogLevel.php4
-rw-r--r--core/lib/Drupal/Core/Mail/MailFormatHelper.php7
-rw-r--r--core/lib/Drupal/Core/Mail/MailManager.php22
-rw-r--r--core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php48
-rw-r--r--core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php1
-rw-r--r--core/lib/Drupal/Core/Menu/LocalActionDefault.php27
-rw-r--r--core/lib/Drupal/Core/Menu/LocalActionManager.php38
-rw-r--r--core/lib/Drupal/Core/Menu/LocalTaskDefault.php28
-rw-r--r--core/lib/Drupal/Core/Menu/LocalTaskManager.php32
-rw-r--r--core/lib/Drupal/Core/Menu/MenuLinkManager.php1
-rw-r--r--core/lib/Drupal/Core/Menu/MenuLinkTreeInterface.php2
-rw-r--r--core/lib/Drupal/Core/Menu/Plugin/Block/LocalTasksBlock.php1
-rw-r--r--core/lib/Drupal/Core/Menu/menu.api.php10
-rw-r--r--core/lib/Drupal/Core/Messenger/LegacyMessenger.php269
-rw-r--r--core/lib/Drupal/Core/Messenger/Messenger.php112
-rw-r--r--core/lib/Drupal/Core/Messenger/MessengerInterface.php9
-rw-r--r--core/lib/Drupal/Core/Messenger/MessengerTrait.php40
-rw-r--r--core/lib/Drupal/Core/PageCache/RequestPolicy/NoSessionOpen.php2
-rw-r--r--core/lib/Drupal/Core/ParamConverter/EntityConverter.php154
-rw-r--r--core/lib/Drupal/Core/Password/PhpassHashedPassword.php2
-rw-r--r--core/lib/Drupal/Core/Path/AliasManager.php2
-rw-r--r--core/lib/Drupal/Core/PathProcessor/InboundPathProcessorInterface.php9
-rw-r--r--core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php9
-rw-r--r--core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php1
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/Context.php1
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/ContextAwarePluginManagerInterface.php2
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php188
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php12
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/ContextHandler.php65
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/ContextHandlerInterface.php3
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/ContextProviderInterface.php4
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/EntityContext.php62
-rw-r--r--core/lib/Drupal/Core/Plugin/Context/EntityContextDefinition.php116
-rw-r--r--core/lib/Drupal/Core/Plugin/DefaultPluginManager.php5
-rw-r--r--core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php3
-rw-r--r--core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php5
-rw-r--r--core/lib/Drupal/Core/Plugin/FilteredPluginManagerInterface.php36
-rw-r--r--core/lib/Drupal/Core/Plugin/FilteredPluginManagerTrait.php75
-rw-r--r--core/lib/Drupal/Core/Plugin/PluginBase.php2
-rw-r--r--core/lib/Drupal/Core/Plugin/PluginDependencyTrait.php36
-rw-r--r--core/lib/Drupal/Core/Plugin/plugin.api.php68
-rw-r--r--core/lib/Drupal/Core/ProxyBuilder/ProxyBuilder.php2
-rw-r--r--core/lib/Drupal/Core/ProxyClass/Field/FieldModuleUninstallValidator.php88
-rw-r--r--core/lib/Drupal/Core/Queue/DatabaseQueue.php2
-rw-r--r--core/lib/Drupal/Core/Queue/QueueFactory.php1
-rw-r--r--core/lib/Drupal/Core/Queue/QueueInterface.php2
-rw-r--r--core/lib/Drupal/Core/Render/Element.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Details.php3
-rw-r--r--core/lib/Drupal/Core/Render/Element/HtmlTag.php10
-rw-r--r--core/lib/Drupal/Core/Render/Element/Link.php6
-rw-r--r--core/lib/Drupal/Core/Render/Element/MachineName.php21
-rw-r--r--core/lib/Drupal/Core/Render/Element/RenderElement.php2
-rw-r--r--core/lib/Drupal/Core/Render/Element/StatusMessages.php18
-rw-r--r--core/lib/Drupal/Core/Render/Element/SystemCompactLink.php2
-rw-r--r--core/lib/Drupal/Core/Render/Element/Weight.php5
-rw-r--r--core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php4
-rw-r--r--core/lib/Drupal/Core/Render/MainContent/DialogRenderer.php2
-rw-r--r--core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php2
-rw-r--r--core/lib/Drupal/Core/Render/MainContent/OffCanvasRenderer.php14
-rw-r--r--core/lib/Drupal/Core/Render/Markup.php1
-rw-r--r--core/lib/Drupal/Core/Render/Placeholder/ChainedPlaceholderStrategy.php4
-rw-r--r--core/lib/Drupal/Core/Render/RenderCache.php2
-rw-r--r--core/lib/Drupal/Core/Render/Renderer.php32
-rw-r--r--core/lib/Drupal/Core/Render/theme.api.php14
-rw-r--r--core/lib/Drupal/Core/Routing/AccessAwareRouter.php2
-rw-r--r--core/lib/Drupal/Core/Routing/AccessAwareRouterInterface.php1
-rw-r--r--core/lib/Drupal/Core/Routing/BcRoute.php29
-rw-r--r--core/lib/Drupal/Core/Routing/CompiledRoute.php3
-rw-r--r--core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php7
-rw-r--r--core/lib/Drupal/Core/Routing/Enhancer/RouteEnhancerInterface.php2
-rw-r--r--core/lib/Drupal/Core/Routing/NullGenerator.php4
-rw-r--r--core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php63
-rw-r--r--core/lib/Drupal/Core/Routing/RouteFilterInterface.php2
-rw-r--r--core/lib/Drupal/Core/Routing/RouteMatch.php2
-rw-r--r--core/lib/Drupal/Core/Routing/RouteProvider.php54
-rw-r--r--core/lib/Drupal/Core/Routing/RouteProviderLazyBuilder.php40
-rw-r--r--core/lib/Drupal/Core/Routing/StackedRouteMatchInterface.php2
-rw-r--r--core/lib/Drupal/Core/Routing/UrlGenerator.php7
-rw-r--r--core/lib/Drupal/Core/Security/RequestSanitizer.php168
-rw-r--r--core/lib/Drupal/Core/Session/AccountInterface.php15
-rw-r--r--core/lib/Drupal/Core/Session/AccountProxyInterface.php5
-rw-r--r--core/lib/Drupal/Core/Session/SessionConfiguration.php2
-rw-r--r--core/lib/Drupal/Core/Session/SessionManager.php5
-rw-r--r--core/lib/Drupal/Core/Session/UserSession.php2
-rw-r--r--core/lib/Drupal/Core/Session/WriteSafeSessionHandlerInterface.php6
-rw-r--r--core/lib/Drupal/Core/Site/Settings.php9
-rw-r--r--core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php8
-rw-r--r--core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php10
-rw-r--r--core/lib/Drupal/Core/StreamWrapper/LocalReadOnlyStream.php1
-rw-r--r--core/lib/Drupal/Core/StreamWrapper/PrivateStream.php2
-rw-r--r--core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php4
-rw-r--r--core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php2
-rw-r--r--core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php13
-rw-r--r--core/lib/Drupal/Core/StringTranslation/TranslationInterface.php4
-rw-r--r--core/lib/Drupal/Core/TempStore/PrivateTempStore.php253
-rw-r--r--core/lib/Drupal/Core/TempStore/PrivateTempStoreFactory.php88
-rw-r--r--core/lib/Drupal/Core/TempStore/SharedTempStore.php279
-rw-r--r--core/lib/Drupal/Core/TempStore/SharedTempStoreFactory.php87
-rw-r--r--core/lib/Drupal/Core/TempStore/TempStoreException.php11
-rw-r--r--core/lib/Drupal/Core/Template/Loader/ThemeRegistryLoader.php6
-rw-r--r--core/lib/Drupal/Core/Template/TwigEnvironment.php31
-rw-r--r--core/lib/Drupal/Core/Template/TwigNodeTrans.php7
-rw-r--r--core/lib/Drupal/Core/Template/TwigNodeVisitor.php7
-rw-r--r--core/lib/Drupal/Core/Template/TwigSandboxPolicy.php12
-rw-r--r--core/lib/Drupal/Core/Test/AssertMailTrait.php6
-rw-r--r--core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php55
-rw-r--r--core/lib/Drupal/Core/Test/RefreshVariablesTrait.php38
-rw-r--r--core/lib/Drupal/Core/Test/TestDatabase.php42
-rw-r--r--core/lib/Drupal/Core/Test/TestSetupTrait.php3
-rw-r--r--core/lib/Drupal/Core/Test/TestStatus.php2
-rw-r--r--core/lib/Drupal/Core/Theme/ActiveTheme.php26
-rw-r--r--core/lib/Drupal/Core/Theme/Registry.php11
-rw-r--r--core/lib/Drupal/Core/Theme/ThemeAccessCheck.php1
-rw-r--r--core/lib/Drupal/Core/Theme/ThemeInitialization.php9
-rw-r--r--core/lib/Drupal/Core/TypedData/DataDefinition.php9
-rw-r--r--core/lib/Drupal/Core/TypedData/ListDataDefinition.php22
-rw-r--r--core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php2
-rw-r--r--core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php2
-rw-r--r--core/lib/Drupal/Core/TypedData/TranslatableInterface.php17
-rw-r--r--core/lib/Drupal/Core/TypedData/TypedData.php6
-rw-r--r--core/lib/Drupal/Core/TypedData/TypedDataManager.php7
-rw-r--r--core/lib/Drupal/Core/Update/UpdateKernel.php8
-rw-r--r--core/lib/Drupal/Core/Update/UpdateRegistry.php2
-rw-r--r--core/lib/Drupal/Core/Updater/Module.php7
-rw-r--r--core/lib/Drupal/Core/Updater/Theme.php2
-rw-r--r--core/lib/Drupal/Core/Updater/Updater.php3
-rw-r--r--core/lib/Drupal/Core/Utility/Error.php4
-rw-r--r--core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php2
-rw-r--r--core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php21
-rw-r--r--core/lib/Drupal/Core/Validation/ConstraintManager.php1
-rw-r--r--core/lib/Drupal/Core/Validation/DrupalTranslator.php15
-rw-r--r--core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php2
-rw-r--r--core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php3
502 files changed, 12832 insertions, 2714 deletions
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 07dc12f..94130ac 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -6,8 +6,9 @@
*/
use Drupal\Core\DependencyInjection\ContainerNotInitializedException;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Messenger\LegacyMessenger;
use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Static Service Container wrapper.
@@ -81,7 +82,7 @@ class Drupal {
/**
* The current system version.
*/
- const VERSION = '8.5.0-dev';
+ const VERSION = '8.6.3-dev';
/**
* Core API compatibility.
@@ -140,7 +141,6 @@ class Drupal {
return static::$container !== NULL;
}
-
/**
* Retrieves a service from the container.
*
@@ -229,7 +229,7 @@ class Drupal {
}
/**
- * Retrives the request stack.
+ * Retrieves the request stack.
*
* @return \Symfony\Component\HttpFoundation\RequestStack
* The request stack
@@ -319,10 +319,20 @@ class Drupal {
* One common usecase is to provide a class which contains the actual code
* of a hook implementation, without having to create a service.
*
- * @return \Drupal\Core\DependencyInjection\ClassResolverInterface
- * The class resolver.
+ * @param string $class
+ * (optional) A class name to instantiate.
+ *
+ * @return \Drupal\Core\DependencyInjection\ClassResolverInterface|object
+ * The class resolver or if $class is provided, a class instance with a
+ * given class definition.
+ *
+ * @throws \InvalidArgumentException
+ * If $class does not exist.
*/
- public static function classResolver() {
+ public static function classResolver($class = NULL) {
+ if ($class) {
+ return static::getContainer()->get('class_resolver')->getInstanceFromDefinition($class);
+ }
return static::getContainer()->get('class_resolver');
}
@@ -757,4 +767,16 @@ class Drupal {
return static::getContainer()->get('datetime.time');
}
+ /**
+ * Returns the messenger.
+ *
+ * @return \Drupal\Core\Messenger\MessengerInterface
+ * The messenger.
+ */
+ public static function messenger() {
+ // @todo Replace with service once LegacyMessenger is removed in 9.0.0.
+ // @see https://www.drupal.org/node/2928994
+ return new LegacyMessenger();
+ }
+
}
diff --git a/core/lib/Drupal/Component/Annotation/composer.json b/core/lib/Drupal/Component/Annotation/composer.json
index e7a2847..51e7d25 100644
--- a/core/lib/Drupal/Component/Annotation/composer.json
+++ b/core/lib/Drupal/Component/Annotation/composer.json
@@ -3,10 +3,10 @@
"description": "Annotation discovery and implementation of plugins.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
- "doctrine/common": "2.5.*",
+ "doctrine/common": "^2.5",
"doctrine/annotations": "1.2.*",
"drupal/core-file-cache": "^8.2",
"drupal/core-plugin": "^8.2",
diff --git a/core/lib/Drupal/Component/Assertion/Inspector.php b/core/lib/Drupal/Component/Assertion/Inspector.php
index 17cd434..681f192 100644
--- a/core/lib/Drupal/Component/Assertion/Inspector.php
+++ b/core/lib/Drupal/Component/Assertion/Inspector.php
@@ -13,7 +13,7 @@ use Traversable;
*
* Example call:
* @code
- * assert('Drupal\\Component\\Assertion\\Inspector::assertAllStrings($array)');
+ * assert(Inspector::assertAllStrings($array));
* @endcode
*
* @ingroup php_assert
@@ -187,8 +187,8 @@ class Inspector {
* As an example, this assertion tests for the keys of a theme registry.
*
* @code
- * assert('Drupal\\Component\\Assertion\\Inspector::assertAllHaveKey(
- * $arrayToTest, "type", "theme path", "function", "template", "variables", "render element", "preprocess functions")');
+ * assert(Inspector::assertAllHaveKey(
+ * $arrayToTest, "type", "theme path", "function", "template", "variables", "render element", "preprocess functions"));
* @endcode
*
* Note: If a method requires certain keys to be present it will usually be
@@ -333,7 +333,6 @@ class Inspector {
return FALSE;
}
-
/**
* Asserts that all members are strings matching a regular expression.
*
@@ -375,16 +374,13 @@ class Inspector {
* Here are some examples:
* @code
* // Just test all are objects, like a cache.
- * assert('Drupal\\Component\\Assertion\\Inspector::assertAllObjects(
- * $collection');
+ * assert(Inspector::assertAllObjects($collection));
*
* // Test if traversable objects (arrays won't pass this)
- * assert('Drupal\\Component\\Assertion\\Inspector::assertAllObjects(
- * $collection', \'\\Traversable\');
+ * assert(Inspector::assertAllObjects($collection, '\\Traversable'));
*
* // Test for the Foo class or Bar\None interface
- * assert('Drupal\\Component\\Assertion\\Inspector::assertAllObjects(
- * $collection', \'\\Foo\', \'\\Bar\\None\'');
+ * assert(Inspector::assertAllObjects($collection, '\\Foo', '\\Bar\\None'));
* @endcode
*
* @param mixed $traversable
diff --git a/core/lib/Drupal/Component/Assertion/composer.json b/core/lib/Drupal/Component/Assertion/composer.json
index 28d9ca4..cdc8c92 100644
--- a/core/lib/Drupal/Component/Assertion/composer.json
+++ b/core/lib/Drupal/Component/Assertion/composer.json
@@ -3,7 +3,7 @@
"description": "Provides runtime assertions similar to those in PHP 7, under PHP 5.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9"
},
diff --git a/core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php b/core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php
index c657946..59ae9f5 100644
--- a/core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php
+++ b/core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php
@@ -4,6 +4,7 @@ namespace Drupal\Component\Bridge;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Zend\Feed\Reader\ExtensionManagerInterface as ReaderManagerInterface;
use Zend\Feed\Writer\ExtensionManagerInterface as WriterManagerInterface;
@@ -49,6 +50,11 @@ class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterMan
protected $canonicalNames;
/**
+ * @var \Zend\Feed\Reader\ExtensionManagerInterface|\Zend\Feed\Writer\ExtensionManagerInterface
+ */
+ protected $standalone;
+
+ /**
* Constructs a ZfExtensionManagerSfContainer object.
*
* @param string $prefix
@@ -62,14 +68,25 @@ class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterMan
* {@inheritdoc}
*/
public function get($extension) {
- return $this->container->get($this->prefix . $this->canonicalizeName($extension));
+ try {
+ return $this->container->get($this->prefix . $this->canonicalizeName($extension));
+ }
+ catch (ServiceNotFoundException $e) {
+ if ($this->standalone && $this->standalone->has($extension)) {
+ return $this->standalone->get($extension);
+ }
+ throw $e;
+ }
}
/**
* {@inheritdoc}
*/
public function has($extension) {
- return $this->container->has($this->prefix . $this->canonicalizeName($extension));
+ if ($this->container->has($this->prefix . $this->canonicalizeName($extension))) {
+ return TRUE;
+ }
+ return $this->standalone && $this->standalone->has($extension);
}
/**
@@ -102,4 +119,14 @@ class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterMan
$this->container = $container;
}
+ /**
+ * @param $class
+ */
+ public function setStandalone($class) {
+ if (!is_subclass_of($class, ReaderManagerInterface::class) && !is_subclass_of($class, WriterManagerInterface::class)) {
+ throw new \RuntimeException("$class must implement Zend\Feed\Reader\ExtensionManagerInterface or Zend\Feed\Writer\ExtensionManagerInterface");
+ }
+ $this->standalone = new $class();
+ }
+
}
diff --git a/core/lib/Drupal/Component/Bridge/composer.json b/core/lib/Drupal/Component/Bridge/composer.json
index 928d867..19c8a3b 100644
--- a/core/lib/Drupal/Component/Bridge/composer.json
+++ b/core/lib/Drupal/Component/Bridge/composer.json
@@ -3,7 +3,7 @@
"description": "Bridge.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
"zendframework/zend-feed": "^2.4"
diff --git a/core/lib/Drupal/Component/ClassFinder/composer.json b/core/lib/Drupal/Component/ClassFinder/composer.json
index 97d3668..c5a97b0 100644
--- a/core/lib/Drupal/Component/ClassFinder/composer.json
+++ b/core/lib/Drupal/Component/ClassFinder/composer.json
@@ -3,10 +3,10 @@
"description": "This class provides a class finding utility.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
- "doctrine/common": "2.5.*"
+ "doctrine/common": "^2.5"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
index 5a8365c..bb56ba5 100644
--- a/core/lib/Drupal/Component/Datetime/DateTimePlus.php
+++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
@@ -41,12 +41,12 @@ class DateTimePlus {
use ToStringTrait;
- const FORMAT = 'Y-m-d H:i:s';
+ const FORMAT = 'Y-m-d H:i:s';
/**
* A RFC7231 Compliant date.
*
- * http://tools.ietf.org/html/rfc7231#section-7.1.1.1
+ * @see http://tools.ietf.org/html/rfc7231#section-7.1.1.1
*
* Example: Sun, 06 Nov 1994 08:49:37 GMT
*/
@@ -66,31 +66,43 @@ class DateTimePlus {
/**
* The value of the time value passed to the constructor.
+ *
+ * @var string
*/
protected $inputTimeRaw = '';
/**
* The prepared time, without timezone, for this date.
+ *
+ * @var string
*/
protected $inputTimeAdjusted = '';
/**
* The value of the timezone passed to the constructor.
+ *
+ * @var string
*/
protected $inputTimeZoneRaw = '';
/**
* The prepared timezone object used to construct this date.
+ *
+ * @var string
*/
protected $inputTimeZoneAdjusted = '';
/**
* The value of the format passed to the constructor.
+ *
+ * @var string
*/
protected $inputFormatRaw = '';
/**
* The prepared format, if provided.
+ *
+ * @var string
*/
protected $inputFormatAdjusted = '';
@@ -268,7 +280,7 @@ class DateTimePlus {
* parameter and the current timezone are ignored when the $time parameter
* either is a UNIX timestamp (e.g. @946684800) or specifies a timezone
* (e.g. 2010-01-28T15:00:00+02:00).
- * @see http://php.net/manual/en/datetime.construct.php
+ * @see http://php.net/manual/datetime.construct.php
* @param array $settings
* (optional) Keyed array of settings. Defaults to empty array.
* - langcode: (optional) String two letter language code used to control
@@ -465,7 +477,6 @@ class DateTimePlus {
return $format;
}
-
/**
* Examines getLastErrors() to see what errors to report.
*
@@ -659,7 +670,7 @@ class DateTimePlus {
* Formats the date for display.
*
* @param string $format
- * A format string using either PHP's date().
+ * Format accepted by date().
* @param array $settings
* - timezone: (optional) String timezone name. Defaults to the timezone
* of the date object.
@@ -703,4 +714,14 @@ class DateTimePlus {
$this->dateTimeObject->setTime(12, 0, 0);
}
+ /**
+ * Gets a clone of the proxied PHP \DateTime object wrapped by this class.
+ *
+ * @return \DateTime
+ * A clone of the wrapped PHP \DateTime object.
+ */
+ public function getPhpDateTime() {
+ return clone $this->dateTimeObject;
+ }
+
}
diff --git a/core/lib/Drupal/Component/Datetime/composer.json b/core/lib/Drupal/Component/Datetime/composer.json
index 1df4328..6b7f89f 100644
--- a/core/lib/Drupal/Component/Datetime/composer.json
+++ b/core/lib/Drupal/Component/Datetime/composer.json
@@ -3,7 +3,7 @@
"description": "This class wraps the PHP DateTime class with more flexible initialization parameters, allowing a date to be created from an existing date object, a timestamp, a string with an unknown format, a string with a known format, or an array of date parts. It also adds an errors array and a __toString() method to the date object.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
"drupal/core-utility": "^8.2"
diff --git a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
index 11768d0..b4c1da8 100644
--- a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
+++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
@@ -65,7 +65,7 @@ class OptimizedPhpArrayDumper extends Dumper {
$definition['aliases'] = $this->getAliases();
$definition['parameters'] = $this->getParameters();
$definition['services'] = $this->getServiceDefinitions();
- $definition['frozen'] = $this->container->isFrozen();
+ $definition['frozen'] = $this->container->isCompiled();
$definition['machine_format'] = $this->supportsMachineFormat();
return $definition;
}
@@ -103,8 +103,8 @@ class OptimizedPhpArrayDumper extends Dumper {
}
$parameters = $this->container->getParameterBag()->all();
- $is_frozen = $this->container->isFrozen();
- return $this->prepareParameters($parameters, $is_frozen);
+ $is_compiled = $this->container->isCompiled();
+ return $this->prepareParameters($parameters, $is_compiled);
}
/**
@@ -282,7 +282,6 @@ class OptimizedPhpArrayDumper extends Dumper {
return $code;
}
-
/**
* Dumps a collection to a PHP array.
*
@@ -309,10 +308,10 @@ class OptimizedPhpArrayDumper extends Dumper {
}
}
else {
- if (is_object($value)) {
+ $code[$key] = $this->dumpValue($value);
+ if (is_object($code[$key])) {
$resolve = TRUE;
}
- $code[$key] = $this->dumpValue($value);
}
}
@@ -403,6 +402,9 @@ class OptimizedPhpArrayDumper extends Dumper {
elseif ($value instanceof Parameter) {
return $this->getParameterCall((string) $value);
}
+ elseif (is_string($value) && preg_match('/^\%(.*)\%$/', $value, $matches)) {
+ return $this->getParameterCall($matches[1]);
+ }
elseif ($value instanceof Expression) {
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
diff --git a/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php b/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php
index 5d615db..ba45468 100644
--- a/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php
+++ b/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php
@@ -200,7 +200,7 @@ class PhpArrayContainer extends Container {
// The PhpArrayDumper just uses the hash of the private service
// definition to generate a unique ID.
//
- // @see \Drupal\Component\DependecyInjection\Dumper\OptimizedPhpArrayDumper::getPrivateServiceCall
+ // @see \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper::getPrivateServiceCall
if ($type == 'private_service') {
$id = $argument->id;
diff --git a/core/lib/Drupal/Component/DependencyInjection/composer.json b/core/lib/Drupal/Component/DependencyInjection/composer.json
index cf25192..846034a 100644
--- a/core/lib/Drupal/Component/DependencyInjection/composer.json
+++ b/core/lib/Drupal/Component/DependencyInjection/composer.json
@@ -4,7 +4,7 @@
"keywords": ["drupal", "dependency injection"],
"type": "library",
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"support": {
"issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute",
@@ -12,7 +12,7 @@
},
"require": {
"php": ">=5.5.9",
- "symfony/dependency-injection": "^2.8"
+ "symfony/dependency-injection": ">=2.8 <4.0.0"
},
"suggest": {
"symfony/expression-language": "For using expressions in service container configuration"
diff --git a/core/lib/Drupal/Component/Diff/Engine/DiffEngine.php b/core/lib/Drupal/Component/Diff/Engine/DiffEngine.php
index b56aa99..34c01f3 100644
--- a/core/lib/Drupal/Component/Diff/Engine/DiffEngine.php
+++ b/core/lib/Drupal/Component/Diff/Engine/DiffEngine.php
@@ -2,8 +2,6 @@
namespace Drupal\Component\Diff\Engine;
-use Drupal\Component\Utility\Unicode;
-
/**
* Class used internally by Diff to actually compute the diffs.
*
@@ -134,7 +132,7 @@ class DiffEngine {
* Returns the whole line if it's small enough, or the MD5 hash otherwise.
*/
protected function _line_hash($line) {
- if (Unicode::strlen($line) > $this::MAX_XREF_LENGTH) {
+ if (mb_strlen($line) > $this::MAX_XREF_LENGTH) {
return md5($line);
}
else {
@@ -201,28 +199,30 @@ class DiffEngine {
continue;
}
$matches = $ymatches[$line];
- reset($matches);
- while (list ($junk, $y) = each($matches)) {
- if (empty($this->in_seq[$y])) {
- $k = $this->_lcs_pos($y);
- $this::USE_ASSERTS && assert($k > 0);
- $ymids[$k] = $ymids[$k - 1];
- break;
- }
- }
- while (list ($junk, $y) = each($matches)) {
- if ($y > $this->seq[$k - 1]) {
- $this::USE_ASSERTS && assert($y < $this->seq[$k]);
- // Optimization: this is a common case:
- // next match is just replacing previous match.
- $this->in_seq[$this->seq[$k]] = FALSE;
- $this->seq[$k] = $y;
- $this->in_seq[$y] = 1;
+ $found_empty = FALSE;
+ foreach ($matches as $y) {
+ if (!$found_empty) {
+ if (empty($this->in_seq[$y])) {
+ $k = $this->_lcs_pos($y);
+ $this::USE_ASSERTS && assert($k > 0);
+ $ymids[$k] = $ymids[$k - 1];
+ $found_empty = TRUE;
+ }
}
- elseif (empty($this->in_seq[$y])) {
- $k = $this->_lcs_pos($y);
- $this::USE_ASSERTS && assert($k > 0);
- $ymids[$k] = $ymids[$k - 1];
+ else {
+ if ($y > $this->seq[$k - 1]) {
+ $this::USE_ASSERTS && assert($y < $this->seq[$k]);
+ // Optimization: this is a common case:
+ // next match is just replacing previous match.
+ $this->in_seq[$this->seq[$k]] = FALSE;
+ $this->seq[$k] = $y;
+ $this->in_seq[$y] = 1;
+ }
+ elseif (empty($this->in_seq[$y])) {
+ $k = $this->_lcs_pos($y);
+ $this::USE_ASSERTS && assert($k > 0);
+ $ymids[$k] = $ymids[$k - 1];
+ }
}
}
}
@@ -343,7 +343,7 @@ class DiffEngine {
$i = 0;
$j = 0;
- $this::USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)');
+ $this::USE_ASSERTS && assert(sizeof($lines) == sizeof($changed));
$len = sizeof($lines);
$other_len = sizeof($other_changed);
@@ -363,7 +363,7 @@ class DiffEngine {
$j++;
}
while ($i < $len && !$changed[$i]) {
- $this::USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
+ $this::USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]);
$i++;
$j++;
while ($j < $other_len && $other_changed[$j]) {
@@ -399,11 +399,11 @@ class DiffEngine {
while ($start > 0 && $changed[$start - 1]) {
$start--;
}
- $this::USE_ASSERTS && assert('$j > 0');
+ $this::USE_ASSERTS && assert($j > 0);
while ($other_changed[--$j]) {
continue;
}
- $this::USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
+ $this::USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
}
/*
@@ -426,7 +426,7 @@ class DiffEngine {
while ($i < $len && $changed[$i]) {
$i++;
}
- $this::USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
+ $this::USE_ASSERTS && assert($j < $other_len && ! $other_changed[$j]);
$j++;
if ($j < $other_len && $other_changed[$j]) {
$corresponding = $i;
@@ -444,11 +444,11 @@ class DiffEngine {
while ($corresponding < $i) {
$changed[--$start] = 1;
$changed[--$i] = 0;
- $this::USE_ASSERTS && assert('$j > 0');
+ $this::USE_ASSERTS && assert($j > 0);
while ($other_changed[--$j]) {
continue;
}
- $this::USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
+ $this::USE_ASSERTS && assert($j >= 0 && !$other_changed[$j]);
}
}
}
diff --git a/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php b/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php
index eb7d943..98d430c 100644
--- a/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php
+++ b/core/lib/Drupal/Component/Diff/Engine/HWLDFWordAccumulator.php
@@ -2,8 +2,6 @@
namespace Drupal\Component\Diff\Engine;
-use Drupal\Component\Utility\Unicode;
-
/**
* Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
*/
@@ -64,7 +62,7 @@ class HWLDFWordAccumulator {
}
if ($word[0] == "\n") {
$this->_flushLine($tag);
- $word = Unicode::substr($word, 1);
+ $word = mb_substr($word, 1);
}
assert(!strstr($word, "\n"));
$this->group .= $word;
diff --git a/core/lib/Drupal/Component/Diff/WordLevelDiff.php b/core/lib/Drupal/Component/Diff/WordLevelDiff.php
index a8c2f80..66c6e1a 100644
--- a/core/lib/Drupal/Component/Diff/WordLevelDiff.php
+++ b/core/lib/Drupal/Component/Diff/WordLevelDiff.php
@@ -3,7 +3,6 @@
namespace Drupal\Component\Diff;
use Drupal\Component\Diff\Engine\HWLDFWordAccumulator;
-use Drupal\Component\Utility\Unicode;
/**
* @todo document
@@ -35,7 +34,7 @@ class WordLevelDiff extends MappedDiff {
$words[] = "\n";
$stripped[] = "\n";
}
- if (Unicode::strlen($line) > $this::MAX_LINE_LENGTH) {
+ if (mb_strlen($line) > $this::MAX_LINE_LENGTH) {
$words[] = $line;
$stripped[] = $line;
}
diff --git a/core/lib/Drupal/Component/Diff/composer.json b/core/lib/Drupal/Component/Diff/composer.json
index 517f0d7..b301608 100644
--- a/core/lib/Drupal/Component/Diff/composer.json
+++ b/core/lib/Drupal/Component/Diff/composer.json
@@ -3,10 +3,10 @@
"description": "Diff.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
- "drupal/core-utility": "^8.2"
+ "symfony/polyfill-mbstring": "~1.0"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/Discovery/composer.json b/core/lib/Drupal/Component/Discovery/composer.json
index 7a267c2..4de1894 100644
--- a/core/lib/Drupal/Component/Discovery/composer.json
+++ b/core/lib/Drupal/Component/Discovery/composer.json
@@ -3,7 +3,7 @@
"description": "Discovery.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
"drupal/core-file-cache": "^8.2",
diff --git a/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php b/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
index a23f333..2e790ee 100644
--- a/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
+++ b/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php
@@ -36,7 +36,7 @@ class ContainerAwareEventDispatcher implements EventDispatcherInterface {
/**
* The service container.
*
- * @var \Symfony\Component\DependencyInjection\ContainerInterface;
+ * @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
@@ -104,8 +104,11 @@ class ContainerAwareEventDispatcher implements EventDispatcherInterface {
if (!isset($definition['callable'])) {
$definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
}
+ if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
+ $definition['callable'][0] = $definition['callable'][0]();
+ }
- $definition['callable']($event, $event_name, $this);
+ call_user_func($definition['callable'], $event, $event_name, $this);
if ($event->isPropagationStopped()) {
return $event;
}
@@ -144,6 +147,9 @@ class ContainerAwareEventDispatcher implements EventDispatcherInterface {
if (!isset($definition['callable'])) {
$definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
}
+ if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
+ $definition['callable'][0] = $definition['callable'][0]();
+ }
$result[] = $definition['callable'];
}
@@ -156,27 +162,29 @@ class ContainerAwareEventDispatcher implements EventDispatcherInterface {
/**
* {@inheritdoc}
*/
- public function getListenerPriority($eventName, $listener) {
- // Parts copied from \Symfony\Component\EventDispatcher, that's why you see
- // a yoda condition here.
- if (!isset($this->listeners[$eventName])) {
+ public function getListenerPriority($event_name, $listener) {
+ if (!isset($this->listeners[$event_name])) {
return;
}
- foreach ($this->listeners[$eventName] as $priority => $listeners) {
- if (FALSE !== ($key = array_search(['callable' => $listener], $listeners, TRUE))) {
- return $priority;
- }
+ if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+ $listener[0] = $listener[0]();
}
// Resolve service definitions if the listener has not been found so far.
- foreach ($this->listeners[$eventName] as $priority => &$definitions) {
+ foreach ($this->listeners[$event_name] as $priority => &$definitions) {
foreach ($definitions as $key => &$definition) {
if (!isset($definition['callable'])) {
// Once the callable is retrieved we keep it for subsequent method
// invocations on this class.
- $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
- if ($definition['callable'] === $listener) {
- return $priority;
- }
+ $definition['callable'] = [
+ $this->container->get($definition['service'][0]),
+ $definition['service'][1],
+ ];
+ }
+ if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
+ $definition['callable'][0] = $definition['callable'][0]();
+ }
+ if ($definition['callable'] === $listener) {
+ return $priority;
}
}
}
@@ -186,7 +194,17 @@ class ContainerAwareEventDispatcher implements EventDispatcherInterface {
* {@inheritdoc}
*/
public function hasListeners($event_name = NULL) {
- return (bool) count($this->getListeners($event_name));
+ if ($event_name !== NULL) {
+ return !empty($this->listeners[$event_name]);
+ }
+
+ foreach ($this->listeners as $event_listeners) {
+ if ($event_listeners) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
}
/**
@@ -214,10 +232,23 @@ class ContainerAwareEventDispatcher implements EventDispatcherInterface {
$definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
}
+ if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure && !$listener instanceof \Closure) {
+ $definition['callable'][0] = $definition['callable'][0]();
+ }
+
+ if (is_array($definition['callable']) && isset($definition['callable'][0]) && !$definition['callable'][0] instanceof \Closure && is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
+ $listener[0] = $listener[0]();
+ }
if ($definition['callable'] === $listener) {
- unset($this->listeners[$event_name][$priority][$key]);
+ unset($definitions[$key]);
}
}
+ if ($definitions) {
+ $this->listeners[$event_name][$priority] = $definitions;
+ }
+ else {
+ unset($this->listeners[$event_name][$priority]);
+ }
}
}
diff --git a/core/lib/Drupal/Component/EventDispatcher/composer.json b/core/lib/Drupal/Component/EventDispatcher/composer.json
index a4db6fa..cfbcc72 100644
--- a/core/lib/Drupal/Component/EventDispatcher/composer.json
+++ b/core/lib/Drupal/Component/EventDispatcher/composer.json
@@ -3,11 +3,11 @@
"description": "EventDispatcher.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
- "symfony/dependency-injection": "^2.8",
- "symfony/event-dispatcher": "^2.7"
+ "symfony/dependency-injection": ">=2.8 <4.0.0",
+ "symfony/event-dispatcher": ">=2.7 <4.0.0"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/FileCache/composer.json b/core/lib/Drupal/Component/FileCache/composer.json
index db9a275..86e33a5 100644
--- a/core/lib/Drupal/Component/FileCache/composer.json
+++ b/core/lib/Drupal/Component/FileCache/composer.json
@@ -3,7 +3,7 @@
"description": "FileCache.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9"
},
diff --git a/core/lib/Drupal/Component/FileSystem/composer.json b/core/lib/Drupal/Component/FileSystem/composer.json
index ed84f18..528bb07 100644
--- a/core/lib/Drupal/Component/FileSystem/composer.json
+++ b/core/lib/Drupal/Component/FileSystem/composer.json
@@ -3,7 +3,7 @@
"description": "FileSystem.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9"
},
diff --git a/core/lib/Drupal/Component/Gettext/PoHeader.php b/core/lib/Drupal/Component/Gettext/PoHeader.php
index 5dd13a5..5d38de5 100644
--- a/core/lib/Drupal/Component/Gettext/PoHeader.php
+++ b/core/lib/Drupal/Component/Gettext/PoHeader.php
@@ -27,42 +27,42 @@ class PoHeader {
*
* @var string
*/
- private $_langcode;
+ protected $langcode;
/**
* Formula for the plural form.
*
* @var string
*/
- private $_pluralForms;
+ protected $pluralForms;
/**
* Author(s) of the file.
*
* @var string
*/
- private $_authors;
+ protected $authors;
/**
* Date the po file got created.
*
* @var string
*/
- private $_po_date;
+ protected $poDate;
/**
* Human readable language name.
*
* @var string
*/
- private $_languageName;
+ protected $languageName;
/**
* Name of the project the translation belongs to.
*
* @var string
*/
- private $_projectName;
+ protected $projectName;
/**
* Constructor, creates a PoHeader with default values.
@@ -71,11 +71,11 @@ class PoHeader {
* Language code.
*/
public function __construct($langcode = NULL) {
- $this->_langcode = $langcode;
+ $this->langcode = $langcode;
// Ignore errors when run during site installation before
// date_default_timezone_set() is called.
- $this->_po_date = @date("Y-m-d H:iO");
- $this->_pluralForms = 'nplurals=2; plural=(n > 1);';
+ $this->poDate = @date("Y-m-d H:iO");
+ $this->pluralForms = 'nplurals=2; plural=(n > 1);';
}
/**
@@ -86,7 +86,7 @@ class PoHeader {
* 'nplurals=2; plural=(n > 1);'.
*/
public function getPluralForms() {
- return $this->_pluralForms;
+ return $this->pluralForms;
}
/**
@@ -96,7 +96,7 @@ class PoHeader {
* Human readable language name.
*/
public function setLanguageName($languageName) {
- $this->_languageName = $languageName;
+ $this->languageName = $languageName;
}
/**
@@ -106,7 +106,7 @@ class PoHeader {
* The human readable language name.
*/
public function getLanguageName() {
- return $this->_languageName;
+ return $this->languageName;
}
/**
@@ -116,7 +116,7 @@ class PoHeader {
* Human readable project name.
*/
public function setProjectName($projectName) {
- $this->_projectName = $projectName;
+ $this->projectName = $projectName;
}
/**
@@ -126,7 +126,7 @@ class PoHeader {
* The human readable project name.
*/
public function getProjectName() {
- return $this->_projectName;
+ return $this->projectName;
}
/**
@@ -142,7 +142,7 @@ class PoHeader {
// There is only one value relevant for our header implementation when
// reading, and that is the plural formula.
if (!empty($values['Plural-Forms'])) {
- $this->_pluralForms = $values['Plural-Forms'];
+ $this->pluralForms = $values['Plural-Forms'];
}
}
@@ -152,11 +152,11 @@ class PoHeader {
public function __toString() {
$output = '';
- $isTemplate = empty($this->_languageName);
+ $isTemplate = empty($this->languageName);
- $output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->_languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->_projectName) . "\n";
- if (!empty($this->_authors)) {
- $output .= '# Generated by ' . implode("\n# ", $this->_authors) . "\n";
+ $output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->projectName) . "\n";
+ if (!empty($this->authors)) {
+ $output .= '# Generated by ' . implode("\n# ", $this->authors) . "\n";
}
$output .= "#\n";
@@ -164,14 +164,14 @@ class PoHeader {
$output .= "msgid \"\"\n";
$output .= "msgstr \"\"\n";
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
- $output .= "\"POT-Creation-Date: " . $this->_po_date . "\\n\"\n";
- $output .= "\"PO-Revision-Date: " . $this->_po_date . "\\n\"\n";
+ $output .= "\"POT-Creation-Date: " . $this->poDate . "\\n\"\n";
+ $output .= "\"PO-Revision-Date: " . $this->poDate . "\\n\"\n";
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"MIME-Version: 1.0\\n\"\n";
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
- $output .= "\"Plural-Forms: " . $this->_pluralForms . "\\n\"\n";
+ $output .= "\"Plural-Forms: " . $this->pluralForms . "\\n\"\n";
$output .= "\n";
return $output;
@@ -188,7 +188,7 @@ class PoHeader {
* - 'nplurals': The number of plural forms defined by the plural formula.
* - 'plurals': Array of plural positions keyed by plural value.
*
- * @throws Exception
+ * @throws \Exception
*/
public function parsePluralForms($pluralforms) {
$plurals = [];
@@ -473,7 +473,7 @@ class PoHeader {
* Number of the plural string to be used for the given plural value.
*
* @see parseArithmetic()
- * @throws Exception
+ * @throws \Exception
*/
protected function evaluatePlural($element_stack, $n) {
$count = count($element_stack);
diff --git a/core/lib/Drupal/Component/Gettext/PoItem.php b/core/lib/Drupal/Component/Gettext/PoItem.php
index c1c3877..464875e 100644
--- a/core/lib/Drupal/Component/Gettext/PoItem.php
+++ b/core/lib/Drupal/Component/Gettext/PoItem.php
@@ -13,46 +13,47 @@ class PoItem {
/**
* The language code this translation is in.
*
- * @car string
+ * @var string
*/
- private $_langcode;
+ protected $langcode;
/**
* The context this translation belongs to.
*
* @var string
*/
- private $_context = '';
+ protected $context = '';
/**
* The source string or array of strings if it has plurals.
*
- * @var string or array
- * @see $_plural
+ * @var string|array
+ *
+ * @see $plural
*/
- private $_source;
+ protected $source;
/**
* Flag indicating if this translation has plurals.
*
* @var bool
*/
- private $_plural;
+ protected $plural;
/**
* The comment of this translation.
*
* @var string
*/
- private $_comment;
+ protected $comment;
/**
* The translation string or array of strings if it has plurals.
*
- * @var string or array
- * @see $_plural
+ * @var string|array
+ * @see $plural
*/
- private $_translation;
+ protected $translation;
/**
* Gets the language code of the currently used language.
@@ -60,7 +61,7 @@ class PoItem {
* @return string with langcode
*/
public function getLangcode() {
- return $this->_langcode;
+ return $this->langcode;
}
/**
@@ -69,7 +70,7 @@ class PoItem {
* @param string $langcode
*/
public function setLangcode($langcode) {
- $this->_langcode = $langcode;
+ $this->langcode = $langcode;
}
/**
@@ -78,7 +79,7 @@ class PoItem {
* @return string $context
*/
public function getContext() {
- return $this->_context;
+ return $this->context;
}
/**
@@ -87,7 +88,7 @@ class PoItem {
* @param string $context
*/
public function setContext($context) {
- $this->_context = $context;
+ $this->context = $context;
}
/**
@@ -97,7 +98,7 @@ class PoItem {
* @return string or array $translation
*/
public function getSource() {
- return $this->_source;
+ return $this->source;
}
/**
@@ -107,7 +108,7 @@ class PoItem {
* @param string|array $source
*/
public function setSource($source) {
- $this->_source = $source;
+ $this->source = $source;
}
/**
@@ -117,7 +118,7 @@ class PoItem {
* @return string or array $translation
*/
public function getTranslation() {
- return $this->_translation;
+ return $this->translation;
}
/**
@@ -127,7 +128,7 @@ class PoItem {
* @param string|array $translation
*/
public function setTranslation($translation) {
- $this->_translation = $translation;
+ $this->translation = $translation;
}
/**
@@ -136,7 +137,7 @@ class PoItem {
* @param bool $plural
*/
public function setPlural($plural) {
- $this->_plural = $plural;
+ $this->plural = $plural;
}
/**
@@ -145,7 +146,7 @@ class PoItem {
* @return bool
*/
public function isPlural() {
- return $this->_plural;
+ return $this->plural;
}
/**
@@ -154,7 +155,7 @@ class PoItem {
* @return String $comment
*/
public function getComment() {
- return $this->_comment;
+ return $this->comment;
}
/**
@@ -163,7 +164,7 @@ class PoItem {
* @param string $comment
*/
public function setComment($comment) {
- $this->_comment = $comment;
+ $this->comment = $comment;
}
/**
@@ -184,11 +185,11 @@ class PoItem {
if (isset($values['comment'])) {
$this->setComment($values['comment']);
}
- if (isset($this->_source) &&
- strpos($this->_source, LOCALE_PLURAL_DELIMITER) !== FALSE) {
- $this->setSource(explode(LOCALE_PLURAL_DELIMITER, $this->_source));
- $this->setTranslation(explode(LOCALE_PLURAL_DELIMITER, $this->_translation));
- $this->setPlural(count($this->_source) > 1);
+ if (isset($this->source) &&
+ strpos($this->source, LOCALE_PLURAL_DELIMITER) !== FALSE) {
+ $this->setSource(explode(LOCALE_PLURAL_DELIMITER, $this->source));
+ $this->setTranslation(explode(LOCALE_PLURAL_DELIMITER, $this->translation));
+ $this->setPlural(count($this->source) > 1);
}
}
@@ -206,12 +207,12 @@ class PoItem {
$output = '';
// Format string context.
- if (!empty($this->_context)) {
- $output .= 'msgctxt ' . $this->formatString($this->_context);
+ if (!empty($this->context)) {
+ $output .= 'msgctxt ' . $this->formatString($this->context);
}
// Format translation.
- if ($this->_plural) {
+ if ($this->plural) {
$output .= $this->formatPlural();
}
else {
@@ -231,11 +232,11 @@ class PoItem {
$output = '';
// Format source strings.
- $output .= 'msgid ' . $this->formatString($this->_source[0]);
- $output .= 'msgid_plural ' . $this->formatString($this->_source[1]);
+ $output .= 'msgid ' . $this->formatString($this->source[0]);
+ $output .= 'msgid_plural ' . $this->formatString($this->source[1]);
- foreach ($this->_translation as $i => $trans) {
- if (isset($this->_translation[$i])) {
+ foreach ($this->translation as $i => $trans) {
+ if (isset($this->translation[$i])) {
$output .= 'msgstr[' . $i . '] ' . $this->formatString($trans);
}
else {
@@ -251,8 +252,8 @@ class PoItem {
*/
private function formatSingular() {
$output = '';
- $output .= 'msgid ' . $this->formatString($this->_source);
- $output .= 'msgstr ' . (isset($this->_translation) ? $this->formatString($this->_translation) : '""');
+ $output .= 'msgid ' . $this->formatString($this->source);
+ $output .= 'msgstr ' . (isset($this->translation) ? $this->formatString($this->translation) : '""');
return $output;
}
diff --git a/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php b/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php
index c5416b6..bac9e44 100644
--- a/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php
+++ b/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php
@@ -12,13 +12,13 @@ class PoMemoryWriter implements PoWriterInterface {
*
* @var array
*/
- private $_items;
+ protected $items;
/**
* Constructor, initialize empty items.
*/
public function __construct() {
- $this->_items = [];
+ $this->items = [];
}
/**
@@ -30,7 +30,7 @@ class PoMemoryWriter implements PoWriterInterface {
$item->setTranslation(implode(LOCALE_PLURAL_DELIMITER, $item->getTranslation()));
}
$context = $item->getContext();
- $this->_items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
+ $this->items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
}
/**
@@ -49,7 +49,7 @@ class PoMemoryWriter implements PoWriterInterface {
* @return array PoItem
*/
public function getData() {
- return $this->_items;
+ return $this->items;
}
/**
diff --git a/core/lib/Drupal/Component/Gettext/PoStreamReader.php b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
index f84d251..a69c03c 100644
--- a/core/lib/Drupal/Component/Gettext/PoStreamReader.php
+++ b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
@@ -2,7 +2,7 @@
namespace Drupal\Component\Gettext;
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Render\FormattableMarkup;
/**
* Implements Gettext PO stream reader.
@@ -17,7 +17,7 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
*
* @var int
*/
- private $_line_number = 0;
+ protected $lineNumber = 0;
/**
* Parser context for the stream reader state machine.
@@ -32,90 +32,90 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
*
* @var string
*/
- private $_context = 'COMMENT';
+ protected $context = 'COMMENT';
/**
* Current entry being read. Incomplete.
*
* @var array
*/
- private $_current_item = [];
+ protected $currentItem = [];
/**
* Current plural index for plural translations.
*
* @var int
*/
- private $_current_plural_index = 0;
+ protected $currentPluralIndex = 0;
/**
* URI of the PO stream that is being read.
*
* @var string
*/
- private $_uri = '';
+ protected $uri = '';
/**
* Language code for the PO stream being read.
*
* @var string
*/
- private $_langcode = NULL;
+ protected $langcode = NULL;
/**
* File handle of the current PO stream.
*
* @var resource
*/
- private $_fd;
+ protected $fd;
/**
* The PO stream header.
*
* @var \Drupal\Component\Gettext\PoHeader
*/
- private $_header;
+ protected $header;
/**
* Object wrapper for the last read source/translation pair.
*
* @var \Drupal\Component\Gettext\PoItem
*/
- private $_last_item;
+ protected $lastItem;
/**
* Indicator of whether the stream reading is finished.
*
* @var bool
*/
- private $_finished;
+ protected $finished;
/**
* Array of translated error strings recorded on reading this stream so far.
*
* @var array
*/
- private $_errors;
+ protected $errors;
/**
* {@inheritdoc}
*/
public function getLangcode() {
- return $this->_langcode;
+ return $this->langcode;
}
/**
* {@inheritdoc}
*/
public function setLangcode($langcode) {
- $this->_langcode = $langcode;
+ $this->langcode = $langcode;
}
/**
* {@inheritdoc}
*/
public function getHeader() {
- return $this->_header;
+ return $this->header;
}
/**
@@ -130,14 +130,14 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
* {@inheritdoc}
*/
public function getURI() {
- return $this->_uri;
+ return $this->uri;
}
/**
* {@inheritdoc}
*/
public function setURI($uri) {
- $this->_uri = $uri;
+ $this->uri = $uri;
}
/**
@@ -146,12 +146,12 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
* Opens the stream and reads the header. The stream is ready for reading
* items after.
*
- * @throws Exception
+ * @throws \Exception
* If the URI is not yet set.
*/
public function open() {
- if (!empty($this->_uri)) {
- $this->_fd = fopen($this->_uri, 'rb');
+ if (!empty($this->uri)) {
+ $this->fd = fopen($this->uri, 'rb');
$this->readHeader();
}
else {
@@ -162,12 +162,12 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
/**
* Implements Drupal\Component\Gettext\PoStreamInterface::close().
*
- * @throws Exception
+ * @throws \Exception
* If the stream is not open.
*/
public function close() {
- if ($this->_fd) {
- fclose($this->_fd);
+ if ($this->fd) {
+ fclose($this->fd);
}
else {
throw new \Exception('Cannot close stream that is not open.');
@@ -179,14 +179,14 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
*/
public function readItem() {
// Clear out the last item.
- $this->_last_item = NULL;
+ $this->lastItem = NULL;
// Read until finished with the stream or a complete item was identified.
- while (!$this->_finished && is_null($this->_last_item)) {
+ while (!$this->finished && is_null($this->lastItem)) {
$this->readLine();
}
- return $this->_last_item;
+ return $this->lastItem;
}
/**
@@ -196,14 +196,14 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
* The new seek position to set.
*/
public function setSeek($seek) {
- fseek($this->_fd, $seek);
+ fseek($this->fd, $seek);
}
/**
* Gets the pointer position of the current PO stream.
*/
public function getSeek() {
- return ftell($this->_fd);
+ return ftell($this->fd);
}
/**
@@ -221,18 +221,18 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
}
$header = new PoHeader();
$header->setFromString(trim($item->getTranslation()));
- $this->_header = $header;
+ $this->header = $header;
}
/**
* Reads a line from the PO stream and stores data internally.
*
- * Expands $this->_current_item based on new data for the current item. If
+ * Expands $this->current_item based on new data for the current item. If
* this line ends the current item, it is saved with setItemFromArray() with
- * data from $this->_current_item.
+ * data from $this->current_item.
*
* An internal state machine is maintained in this reader using
- * $this->_context as the reading state. PO items are in between COMMENT
+ * $this->context as the reading state. PO items are in between COMMENT
* states (when items have at least one line or comment in between them) or
* indicated by MSGSTR or MSGSTR_ARR followed immediately by an MSGID or
* MSGCTXT (when items closely follow each other).
@@ -245,25 +245,25 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
private function readLine() {
// Read a line and set the stream finished indicator if it was not
// possible anymore.
- $line = fgets($this->_fd);
- $this->_finished = ($line === FALSE);
+ $line = fgets($this->fd);
+ $this->finished = ($line === FALSE);
- if (!$this->_finished) {
+ if (!$this->finished) {
- if ($this->_line_number == 0) {
+ if ($this->lineNumber == 0) {
// The first line might come with a UTF-8 BOM, which should be removed.
$line = str_replace("\xEF\xBB\xBF", '', $line);
// Current plurality for 'msgstr[]'.
- $this->_current_plural_index = 0;
+ $this->currentPluralIndex = 0;
}
// Track the line number for error reporting.
- $this->_line_number++;
+ $this->lineNumber++;
// Initialize common values for error logging.
$log_vars = [
'%uri' => $this->getURI(),
- '%line' => $this->_line_number,
+ '%line' => $this->lineNumber,
];
// Trim away the linefeed. \\n might appear at the end of the string if
@@ -273,24 +273,24 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
if (!strncmp('#', $line, 1)) {
// Lines starting with '#' are comments.
- if ($this->_context == 'COMMENT') {
+ if ($this->context == 'COMMENT') {
// Already in comment context, add to current comment.
- $this->_current_item['#'][] = substr($line, 1);
+ $this->currentItem['#'][] = substr($line, 1);
}
- elseif (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
+ elseif (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
// We are currently in string context, save current item.
- $this->setItemFromArray($this->_current_item);
+ $this->setItemFromArray($this->currentItem);
// Start a new entry for the comment.
- $this->_current_item = [];
- $this->_current_item['#'][] = substr($line, 1);
+ $this->currentItem = [];
+ $this->currentItem['#'][] = substr($line, 1);
- $this->_context = 'COMMENT';
+ $this->context = 'COMMENT';
return;
}
else {
// A comment following any other context is a syntax error.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
return FALSE;
}
return;
@@ -298,9 +298,9 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
elseif (!strncmp('msgid_plural', $line, 12)) {
// A plural form for the current source string.
- if ($this->_context != 'MSGID') {
+ if ($this->context != 'MSGID') {
// A plural form can only be added to an msgid directly.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
return FALSE;
}
@@ -311,34 +311,34 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The plural form must be wrapped in quotes.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains a syntax error on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains a syntax error on line %line.', $log_vars);
return FALSE;
}
// Append the plural source to the current entry.
- if (is_string($this->_current_item['msgid'])) {
+ if (is_string($this->currentItem['msgid'])) {
// The first value was stored as string. Now we know the context is
// plural, it is converted to array.
- $this->_current_item['msgid'] = [$this->_current_item['msgid']];
+ $this->currentItem['msgid'] = [$this->currentItem['msgid']];
}
- $this->_current_item['msgid'][] = $quoted;
+ $this->currentItem['msgid'][] = $quoted;
- $this->_context = 'MSGID_PLURAL';
+ $this->context = 'MSGID_PLURAL';
return;
}
elseif (!strncmp('msgid', $line, 5)) {
// Starting a new message.
- if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
+ if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
// We are currently in string context, save current item.
- $this->setItemFromArray($this->_current_item);
+ $this->setItemFromArray($this->currentItem);
// Start a new context for the msgid.
- $this->_current_item = [];
+ $this->currentItem = [];
}
- elseif ($this->_context == 'MSGID') {
+ elseif ($this->context == 'MSGID') {
// We are currently already in the context, meaning we passed an id with no data.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
return FALSE;
}
@@ -349,25 +349,25 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The message id must be wrapped in quotes.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
return FALSE;
}
- $this->_current_item['msgid'] = $quoted;
- $this->_context = 'MSGID';
+ $this->currentItem['msgid'] = $quoted;
+ $this->context = 'MSGID';
return;
}
elseif (!strncmp('msgctxt', $line, 7)) {
// Starting a new context.
- if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
+ if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
// We are currently in string context, save current item.
- $this->setItemFromArray($this->_current_item);
- $this->_current_item = [];
+ $this->setItemFromArray($this->currentItem);
+ $this->currentItem = [];
}
- elseif (!empty($this->_current_item['msgctxt'])) {
+ elseif (!empty($this->currentItem['msgctxt'])) {
// A context cannot apply to another context.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
return FALSE;
}
@@ -378,37 +378,37 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The context string must be quoted.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
return FALSE;
}
- $this->_current_item['msgctxt'] = $quoted;
+ $this->currentItem['msgctxt'] = $quoted;
- $this->_context = 'MSGCTXT';
+ $this->context = 'MSGCTXT';
return;
}
elseif (!strncmp('msgstr[', $line, 7)) {
// A message string for a specific plurality.
- if (($this->_context != 'MSGID') &&
- ($this->_context != 'MSGCTXT') &&
- ($this->_context != 'MSGID_PLURAL') &&
- ($this->_context != 'MSGSTR_ARR')) {
- // Plural message strings must come after msgid, msgxtxt,
+ if (($this->context != 'MSGID') &&
+ ($this->context != 'MSGCTXT') &&
+ ($this->context != 'MSGID_PLURAL') &&
+ ($this->context != 'MSGSTR_ARR')) {
+ // Plural message strings must come after msgid, msgctxt,
// msgid_plural, or other msgstr[] entries.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
return FALSE;
}
// Ensure the plurality is terminated.
if (strpos($line, ']') === FALSE) {
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
return FALSE;
}
// Extract the plurality.
$frombracket = strstr($line, '[');
- $this->_current_plural_index = substr($frombracket, 1, strpos($frombracket, ']') - 1);
+ $this->currentPluralIndex = substr($frombracket, 1, strpos($frombracket, ']') - 1);
// Skip to the next whitespace and trim away any further whitespace,
// bringing $line to the message text only.
@@ -417,24 +417,24 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The string must be quoted.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
return FALSE;
}
- if (!isset($this->_current_item['msgstr']) || !is_array($this->_current_item['msgstr'])) {
- $this->_current_item['msgstr'] = [];
+ if (!isset($this->currentItem['msgstr']) || !is_array($this->currentItem['msgstr'])) {
+ $this->currentItem['msgstr'] = [];
}
- $this->_current_item['msgstr'][$this->_current_plural_index] = $quoted;
+ $this->currentItem['msgstr'][$this->currentPluralIndex] = $quoted;
- $this->_context = 'MSGSTR_ARR';
+ $this->context = 'MSGSTR_ARR';
return;
}
elseif (!strncmp("msgstr", $line, 6)) {
// A string pair for an msgid (with optional context).
- if (($this->_context != 'MSGID') && ($this->_context != 'MSGCTXT')) {
+ if (($this->context != 'MSGID') && ($this->context != 'MSGCTXT')) {
// Strings are only valid within an id or context scope.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
return FALSE;
}
@@ -445,13 +445,13 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// The string must be quoted.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
return FALSE;
}
- $this->_current_item['msgstr'] = $quoted;
+ $this->currentItem['msgstr'] = $quoted;
- $this->_context = 'MSGSTR';
+ $this->context = 'MSGSTR';
return;
}
elseif ($line != '') {
@@ -460,37 +460,37 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$quoted = $this->parseQuoted($line);
if ($quoted === FALSE) {
// This string must be quoted.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
return FALSE;
}
// Append the string to the current item.
- if (($this->_context == 'MSGID') || ($this->_context == 'MSGID_PLURAL')) {
- if (is_array($this->_current_item['msgid'])) {
+ if (($this->context == 'MSGID') || ($this->context == 'MSGID_PLURAL')) {
+ if (is_array($this->currentItem['msgid'])) {
// Add string to last array element for plural sources.
- $last_index = count($this->_current_item['msgid']) - 1;
- $this->_current_item['msgid'][$last_index] .= $quoted;
+ $last_index = count($this->currentItem['msgid']) - 1;
+ $this->currentItem['msgid'][$last_index] .= $quoted;
}
else {
// Singular source, just append the string.
- $this->_current_item['msgid'] .= $quoted;
+ $this->currentItem['msgid'] .= $quoted;
}
}
- elseif ($this->_context == 'MSGCTXT') {
+ elseif ($this->context == 'MSGCTXT') {
// Multiline context name.
- $this->_current_item['msgctxt'] .= $quoted;
+ $this->currentItem['msgctxt'] .= $quoted;
}
- elseif ($this->_context == 'MSGSTR') {
+ elseif ($this->context == 'MSGSTR') {
// Multiline translation string.
- $this->_current_item['msgstr'] .= $quoted;
+ $this->currentItem['msgstr'] .= $quoted;
}
- elseif ($this->_context == 'MSGSTR_ARR') {
+ elseif ($this->context == 'MSGSTR_ARR') {
// Multiline plural translation string.
- $this->_current_item['msgstr'][$this->_current_plural_index] .= $quoted;
+ $this->currentItem['msgstr'][$this->currentPluralIndex] .= $quoted;
}
else {
// No valid context to append to.
- $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
+ $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
return FALSE;
}
return;
@@ -498,14 +498,16 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
}
// Empty line read or EOF of PO stream, close out the last entry.
- if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
- $this->setItemFromArray($this->_current_item);
- $this->_current_item = [];
+ if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
+ $this->setItemFromArray($this->currentItem);
+ $this->currentItem = [];
}
- elseif ($this->_context != 'COMMENT') {
- $this->_errors[] = SafeMarkup::format('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
+ elseif ($this->context != 'COMMENT') {
+ $this->errors[] = new FormattableMarkup('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
return FALSE;
}
+
+ return;
}
/**
@@ -531,11 +533,11 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
$item->setTranslation($value['msgstr']);
$item->setPlural($plural);
$item->setComment($comments);
- $item->setLangcode($this->_langcode);
+ $item->setLangcode($this->langcode);
- $this->_last_item = $item;
+ $this->lastItem = $item;
- $this->_context = 'COMMENT';
+ $this->context = 'COMMENT';
}
/**
diff --git a/core/lib/Drupal/Component/Gettext/PoStreamWriter.php b/core/lib/Drupal/Component/Gettext/PoStreamWriter.php
index 5419b21..a441a3b 100644
--- a/core/lib/Drupal/Component/Gettext/PoStreamWriter.php
+++ b/core/lib/Drupal/Component/Gettext/PoStreamWriter.php
@@ -12,21 +12,28 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
*
* @var string
*/
- private $_uri;
+ protected $uri;
/**
* The Gettext PO header.
*
* @var \Drupal\Component\Gettext\PoHeader
*/
- private $_header;
+ protected $header;
/**
* File handle of the current PO stream.
*
* @var resource
*/
- private $_fd;
+ protected $fd;
+
+ /**
+ * The language code of this writer.
+ *
+ * @var string
+ */
+ protected $langcode;
/**
* Gets the PO header of the current stream.
@@ -35,7 +42,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The Gettext PO header.
*/
public function getHeader() {
- return $this->_header;
+ return $this->header;
}
/**
@@ -45,7 +52,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The Gettext PO header to set.
*/
public function setHeader(PoHeader $header) {
- $this->_header = $header;
+ $this->header = $header;
}
/**
@@ -55,7 +62,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The language code.
*/
public function getLangcode() {
- return $this->_langcode;
+ return $this->langcode;
}
/**
@@ -65,7 +72,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* The language code.
*/
public function setLangcode($langcode) {
- $this->_langcode = $langcode;
+ $this->langcode = $langcode;
}
/**
@@ -73,7 +80,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
*/
public function open() {
// Open in write mode. Will overwrite the stream if it already exists.
- $this->_fd = fopen($this->getURI(), 'w');
+ $this->fd = fopen($this->getURI(), 'w');
// Write the header at the start.
$this->writeHeader();
}
@@ -81,15 +88,15 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
/**
* Implements Drupal\Component\Gettext\PoStreamInterface::close().
*
- * @throws Exception
+ * @throws \Exception
* If the stream is not open.
*/
public function close() {
- if ($this->_fd) {
- fclose($this->_fd);
+ if ($this->fd) {
+ fclose($this->fd);
}
else {
- throw new Exception('Cannot close stream that is not open.');
+ throw new \Exception('Cannot close stream that is not open.');
}
}
@@ -100,13 +107,13 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* Piece of string to write to the stream. If the value is not directly a
* string, casting will happen in writing.
*
- * @throws Exception
+ * @throws \Exception
* If writing the data is not possible.
*/
private function write($data) {
- $result = fwrite($this->_fd, $data);
- if ($result === FALSE) {
- throw new Exception('Unable to write data: ' . substr($data, 0, 20));
+ $result = fwrite($this->fd, $data);
+ if ($result === FALSE || $result != strlen($data)) {
+ throw new \Exception('Unable to write data: ' . substr($data, 0, 20));
}
}
@@ -114,7 +121,7 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
* Write the PO header to the stream.
*/
private function writeHeader() {
- $this->write($this->_header);
+ $this->write($this->header);
}
/**
@@ -137,21 +144,21 @@ class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
/**
* Implements Drupal\Component\Gettext\PoStreamInterface::getURI().
*
- * @throws Exception
+ * @throws \Exception
* If the URI is not set.
*/
public function getURI() {
- if (empty($this->_uri)) {
- throw new Exception('No URI set.');
+ if (empty($this->uri)) {
+ throw new \Exception('No URI set.');
}
- return $this->_uri;
+ return $this->uri;
}
/**
* {@inheritdoc}
*/
public function setURI($uri) {
- $this->_uri = $uri;
+ $this->uri = $uri;
}
}
diff --git a/core/lib/Drupal/Component/Gettext/composer.json b/core/lib/Drupal/Component/Gettext/composer.json
index 1651234..87f500a 100644
--- a/core/lib/Drupal/Component/Gettext/composer.json
+++ b/core/lib/Drupal/Component/Gettext/composer.json
@@ -2,7 +2,7 @@
"name": "drupal/core-gettext",
"description": "PHP library for reading PO files.",
"type": "library",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"support": {
"issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute",
diff --git a/core/lib/Drupal/Component/Graph/composer.json b/core/lib/Drupal/Component/Graph/composer.json
index 5db5be1..e8f645f 100644
--- a/core/lib/Drupal/Component/Graph/composer.json
+++ b/core/lib/Drupal/Component/Graph/composer.json
@@ -3,7 +3,7 @@
"description": "Graph.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9"
},
diff --git a/core/lib/Drupal/Component/HttpFoundation/SecuredRedirectResponse.php b/core/lib/Drupal/Component/HttpFoundation/SecuredRedirectResponse.php
index 931f816..03c44fa 100644
--- a/core/lib/Drupal/Component/HttpFoundation/SecuredRedirectResponse.php
+++ b/core/lib/Drupal/Component/HttpFoundation/SecuredRedirectResponse.php
@@ -36,7 +36,7 @@ abstract class SecuredRedirectResponse extends RedirectResponse {
* Copies over the values from the given response.
*
* @param \Symfony\Component\HttpFoundation\RedirectResponse $response
- * The redirect reponse object.
+ * The redirect response object.
*/
protected function fromResponse(RedirectResponse $response) {
$this->setProtocolVersion($response->getProtocolVersion());
diff --git a/core/lib/Drupal/Component/HttpFoundation/composer.json b/core/lib/Drupal/Component/HttpFoundation/composer.json
index b86ab16..910a648 100644
--- a/core/lib/Drupal/Component/HttpFoundation/composer.json
+++ b/core/lib/Drupal/Component/HttpFoundation/composer.json
@@ -3,10 +3,10 @@
"description": "HttpFoundation.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
- "symfony/http-foundation": "^2.7"
+ "symfony/http-foundation": ">=2.7 <4.0.0"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/PhpStorage/FileStorage.php b/core/lib/Drupal/Component/PhpStorage/FileStorage.php
index e82ec2d..cd7119f 100644
--- a/core/lib/Drupal/Component/PhpStorage/FileStorage.php
+++ b/core/lib/Drupal/Component/PhpStorage/FileStorage.php
@@ -95,6 +95,7 @@ EOF;
<IfModule !mod_authz_core.c>
Deny from all
</IfModule>
+
$lines
EOF;
}
@@ -138,34 +139,42 @@ EOF;
* The directory path.
* @param int $mode
* The mode, permissions, the directory should have.
- * @param bool $is_backwards_recursive
- * Internal use only.
*
* @return bool
* TRUE if the directory exists or has been created, FALSE otherwise.
*/
- protected function createDirectory($directory, $mode = 0777, $is_backwards_recursive = FALSE) {
+ protected function createDirectory($directory, $mode = 0777) {
// If the directory exists already, there's nothing to do.
if (is_dir($directory)) {
return TRUE;
}
- // Otherwise, try to create the directory and ensure to set its permissions,
- // because mkdir() obeys the umask of the current process.
- if (is_dir($parent = dirname($directory))) {
- // If the parent directory exists, then the backwards recursion must end,
- // regardless of whether the subdirectory could be created.
- if ($status = mkdir($directory)) {
- // Only try to chmod() if the subdirectory could be created.
- $status = chmod($directory, $mode);
+
+ // If the parent directory doesn't exist, try to create it.
+ $parent_exists = is_dir($parent = dirname($directory));
+ if (!$parent_exists) {
+ $parent_exists = $this->createDirectory($parent, $mode);
+ }
+
+ // If parent exists, try to create the directory and ensure to set its
+ // permissions, because mkdir() obeys the umask of the current process.
+ if ($parent_exists) {
+ // We hide warnings and ignore the return because there may have been a
+ // race getting here and the directory could already exist.
+ @mkdir($directory);
+ // Only try to chmod() if the subdirectory could be created.
+ if (is_dir($directory)) {
+ // Avoid writing permissions if possible.
+ if (fileperms($directory) !== $mode) {
+ return chmod($directory, $mode);
+ }
+ return TRUE;
+ }
+ else {
+ // Something failed and the directory doesn't exist.
+ trigger_error('mkdir(): Permission Denied', E_USER_WARNING);
}
- return $is_backwards_recursive ? TRUE : $status;
}
- // If the parent directory and the requested directory does not exist and
- // could not be created above, walk the requested directory path back up
- // until an existing directory is hit, and from there, recursively create
- // the sub-directories. Only if that recursion succeeds, create the final,
- // originally requested subdirectory.
- return $this->createDirectory($parent, $mode, TRUE) && mkdir($directory) && chmod($directory, $mode);
+ return FALSE;
}
/**
diff --git a/core/lib/Drupal/Component/PhpStorage/composer.json b/core/lib/Drupal/Component/PhpStorage/composer.json
index 16b561d..614652a 100644
--- a/core/lib/Drupal/Component/PhpStorage/composer.json
+++ b/core/lib/Drupal/Component/PhpStorage/composer.json
@@ -3,7 +3,7 @@
"description": "PhpStorage.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9"
},
diff --git a/core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionInterface.php
new file mode 100644
index 0000000..d1ff953
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionInterface.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+
+/**
+ * Provides an interface for plugin definitions which use contexts.
+ *
+ * @ingroup Plugin
+ */
+interface ContextAwarePluginDefinitionInterface extends PluginDefinitionInterface {
+
+ /**
+ * Checks if the plugin defines a particular context.
+ *
+ * @param string $name
+ * The context name.
+ *
+ * @return bool
+ * TRUE if the plugin defines the given context, otherwise FALSE.
+ */
+ public function hasContextDefinition($name);
+
+ /**
+ * Returns all context definitions for this plugin.
+ *
+ * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]
+ * The context definitions.
+ */
+ public function getContextDefinitions();
+
+ /**
+ * Returns a particular context definition for this plugin.
+ *
+ * @param string $name
+ * The context name.
+ *
+ * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface
+ * The context definition.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\ContextException
+ * Thrown if the plugin does not define the given context.
+ */
+ public function getContextDefinition($name);
+
+ /**
+ * Adds a context to this plugin definition.
+ *
+ * @param string $name
+ * The context name.
+ * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $definition
+ * The context definition.
+ *
+ * @return $this
+ * The called object.
+ */
+ public function addContextDefinition($name, ContextDefinitionInterface $definition);
+
+ /**
+ * Removes a context definition from this plugin.
+ *
+ * @param string $name
+ * The context name.
+ *
+ * @return $this
+ * The called object.
+ */
+ public function removeContextDefinition($name);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionTrait.php b/core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionTrait.php
new file mode 100644
index 0000000..54b8e5f
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/ContextAwarePluginDefinitionTrait.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+use Drupal\Component\Plugin\Exception\ContextException;
+
+/**
+ * Provides a trait for context-aware object-based plugin definitions.
+ */
+trait ContextAwarePluginDefinitionTrait {
+
+ /**
+ * The context definitions for this plugin definition.
+ *
+ * @var \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]
+ */
+ protected $contextDefinitions = [];
+
+ /**
+ * Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::hasContextDefinition().
+ */
+ public function hasContextDefinition($name) {
+ return array_key_exists($name, $this->contextDefinitions);
+ }
+
+ /**
+ * Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::getContextDefinitions().
+ */
+ public function getContextDefinitions() {
+ return $this->contextDefinitions;
+ }
+
+ /**
+ * Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::getContextDefinition().
+ */
+ public function getContextDefinition($name) {
+ if ($this->hasContextDefinition($name)) {
+ return $this->contextDefinitions[$name];
+ }
+ throw new ContextException($this->id() . " does not define a '$name' context");
+ }
+
+ /**
+ * Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::addContextDefinition().
+ */
+ public function addContextDefinition($name, ContextDefinitionInterface $definition) {
+ $this->contextDefinitions[$name] = $definition;
+ return $this;
+ }
+
+ /**
+ * Implements \Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface::removeContextDefinition().
+ */
+ public function removeContextDefinition($name) {
+ unset($this->contextDefinitions[$name]);
+ return $this;
+ }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php
index bf5e0aa..66e6a2e 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php
@@ -33,6 +33,8 @@ interface DiscoveryInterface {
* @return mixed[]
* An array of plugin definitions (empty array if no definitions were
* found). Keys are plugin IDs.
+ *
+ * @see \Drupal\Core\Plugin\FilteredPluginManagerInterface::getFilteredDefinitions()
*/
public function getDefinitions();
diff --git a/core/lib/Drupal/Component/Plugin/Exception/MissingValueContextException.php b/core/lib/Drupal/Component/Plugin/Exception/MissingValueContextException.php
new file mode 100644
index 0000000..67b5d92
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Exception/MissingValueContextException.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\Component\Plugin\Exception;
+
+/**
+ * An exception class thrown when contexts exist but are missing a value.
+ */
+class MissingValueContextException extends ContextException {
+
+ /**
+ * MissingValueContextException constructor.
+ *
+ * @param string[] $contexts_without_value
+ * List of contexts with missing value.
+ */
+ public function __construct(array $contexts_without_value = []) {
+ $message = 'Required contexts without a value: ' . implode(', ', $contexts_without_value);
+ parent::__construct($message);
+ }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
index 1f203fb..b783e40 100644
--- a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
+++ b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
@@ -63,7 +63,7 @@ class DefaultFactory implements FactoryInterface {
* @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface|mixed[] $plugin_definition
* The plugin definition associated with the plugin ID.
* @param string $required_interface
- * (optional) THe required plugin interface.
+ * (optional) The required plugin interface.
*
* @return string
* The appropriate class name.
diff --git a/core/lib/Drupal/Component/Plugin/PluginBase.php b/core/lib/Drupal/Component/Plugin/PluginBase.php
index b608e90..c56442b 100644
--- a/core/lib/Drupal/Component/Plugin/PluginBase.php
+++ b/core/lib/Drupal/Component/Plugin/PluginBase.php
@@ -41,7 +41,7 @@ abstract class PluginBase implements PluginInspectionInterface, DerivativeInspec
protected $configuration;
/**
- * Constructs a Drupal\Component\Plugin\PluginBase object.
+ * Constructs a \Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
diff --git a/core/lib/Drupal/Component/Plugin/PluginManagerBase.php b/core/lib/Drupal/Component/Plugin/PluginManagerBase.php
index 34416ff..d7342da 100644
--- a/core/lib/Drupal/Component/Plugin/PluginManagerBase.php
+++ b/core/lib/Drupal/Component/Plugin/PluginManagerBase.php
@@ -29,7 +29,7 @@ abstract class PluginManagerBase implements PluginManagerInterface {
/**
* The object that returns the preconfigured plugin instance appropriate for a particular runtime condition.
*
- * @var \Drupal\Component\Plugin\Mapper\MapperInterface
+ * @var \Drupal\Component\Plugin\Mapper\MapperInterface|null
*/
protected $mapper;
@@ -76,8 +76,7 @@ abstract class PluginManagerBase implements PluginManagerInterface {
return $this->getFactory()->createInstance($plugin_id, $configuration);
}
catch (PluginNotFoundException $e) {
- $fallback_id = $this->getFallbackPluginId($plugin_id, $configuration);
- return $this->getFactory()->createInstance($fallback_id, $configuration);
+ return $this->handlePluginNotFound($plugin_id, $configuration);
}
}
else {
@@ -86,9 +85,28 @@ abstract class PluginManagerBase implements PluginManagerInterface {
}
/**
+ * Allows plugin managers to specify custom behavior if a plugin is not found.
+ *
+ * @param string $plugin_id
+ * The ID of the missing requested plugin.
+ * @param array $configuration
+ * An array of configuration relevant to the plugin instance.
+ *
+ * @return object
+ * A fallback plugin instance.
+ */
+ protected function handlePluginNotFound($plugin_id, array $configuration) {
+ $fallback_id = $this->getFallbackPluginId($plugin_id, $configuration);
+ return $this->getFactory()->createInstance($fallback_id, $configuration);
+ }
+
+ /**
* {@inheritdoc}
*/
public function getInstance(array $options) {
+ if (!$this->mapper) {
+ throw new \BadMethodCallException(sprintf('%s does not support this method unless %s::$mapper is set.', static::class, static::class));
+ }
return $this->mapper->getInstance($options);
}
diff --git a/core/lib/Drupal/Component/Plugin/composer.json b/core/lib/Drupal/Component/Plugin/composer.json
index 1182657..77f02d6 100644
--- a/core/lib/Drupal/Component/Plugin/composer.json
+++ b/core/lib/Drupal/Component/Plugin/composer.json
@@ -3,10 +3,10 @@
"description": "Base building block for a scalable and extensible plug-in system for PHP components and application framework extensions.",
"keywords": ["drupal", "plugin", "plugins"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
- "symfony/validator": "^2.7"
+ "symfony/validator": ">=2.7 <4.0.0"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/ProxyBuilder/composer.json b/core/lib/Drupal/Component/ProxyBuilder/composer.json
index ff9a5ea..52fbdb8 100644
--- a/core/lib/Drupal/Component/ProxyBuilder/composer.json
+++ b/core/lib/Drupal/Component/ProxyBuilder/composer.json
@@ -3,7 +3,7 @@
"description": "Provides a lightweight mechanism to provide lazy loaded proxies.",
"keywords": ["drupal", "proxy"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9"
},
diff --git a/core/lib/Drupal/Component/Render/FormattableMarkup.php b/core/lib/Drupal/Component/Render/FormattableMarkup.php
index 6e98928..2bad2ec 100644
--- a/core/lib/Drupal/Component/Render/FormattableMarkup.php
+++ b/core/lib/Drupal/Component/Render/FormattableMarkup.php
@@ -3,7 +3,6 @@
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Html;
-use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
/**
@@ -62,6 +61,13 @@ use Drupal\Component\Utility\UrlHelper;
class FormattableMarkup implements MarkupInterface, \Countable {
/**
+ * The string containing placeholders.
+ *
+ * @var string
+ */
+ protected $string;
+
+ /**
* The arguments to replace placeholders with.
*
* @var array
@@ -100,7 +106,7 @@ class FormattableMarkup implements MarkupInterface, \Countable {
* The length of the string.
*/
public function count() {
- return Unicode::strlen($this->string);
+ return mb_strlen($this->string);
}
/**
diff --git a/core/lib/Drupal/Component/Render/HtmlEscapedText.php b/core/lib/Drupal/Component/Render/HtmlEscapedText.php
index b286f57..0ddc4fa 100644
--- a/core/lib/Drupal/Component/Render/HtmlEscapedText.php
+++ b/core/lib/Drupal/Component/Render/HtmlEscapedText.php
@@ -3,7 +3,6 @@
namespace Drupal\Component\Render;
use Drupal\Component\Utility\Html;
-use Drupal\Component\Utility\Unicode;
/**
* Escapes HTML syntax characters to HTML entities for display in markup.
@@ -43,7 +42,7 @@ class HtmlEscapedText implements MarkupInterface, \Countable {
* {@inheritdoc}
*/
public function count() {
- return Unicode::strlen($this->string);
+ return mb_strlen($this->string);
}
/**
diff --git a/core/lib/Drupal/Component/Render/MarkupTrait.php b/core/lib/Drupal/Component/Render/MarkupTrait.php
index 59e98c3..c7faa81 100644
--- a/core/lib/Drupal/Component/Render/MarkupTrait.php
+++ b/core/lib/Drupal/Component/Render/MarkupTrait.php
@@ -2,8 +2,6 @@
namespace Drupal\Component\Render;
-use Drupal\Component\Utility\Unicode;
-
/**
* Implements MarkupInterface and Countable for rendered objects.
*
@@ -61,7 +59,7 @@ trait MarkupTrait {
* The length of the string.
*/
public function count() {
- return Unicode::strlen($this->string);
+ return mb_strlen($this->string);
}
/**
diff --git a/core/lib/Drupal/Component/Render/composer.json b/core/lib/Drupal/Component/Render/composer.json
index 50c2f70..9e44b2d 100644
--- a/core/lib/Drupal/Component/Render/composer.json
+++ b/core/lib/Drupal/Component/Render/composer.json
@@ -3,7 +3,7 @@
"description": "Renders placeholder variables for HTML and plain-text display.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
"drupal/core-utility": "^8.2"
diff --git a/core/lib/Drupal/Component/Serialization/LICENSE.txt b/core/lib/Drupal/Component/Serialization/LICENSE.txt
index e69de29..94fb846 100644
--- a/core/lib/Drupal/Component/Serialization/LICENSE.txt
+++ b/core/lib/Drupal/Component/Serialization/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/core/lib/Drupal/Component/Serialization/YamlPecl.php b/core/lib/Drupal/Component/Serialization/YamlPecl.php
index a01f980..8200f49 100644
--- a/core/lib/Drupal/Component/Serialization/YamlPecl.php
+++ b/core/lib/Drupal/Component/Serialization/YamlPecl.php
@@ -47,7 +47,7 @@ class YamlPecl implements SerializationInterface {
// and then restore it after decoding has occurred. This allows us to turn
// parsing errors into a throwable exception.
// @see Drupal\Component\Serialization\Exception\InvalidDataTypeException
- // @see http://php.net/manual/en/class.errorexception.php
+ // @see http://php.net/manual/class.errorexception.php
set_error_handler([__CLASS__, 'errorHandler']);
$ndocs = 0;
$data = yaml_parse($raw, 0, $ndocs, [
diff --git a/core/lib/Drupal/Component/Serialization/composer.json b/core/lib/Drupal/Component/Serialization/composer.json
index 97fd639..3439f58 100644
--- a/core/lib/Drupal/Component/Serialization/composer.json
+++ b/core/lib/Drupal/Component/Serialization/composer.json
@@ -3,10 +3,10 @@
"description": "Serialization.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
- "symfony/yaml": "^2.7"
+ "symfony/yaml": ">=2.7 <4.0.0"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/Transliteration/LICENSE.txt b/core/lib/Drupal/Component/Transliteration/LICENSE.txt
index e69de29..94fb846 100644
--- a/core/lib/Drupal/Component/Transliteration/LICENSE.txt
+++ b/core/lib/Drupal/Component/Transliteration/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/core/lib/Drupal/Component/Transliteration/composer.json b/core/lib/Drupal/Component/Transliteration/composer.json
index 0bcc46b..abdce4f 100644
--- a/core/lib/Drupal/Component/Transliteration/composer.json
+++ b/core/lib/Drupal/Component/Transliteration/composer.json
@@ -2,7 +2,7 @@
"name": "drupal/core-transliteration",
"description": "Transliteration.",
"type": "library",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"support": {
"issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute",
diff --git a/core/lib/Drupal/Component/Transliteration/data/x03.php b/core/lib/Drupal/Component/Transliteration/data/x03.php
index f61602d..1cc3129 100644
--- a/core/lib/Drupal/Component/Transliteration/data/x03.php
+++ b/core/lib/Drupal/Component/Transliteration/data/x03.php
@@ -14,11 +14,11 @@ $base = [
0x50 => NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
0x60 => '', '', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
0x70 => NULL, NULL, NULL, NULL, '\'', ',', NULL, NULL, NULL, NULL, 'i', NULL, NULL, NULL, '?', NULL,
- 0x80 => NULL, NULL, NULL, NULL, '', '', 'A', ':', 'E', 'E', 'I', NULL, 'O', NULL, 'Y', 'O',
- 0x90 => 'i', 'A', 'B', 'G', 'D', 'E', 'Z', 'E', 'TH', 'I', 'K', 'L', 'M', 'N', 'X', 'O',
- 0xA0 => 'P', 'R', NULL, 'S', 'T', 'Y', 'PH', 'CH', 'PS', 'O', 'I', 'Y', 'a', 'e', 'e', 'i',
- 0xB0 => 'y', 'a', 'b', 'g', 'd', 'e', 'z', 'e', 'th', 'i', 'k', 'l', 'm', 'n', 'x', 'o',
- 0xC0 => 'p', 'r', 's', 's', 't', 'y', 'ph', 'ch', 'ps', 'o', 'i', 'y', 'o', 'y', 'o', NULL,
+ 0x80 => NULL, NULL, NULL, NULL, '', '', 'A', ';', 'E', 'I', 'I', NULL, 'O', NULL, 'U', 'O',
+ 0x90 => 'I', 'A', 'B', 'G', 'D', 'E', 'Z', 'I', 'Th', 'I', 'K', 'L', 'M', 'N', 'X', 'O',
+ 0xA0 => 'P', 'R', NULL, 'S', 'T', 'Y', 'F', 'H', 'Ps', 'O', 'I', 'Y', 'a', 'e', 'i', 'i',
+ 0xB0 => 'y', 'a', 'b', 'g', 'd', 'e', 'z', 'i', 'th', 'i', 'k', 'l', 'm', 'n', 'x', 'o',
+ 0xC0 => 'p', 'r', 's', 's', 't', 'y', 'f', 'h', 'ps', 'o', 'i', 'y', 'o', 'y', 'o', NULL,
0xD0 => 'b', 'th', 'Y', 'Y', 'Y', 'ph', 'p', '&', NULL, NULL, 'St', 'st', 'W', 'w', 'Q', 'q',
0xE0 => 'Sp', 'sp', 'Sh', 'sh', 'F', 'f', 'Kh', 'kh', 'H', 'h', 'G', 'g', 'CH', 'ch', 'Ti', 'ti',
0xF0 => 'k', 'r', 's', 'j', 'TH', 'e', NULL, 'S', 's', 'S', 'S', 's', NULL, NULL, NULL, NULL,
diff --git a/core/lib/Drupal/Component/Transliteration/data/x04.php b/core/lib/Drupal/Component/Transliteration/data/x04.php
index 1be0d43..64834b8 100644
--- a/core/lib/Drupal/Component/Transliteration/data/x04.php
+++ b/core/lib/Drupal/Component/Transliteration/data/x04.php
@@ -6,12 +6,12 @@
*/
$base = [
- 0x00 => 'E', 'E', 'D', 'G', 'E', 'Z', 'I', 'I', 'J', 'L', 'N', 'C', 'K', 'I', 'U', 'D',
- 0x10 => 'A', 'B', 'V', 'G', 'D', 'E', 'Z', 'Z', 'I', 'I', 'K', 'L', 'M', 'N', 'O', 'P',
- 0x20 => 'R', 'S', 'T', 'U', 'F', 'H', 'C', 'C', 'S', 'S', '', 'Y', '', 'E', 'U', 'A',
- 0x30 => 'a', 'b', 'v', 'g', 'd', 'e', 'z', 'z', 'i', 'i', 'k', 'l', 'm', 'n', 'o', 'p',
- 0x40 => 'r', 's', 't', 'u', 'f', 'h', 'c', 'c', 's', 's', '', 'y', '', 'e', 'u', 'a',
- 0x50 => 'e', 'e', 'd', 'g', 'e', 'z', 'i', 'i', 'j', 'l', 'n', 'c', 'k', 'i', 'u', 'd',
+ 0x00 => 'E', 'YO', 'D', 'G', 'E', 'Z', 'I', 'I', 'J', 'L', 'N', 'C', 'K', 'I', 'U', 'D',
+ 0x10 => 'A', 'B', 'V', 'G', 'D', 'E', 'ZH', 'Z', 'I', 'Y', 'K', 'L', 'M', 'N', 'O', 'P',
+ 0x20 => 'R', 'S', 'T', 'U', 'F', 'KH', 'C', 'CH', 'SH', 'SCH', '', 'Y', '', 'E', 'YU', 'YA',
+ 0x30 => 'a', 'b', 'v', 'g', 'd', 'e', 'zh', 'z', 'i', 'y', 'k', 'l', 'm', 'n', 'o', 'p',
+ 0x40 => 'r', 's', 't', 'u', 'f', 'kh', 'c', 'ch', 'sh', 'sch', '', 'y', '', 'e', 'yu', 'ya',
+ 0x50 => 'e', 'yo', 'd', 'g', 'e', 'z', 'i', 'i', 'j', 'l', 'n', 'c', 'k', 'i', 'u', 'd',
0x60 => 'O', 'o', 'E', 'e', 'Ie', 'ie', 'E', 'e', 'Ie', 'ie', 'O', 'o', 'Io', 'io', 'Ks', 'ks',
0x70 => 'Ps', 'ps', 'F', 'f', 'Y', 'y', 'Y', 'y', 'u', 'u', 'O', 'o', 'O', 'o', 'Ot', 'ot',
0x80 => 'Q', 'q', '*1000*', '', '', '', '', NULL, '*100.000*', '*1.000.000*', NULL, NULL, '"', '"', 'R\'', 'r\'',
diff --git a/core/lib/Drupal/Component/Utility/Color.php b/core/lib/Drupal/Component/Utility/Color.php
index aa296e9..ff06f39 100644
--- a/core/lib/Drupal/Component/Utility/Color.php
+++ b/core/lib/Drupal/Component/Utility/Color.php
@@ -23,7 +23,7 @@ class Color {
// Hash prefix is optional.
$hex = ltrim($hex, '#');
// Must be either RGB or RRGGBB.
- $length = Unicode::strlen($hex);
+ $length = mb_strlen($hex);
$valid = $valid && ($length === 3 || $length === 6);
// Must be a valid hex value.
$valid = $valid && ctype_xdigit($hex);
@@ -94,4 +94,28 @@ class Color {
return '#' . str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
}
+ /**
+ * Normalize the hex color length to 6 characters for comparison.
+ *
+ * @param string $hex
+ * The hex color to normalize.
+ *
+ * @return string
+ * The 6 character hex color.
+ */
+ public static function normalizeHexLength($hex) {
+ // Ignore '#' prefixes.
+ $hex = ltrim($hex, '#');
+
+ if (strlen($hex) === 3) {
+ $hex[5] = $hex[2];
+ $hex[4] = $hex[2];
+ $hex[3] = $hex[1];
+ $hex[2] = $hex[1];
+ $hex[1] = $hex[0];
+ }
+
+ return '#' . $hex;
+ }
+
}
diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
index 97a14ec..b3893d6 100644
--- a/core/lib/Drupal/Component/Utility/Html.php
+++ b/core/lib/Drupal/Component/Utility/Html.php
@@ -71,7 +71,7 @@ class Html {
public static function getClass($class) {
$class = (string) $class;
if (!isset(static::$classes[$class])) {
- static::$classes[$class] = static::cleanCssIdentifier(Unicode::strtolower($class));
+ static::$classes[$class] = static::cleanCssIdentifier(mb_strtolower($class));
}
return static::$classes[$class];
}
@@ -79,9 +79,10 @@ class Html {
/**
* Prepares a string for use as a CSS identifier (element, class, or ID name).
*
- * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for
- * valid CSS identifiers (including element names, classes, and IDs in
- * selectors.)
+ * Link below shows the syntax for valid CSS identifiers (including element
+ * names, classes, and IDs in selectors).
+ *
+ * @see http://www.w3.org/TR/CSS21/syndata.html#characters
*
* @param string $identifier
* The identifier to clean.
@@ -124,7 +125,7 @@ class Html {
// Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
$identifier = preg_replace([
'/^[0-9]/',
- '/^(-[0-9])|^(--)/'
+ '/^(-[0-9])|^(--)/',
], ['_', '__'], $identifier);
return $identifier;
}
@@ -215,7 +216,7 @@ class Html {
* @see self::getUniqueId()
*/
public static function getId($id) {
- $id = str_replace([' ', '_', '[', ']'], ['-', '-', '-', ''], Unicode::strtolower($id));
+ $id = str_replace([' ', '_', '[', ']'], ['-', '-', '-', ''], mb_strtolower($id));
// As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
// only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
@@ -451,9 +452,9 @@ EOD;
* The updated (X)HTML snippet.
*/
public static function transformRootRelativeUrlsToAbsolute($html, $scheme_and_host) {
- assert('empty(array_diff(array_keys(parse_url($scheme_and_host)), ["scheme", "host", "port"]))', '$scheme_and_host contains scheme, host and port at most.');
- assert('isset(parse_url($scheme_and_host)["scheme"])', '$scheme_and_host is absolute and hence has a scheme.');
- assert('isset(parse_url($scheme_and_host)["host"])', '$base_url is absolute and hence has a host.');
+ assert(empty(array_diff(array_keys(parse_url($scheme_and_host)), ["scheme", "host", "port"])), '$scheme_and_host contains scheme, host and port at most.');
+ assert(isset(parse_url($scheme_and_host)["scheme"]), '$scheme_and_host is absolute and hence has a scheme.');
+ assert(isset(parse_url($scheme_and_host)["host"]), '$base_url is absolute and hence has a host.');
$html_dom = Html::load($html);
$xpath = new \DOMXpath($html_dom);
diff --git a/core/lib/Drupal/Component/Utility/Random.php b/core/lib/Drupal/Component/Utility/Random.php
index cf34835..d1f25ff 100644
--- a/core/lib/Drupal/Component/Utility/Random.php
+++ b/core/lib/Drupal/Component/Utility/Random.php
@@ -259,7 +259,6 @@ class Random {
return $output;
}
-
/**
* Create a placeholder image.
*
diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index b2d00a3..a3f2d68 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -40,6 +40,7 @@ class SafeMarkup {
* @see https://www.drupal.org/node/2549395
*/
public static function isSafe($string, $strategy = 'html') {
+ @trigger_error('SafeMarkup::isSafe() is scheduled for removal in Drupal 9.0.0. Instead, you should just check if a variable is an instance of \Drupal\Component\Render\MarkupInterface. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
return $string instanceof MarkupInterface;
}
@@ -66,6 +67,7 @@ class SafeMarkup {
* @see drupal_validate_utf8()
*/
public static function checkPlain($text) {
+ @trigger_error('SafeMarkup::checkPlain() is scheduled for removal in Drupal 9.0.0. Rely on Twig\'s auto-escaping feature, or use the @link theme_render #plain_text @endlink key when constructing a render array that contains plain text in order to use the renderer\'s auto-escaping feature. If neither of these are possible, \Drupal\Component\Utility\Html::escape() can be used in places where explicit escaping is needed. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
return new HtmlEscapedText($text);
}
@@ -93,6 +95,7 @@ class SafeMarkup {
* @see https://www.drupal.org/node/2549395
*/
public static function format($string, array $args) {
+ @trigger_error('SafeMarkup::format() is scheduled for removal in Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup. See https://www.drupal.org/node/2549395.', E_USER_DEPRECATED);
return new FormattableMarkup($string, $args);
}
diff --git a/core/lib/Drupal/Component/Utility/Unicode.php b/core/lib/Drupal/Component/Utility/Unicode.php
index b222f78..7cf2351 100644
--- a/core/lib/Drupal/Component/Utility/Unicode.php
+++ b/core/lib/Drupal/Component/Utility/Unicode.php
@@ -88,13 +88,6 @@ EOD;
const STATUS_ERROR = -1;
/**
- * Holds the multibyte capabilities of the current environment.
- *
- * @var int
- */
- protected static $status = 0;
-
- /**
* Gets the current status of unicode/multibyte support on this environment.
*
* @return int
@@ -107,7 +100,13 @@ EOD;
* An error occurred. No unicode support.
*/
public static function getStatus() {
- return static::$status;
+ switch (static::check()) {
+ case 'mb_strlen':
+ return Unicode::STATUS_SINGLEBYTE;
+ case '':
+ return Unicode::STATUS_MULTIBYTE;
+ }
+ return Unicode::STATUS_ERROR;
}
/**
@@ -123,12 +122,16 @@ EOD;
*
* @param int $status
* The new status of multibyte support.
+ *
+ * @deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
+ * Drupal 9 there will be no way to set the status and in Drupal 8 this
+ * ability has been removed because mb_*() functions are supplied using
+ * Symfony's polyfill.
+ *
+ * @see https://www.drupal.org/node/2850048
*/
public static function setStatus($status) {
- if (!in_array($status, [static::STATUS_SINGLEBYTE, static::STATUS_MULTIBYTE, static::STATUS_ERROR])) {
- throw new \InvalidArgumentException('Invalid status value for unicode support.');
- }
- static::$status = $status;
+ @trigger_error('\Drupal\Component\Utility\Unicode::setStatus() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In Drupal 9 there will be no way to set the status and in Drupal 8 this ability has been removed because mb_*() functions are supplied using Symfony\'s polyfill. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
}
/**
@@ -143,38 +146,33 @@ EOD;
* Otherwise, an empty string.
*/
public static function check() {
+ // Set appropriate configuration.
+ mb_internal_encoding('utf-8');
+ mb_language('uni');
+
// Check for mbstring extension.
- if (!function_exists('mb_strlen')) {
- static::$status = static::STATUS_SINGLEBYTE;
+ if (!extension_loaded('mbstring')) {
return 'mb_strlen';
}
// Check mbstring configuration.
if (ini_get('mbstring.func_overload') != 0) {
- static::$status = static::STATUS_ERROR;
return 'mbstring.func_overload';
}
if (ini_get('mbstring.encoding_translation') != 0) {
- static::$status = static::STATUS_ERROR;
return 'mbstring.encoding_translation';
}
// mbstring.http_input and mbstring.http_output are deprecated and empty by
// default in PHP 5.6.
if (version_compare(PHP_VERSION, '5.6.0') == -1) {
if (ini_get('mbstring.http_input') != 'pass') {
- static::$status = static::STATUS_ERROR;
return 'mbstring.http_input';
}
if (ini_get('mbstring.http_output') != 'pass') {
- static::$status = static::STATUS_ERROR;
return 'mbstring.http_output';
}
}
- // Set appropriate configuration.
- mb_internal_encoding('utf-8');
- mb_language('uni');
- static::$status = static::STATUS_MULTIBYTE;
return '';
}
@@ -224,17 +222,7 @@ EOD;
* Converted data or FALSE.
*/
public static function convertToUtf8($data, $encoding) {
- if (function_exists('iconv')) {
- return @iconv($encoding, 'utf-8', $data);
- }
- elseif (function_exists('mb_convert_encoding')) {
- return @mb_convert_encoding($data, 'utf-8', $encoding);
- }
- elseif (function_exists('recode_string')) {
- return @recode_string($encoding . '..utf-8', $data);
- }
- // Cannot convert.
- return FALSE;
+ return @iconv($encoding, 'utf-8', $data);
}
/**
@@ -281,15 +269,15 @@ EOD;
*
* @return int
* The length of the string.
+ *
+ * @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
+ * mb_strlen() instead.
+ *
+ * @see https://www.drupal.org/node/2850048
*/
public static function strlen($text) {
- if (static::getStatus() == static::STATUS_MULTIBYTE) {
- return mb_strlen($text);
- }
- else {
- // Do not count UTF-8 continuation bytes.
- return strlen(preg_replace("/[\x80-\xBF]/", '', $text));
- }
+ @trigger_error('\Drupal\Component\Utility\Unicode::strlen() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strlen() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
+ return mb_strlen($text);
}
/**
@@ -300,18 +288,15 @@ EOD;
*
* @return string
* The string in uppercase.
+ *
+ * @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
+ * mb_strtoupper() instead.
+ *
+ * @see https://www.drupal.org/node/2850048
*/
public static function strtoupper($text) {
- if (static::getStatus() == static::STATUS_MULTIBYTE) {
- return mb_strtoupper($text);
- }
- else {
- // Use C-locale for ASCII-only uppercase.
- $text = strtoupper($text);
- // Case flip Latin-1 accented letters.
- $text = preg_replace_callback('/\xC3[\xA0-\xB6\xB8-\xBE]/', '\Drupal\Component\Utility\Unicode::caseFlip', $text);
- return $text;
- }
+ @trigger_error('\Drupal\Component\Utility\Unicode::strtoupper() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strtoupper() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
+ return mb_strtoupper($text);
}
/**
@@ -322,18 +307,15 @@ EOD;
*
* @return string
* The string in lowercase.
+ *
+ * @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
+ * mb_strtolower() instead.
+ *
+ * @see https://www.drupal.org/node/2850048
*/
public static function strtolower($text) {
- if (static::getStatus() == static::STATUS_MULTIBYTE) {
- return mb_strtolower($text);
- }
- else {
- // Use C-locale for ASCII-only lowercase.
- $text = strtolower($text);
- // Case flip Latin-1 accented letters.
- $text = preg_replace_callback('/\xC3[\x80-\x96\x98-\x9E]/', '\Drupal\Component\Utility\Unicode::caseFlip', $text);
- return $text;
- }
+ @trigger_error('\Drupal\Component\Utility\Unicode::strtolower() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strtolower() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
+ return mb_strtolower($text);
}
/**
@@ -346,7 +328,7 @@ EOD;
* The string with the first character as uppercase.
*/
public static function ucfirst($text) {
- return static::strtoupper(static::substr($text, 0, 1)) . static::substr($text, 1);
+ return mb_strtoupper(mb_substr($text, 0, 1)) . mb_substr($text, 1);
}
/**
@@ -362,7 +344,7 @@ EOD;
*/
public static function lcfirst($text) {
// Note: no mbstring equivalent!
- return static::strtolower(static::substr($text, 0, 1)) . static::substr($text, 1);
+ return mb_strtolower(mb_substr($text, 0, 1)) . mb_substr($text, 1);
}
/**
@@ -379,7 +361,7 @@ EOD;
public static function ucwords($text) {
$regex = '/(^|[' . static::PREG_CLASS_WORD_BOUNDARY . '])([^' . static::PREG_CLASS_WORD_BOUNDARY . '])/u';
return preg_replace_callback($regex, function (array $matches) {
- return $matches[1] . Unicode::strtoupper($matches[2]);
+ return $matches[1] . mb_strtoupper($matches[2]);
}, $text);
}
@@ -399,92 +381,15 @@ EOD;
*
* @return string
* The shortened string.
+ *
+ * @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
+ * mb_substr() instead.
+ *
+ * @see https://www.drupal.org/node/2850048
*/
public static function substr($text, $start, $length = NULL) {
- if (static::getStatus() == static::STATUS_MULTIBYTE) {
- return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length);
- }
- else {
- $strlen = strlen($text);
- // Find the starting byte offset.
- $bytes = 0;
- if ($start > 0) {
- // Count all the characters except continuation bytes from the start
- // until we have found $start characters or the end of the string.
- $bytes = -1; $chars = -1;
- while ($bytes < $strlen - 1 && $chars < $start) {
- $bytes++;
- $c = ord($text[$bytes]);
- if ($c < 0x80 || $c >= 0xC0) {
- $chars++;
- }
- }
- }
- elseif ($start < 0) {
- // Count all the characters except continuation bytes from the end
- // until we have found abs($start) characters.
- $start = abs($start);
- $bytes = $strlen; $chars = 0;
- while ($bytes > 0 && $chars < $start) {
- $bytes--;
- $c = ord($text[$bytes]);
- if ($c < 0x80 || $c >= 0xC0) {
- $chars++;
- }
- }
- }
- $istart = $bytes;
-
- // Find the ending byte offset.
- if ($length === NULL) {
- $iend = $strlen;
- }
- elseif ($length > 0) {
- // Count all the characters except continuation bytes from the starting
- // index until we have found $length characters or reached the end of
- // the string, then backtrace one byte.
- $iend = $istart - 1;
- $chars = -1;
- $last_real = FALSE;
- while ($iend < $strlen - 1 && $chars < $length) {
- $iend++;
- $c = ord($text[$iend]);
- $last_real = FALSE;
- if ($c < 0x80 || $c >= 0xC0) {
- $chars++;
- $last_real = TRUE;
- }
- }
- // Backtrace one byte if the last character we found was a real
- // character and we don't need it.
- if ($last_real && $chars >= $length) {
- $iend--;
- }
- }
- elseif ($length < 0) {
- // Count all the characters except continuation bytes from the end
- // until we have found abs($start) characters, then backtrace one byte.
- $length = abs($length);
- $iend = $strlen; $chars = 0;
- while ($iend > 0 && $chars < $length) {
- $iend--;
- $c = ord($text[$iend]);
- if ($c < 0x80 || $c >= 0xC0) {
- $chars++;
- }
- }
- // Backtrace one byte if we are not at the beginning of the string.
- if ($iend > 0) {
- $iend--;
- }
- }
- else {
- // $length == 0, return an empty string.
- return '';
- }
-
- return substr($text, $istart, max(0, $iend - $istart + 1));
- }
+ @trigger_error('\Drupal\Component\Utility\Unicode::substr() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_substr() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
+ return mb_substr($text, $start, $length);
}
/**
@@ -526,15 +431,15 @@ EOD;
$max_length = max($max_length, 0);
$min_wordsafe_length = max($min_wordsafe_length, 0);
- if (static::strlen($string) <= $max_length) {
+ if (mb_strlen($string) <= $max_length) {
// No truncation needed, so don't add ellipsis, just return.
return $string;
}
if ($add_ellipsis) {
// Truncate ellipsis in case $max_length is small.
- $ellipsis = static::substr('…', 0, $max_length);
- $max_length -= static::strlen($ellipsis);
+ $ellipsis = mb_substr('…', 0, $max_length);
+ $max_length -= mb_strlen($ellipsis);
$max_length = max($max_length, 0);
}
@@ -548,16 +453,16 @@ EOD;
// Find the last word boundary, if there is one within $min_wordsafe_length
// to $max_length characters. preg_match() is always greedy, so it will
// find the longest string possible.
- $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . Unicode::PREG_CLASS_WORD_BOUNDARY . ']/u', $string, $matches);
+ $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . Unicode::PREG_CLASS_WORD_BOUNDARY . ']/us', $string, $matches);
if ($found) {
$string = $matches[1];
}
else {
- $string = static::substr($string, 0, $max_length);
+ $string = mb_substr($string, 0, $max_length);
}
}
else {
- $string = static::substr($string, 0, $max_length);
+ $string = mb_substr($string, 0, $max_length);
}
if ($add_ellipsis) {
@@ -583,7 +488,7 @@ EOD;
* $str2, and 0 if they are equal.
*/
public static function strcasecmp($str1, $str2) {
- return strcmp(static::strtoupper($str1), static::strtoupper($str2));
+ return strcmp(mb_strtoupper($str1), mb_strtoupper($str2));
}
/**
@@ -603,11 +508,13 @@ EOD;
*
* @param string $string
* The header to encode.
+ * @param bool $shorten
+ * If TRUE, only return the first chunk of a multi-chunk encoded string.
*
* @return string
* The mime-encoded header.
*/
- public static function mimeHeaderEncode($string) {
+ public static function mimeHeaderEncode($string, $shorten = FALSE) {
if (preg_match('/[^\x20-\x7E]/', $string)) {
// floor((75 - strlen("=?UTF-8?B??=")) * 0.75);
$chunk_size = 47;
@@ -616,6 +523,9 @@ EOD;
while ($len > 0) {
$chunk = static::truncateBytes($string, $chunk_size);
$output .= ' =?UTF-8?B?' . base64_encode($chunk) . "?=\n";
+ if ($shorten) {
+ break;
+ }
$c = strlen($chunk);
$string = substr($string, $c);
$len -= $c;
@@ -710,18 +620,15 @@ EOD;
* The position where $needle occurs in $haystack, always relative to the
* beginning (independent of $offset), or FALSE if not found. Note that
* a return value of 0 is not the same as FALSE.
+ *
+ * @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
+ * mb_strpos() instead.
+ *
+ * @see https://www.drupal.org/node/2850048
*/
public static function strpos($haystack, $needle, $offset = 0) {
- if (static::getStatus() == static::STATUS_MULTIBYTE) {
- return mb_strpos($haystack, $needle, $offset);
- }
- else {
- // Remove Unicode continuation characters, to be compatible with
- // Unicode::strlen() and Unicode::substr().
- $haystack = preg_replace("/[\x80-\xBF]/", '', $haystack);
- $needle = preg_replace("/[\x80-\xBF]/", '', $needle);
- return strpos($haystack, $needle, $offset);
- }
+ @trigger_error('\Drupal\Component\Utility\Unicode::strpos() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use mb_strpos() instead. See https://www.drupal.org/node/2850048.', E_USER_DEPRECATED);
+ return mb_strpos($haystack, $needle, $offset);
}
}
diff --git a/core/lib/Drupal/Component/Utility/UrlHelper.php b/core/lib/Drupal/Component/Utility/UrlHelper.php
index 3728035..9e2365c 100644
--- a/core/lib/Drupal/Component/Utility/UrlHelper.php
+++ b/core/lib/Drupal/Component/Utility/UrlHelper.php
@@ -19,7 +19,7 @@ class UrlHelper {
/**
* Parses an array into a valid, rawurlencoded query string.
*
- * rawurlencode() is RFC3986 compliant, and as a consequence RFC3987
+ * Function rawurlencode() is RFC3986 compliant, and as a consequence RFC3987
* compliant. The latter defines the required format of "URLs" in HTML5.
* urlencode() is almost the same as rawurlencode(), except that it encodes
* spaces as "+" instead of "%20". This makes its result non compliant to
@@ -248,6 +248,16 @@ class UrlHelper {
* Exception thrown when a either $url or $bath_url are not fully qualified.
*/
public static function externalIsLocal($url, $base_url) {
+ // Some browsers treat \ as / so normalize to forward slashes.
+ $url = str_replace('\\', '/', $url);
+
+ // Leading control characters may be ignored or mishandled by browsers, so
+ // assume such a path may lead to an non-local location. The \p{C} character
+ // class matches all UTF-8 control, unassigned, and private characters.
+ if (preg_match('/^\p{C}/u', $url) !== 0) {
+ return FALSE;
+ }
+
$url_parts = parse_url($url);
$base_parts = parse_url($base_url);
diff --git a/core/lib/Drupal/Component/Utility/composer.json b/core/lib/Drupal/Component/Utility/composer.json
index 5fa2079..b89e813 100644
--- a/core/lib/Drupal/Component/Utility/composer.json
+++ b/core/lib/Drupal/Component/Utility/composer.json
@@ -3,11 +3,13 @@
"description": "Mostly static utility classes for string, xss, array, image, and other commonly needed manipulations.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"require": {
"php": ">=5.5.9",
"paragonie/random_compat": "^1.0|^2.0",
- "drupal/core-render": "^8.2"
+ "drupal/core-render": "^8.2",
+ "symfony/polyfill-iconv": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
},
"autoload": {
"psr-4": {
diff --git a/core/lib/Drupal/Component/Uuid/Com.php b/core/lib/Drupal/Component/Uuid/Com.php
index f9305f5..c18b39a 100644
--- a/core/lib/Drupal/Component/Uuid/Com.php
+++ b/core/lib/Drupal/Component/Uuid/Com.php
@@ -8,6 +8,7 @@ namespace Drupal\Component\Uuid;
* @see http://php.net/com_create_guid
*/
class Com implements UuidInterface {
+
/**
* {@inheritdoc}
*/
diff --git a/core/lib/Drupal/Component/Uuid/composer.json b/core/lib/Drupal/Component/Uuid/composer.json
index 7c43422..fb78d9e 100644
--- a/core/lib/Drupal/Component/Uuid/composer.json
+++ b/core/lib/Drupal/Component/Uuid/composer.json
@@ -1,8 +1,8 @@
{
"name": "drupal/core-uuid",
- "description": "PHP library for reading PO files.",
+ "description": "UUID generation and validation.",
"type": "library",
- "license": "GPL-2.0+",
+ "license": "GPL-2.0-or-later",
"support": {
"issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute",
diff --git a/core/lib/Drupal/Core/Access/AccessManagerInterface.php b/core/lib/Drupal/Core/Access/AccessManagerInterface.php
index bbb1ec9..2ea6a6a 100644
--- a/core/lib/Drupal/Core/Access/AccessManagerInterface.php
+++ b/core/lib/Drupal/Core/Access/AccessManagerInterface.php
@@ -38,7 +38,7 @@ interface AccessManagerInterface {
/**
* Execute access checks against the incoming request.
*
- * @param Request $request
+ * @param \Symfony\Component\HttpFoundation\Request $request
* The incoming request.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) Run access checks for this account. Defaults to the current
diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php
index f4962ec..3abbf33 100644
--- a/core/lib/Drupal/Core/Access/AccessResult.php
+++ b/core/lib/Drupal/Core/Access/AccessResult.php
@@ -39,7 +39,7 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
* isNeutral() will be TRUE.
*/
public static function neutral($reason = NULL) {
- assert('is_string($reason) || is_null($reason)');
+ assert(is_string($reason) || is_null($reason));
return new AccessResultNeutral($reason);
}
@@ -64,7 +64,7 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
* isForbidden() will be TRUE.
*/
public static function forbidden($reason = NULL) {
- assert('is_string($reason) || is_null($reason)');
+ assert(is_string($reason) || is_null($reason));
return new AccessResultForbidden($reason);
}
@@ -336,10 +336,10 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
$merge_other = TRUE;
}
- if ($this->isForbidden() && $this instanceof AccessResultReasonInterface) {
+ if ($this->isForbidden() && $this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
$result->setReason($this->getReason());
}
- elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface) {
+ elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
$result->setReason($other->getReason());
}
}
@@ -353,14 +353,13 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
$result = static::neutral();
if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
$merge_other = TRUE;
- if ($other instanceof AccessResultReasonInterface) {
- $result->setReason($other->getReason());
- }
}
- else {
- if ($this instanceof AccessResultReasonInterface) {
- $result->setReason($this->getReason());
- }
+
+ if ($this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
+ $result->setReason($this->getReason());
+ }
+ elseif ($other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
+ $result->setReason($other->getReason());
}
}
$result->inheritCacheability($this);
@@ -427,9 +426,9 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
/**
* Inherits the cacheability of the other access result, if any.
*
- * inheritCacheability() differs from addCacheableDependency() in how it
- * handles max-age, because it is designed to inherit the cacheability of the
- * second operand in the andIf() and orIf() operations. There, the situation
+ * This method differs from addCacheableDependency() in how it handles
+ * max-age, because it is designed to inherit the cacheability of the second
+ * operand in the andIf() and orIf() operations. There, the situation
* "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000
* as the end result.
*
diff --git a/core/lib/Drupal/Core/Access/AccessResultForbidden.php b/core/lib/Drupal/Core/Access/AccessResultForbidden.php
index 3edae61..dbdbafe 100644
--- a/core/lib/Drupal/Core/Access/AccessResultForbidden.php
+++ b/core/lib/Drupal/Core/Access/AccessResultForbidden.php
@@ -24,7 +24,6 @@ class AccessResultForbidden extends AccessResult implements AccessResultReasonIn
$this->reason = $reason;
}
-
/**
* {@inheritdoc}
*/
diff --git a/core/lib/Drupal/Core/Access/CheckProvider.php b/core/lib/Drupal/Core/Access/CheckProvider.php
index d6ba1cf..51423bd 100644
--- a/core/lib/Drupal/Core/Access/CheckProvider.php
+++ b/core/lib/Drupal/Core/Access/CheckProvider.php
@@ -142,6 +142,7 @@ class CheckProvider implements CheckProviderInterface, ContainerAwareInterface {
return $checks;
}
+
/**
* Compiles a mapping of requirement keys to access checker service IDs.
*/
diff --git a/core/lib/Drupal/Core/Access/CheckProviderInterface.php b/core/lib/Drupal/Core/Access/CheckProviderInterface.php
index a94a92c..35e3c18 100644
--- a/core/lib/Drupal/Core/Access/CheckProviderInterface.php
+++ b/core/lib/Drupal/Core/Access/CheckProviderInterface.php
@@ -15,7 +15,6 @@ use Symfony\Component\Routing\RouteCollection;
*/
interface CheckProviderInterface {
-
/**
* For each route, saves a list of applicable access checks to the route.
*
diff --git a/core/lib/Drupal/Core/Access/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
index e7a3b08..353d09c 100644
--- a/core/lib/Drupal/Core/Access/CustomAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
@@ -61,7 +61,14 @@ class CustomAccessCheck implements RoutingAccessInterface {
* The access result.
*/
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
- $callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
+ try {
+ $callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
+ }
+ catch (\InvalidArgumentException $e) {
+ // The custom access controller method was not found.
+ throw new \BadMethodCallException(sprintf('The "%s" method is not callable as a _custom_access callback in route "%s"', $route->getRequirement('_custom_access'), $route->getPath()));
+ }
+
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
$arguments = $arguments_resolver->getArguments($callable);
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php
new file mode 100644
index 0000000..fcf76a4
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Redirects to an entity deletion form.
+ *
+ * @Action(
+ * id = "entity:delete_action",
+ * action_label = @Translation("Delete"),
+ * deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver",
+ * )
+ */
+class DeleteAction extends EntityActionBase {
+
+ /**
+ * The tempstore object.
+ *
+ * @var \Drupal\Core\TempStore\SharedTempStore
+ */
+ protected $tempStore;
+
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $currentUser;
+
+ /**
+ * Constructs a new DeleteAction object.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
+ * The tempstore factory.
+ * @param \Drupal\Core\Session\AccountInterface $current_user
+ * Current user.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
+ $this->currentUser = $current_user;
+ $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
+
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager'),
+ $container->get('tempstore.private'),
+ $container->get('current_user')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeMultiple(array $entities) {
+ /** @var \Drupal\Core\Entity\EntityInterface[] $entities */
+ $selection = [];
+ foreach ($entities as $entity) {
+ $langcode = $entity->language()->getId();
+ $selection[$entity->id()][$langcode] = $langcode;
+ }
+ $this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($object = NULL) {
+ $this->executeMultiple([$object]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ return $object->access('delete', $account, $return_as_object);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php
new file mode 100644
index 0000000..5cd529a
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a base action for each entity type with specific interfaces.
+ */
+abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface {
+
+ use StringTranslationTrait;
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * Constructs a new EntityActionDeriverBase object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+ * The string translation service.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
+ $this->entityTypeManager = $entity_type_manager;
+ $this->stringTranslation = $string_translation;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, $base_plugin_id) {
+ return new static(
+ $container->get('entity_type.manager'),
+ $container->get('string_translation')
+ );
+ }
+
+ /**
+ * Indicates whether the deriver can be used for the provided entity type.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+ * The entity type.
+ *
+ * @return bool
+ * TRUE if the entity type can be used, FALSE otherwise.
+ */
+ abstract protected function isApplicable(EntityTypeInterface $entity_type);
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDerivativeDefinitions($base_plugin_definition) {
+ if (empty($this->derivatives)) {
+ $definitions = [];
+ foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
+ $definition = $base_plugin_definition;
+ $definition['type'] = $entity_type_id;
+ $definition['label'] = sprintf('%s %s', $base_plugin_definition['action_label'], $entity_type->getSingularLabel());
+ $definitions[$entity_type_id] = $definition;
+ }
+ $this->derivatives = $definitions;
+ }
+
+ return parent::getDerivativeDefinitions($base_plugin_definition);
+ }
+
+ /**
+ * Gets a list of applicable entity types.
+ *
+ * The list consists of all entity types which match the conditions for the
+ * given deriver.
+ * For example, if the action applies to entities that are publishable,
+ * this method will find all entity types that are publishable.
+ *
+ * @return \Drupal\Core\Entity\EntityTypeInterface[]
+ * The applicable entity types, keyed by entity type ID.
+ */
+ protected function getApplicableEntityTypes() {
+ $entity_types = $this->entityTypeManager->getDefinitions();
+ $entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
+ return $this->isApplicable($entity_type);
+ });
+
+ return $entity_types;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityChangedActionDeriver.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityChangedActionDeriver.php
new file mode 100644
index 0000000..2df090b
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityChangedActionDeriver.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action\Derivative;
+
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides an action deriver that finds entity types of EntityChangedInterface.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\SaveAction
+ */
+class EntityChangedActionDeriver extends EntityActionDeriverBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isApplicable(EntityTypeInterface $entity_type) {
+ return $entity_type->entityClassImplements(EntityChangedInterface::class);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php
new file mode 100644
index 0000000..4be4588
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action\Derivative;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides an action deriver that finds entity types with delete form.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\DeleteAction
+ */
+class EntityDeleteActionDeriver extends EntityActionDeriverBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDerivativeDefinitions($base_plugin_definition) {
+ if (empty($this->derivatives)) {
+ $definitions = [];
+ foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
+ $definition = $base_plugin_definition;
+ $definition['type'] = $entity_type_id;
+ $definition['label'] = $this->t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]);
+ $definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.delete_multiple_form';
+ $definitions[$entity_type_id] = $definition;
+ }
+ $this->derivatives = $definitions;
+ }
+
+ return $this->derivatives;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isApplicable(EntityTypeInterface $entity_type) {
+ return $entity_type->hasLinkTemplate('delete-multiple-form');
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityPublishedActionDeriver.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityPublishedActionDeriver.php
new file mode 100644
index 0000000..05548ee
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityPublishedActionDeriver.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action\Derivative;
+
+use Drupal\Core\Entity\EntityPublishedInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides an action deriver that finds publishable entity types.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\PublishAction
+ * @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
+ */
+class EntityPublishedActionDeriver extends EntityActionDeriverBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isApplicable(EntityTypeInterface $entity_type) {
+ return $entity_type->entityClassImplements(EntityPublishedInterface::class);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/EntityActionBase.php b/core/lib/Drupal/Core/Action/Plugin/Action/EntityActionBase.php
new file mode 100644
index 0000000..2624024
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/EntityActionBase.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for entity-based actions.
+ */
+abstract class EntityActionBase extends ActionBase implements DependentPluginInterface, ContainerFactoryPluginInterface {
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * Constructs a EntityActionBase object.
+ *
+ * @param mixed[] $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+ $this->entityTypeManager = $entity_type_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function calculateDependencies() {
+ $module_name = $this->entityTypeManager
+ ->getDefinition($this->getPluginDefinition()['type'])
+ ->getProvider();
+ return ['module' => [$module_name]];
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/PublishAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/PublishAction.php
new file mode 100644
index 0000000..8d0cf75
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/PublishAction.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Publishes an entity.
+ *
+ * @Action(
+ * id = "entity:publish_action",
+ * action_label = @Translation("Publish"),
+ * deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
+ * )
+ */
+class PublishAction extends EntityActionBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($entity = NULL) {
+ $entity->setPublished()->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $key = $object->getEntityType()->getKey('published');
+
+ /** @var \Drupal\Core\Entity\EntityInterface $object */
+ $result = $object->access('update', $account, TRUE)
+ ->andIf($object->$key->access('edit', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
new file mode 100644
index 0000000..fc63e5c
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides an action that can save any entity.
+ *
+ * @Action(
+ * id = "entity:save_action",
+ * action_label = @Translation("Save"),
+ * deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver",
+ * )
+ */
+class SaveAction extends EntityActionBase {
+
+ /**
+ * The time service.
+ *
+ * @var \Drupal\Component\Datetime\TimeInterface
+ */
+ protected $time;
+
+ /**
+ * Constructs a SaveAction object.
+ *
+ * @param mixed[] $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\Component\Datetime\TimeInterface $time
+ * The time service.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
+ $this->time = $time;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager'),
+ $container->get('datetime.time')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($entity = NULL) {
+ $entity->setChangedTime($this->time->getRequestTime())->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ // It's not necessary to check the changed field access here, because
+ // Drupal\Core\Field\ChangedFieldItemList would anyway return 'not allowed'.
+ // Also changing the changed field value is only a workaround to trigger an
+ // entity resave. Without a field change, this would not be possible.
+ /** @var \Drupal\Core\Entity\EntityInterface $object */
+ return $object->access('update', $account, $return_as_object);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/UnpublishAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/UnpublishAction.php
new file mode 100644
index 0000000..bb3f6c2
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/UnpublishAction.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Unpublishes an entity.
+ *
+ * @Action(
+ * id = "entity:unpublish_action",
+ * action_label = @Translation("Unpublish"),
+ * deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
+ * )
+ */
+class UnpublishAction extends EntityActionBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($entity = NULL) {
+ $entity->setUnpublished()->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $key = $object->getEntityType()->getKey('published');
+
+ /** @var \Drupal\Core\Entity\EntityInterface $object */
+ $result = $object->access('update', $account, TRUE)
+ ->andIf($object->$key->access('edit', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php b/core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php
new file mode 100644
index 0000000..49932df
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a helper to for submitting an AJAX form.
+ *
+ * @internal
+ */
+trait AjaxFormHelperTrait {
+
+ use AjaxHelperTrait;
+
+ /**
+ * Submit form dialog #ajax callback.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return \Drupal\Core\Ajax\AjaxResponse
+ * An AJAX response that display validation error messages or represents a
+ * successful submission.
+ */
+ public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
+ if ($form_state->hasAnyErrors()) {
+ $form['status_messages'] = [
+ '#type' => 'status_messages',
+ '#weight' => -1000,
+ ];
+ $response = new AjaxResponse();
+ $response->addCommand(new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
+ }
+ else {
+ $response = $this->successfulAjaxSubmit($form, $form_state);
+ }
+ return $response;
+ }
+
+ /**
+ * Allows the form to respond to a successful AJAX submission.
+ *
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return \Drupal\Core\Ajax\AjaxResponse
+ * An AJAX response.
+ */
+ abstract protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state);
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php b/core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php
new file mode 100644
index 0000000..287abae
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
+
+/**
+ * Provides a helper to determine if the current request is via AJAX.
+ *
+ * @internal
+ */
+trait AjaxHelperTrait {
+
+ /**
+ * Determines if the current request is via AJAX.
+ *
+ * @return bool
+ * TRUE if the current request is via AJAX, FALSE otherwise.
+ */
+ protected function isAjax() {
+ foreach (['drupal_ajax', 'drupal_modal', 'drupal_dialog'] as $wrapper) {
+ if (strpos($this->getRequestWrapperFormat(), $wrapper) !== FALSE) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Gets the wrapper format of the current request.
+ *
+ * @string
+ * The wrapper format.
+ */
+ protected function getRequestWrapperFormat() {
+ return \Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
index 2b0fcdd..623d396 100644
--- a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
+++ b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
@@ -119,7 +119,7 @@ class OpenDialogCommand implements CommandInterface, CommandWithAttachedAssetsIn
* The new title of the dialog.
*/
public function setDialogTitle($title) {
- $this->setDialogOptions('title', $title);
+ $this->setDialogOption('title', $title);
}
/**
diff --git a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
index 53d6e82..a55b0ea 100644
--- a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
+++ b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
@@ -8,6 +8,7 @@ namespace Drupal\Core\Ajax;
* @ingroup ajax
*/
class OpenModalDialogCommand extends OpenDialogCommand {
+
/**
* Constructs an OpenModalDialog object.
*
diff --git a/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
index da6a26e..78c406b 100644
--- a/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
+++ b/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
@@ -34,19 +34,22 @@ class OpenOffCanvasDialogCommand extends OpenDialogCommand {
* (optional) Custom settings that will be passed to the Drupal behaviors
* on the content of the dialog. If left empty, the settings will be
* populated automatically from the current request.
+ * @param string $position
+ * (optional) The position to render the off-canvas dialog.
*/
- public function __construct($title, $content, array $dialog_options = [], $settings = NULL) {
+ public function __construct($title, $content, array $dialog_options = [], $settings = NULL, $position = 'side') {
parent::__construct('#drupal-off-canvas', $title, $content, $dialog_options, $settings);
$this->dialogOptions['modal'] = FALSE;
$this->dialogOptions['autoResize'] = FALSE;
$this->dialogOptions['resizable'] = 'w';
$this->dialogOptions['draggable'] = FALSE;
$this->dialogOptions['drupalAutoButtons'] = FALSE;
+ $this->dialogOptions['drupalOffCanvasPosition'] = $position;
// @todo drupal.ajax.js does not respect drupalAutoButtons properly, pass an
// empty set of buttons until https://www.drupal.org/node/2793343 is in.
$this->dialogOptions['buttons'] = [];
if (empty($dialog_options['dialogClass'])) {
- $this->dialogOptions['dialogClass'] = 'ui-dialog-off-canvas';
+ $this->dialogOptions['dialogClass'] = "ui-dialog-off-canvas ui-dialog-position-$position";
}
// If no width option is provided then use the default width to avoid the
// dialog staying at the width of the previous instance when opened
diff --git a/core/lib/Drupal/Core/Annotation/ContextDefinition.php b/core/lib/Drupal/Core/Annotation/ContextDefinition.php
index 0439752..9e28d5a 100644
--- a/core/lib/Drupal/Core/Annotation/ContextDefinition.php
+++ b/core/lib/Drupal/Core/Annotation/ContextDefinition.php
@@ -116,11 +116,37 @@ class ContextDefinition extends Plugin {
if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) {
throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.');
}
- $class = isset($values['class']) ? $values['class'] : 'Drupal\Core\Plugin\Context\ContextDefinition';
+
+ $class = $this->getDefinitionClass($values);
$this->definition = new $class($values['value'], $values['label'], $values['required'], $values['multiple'], $values['description'], $values['default_value']);
}
/**
+ * Determines the context definition class to use.
+ *
+ * If the annotation specifies a specific context definition class, we use
+ * that. Otherwise, we use \Drupal\Core\Plugin\Context\EntityContextDefinition
+ * if the data type starts with 'entity:', since it contains specialized logic
+ * specific to entities. Otherwise, we fall back to the generic
+ * \Drupal\Core\Plugin\Context\ContextDefinition class.
+ *
+ * @param array $values
+ * The annotation values.
+ *
+ * @return string
+ * The fully-qualified name of the context definition class.
+ */
+ protected function getDefinitionClass(array $values) {
+ if (isset($values['class'])) {
+ return $values['class'];
+ }
+ if (strpos($values['value'], 'entity:') === 0) {
+ return 'Drupal\Core\Plugin\Context\EntityContextDefinition';
+ }
+ return 'Drupal\Core\Plugin\Context\ContextDefinition';
+ }
+
+ /**
* Returns the value of an annotation.
*
* @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface
diff --git a/core/lib/Drupal/Core/Archiver/ArchiveTar.php b/core/lib/Drupal/Core/Archiver/ArchiveTar.php
index 24085ce..716511e 100644
--- a/core/lib/Drupal/Core/Archiver/ArchiveTar.php
+++ b/core/lib/Drupal/Core/Archiver/ArchiveTar.php
@@ -49,7 +49,7 @@
* The following changes have been done:
* Added namespace Drupal\Core\Archiver.
* Removed require_once 'PEAR.php'.
- * Added defintion of OS_WINDOWS taken from PEAR.php.
+ * Added definition of OS_WINDOWS taken from PEAR.php.
* Renamed class to ArchiveTar.
* Removed extends PEAR from class.
* Removed call parent:: __construct().
@@ -181,7 +181,7 @@ class ArchiveTar
if ($data == "\37\213") {
$this->_compress = true;
$this->_compress_type = 'gz';
- // No sure it's enought for a magic code ....
+ // Not sure it's enough for a magic code ....
} elseif ($data == "BZ") {
$this->_compress = true;
$this->_compress_type = 'bz2';
@@ -577,7 +577,7 @@ class ArchiveTar
* indicated by $p_path. When relevant the memorized path of the
* files/dir can be modified by removing the $p_remove_path path at the
* beginning of the file/dir path.
- * While extracting a file, if the directory path does not exists it is
+ * While extracting a file, if the directory path does not exist it is
* created.
* While extracting a file, if the file already exists it is replaced
* without looking for last modification date.
@@ -2385,7 +2385,7 @@ class ArchiveTar
/**
* Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
- * rand emove double slashes.
+ * and remove double slashes.
*
* @param string $p_dir path to reduce
*
diff --git a/core/lib/Drupal/Core/Asset/CssOptimizer.php b/core/lib/Drupal/Core/Asset/CssOptimizer.php
index 4ad7ba1..94c2240 100644
--- a/core/lib/Drupal/Core/Asset/CssOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php
@@ -119,7 +119,7 @@ class CssOptimizer implements AssetOptimizerInterface {
// If a BOM is found, convert the file to UTF-8, then use substr() to
// remove the BOM from the result.
if ($encoding = (Unicode::encodingFromBOM($contents))) {
- $contents = Unicode::substr(Unicode::convertToUtf8($contents, $encoding), 1);
+ $contents = mb_substr(Unicode::convertToUtf8($contents, $encoding), 1);
}
// If no BOM, check for fallback encoding. Per CSS spec the regex is very strict.
elseif (preg_match('/^@charset "([^"]+)";/', $contents, $matches)) {
@@ -189,7 +189,7 @@ class CssOptimizer implements AssetOptimizerInterface {
if ($optimize) {
// Perform some safe CSS optimizations.
// Regexp to match comment blocks.
- $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
+ $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
// Regexp to match double quoted strings.
$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
// Regexp to match single quoted strings.
diff --git a/core/lib/Drupal/Core/Asset/JsOptimizer.php b/core/lib/Drupal/Core/Asset/JsOptimizer.php
index 6b8c217..243b402 100644
--- a/core/lib/Drupal/Core/Asset/JsOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/JsOptimizer.php
@@ -24,7 +24,7 @@ class JsOptimizer implements AssetOptimizerInterface {
// remove the BOM from the result.
$data = file_get_contents($js_asset['data']);
if ($encoding = (Unicode::encodingFromBOM($data))) {
- $data = Unicode::substr(Unicode::convertToUtf8($data, $encoding), 1);
+ $data = mb_substr(Unicode::convertToUtf8($data, $encoding), 1);
}
// If no BOM is found, check for the charset attribute.
elseif (isset($js_asset['attributes']['charset'])) {
diff --git a/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php b/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php
index 95fca18..b84b8fa 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php
@@ -65,6 +65,8 @@ class LibraryDependencyResolver implements LibraryDependencyResolverInterface {
* {@inheritdoc}
*/
public function getMinimalRepresentativeSubset(array $libraries) {
+ assert(count($libraries) === count(array_unique($libraries)), '$libraries can\'t contain duplicate items.');
+
$minimal = [];
// Determine each library's dependencies.
diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
index a49ea97..39c3e2b 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
@@ -19,7 +19,7 @@ class LibraryDiscovery implements LibraryDiscoveryInterface {
/**
* The final library definitions, statically cached.
*
- * hook_library_info_alter() and hook_js_settings_alter() allows modules
+ * Hooks hook_library_info_alter() and hook_js_settings_alter() allow modules
* and themes to dynamically alter a library definition (once per request).
*
* @var array
diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
index 91f702f..07df709 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
@@ -129,11 +129,11 @@ class LibraryDiscoveryParser {
// properly resolve dependencies for all (css) libraries per category,
// and only once prior to rendering out an HTML page.
if ($type == 'css' && !empty($library[$type])) {
- assert('\Drupal\Core\Asset\LibraryDiscoveryParser::validateCssLibrary($library[$type]) < 2', 'CSS files should be specified as key/value pairs, where the values are configuration options. See https://www.drupal.org/node/2274843.');
- assert('\Drupal\Core\Asset\LibraryDiscoveryParser::validateCssLibrary($library[$type]) === 0', 'CSS must be nested under a category. See https://www.drupal.org/node/2274843.');
+ assert(static::validateCssLibrary($library[$type]) < 2, 'CSS files should be specified as key/value pairs, where the values are configuration options. See https://www.drupal.org/node/2274843.');
+ assert(static::validateCssLibrary($library[$type]) === 0, 'CSS must be nested under a category. See https://www.drupal.org/node/2274843.');
foreach ($library[$type] as $category => $files) {
$category_weight = 'CSS_' . strtoupper($category);
- assert('defined($category_weight)', 'Invalid CSS category: ' . $category . '. See https://www.drupal.org/node/2274843.');
+ assert(defined($category_weight), 'Invalid CSS category: ' . $category . '. See https://www.drupal.org/node/2274843.');
foreach ($files as $source => $options) {
if (!isset($options['weight'])) {
$options['weight'] = 0;
diff --git a/core/lib/Drupal/Core/Batch/BatchBuilder.php b/core/lib/Drupal/Core/Batch/BatchBuilder.php
new file mode 100644
index 0000000..dce6698
--- /dev/null
+++ b/core/lib/Drupal/Core/Batch/BatchBuilder.php
@@ -0,0 +1,340 @@
+<?php
+
+namespace Drupal\Core\Batch;
+
+use Drupal\Core\Queue\QueueInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Builds an array for a batch process.
+ *
+ * Example code to create a batch:
+ * @code
+ * $batch_builder = (new BatchBuilder())
+ * ->setTitle(t('Batch Title'))
+ * ->setFinishCallback('batch_example_finished_callback')
+ * ->setInitMessage(t('The initialization message (optional)'));
+ * foreach ($ids as $id) {
+ * $batch_builder->addOperation('batch_example_callback', [$id]);
+ * }
+ * batch_set($batch_builder->toArray());
+ * @endcode
+ */
+class BatchBuilder {
+
+ /**
+ * The set of operations to be processed.
+ *
+ * Each operation is a tuple of the function / method to use and an array
+ * containing any parameters to be passed.
+ *
+ * @var array
+ */
+ protected $operations = [];
+
+ /**
+ * The title for the batch.
+ *
+ * @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
+ */
+ protected $title;
+
+ /**
+ * The initializing message for the batch.
+ *
+ * @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
+ */
+ protected $initMessage;
+
+ /**
+ * The message to be shown while the batch is in progress.
+ *
+ * @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
+ */
+ protected $progressMessage;
+
+ /**
+ * The message to be shown if a problem occurs.
+ *
+ * @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
+ */
+ protected $errorMessage;
+
+ /**
+ * The name of a function / method to be called when the batch finishes.
+ *
+ * @var string
+ */
+ protected $finished;
+
+ /**
+ * The file containing the operation and finished callbacks.
+ *
+ * If the callbacks are in the .module file or can be autoloaded, for example,
+ * static methods on a class, then this does not need to be set.
+ *
+ * @var string
+ */
+ protected $file;
+
+ /**
+ * An array of libraries to be included when processing the batch.
+ *
+ * @var string[]
+ */
+ protected $libraries = [];
+
+ /**
+ * An array of options to be used with the redirect URL.
+ *
+ * @var array
+ */
+ protected $urlOptions = [];
+
+ /**
+ * Specifies if the batch is progressive.
+ *
+ * If true, multiple calls are used. Otherwise an attempt is made to process
+ * the batch in a single run.
+ *
+ * @var bool
+ */
+ protected $progressive = TRUE;
+
+ /**
+ * The details of the queue to use.
+ *
+ * A tuple containing the name of the queue and the class of the queue to use.
+ *
+ * @var array
+ */
+ protected $queue;
+
+ /**
+ * Sets the default values for the batch builder.
+ */
+ public function __construct() {
+ $this->title = new TranslatableMarkup('Processing');
+ $this->initMessage = new TranslatableMarkup('Initializing.');
+ $this->progressMessage = new TranslatableMarkup('Completed @current of @total.');
+ $this->errorMessage = new TranslatableMarkup('An error has occurred.');
+ }
+
+ /**
+ * Sets the title.
+ *
+ * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $title
+ * The title.
+ *
+ * @return $this
+ */
+ public function setTitle($title) {
+ $this->title = $title;
+ return $this;
+ }
+
+ /**
+ * Sets the finished callback.
+ *
+ * This callback will be executed if the batch process is done.
+ *
+ * @param callable $callback
+ * The callback.
+ *
+ * @return $this
+ */
+ public function setFinishCallback(callable $callback) {
+ $this->finished = $callback;
+ return $this;
+ }
+
+ /**
+ * Sets the displayed message while processing is initialized.
+ *
+ * Defaults to 'Initializing.'.
+ *
+ * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
+ * The text to display.
+ *
+ * @return $this
+ */
+ public function setInitMessage($message) {
+ $this->initMessage = $message;
+ return $this;
+ }
+
+ /**
+ * Sets the message to display when the batch is being processed.
+ *
+ * Defaults to 'Completed @current of @total.'.
+ *
+ * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
+ * The text to display. Available placeholders are:
+ * - '@current'
+ * - '@remaining'
+ * - '@total'
+ * - '@percentage'
+ * - '@estimate'
+ * - '@elapsed'.
+ *
+ * @return $this
+ */
+ public function setProgressMessage($message) {
+ $this->progressMessage = $message;
+ return $this;
+ }
+
+ /**
+ * Sets the message to display if an error occurs while processing.
+ *
+ * Defaults to 'An error has occurred.'.
+ *
+ * @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
+ * The text to display.
+ *
+ * @return $this
+ */
+ public function setErrorMessage($message) {
+ $this->errorMessage = $message;
+ return $this;
+ }
+
+ /**
+ * Sets the file that contains the callback functions.
+ *
+ * The path should be relative to base_path(), and thus should be built using
+ * drupal_get_path(). Defaults to {module_name}.module.
+ *
+ * @param string $filename
+ * The path to the file.
+ *
+ * @return $this
+ */
+ public function setFile($filename) {
+ $this->file = $filename;
+ return $this;
+ }
+
+ /**
+ * Sets the libraries to use when processing the batch.
+ *
+ * Adds the libraries for use on the progress page. Any previously added
+ * libraries are removed.
+ *
+ * @param string[] $libraries
+ * The libraries to be used.
+ *
+ * @return $this
+ */
+ public function setLibraries(array $libraries) {
+ $this->libraries = $libraries;
+ return $this;
+ }
+
+ /**
+ * Sets the options for redirect URLs.
+ *
+ * @param array $options
+ * The options to use.
+ *
+ * @return $this
+ *
+ * @see \Drupal\Core\Url
+ */
+ public function setUrlOptions(array $options) {
+ $this->urlOptions = $options;
+ return $this;
+ }
+
+ /**
+ * Sets the batch to run progressively.
+ *
+ * @param bool $is_progressive
+ * (optional) A Boolean that indicates whether or not the batch needs to run
+ * progressively. TRUE indicates that the batch will run in more than one
+ * run. FALSE indicates that the batch will finish in a single run. Defaults
+ * to TRUE.
+ *
+ * @return $this
+ */
+ public function setProgressive($is_progressive = TRUE) {
+ $this->progressive = $is_progressive;
+ return $this;
+ }
+
+ /**
+ * Sets an override for the default queue.
+ *
+ * The class will typically either be \Drupal\Core\Queue\Batch or
+ * \Drupal\Core\Queue\BatchMemory. The class defaults to Batch if progressive
+ * is TRUE, or to BatchMemory if progressive is FALSE.
+ *
+ * @param string $name
+ * The unique identifier for the queue.
+ * @param string $class
+ * The fully qualified name of a class that implements
+ * \Drupal\Core\Queue\QueueInterface.
+ *
+ * @return $this
+ */
+ public function setQueue($name, $class) {
+ if (!class_exists($class)) {
+ throw new \InvalidArgumentException('Class ' . $class . ' does not exist.');
+ }
+
+ if (!in_array(QueueInterface::class, class_implements($class))) {
+ throw new \InvalidArgumentException(
+ 'Class ' . $class . ' does not implement \Drupal\Core\Queue\QueueInterface.'
+ );
+ }
+
+ $this->queue = [
+ 'name' => $name,
+ 'class' => $class,
+ ];
+ return $this;
+ }
+
+ /**
+ * Adds a batch operation.
+ *
+ * @param callable $callback
+ * The name of the callback function.
+ * @param array $arguments
+ * An array of arguments to pass to the callback function.
+ *
+ * @return $this
+ */
+ public function addOperation(callable $callback, array $arguments = []) {
+ $this->operations[] = [$callback, $arguments];
+ return $this;
+ }
+
+ /**
+ * Converts a \Drupal\Core\Batch\Batch object into an array.
+ *
+ * @return array
+ * The array representation of the object.
+ */
+ public function toArray() {
+ $array = [
+ 'operations' => $this->operations ?: [],
+ 'title' => $this->title ?: '',
+ 'init_message' => $this->initMessage ?: '',
+ 'progress_message' => $this->progressMessage ?: '',
+ 'error_message' => $this->errorMessage ?: '',
+ 'finished' => $this->finished,
+ 'file' => $this->file,
+ 'library' => $this->libraries ?: [],
+ 'url_options' => $this->urlOptions ?: [],
+ 'progressive' => $this->progressive,
+ ];
+
+ if ($this->queue) {
+ $array['queue'] = $this->queue;
+ }
+
+ return $array;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php
index ee90783..a5f7fa9 100644
--- a/core/lib/Drupal/Core/Block/BlockBase.php
+++ b/core/lib/Drupal/Core/Block/BlockBase.php
@@ -4,9 +4,9 @@ namespace Drupal\Core\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
use Drupal\Core\Plugin\ContextAwarePluginBase;
-use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
@@ -26,6 +26,7 @@ use Drupal\Component\Transliteration\TransliterationInterface;
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface {
use ContextAwarePluginAssignmentTrait;
+ use MessengerTrait;
use PluginWithFormsTrait;
/**
@@ -244,7 +245,7 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
// \Drupal\system\MachineNameController::transliterate(), so it might make
// sense to provide a common service for the two.
$transliterated = $this->transliteration()->transliterate($admin_label, LanguageInterface::LANGCODE_DEFAULT, '_');
- $transliterated = Unicode::strtolower($transliterated);
+ $transliterated = mb_strtolower($transliterated);
$transliterated = preg_replace('@[^a-z0-9_.]+@', '', $transliterated);
diff --git a/core/lib/Drupal/Core/Block/BlockManager.php b/core/lib/Drupal/Core/Block/BlockManager.php
index 30b52b6..026d810 100644
--- a/core/lib/Drupal/Core/Block/BlockManager.php
+++ b/core/lib/Drupal/Core/Block/BlockManager.php
@@ -6,8 +6,9 @@ use Drupal\Component\Plugin\FallbackPluginManagerInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
-use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\FilteredPluginManagerTrait;
+use Psr\Log\LoggerInterface;
/**
* Manages discovery and instantiation of block plugins.
@@ -21,7 +22,14 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
use CategorizingPluginManagerTrait {
getSortedDefinitions as traitGetSortedDefinitions;
}
- use ContextAwarePluginManagerTrait;
+ use FilteredPluginManagerTrait;
+
+ /**
+ * The logger.
+ *
+ * @var \Psr\Log\LoggerInterface
+ */
+ protected $logger;
/**
* Constructs a new \Drupal\Core\Block\BlockManager object.
@@ -33,12 +41,22 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
+ * @param \Psr\Log\LoggerInterface $logger
+ * The logger.
*/
- public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+ public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, LoggerInterface $logger) {
parent::__construct('Plugin/Block', $namespaces, $module_handler, 'Drupal\Core\Block\BlockPluginInterface', 'Drupal\Core\Block\Annotation\Block');
- $this->alterInfo('block');
+ $this->alterInfo($this->getType());
$this->setCacheBackend($cache_backend, 'block_plugins');
+ $this->logger = $logger;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getType() {
+ return 'block';
}
/**
@@ -67,4 +85,12 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
return 'broken';
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function handlePluginNotFound($plugin_id, array $configuration) {
+ $this->logger->warning('The "%plugin_id" was not found', ['%plugin_id' => $plugin_id]);
+ return parent::handlePluginNotFound($plugin_id, $configuration);
+ }
+
}
diff --git a/core/lib/Drupal/Core/Block/BlockManagerInterface.php b/core/lib/Drupal/Core/Block/BlockManagerInterface.php
index 3455f23..7b5d5c6 100644
--- a/core/lib/Drupal/Core/Block/BlockManagerInterface.php
+++ b/core/lib/Drupal/Core/Block/BlockManagerInterface.php
@@ -4,10 +4,11 @@ namespace Drupal\Core\Block;
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface;
+use Drupal\Core\Plugin\FilteredPluginManagerInterface;
/**
* Provides an interface for the discovery and instantiation of block plugins.
*/
-interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface {
+interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface, FilteredPluginManagerInterface {
}
diff --git a/core/lib/Drupal/Core/Block/MessagesBlockPluginInterface.php b/core/lib/Drupal/Core/Block/MessagesBlockPluginInterface.php
index 995f4b7..9248ac1 100644
--- a/core/lib/Drupal/Core/Block/MessagesBlockPluginInterface.php
+++ b/core/lib/Drupal/Core/Block/MessagesBlockPluginInterface.php
@@ -5,8 +5,7 @@ namespace Drupal\Core\Block;
/**
* The interface for "messages" (#type => status_messages) blocks.
*
- * @see drupal_set_message()
- * @see drupal_get_message()
+ * @see \Drupal\Core\Messenger\MessengerInterface
* @see \Drupal\Core\Render\Element\StatusMessages
* @see \Drupal\block\Plugin\DisplayVariant\BlockPageVariant
*
diff --git a/core/lib/Drupal/Core/Block/Plugin/Block/Broken.php b/core/lib/Drupal/Core/Block/Plugin/Block/Broken.php
index 0e4732e..b7f3a19 100644
--- a/core/lib/Drupal/Core/Block/Plugin/Block/Broken.php
+++ b/core/lib/Drupal/Core/Block/Plugin/Block/Broken.php
@@ -38,7 +38,7 @@ class Broken extends BlockBase {
*/
protected function brokenMessage() {
$build['message'] = [
- '#markup' => $this->t('This block is broken or missing. You may be missing content or you might need to enable the original module.')
+ '#markup' => $this->t('This block is broken or missing. You may be missing content or you might need to enable the original module.'),
];
return $build;
diff --git a/core/lib/Drupal/Core/Block/Plugin/Block/PageTitleBlock.php b/core/lib/Drupal/Core/Block/Plugin/Block/PageTitleBlock.php
index 4589973..af1fb6e 100644
--- a/core/lib/Drupal/Core/Block/Plugin/Block/PageTitleBlock.php
+++ b/core/lib/Drupal/Core/Block/Plugin/Block/PageTitleBlock.php
@@ -11,6 +11,9 @@ use Drupal\Core\Block\TitleBlockPluginInterface;
* @Block(
* id = "page_title_block",
* admin_label = @Translation("Page title"),
+ * forms = {
+ * "settings_tray" = FALSE,
+ * },
* )
*/
class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface {
diff --git a/core/lib/Drupal/Core/Cache/ApcuBackend.php b/core/lib/Drupal/Core/Cache/ApcuBackend.php
index d7847ff..e04e014 100644
--- a/core/lib/Drupal/Core/Cache/ApcuBackend.php
+++ b/core/lib/Drupal/Core/Cache/ApcuBackend.php
@@ -2,6 +2,8 @@
namespace Drupal\Core\Cache;
+use Drupal\Component\Assertion\Inspector;
+
/**
* Stores cache items in the Alternative PHP Cache User Cache (APCu).
*/
@@ -161,7 +163,7 @@ class ApcuBackend implements CacheBackendInterface {
* {@inheritdoc}
*/
public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = []) {
- assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
+ assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
$tags = array_unique($tags);
$cache = new \stdClass();
$cache->cid = $cid;
diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php
index 9257aab..53aa181 100644
--- a/core/lib/Drupal/Core/Cache/Cache.php
+++ b/core/lib/Drupal/Core/Cache/Cache.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Cache;
+use Drupal\Component\Assertion\Inspector;
use Drupal\Core\Database\Query\SelectInterface;
/**
@@ -29,7 +30,7 @@ class Cache {
*/
public static function mergeContexts(array $a = [], array $b = []) {
$cache_contexts = array_unique(array_merge($a, $b));
- assert('\Drupal::service(\'cache_contexts_manager\')->assertValidTokens($cache_contexts)');
+ assert(\Drupal::service('cache_contexts_manager')->assertValidTokens($cache_contexts));
sort($cache_contexts);
return $cache_contexts;
}
@@ -54,7 +55,7 @@ class Cache {
* The merged array of cache tags.
*/
public static function mergeTags(array $a = [], array $b = []) {
- assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($a) && \Drupal\Component\Assertion\Inspector::assertAllStrings($b)', 'Cache tags must be valid strings');
+ assert(Inspector::assertAllStrings($a) && Inspector::assertAllStrings($b), 'Cache tags must be valid strings');
$cache_tags = array_unique(array_merge($a, $b));
sort($cache_tags);
@@ -96,7 +97,7 @@ class Cache {
* An array of cache tags.
*
* @deprecated
- * Use assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)');
+ * Use assert(Inspector::assertAllStrings($tags));
*
* @throws \LogicException
*/
diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
index 852305f..bf2ed7c 100644
--- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
+++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
@@ -9,7 +9,7 @@ namespace Drupal\Core\Cache;
* Drupal\Core\Cache\DatabaseBackend provides the default implementation, which
* can be consulted as an example.
*
- * The cache indentifiers are case sensitive.
+ * The cache identifiers are case sensitive.
*
* @ingroup cache
*/
diff --git a/core/lib/Drupal/Core/Cache/CacheCollector.php b/core/lib/Drupal/Core/Cache/CacheCollector.php
index b0c9f45..912b60f 100644
--- a/core/lib/Drupal/Core/Cache/CacheCollector.php
+++ b/core/lib/Drupal/Core/Cache/CacheCollector.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Cache;
+use Drupal\Component\Assertion\Inspector;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\DestructableInterface;
use Drupal\Core\Lock\LockBackendInterface;
@@ -111,7 +112,7 @@ abstract class CacheCollector implements CacheCollectorInterface, DestructableIn
* (optional) The tags to specify for the cache item.
*/
public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, array $tags = []) {
- assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
+ assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
$this->cid = $cid;
$this->cache = $cache;
$this->tags = $tags;
diff --git a/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
index ceb8f49..820da98 100644
--- a/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
+++ b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Cache;
+use Drupal\Component\Assertion\Inspector;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
@@ -22,7 +23,7 @@ class CacheTagsInvalidator implements CacheTagsInvalidatorInterface {
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
- assert('Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
+ assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
// Notify all added cache tags invalidators.
foreach ($this->invalidators as $invalidator) {
diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php
index 9a555f8..056f838 100644
--- a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php
+++ b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php
@@ -100,7 +100,7 @@ class CacheContextsManager {
* cacheability metadata.
*/
public function convertTokensToKeys(array $context_tokens) {
- assert('$this->assertValidTokens($context_tokens)');
+ assert($this->assertValidTokens($context_tokens));
$cacheable_metadata = new CacheableMetadata();
$optimized_tokens = $this->optimizeTokens($context_tokens);
// Iterate over cache contexts that have been optimized away and get their
diff --git a/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php
index 952dc14..620787b 100644
--- a/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php
@@ -13,7 +13,7 @@ class LanguagesCacheContext implements CalculatedCacheContextInterface {
/**
* The language manager.
*
- * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+ * @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
diff --git a/core/lib/Drupal/Core/Cache/Context/SiteCacheContext.php b/core/lib/Drupal/Core/Cache/Context/SiteCacheContext.php
index 5c9a937..16516c5 100644
--- a/core/lib/Drupal/Core/Cache/Context/SiteCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/Context/SiteCacheContext.php
@@ -7,7 +7,7 @@ use Drupal\Core\Cache\CacheableMetadata;
/**
* Defines the SiteCacheContext service, for "per site" caching.
*
- * Cache context ID: 'site'.
+ * Cache context ID: 'url.site'.
*
* A "site" is defined as the combination of URI scheme, domain name, port and
* base path. It allows for varying between the *same* site being accessed via
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index 747186b..88f018f 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Cache;
+use Drupal\Component\Assertion\Inspector;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\SchemaObjectExistsException;
@@ -222,7 +223,7 @@ class DatabaseBackend implements CacheBackendInterface {
'tags' => [],
];
- assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($item[\'tags\'])', 'Cache Tags must be strings.');
+ assert(Inspector::assertAllStrings($item['tags']), 'Cache Tags must be strings.');
$item['tags'] = array_unique($item['tags']);
// Sort the cache tags so that they are stored consistently in the DB.
sort($item['tags']);
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
index b86390e..6bde00b 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
@@ -74,7 +74,7 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
$max_rows_settings = $this->settings->get('database_cache_max_rows');
// First, look for a cache bin specific setting.
if (isset($max_rows_settings['bins'][$bin])) {
- $max_rows = $max_rows_settings['bins'][$bin];
+ $max_rows = $max_rows_settings['bins'][$bin];
}
// Second, use configured default backend.
elseif (isset($max_rows_settings['default'])) {
diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php
index edda4d3..99e6e3a 100644
--- a/core/lib/Drupal/Core/Cache/MemoryBackend.php
+++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php
@@ -2,6 +2,8 @@
namespace Drupal\Core\Cache;
+use Drupal\Component\Assertion\Inspector;
+
/**
* Defines a memory cache implementation.
*
@@ -98,7 +100,7 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
- assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
+ assert(Inspector::assertAllStrings($tags), 'Cache Tags must be strings.');
$tags = array_unique($tags);
// Sort the cache tags so that they are stored consistently in the database.
sort($tags);
diff --git a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
new file mode 100644
index 0000000..433edd2
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\Core\Cache\MemoryCache;
+
+use Drupal\Component\Assertion\Inspector;
+use Drupal\Core\Cache\MemoryBackend;
+
+/**
+ * Defines a memory cache implementation.
+ *
+ * Stores cache items in memory using a PHP array.
+ *
+ * @ingroup cache
+ */
+class MemoryCache extends MemoryBackend implements MemoryCacheInterface {
+
+ /**
+ * Prepares a cached item.
+ *
+ * Checks that items are either permanent or did not expire, and returns data
+ * as appropriate.
+ *
+ * @param object $cache
+ * An item loaded from cache_get() or cache_get_multiple().
+ * @param bool $allow_invalid
+ * (optional) If TRUE, cache items may be returned even if they have expired
+ * or been invalidated. Defaults to FALSE.
+ *
+ * @return mixed
+ * The item with data as appropriate or FALSE if there is no
+ * valid item to load.
+ */
+ protected function prepareItem($cache, $allow_invalid = FALSE) {
+ if (!isset($cache->data)) {
+ return FALSE;
+ }
+ // Check expire time.
+ $cache->valid = $cache->expire == static::CACHE_PERMANENT || $cache->expire >= $this->getRequestTime();
+
+ if (!$allow_invalid && !$cache->valid) {
+ return FALSE;
+ }
+
+ return $cache;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($cid, $data, $expire = MemoryCacheInterface::CACHE_PERMANENT, array $tags = []) {
+ assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
+ $tags = array_unique($tags);
+
+ $this->cache[$cid] = (object) [
+ 'cid' => $cid,
+ 'data' => $data,
+ 'created' => $this->getRequestTime(),
+ 'expire' => $expire,
+ 'tags' => $tags,
+ ];
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCacheInterface.php b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCacheInterface.php
new file mode 100644
index 0000000..c794ea1
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCacheInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\Core\Cache\MemoryCache;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+
+/**
+ * Defines an interface for memory cache implementations.
+ *
+ * This has additional requirements over CacheBackendInterface and
+ * CacheTagsInvalidatorInterface. Objects stored must be the same instance when
+ * retrieved from cache, so that this can be used as a replacement for protected
+ * properties and similar.
+ *
+ * @ingroup cache
+ */
+interface MemoryCacheInterface extends CacheBackendInterface, CacheTagsInvalidatorInterface {}
diff --git a/core/lib/Drupal/Core/Cache/PhpBackend.php b/core/lib/Drupal/Core/Cache/PhpBackend.php
index 2404493..d3af7f5 100644
--- a/core/lib/Drupal/Core/Cache/PhpBackend.php
+++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Cache;
+use Drupal\Component\Assertion\Inspector;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Component\Utility\Crypt;
@@ -143,7 +144,8 @@ class PhpBackend implements CacheBackendInterface {
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
- assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
+ assert(Inspector::assertAllStrings($tags), 'Cache Tags must be strings.');
+
$item = (object) [
'cid' => $cid,
'data' => $data,
diff --git a/core/lib/Drupal/Core/Command/DbDumpCommand.php b/core/lib/Drupal/Core/Command/DbDumpCommand.php
index aa81af0..26b4363 100644
--- a/core/lib/Drupal/Core/Command/DbDumpCommand.php
+++ b/core/lib/Drupal/Core/Command/DbDumpCommand.php
@@ -145,7 +145,7 @@ class DbDumpCommand extends DbCommandBase {
$name = $row['Field'];
// Parse out the field type and meta information.
preg_match('@([a-z]+)(?:\((\d+)(?:,(\d+))?\))?\s*(unsigned)?@', $row['Type'], $matches);
- $type = $this->fieldTypeMap($connection, $matches[1]);
+ $type = $this->fieldTypeMap($connection, $matches[1]);
if ($row['Extra'] === 'auto_increment') {
// If this is an auto increment, then the type is 'serial'.
$type = 'serial';
@@ -259,8 +259,12 @@ class DbDumpCommand extends DbCommandBase {
$query = $connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
$data = $query->fetchAssoc();
+ // Map the collation to a character set. For example, 'utf8mb4_general_ci'
+ // (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) will be mapped to 'utf8mb4'.
+ list($charset,) = explode('_', $data['Collation'], 2);
+
// Set `mysql_character_set`. This will be ignored by other backends.
- $definition['mysql_character_set'] = str_replace('_general_ci', '', $data['Collation']);
+ $definition['mysql_character_set'] = $charset;
}
/**
diff --git a/core/lib/Drupal/Core/Command/InstallCommand.php b/core/lib/Drupal/Core/Command/InstallCommand.php
new file mode 100644
index 0000000..bff4bc4
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/InstallCommand.php
@@ -0,0 +1,337 @@
+<?php
+
+namespace Drupal\Core\Command;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Database\ConnectionNotDefinedException;
+use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Extension\InfoParserDynamic;
+use Drupal\Core\Site\Settings;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Installs a Drupal site for local testing/development.
+ *
+ * @internal
+ * This command makes no guarantee of an API for Drupal extensions.
+ */
+class InstallCommand extends Command {
+
+ /**
+ * The class loader.
+ *
+ * @var object
+ */
+ protected $classLoader;
+
+ /**
+ * Constructs a new InstallCommand command.
+ *
+ * @param object $class_loader
+ * The class loader.
+ */
+ public function __construct($class_loader) {
+ parent::__construct('install');
+ $this->classLoader = $class_loader;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure() {
+ $this->setName('install')
+ ->setDescription('Installs a Drupal demo site. This is not meant for production and might be too simple for custom development. It is a quick and easy way to get Drupal running.')
+ ->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.')
+ ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in.', 'en')
+ ->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name.', 'Drupal')
+ ->addUsage('demo_umami --langcode fr')
+ ->addUsage('standard --site-name QuickInstall');
+
+ parent::configure();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $io = new SymfonyStyle($input, $output);
+ if (!extension_loaded('pdo_sqlite')) {
+ $io->getErrorStyle()->error('You must have the pdo_sqlite PHP extension installed. See core/INSTALL.sqlite.txt for instructions.');
+ return 1;
+ }
+
+ // Change the directory to the Drupal root.
+ chdir(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
+
+ // Check whether there is already an installation.
+ if ($this->isDrupalInstalled()) {
+ // Do not fail if the site is already installed so this command can be
+ // chained with ServerCommand.
+ $output->writeln('<info>Drupal is already installed.</info> If you want to reinstall, remove sites/default/files and sites/default/settings.php.');
+ return 0;
+ }
+
+ $install_profile = $input->getArgument('install-profile');
+ if ($install_profile && !$this->validateProfile($install_profile, $io)) {
+ return 1;
+ }
+ if (!$install_profile) {
+ $install_profile = $this->selectProfile($io);
+ }
+
+ return $this->install($this->classLoader, $io, $install_profile, $input->getOption('langcode'), $this->getSitePath(), $input->getOption('site-name'));
+ }
+
+ /**
+ * Returns whether there is already an existing Drupal installation.
+ *
+ * @return bool
+ */
+ protected function isDrupalInstalled() {
+ try {
+ $kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
+ $kernel::bootEnvironment();
+ $kernel->setSitePath($this->getSitePath());
+ Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
+ $kernel->boot();
+ }
+ catch (ConnectionNotDefinedException $e) {
+ return FALSE;
+ }
+ return !empty(Database::getConnectionInfo());
+ }
+
+ /**
+ * Installs Drupal with specified installation profile.
+ *
+ * @param object $class_loader
+ * The class loader.
+ * @param \Symfony\Component\Console\Style\SymfonyStyle $io
+ * The Symfony output decorator.
+ * @param string $profile
+ * The installation profile to use.
+ * @param string $langcode
+ * The language to install the site in.
+ * @param string $site_path
+ * The path to install the site to, like 'sites/default'.
+ * @param string $site_name
+ * The site name.
+ *
+ * @throws \Exception
+ * Thrown when failing to create the $site_path directory or settings.php.
+ */
+ protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, $site_path, $site_name) {
+ $password = Crypt::randomBytesBase64(12);
+ $parameters = [
+ 'interactive' => FALSE,
+ 'site_path' => $site_path,
+ 'parameters' => [
+ 'profile' => $profile,
+ 'langcode' => $langcode,
+ ],
+ 'forms' => [
+ 'install_settings_form' => [
+ 'driver' => 'sqlite',
+ 'sqlite' => [
+ 'database' => $site_path . '/files/.sqlite',
+ ],
+ ],
+ 'install_configure_form' => [
+ 'site_name' => $site_name,
+ 'site_mail' => 'drupal@localhost',
+ 'account' => [
+ 'name' => 'admin',
+ 'mail' => 'admin@localhost',
+ 'pass' => [
+ 'pass1' => $password,
+ 'pass2' => $password,
+ ],
+ ],
+ 'enable_update_status_module' => TRUE,
+ // form_type_checkboxes_value() requires NULL instead of FALSE values
+ // for programmatic form submissions to disable a checkbox.
+ 'enable_update_status_emails' => NULL,
+ ],
+ ],
+ ];
+
+ // Create the directory and settings.php if not there so that the installer
+ // works.
+ if (!is_dir($site_path)) {
+ if ($io->isVerbose()) {
+ $io->writeln("Creating directory: $site_path");
+ }
+ if (!mkdir($site_path, 0775)) {
+ throw new \RuntimeException("Failed to create directory $site_path");
+ }
+ }
+ if (!file_exists("{$site_path}/settings.php")) {
+ if ($io->isVerbose()) {
+ $io->writeln("Creating file: {$site_path}/settings.php");
+ }
+ if (!copy('sites/default/default.settings.php', "{$site_path}/settings.php")) {
+ throw new \RuntimeException("Copying sites/default/default.settings.php to {$site_path}/settings.php failed.");
+ }
+ }
+
+ require_once 'core/includes/install.core.inc';
+
+ $progress_bar = $io->createProgressBar();
+ install_drupal($class_loader, $parameters, function ($install_state) use ($progress_bar) {
+ static $started = FALSE;
+ if (!$started) {
+ $started = TRUE;
+ // We've already done 1.
+ $progress_bar->setFormat("%current%/%max% [%bar%]\n%message%\n");
+ $progress_bar->setMessage(t('Installing @drupal', ['@drupal' => drupal_install_profile_distribution_name()]));
+ $tasks = install_tasks($install_state);
+ $progress_bar->start(count($tasks) + 1);
+ }
+ $tasks_to_perform = install_tasks_to_perform($install_state);
+ $task = current($tasks_to_perform);
+ if (isset($task['display_name'])) {
+ $progress_bar->setMessage($task['display_name']);
+ }
+ $progress_bar->advance();
+ });
+ $success_message = t('Congratulations, you installed @drupal!', [
+ '@drupal' => drupal_install_profile_distribution_name(),
+ '@name' => 'admin',
+ '@pass' => $password,
+ ], ['langcode' => $langcode]);
+ $progress_bar->setMessage('<info>' . $success_message . '</info>');
+ $progress_bar->display();
+ $progress_bar->finish();
+ $io->writeln('<info>Username:</info> admin');
+ $io->writeln("<info>Password:</info> $password");
+ }
+
+ /**
+ * Gets the site path.
+ *
+ * Defaults to 'sites/default'. For testing purposes this can be overridden
+ * using the DRUPAL_DEV_SITE_PATH environment variable.
+ *
+ * @return string
+ * The site path to use.
+ */
+ protected function getSitePath() {
+ return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
+ }
+
+ /**
+ * Selects the install profile to use.
+ *
+ * @param \Symfony\Component\Console\Style\SymfonyStyle $io
+ * Symfony style output decorator.
+ *
+ * @return string
+ * The selected install profile.
+ *
+ * @see _install_select_profile()
+ * @see \Drupal\Core\Installer\Form\SelectProfileForm
+ */
+ protected function selectProfile(SymfonyStyle $io) {
+ $profiles = $this->getProfiles();
+
+ // If there is a distribution there will be only one profile.
+ if (count($profiles) == 1) {
+ return key($profiles);
+ }
+ // Display alphabetically by human-readable name, but always put the core
+ // profiles first (if they are present in the filesystem).
+ natcasesort($profiles);
+ if (isset($profiles['minimal'])) {
+ // If the expert ("Minimal") core profile is present, put it in front of
+ // any non-core profiles rather than including it with them
+ // alphabetically, since the other profiles might be intended to group
+ // together in a particular way.
+ $profiles = ['minimal' => $profiles['minimal']] + $profiles;
+ }
+ if (isset($profiles['standard'])) {
+ // If the default ("Standard") core profile is present, put it at the very
+ // top of the list. This profile will have its radio button pre-selected,
+ // so we want it to always appear at the top.
+ $profiles = ['standard' => $profiles['standard']] + $profiles;
+ }
+ reset($profiles);
+ return $io->choice('Select an installation profile', $profiles, current($profiles));
+ }
+
+ /**
+ * Validates a user provided install profile.
+ *
+ * @param string $install_profile
+ * Install profile to validate.
+ * @param \Symfony\Component\Console\Style\SymfonyStyle $io
+ * Symfony style output decorator.
+ *
+ * @return bool
+ * TRUE if the profile is valid, FALSE if not.
+ */
+ protected function validateProfile($install_profile, SymfonyStyle $io) {
+ // Allow people to install hidden and non-distribution profiles if they
+ // supply the argument.
+ $profiles = $this->getProfiles(TRUE, FALSE);
+ if (!isset($profiles[$install_profile])) {
+ $error_msg = sprintf("'%s' is not a valid install profile.", $install_profile);
+ $alternatives = [];
+ foreach (array_keys($profiles) as $profile_name) {
+ $lev = levenshtein($install_profile, $profile_name);
+ if ($lev <= strlen($profile_name) / 4 || FALSE !== strpos($profile_name, $install_profile)) {
+ $alternatives[] = $profile_name;
+ }
+ }
+ if (!empty($alternatives)) {
+ $error_msg .= sprintf(" Did you mean '%s'?", implode("' or '", $alternatives));
+ }
+ $io->getErrorStyle()->error($error_msg);
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Gets a list of profiles.
+ *
+ * @param bool $include_hidden
+ * (optional) Whether to include hidden profiles. Defaults to FALSE.
+ * @param bool $auto_select_distributions
+ * (optional) Whether to only return the first distribution found.
+ *
+ * @return string[]
+ * An array of profile descriptions keyed by the profile machine name.
+ */
+ protected function getProfiles($include_hidden = FALSE, $auto_select_distributions = TRUE) {
+ // Build a list of all available profiles.
+ $listing = new ExtensionDiscovery(getcwd(), FALSE);
+ $listing->setProfileDirectories([]);
+ $profiles = [];
+ $info_parser = new InfoParserDynamic();
+ foreach ($listing->scan('profile') as $profile) {
+ $details = $info_parser->parse($profile->getPathname());
+ // Don't show hidden profiles.
+ if (!$include_hidden && !empty($details['hidden'])) {
+ continue;
+ }
+ // Determine the name of the profile; default to the internal name if none
+ // is specified.
+ $name = isset($details['name']) ? $details['name'] : $profile->getName();
+ $description = isset($details['description']) ? $details['description'] : $name;
+ $profiles[$profile->getName()] = $description;
+
+ if ($auto_select_distributions && !empty($details['distribution'])) {
+ return [$profile->getName() => $description];
+ }
+ }
+ return $profiles;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Command/QuickStartCommand.php b/core/lib/Drupal/Core/Command/QuickStartCommand.php
new file mode 100644
index 0000000..690b20e
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/QuickStartCommand.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\Core\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Installs a Drupal site and starts a webserver for local testing/development.
+ *
+ * Wraps 'install' and 'server' commands.
+ *
+ * @internal
+ * This command makes no guarantee of an API for Drupal extensions.
+ *
+ * @see \Drupal\Core\Command\InstallCommand
+ * @see \Drupal\Core\Command\ServerCommand
+ */
+class QuickStartCommand extends Command {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure() {
+ $this->setName('quick-start')
+ ->setDescription('Installs a Drupal site and runs a web server. This is not meant for production and might be too simple for custom development. It is a quick and easy way to get Drupal running.')
+ ->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.')
+ ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en.', 'en')
+ ->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name. Defaults to Drupal.', 'Drupal')
+ ->addOption('host', NULL, InputOption::VALUE_OPTIONAL, 'Provide a host for the server to run on. Defaults to 127.0.0.1.', '127.0.0.1')
+ ->addOption('port', NULL, InputOption::VALUE_OPTIONAL, 'Provide a port for the server to run on. Will be determined automatically if none supplied.')
+ ->addOption('suppress-login', 's', InputOption::VALUE_NONE, 'Disable opening a login URL in a browser.')
+ ->addUsage('demo_umami --langcode fr')
+ ->addUsage('standard --site-name QuickInstall --host localhost --port 8080')
+ ->addUsage('minimal --host my-site.com --port 80');
+
+ parent::configure();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $command = $this->getApplication()->find('install');
+
+ $arguments = [
+ 'command' => 'install',
+ 'install-profile' => $input->getArgument('install-profile'),
+ '--langcode' => $input->getOption('langcode'),
+ '--site-name' => $input->getOption('site-name'),
+ ];
+
+ $installInput = new ArrayInput($arguments);
+ $returnCode = $command->run($installInput, $output);
+
+ if ($returnCode === 0) {
+ $command = $this->getApplication()->find('server');
+ $arguments = [
+ 'command' => 'server',
+ '--host' => $input->getOption('host'),
+ '--port' => $input->getOption('port'),
+ ];
+ if ($input->getOption('suppress-login')) {
+ $arguments['--suppress-login'] = TRUE;
+ }
+ $serverInput = new ArrayInput($arguments);
+ $returnCode = $command->run($serverInput, $output);
+ }
+ return $returnCode;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Command/ServerCommand.php b/core/lib/Drupal/Core/Command/ServerCommand.php
new file mode 100644
index 0000000..182167a
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/ServerCommand.php
@@ -0,0 +1,278 @@
+<?php
+
+namespace Drupal\Core\Command;
+
+use Drupal\Core\Database\ConnectionNotDefinedException;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\DrupalKernelInterface;
+use Drupal\Core\Site\Settings;
+use Drupal\user\Entity\User;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\PhpProcess;
+use Symfony\Component\Process\Process;
+
+/**
+ * Runs the PHP webserver for a Drupal site for local testing/development.
+ *
+ * @internal
+ * This command makes no guarantee of an API for Drupal extensions.
+ */
+class ServerCommand extends Command {
+
+ /**
+ * The class loader.
+ *
+ * @var object
+ */
+ protected $classLoader;
+
+ /**
+ * Constructs a new ServerCommand command.
+ *
+ * @param object $class_loader
+ * The class loader.
+ */
+ public function __construct($class_loader) {
+ parent::__construct('server');
+ $this->classLoader = $class_loader;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure() {
+ $this->setDescription('Starts up a webserver for a site.')
+ ->addOption('host', NULL, InputOption::VALUE_OPTIONAL, 'Provide a host for the server to run on.', '127.0.0.1')
+ ->addOption('port', NULL, InputOption::VALUE_OPTIONAL, 'Provide a port for the server to run on. Will be determined automatically if none supplied.')
+ ->addOption('suppress-login', 's', InputOption::VALUE_NONE, 'Disable opening a login URL in a browser.')
+ ->addUsage('--host localhost --port 8080')
+ ->addUsage('--host my-site.com --port 80');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $io = new SymfonyStyle($input, $output);
+
+ $host = $input->getOption('host');
+ $port = $input->getOption('port');
+ if (!$port) {
+ $port = $this->findAvailablePort($host);
+ }
+ if (!$port) {
+ $io->getErrorStyle()->error('Unable to automatically determine a port. Use the --port to hardcode an available port.');
+ }
+
+ try {
+ $kernel = $this->boot();
+ }
+ catch (ConnectionNotDefinedException $e) {
+ $io->getErrorStyle()->error("No installation found. Use the 'install' command.");
+ return 1;
+ }
+ return $this->start($host, $port, $kernel, $input, $io);
+ }
+
+ /**
+ * Boots up a Drupal environment.
+ *
+ * @return \Drupal\Core\DrupalKernelInterface
+ * The Drupal kernel.
+ *
+ * @throws \Exception
+ * Exception thrown if kernel does not boot.
+ */
+ protected function boot() {
+ $kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
+ $kernel::bootEnvironment();
+ $kernel->setSitePath($this->getSitePath());
+ Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
+ $kernel->boot();
+ // Some services require a request to work. For example, CommentManager.
+ // This is needed as generating the URL fires up entity load hooks.
+ $kernel->getContainer()
+ ->get('request_stack')
+ ->push(Request::createFromGlobals());
+
+ return $kernel;
+ }
+
+ /**
+ * Finds an available port.
+ *
+ * @param string $host
+ * The host to find a port on.
+ *
+ * @return int|false
+ * The available port or FALSE, if no available port found,
+ */
+ protected function findAvailablePort($host) {
+ $port = 8888;
+ while ($port >= 8888 && $port <= 9999) {
+ $connection = @fsockopen($host, $port);
+ if (is_resource($connection)) {
+ // Port is being used.
+ fclose($connection);
+ }
+ else {
+ // Port is available.
+ return $port;
+ }
+ $port++;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Opens a URL in your system default browser.
+ *
+ * @param string $url
+ * The URL to browser to.
+ * @param \Symfony\Component\Console\Style\SymfonyStyle $io
+ * The IO.
+ */
+ protected function openBrowser($url, SymfonyStyle $io) {
+ $is_windows = defined('PHP_WINDOWS_VERSION_BUILD');
+ if ($is_windows) {
+ // Handle escaping ourselves.
+ $cmd = 'start "web" "' . $url . '""';
+ }
+ else {
+ $url = escapeshellarg($url);
+ }
+
+ $is_linux = (new Process('which xdg-open'))->run();
+ $is_osx = (new Process('which open'))->run();
+ if ($is_linux === 0) {
+ $cmd = 'xdg-open ' . $url;
+ }
+ elseif ($is_osx === 0) {
+ $cmd = 'open ' . $url;
+ }
+
+ if (empty($cmd)) {
+ $io->getErrorStyle()
+ ->error('No suitable browser opening command found, open yourself: ' . $url);
+ return;
+ }
+
+ if ($io->isVerbose()) {
+ $io->writeln("<info>Browser command:</info> $cmd");
+ }
+
+ // Need to escape double quotes in the command so the PHP will work.
+ $cmd = str_replace('"', '\"', $cmd);
+ // Sleep for 2 seconds before opening the browser. This allows the command
+ // to start up the PHP built-in webserver in the meantime. We use a
+ // PhpProcess so that Windows powershell users also get a browser opened
+ // for them.
+ $php = "<?php sleep(2); passthru(\"$cmd\"); ?>";
+ $process = new PhpProcess($php);
+ $process->start();
+ return;
+ }
+
+ /**
+ * Gets a one time login URL for user 1.
+ *
+ * @return string
+ * The one time login URL for user 1.
+ */
+ protected function getOneTimeLoginUrl() {
+ $user = User::load(1);
+ \Drupal::moduleHandler()->load('user');
+ return user_pass_reset_url($user);
+ }
+
+ /**
+ * Starts up a webserver with a running Drupal.
+ *
+ * @param string $host
+ * The hostname of the webserver.
+ * @param int $port
+ * The port to start the webserver on.
+ * @param \Drupal\Core\DrupalKernelInterface $kernel
+ * The Drupal kernel.
+ * @param \Symfony\Component\Console\Input\InputInterface $input
+ * The input.
+ * @param \Symfony\Component\Console\Style\SymfonyStyle $io
+ * The IO.
+ *
+ * @return int
+ * The exit status of the PHP in-built webserver command.
+ */
+ protected function start($host, $port, DrupalKernelInterface $kernel, InputInterface $input, SymfonyStyle $io) {
+ $finder = new PhpExecutableFinder();
+ $binary = $finder->find();
+ if ($binary === FALSE) {
+ throw new \RuntimeException('Unable to find the PHP binary.');
+ }
+
+ $io->writeln("<info>Drupal development server started:</info> <http://{$host}:{$port}>");
+ $io->writeln('<info>This server is not meant for production use.</info>');
+ $one_time_login = "http://$host:$port{$this->getOneTimeLoginUrl()}/login";
+ $io->writeln("<info>One time login url:</info> <$one_time_login>");
+ $io->writeln('Press Ctrl-C to quit the Drupal development server.');
+
+ if (!$input->getOption('suppress-login')) {
+ if ($this->openBrowser("$one_time_login?destination=" . urlencode("/"), $io) === 1) {
+ $io->error('Error while opening up a one time login URL');
+ }
+ }
+
+ // Use the Process object to construct an escaped command line.
+ $process = new Process([
+ $binary,
+ '-S',
+ $host . ':' . $port,
+ '.ht.router.php',
+ ], $kernel->getAppRoot(), [], NULL, NULL);
+ if ($io->isVerbose()) {
+ $io->writeln("<info>Server command:</info> {$process->getCommandLine()}");
+ }
+
+ // Carefully manage output so we can display output only in verbose mode.
+ $descriptors = [];
+ $descriptors[0] = STDIN;
+ $descriptors[1] = ['pipe', 'w'];
+ $descriptors[2] = ['pipe', 'w'];
+ $server = proc_open($process->getCommandLine(), $descriptors, $pipes, $kernel->getAppRoot());
+ if (is_resource($server)) {
+ if ($io->isVerbose()) {
+ // Write a blank line so that server output and the useful information are
+ // visually separated.
+ $io->writeln('');
+ }
+ $server_status = proc_get_status($server);
+ while ($server_status['running']) {
+ if ($io->isVerbose()) {
+ fpassthru($pipes[2]);
+ }
+ sleep(1);
+ $server_status = proc_get_status($server);
+ }
+ }
+ return proc_close($server);
+ }
+
+ /**
+ * Gets the site path.
+ *
+ * Defaults to 'sites/default'. For testing purposes this can be overridden
+ * using the DRUPAL_DEV_SITE_PATH environment variable.
+ *
+ * @return string
+ * The site path to use.
+ */
+ protected function getSitePath() {
+ return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Composer/Composer.php b/core/lib/Drupal/Core/Composer/Composer.php
index 016f93a..9833b7e 100644
--- a/core/lib/Drupal/Core/Composer/Composer.php
+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -6,6 +6,7 @@ use Drupal\Component\PhpStorage\FileStorage;
use Composer\Script\Event;
use Composer\Installer\PackageEvent;
use Composer\Semver\Constraint\Constraint;
+use Composer\Util\ProcessExecutor;
/**
* Provides static functions for composer script events.
@@ -144,6 +145,48 @@ EOT;
}
/**
+ * Fires the drupal-phpunit-upgrade script event if necessary.
+ *
+ * @param \Composer\Script\Event $event
+ */
+ public static function upgradePHPUnit(Event $event) {
+ $repository = $event->getComposer()->getRepositoryManager()->getLocalRepository();
+ // This is, essentially, a null constraint. We only care whether the package
+ // is present in the vendor directory yet, but findPackage() requires it.
+ $constraint = new Constraint('>', '');
+ $phpunit_package = $repository->findPackage('phpunit/phpunit', $constraint);
+ if (!$phpunit_package) {
+ // There is nothing to do. The user is probably installing using the
+ // --no-dev flag.
+ return;
+ }
+
+ // If the PHP version is 7.0 or above and PHPUnit is less than version 6
+ // call the drupal-phpunit-upgrade script to upgrade PHPUnit.
+ if (!static::upgradePHPUnitCheck($phpunit_package->getVersion())) {
+ $event->getComposer()
+ ->getEventDispatcher()
+ ->dispatchScript('drupal-phpunit-upgrade');
+ }
+ }
+
+ /**
+ * Determines if PHPUnit needs to be upgraded.
+ *
+ * This method is located in this file because it is possible that it is
+ * called before the autoloader is available.
+ *
+ * @param string $phpunit_version
+ * The PHPUnit version string.
+ *
+ * @return bool
+ * TRUE if the PHPUnit needs to be upgraded, FALSE if not.
+ */
+ public static function upgradePHPUnitCheck($phpunit_version) {
+ return !(version_compare(PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, '7.0') >= 0 && version_compare($phpunit_version, '6.1') < 0);
+ }
+
+ /**
* Remove possibly problematic test files from vendored projects.
*
* @param \Composer\Installer\PackageEvent $event
@@ -228,6 +271,13 @@ EOT;
}
/**
+ * Removes Composer's timeout so that scripts can run indefinitely.
+ */
+ public static function removeTimeout() {
+ ProcessExecutor::setTimeout(0);
+ }
+
+ /**
* Helper method to remove directories and the files they contain.
*
* @param string $path
diff --git a/core/lib/Drupal/Core/Condition/ConditionManager.php b/core/lib/Drupal/Core/Condition/ConditionManager.php
index 2c6b61b..89fe216 100644
--- a/core/lib/Drupal/Core/Condition/ConditionManager.php
+++ b/core/lib/Drupal/Core/Condition/ConditionManager.php
@@ -8,8 +8,9 @@ use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Executable\ExecutableInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
-use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\FilteredPluginManagerInterface;
+use Drupal\Core\Plugin\FilteredPluginManagerTrait;
/**
* A plugin manager for condition plugins.
@@ -20,10 +21,10 @@ use Drupal\Core\Plugin\DefaultPluginManager;
*
* @ingroup plugin_api
*/
-class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface {
+class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface, FilteredPluginManagerInterface {
use CategorizingPluginManagerTrait;
- use ContextAwarePluginManagerTrait;
+ use FilteredPluginManagerTrait;
/**
* Constructs a ConditionManager object.
@@ -46,6 +47,13 @@ class ConditionManager extends DefaultPluginManager implements ExecutableManager
/**
* {@inheritdoc}
*/
+ protected function getType() {
+ return 'condition';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function createInstance($plugin_id, array $configuration = []) {
$plugin = $this->getFactory()->createInstance($plugin_id, $configuration);
diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index 584feb7..f7e0ede 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -219,6 +219,10 @@ class Config extends StorableConfigBase {
}
}
+ // Potentially configuration schema could have changed the underlying data's
+ // types.
+ $this->resetOverriddenData();
+
$this->storage->write($this->name, $this->data);
if (!$this->isNew) {
Cache::invalidateTags($this->getCacheTags());
@@ -226,9 +230,6 @@ class Config extends StorableConfigBase {
$this->isNew = FALSE;
$this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
$this->originalData = $this->data;
- // Potentially configuration schema could have changed the underlying data's
- // types.
- $this->resetOverriddenData();
return $this;
}
@@ -303,4 +304,42 @@ class Config extends StorableConfigBase {
}
}
+ /**
+ * Determines if overrides are applied to a key for this configuration object.
+ *
+ * @param string $key
+ * (optional) A string that maps to a key within the configuration data.
+ * For instance in the following configuration array:
+ * @code
+ * array(
+ * 'foo' => array(
+ * 'bar' => 'baz',
+ * ),
+ * );
+ * @endcode
+ * A key of 'foo.bar' would map to the string 'baz'. However, a key of 'foo'
+ * would map to the array('bar' => 'baz').
+ * If not supplied TRUE will be returned if there are any overrides at all
+ * for this configuration object.
+ *
+ * @return bool
+ * TRUE if there are any overrides for the key, otherwise FALSE.
+ */
+ public function hasOverrides($key = '') {
+ if (empty($key)) {
+ return !(empty($this->moduleOverrides) && empty($this->settingsOverrides));
+ }
+ else {
+ $parts = explode('.', $key);
+ $override_exists = FALSE;
+ if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
+ $override_exists = NestedArray::keyExists($this->moduleOverrides, $parts);
+ }
+ if (!$override_exists && isset($this->settingsOverrides) && is_array($this->settingsOverrides)) {
+ $override_exists = NestedArray::keyExists($this->settingsOverrides, $parts);
+ }
+ return $override_exists;
+ }
+ }
+
}
diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php
index 06fed4b..0f9eae9 100644
--- a/core/lib/Drupal/Core/Config/ConfigImporter.php
+++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
@@ -405,6 +405,14 @@ class ConfigImporter {
$module_list = array_reverse($module_list);
$this->extensionChangelist['module']['install'] = array_intersect(array_keys($module_list), $install);
+ // If we're installing the install profile ensure it comes last. This will
+ // occur when installing a site from configuration.
+ $install_profile_key = array_search($new_extensions['profile'], $this->extensionChangelist['module']['install'], TRUE);
+ if ($install_profile_key !== FALSE) {
+ unset($this->extensionChangelist['module']['install'][$install_profile_key]);
+ $this->extensionChangelist['module']['install'][] = $new_extensions['profile'];
+ }
+
// Work out what themes to install and to uninstall.
$this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
$this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
@@ -725,7 +733,8 @@ class ConfigImporter {
}
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this));
if (count($this->getErrors())) {
- throw new ConfigImporterException('There were errors validating the config synchronization.');
+ $errors = array_merge(['There were errors validating the config synchronization.'], $this->getErrors());
+ throw new ConfigImporterException(implode(PHP_EOL, $errors));
}
else {
$this->validated = TRUE;
@@ -788,9 +797,8 @@ class ConfigImporter {
// services.
$this->reInjectMe();
// During a module install or uninstall the container is rebuilt and the
- // module handler is called from drupal_get_complete_schema(). This causes
- // the container's instance of the module handler not to have loaded all
- // the enabled modules.
+ // module handler is called. This causes the container's instance of the
+ // module handler not to have loaded all the enabled modules.
$this->moduleHandler->loadAll();
}
if ($type == 'theme') {
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index 242d920..4a198a7 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -3,9 +3,7 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\Crypt;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
-use Drupal\Core\Config\Entity\ConfigEntityDependency;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ConfigInstaller implements ConfigInstallerInterface {
@@ -162,7 +160,10 @@ class ConfigInstaller implements ConfigInstallerInterface {
*/
public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = []) {
$profile = $this->drupalGetProfile();
- $optional_profile_config = [];
+ $enabled_extensions = $this->getEnabledExtensions();
+ $existing_config = $this->getActiveStorages()->listAll();
+
+ // Create the storages to read configuration from.
if (!$storage) {
// Search the install profile's optional configuration too.
$storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE, $this->installProfile);
@@ -173,7 +174,6 @@ class ConfigInstaller implements ConfigInstallerInterface {
// Creates a profile storage to search for overrides.
$profile_install_path = $this->drupalGetPath('module', $profile) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
$profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
- $optional_profile_config = $profile_storage->listAll();
}
else {
// Profile has not been set yet. For example during the first steps of the
@@ -181,10 +181,17 @@ class ConfigInstaller implements ConfigInstallerInterface {
$profile_storage = NULL;
}
- $enabled_extensions = $this->getEnabledExtensions();
- $existing_config = $this->getActiveStorages()->listAll();
+ // Build the list of possible configuration to create.
+ $list = $storage->listAll();
+ if ($profile_storage && !empty($dependency)) {
+ // Only add the optional profile configuration into the list if we are
+ // have a dependency to check. This ensures that optional profile
+ // configuration is not unexpectedly re-created after being deleted.
+ $list = array_unique(array_merge($list, $profile_storage->listAll()));
+ }
- $list = array_unique(array_merge($storage->listAll(), $optional_profile_config));
+ // Filter the list of configuration to only include configuration that
+ // should be created.
$list = array_filter($list, function ($config_name) use ($existing_config) {
// Only list configuration that:
// - does not already exist
@@ -205,16 +212,19 @@ class ConfigInstaller implements ConfigInstallerInterface {
$dependency_manager = new ConfigDependencyManager();
$dependency_manager->setData($config_to_create);
$config_to_create = array_merge(array_flip($dependency_manager->sortAll()), $config_to_create);
+ if (!empty($dependency)) {
+ // In order to work out dependencies we need the full config graph.
+ $dependency_manager->setData($this->getActiveStorages()->readMultiple($existing_config) + $config_to_create);
+ $dependencies = $dependency_manager->getDependentEntities(key($dependency), reset($dependency));
+ }
foreach ($config_to_create as $config_name => $data) {
// Remove configuration where its dependencies cannot be met.
$remove = !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config);
- // If $dependency is defined, remove configuration that does not have a
- // matching dependency.
+ // Remove configuration that is not dependent on $dependency, if it is
+ // defined.
if (!$remove && !empty($dependency)) {
- // Create a light weight dependency object to check dependencies.
- $config_entity = new ConfigEntityDependency($config_name, $data);
- $remove = !$config_entity->hasDependency(key($dependency), reset($dependency));
+ $remove = !isset($dependencies[$config_name]);
}
if ($remove) {
@@ -225,6 +235,8 @@ class ConfigInstaller implements ConfigInstallerInterface {
unset($all_config[$config_name]);
}
}
+
+ // Create the optional configuration if there is any left after filtering.
if (!empty($config_to_create)) {
$this->createConfiguration(StorageInterface::DEFAULT_COLLECTION, $config_to_create, TRUE);
}
@@ -316,10 +328,11 @@ class ConfigInstaller implements ConfigInstallerInterface {
$entity_storage = $this->configManager
->getEntityManager()
->getStorage($entity_type);
+
+ $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
// It is possible that secondary writes can occur during configuration
// creation. Updates of such configuration are allowed.
if ($this->getActiveStorages($collection)->exists($name)) {
- $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
$entity = $entity_storage->load($id);
$entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
}
@@ -328,6 +341,9 @@ class ConfigInstaller implements ConfigInstallerInterface {
}
if ($entity->isInstallable()) {
$entity->trustData()->save();
+ if ($id !== $entity->id()) {
+ trigger_error(sprintf('The configuration name "%s" does not match the ID "%s"', $name, $entity->id()), E_USER_WARNING);
+ }
}
}
else {
@@ -344,7 +360,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
// Only install configuration for enabled extensions.
$enabled_extensions = $this->getEnabledExtensions();
$config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
- $provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
+ $provider = mb_substr($config_name, 0, strpos($config_name, '.'));
return in_array($provider, $enabled_extensions);
});
if (!empty($config_to_install)) {
diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
index 3d7077b..41b015e 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php
@@ -43,7 +43,8 @@ interface ConfigInstallerInterface {
* @param \Drupal\Core\Config\StorageInterface $storage
* (optional) The configuration storage to search for optional
* configuration. If not provided, all enabled extension's optional
- * configuration directories will be searched.
+ * configuration directories including the install profile's will be
+ * searched.
* @param array $dependency
* (optional) If set, ensures that the configuration being installed has
* this dependency. The format is dependency type as the key ('module',
diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php
index 8b3b3d6..e766683 100644
--- a/core/lib/Drupal/Core/Config/ConfigManager.php
+++ b/core/lib/Drupal/Core/Config/ConfigManager.php
@@ -292,78 +292,85 @@ class ConfigManager implements ConfigManagerInterface {
* {@inheritdoc}
*/
public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE) {
- // Determine the current list of dependent configuration entities and set up
- // initial values.
$dependency_manager = $this->getConfigDependencyManager();
- $dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
- $original_dependencies = $dependents;
- $delete_uuids = [];
+ // Store the list of dependents in three separate variables. This allows us
+ // to determine how the dependency graph changes as entities are fixed by
+ // calling the onDependencyRemoval() method.
+
+ // The list of original dependents on $names. This list never changes.
+ $original_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
+
+ // The current list of dependents on $names. This list is recalculated when
+ // calling an entity's onDependencyRemoval() method results in the entity
+ // changing. This list is passed to each entity's onDependencyRemoval()
+ // method as the list of affected entities.
+ $current_dependents = $original_dependents;
+
+ // The list of dependents to process. This list changes as entities are
+ // processed and are either fixed or deleted.
+ $dependents_to_process = $original_dependents;
+
+ // Initialize other variables.
+ $affected_uuids = [];
$return = [
'update' => [],
'delete' => [],
'unchanged' => [],
];
- // Create a map of UUIDs to $original_dependencies key so that we can remove
- // fixed dependencies.
- $uuid_map = [];
- foreach ($original_dependencies as $key => $entity) {
- $uuid_map[$entity->uuid()] = $key;
- }
-
- // 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 Menu UI third party dependencies on
- // node types are fixed before processing the node type's other
- // dependencies.
- while ($dependent = array_pop($dependents)) {
+ // Try to fix the dependents 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 Menu UI third party dependencies on node types
+ // are fixed before processing the node type's other dependents.
+ while ($dependent = array_pop($dependents_to_process)) {
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent */
if ($dry_run) {
// Clone the entity so any changes do not change any static caches.
$dependent = clone $dependent;
}
$fixed = FALSE;
- if ($this->callOnDependencyRemoval($dependent, $original_dependencies, $type, $names)) {
+ if ($this->callOnDependencyRemoval($dependent, $current_dependents, $type, $names)) {
// Recalculate dependencies and update the dependency graph data.
$dependent->calculateDependencies();
$dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->getDependencies());
- // Based on the updated data rebuild the list of dependents. This will
- // remove entities that are no longer dependent after the recalculation.
- $dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
- // Remove any entities that we've already marked for deletion.
- $dependents = array_filter($dependents, function ($dependent) use ($delete_uuids) {
- return !in_array($dependent->uuid(), $delete_uuids);
+ // Based on the updated data rebuild the list of current dependents.
+ // This will remove entities that are no longer dependent after the
+ // recalculation.
+ $current_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
+ // Rebuild the list of entities that we need to process using the new
+ // list of current dependents and removing any entities that we've
+ // already processed.
+ $dependents_to_process = array_filter($current_dependents, function ($current_dependent) use ($affected_uuids) {
+ return !in_array($current_dependent->uuid(), $affected_uuids);
});
- // Ensure that the dependency has actually been fixed. It is possible
- // that the dependent has multiple dependencies that cause it to be in
- // the dependency chain.
+ // Ensure that the dependent has actually been fixed. It is possible
+ // that other dependencies cause it to still be in the list.
$fixed = TRUE;
- foreach ($dependents as $key => $entity) {
+ foreach ($dependents_to_process as $key => $entity) {
if ($entity->uuid() == $dependent->uuid()) {
$fixed = FALSE;
- unset($dependents[$key]);
+ unset($dependents_to_process[$key]);
break;
}
}
if ($fixed) {
- // Remove the fixed dependency from the list of original dependencies.
- unset($original_dependencies[$uuid_map[$dependent->uuid()]]);
+ $affected_uuids[] = $dependent->uuid();
$return['update'][] = $dependent;
}
}
// If the entity cannot be fixed then it has to be deleted.
if (!$fixed) {
- $delete_uuids[] = $dependent->uuid();
+ $affected_uuids[] = $dependent->uuid();
// 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
- // configuration entities are unchanged.
- $return['unchanged'] = array_filter($original_dependencies, function ($dependent) use ($delete_uuids) {
- return !(in_array($dependent->uuid(), $delete_uuids));
+ // Use the list of affected UUIDs to filter the original list to work out
+ // which configuration entities are unchanged.
+ $return['unchanged'] = array_filter($original_dependents, function ($dependent) use ($affected_uuids) {
+ return !(in_array($dependent->uuid(), $affected_uuids));
});
return $return;
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index 0ac6134..2560a46 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -228,7 +228,6 @@ class DatabaseStorage implements StorageInterface {
->execute();
}
-
/**
* Implements Drupal\Core\Config\StorageInterface::rename().
*
diff --git a/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php b/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php
index ba69001..0ca1004 100644
--- a/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php
+++ b/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php
@@ -3,7 +3,7 @@
namespace Drupal\Core\Config\Development;
use Drupal\Component\Utility\Crypt;
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\Config\Schema\SchemaCheckTrait;
@@ -90,7 +90,7 @@ class ConfigSchemaChecker implements EventSubscriberInterface {
elseif (is_array($errors)) {
$text_errors = [];
foreach ($errors as $key => $error) {
- $text_errors[] = SafeMarkup::format('@key @error', ['@key' => $key, '@error' => $error]);
+ $text_errors[] = new FormattableMarkup('@key @error', ['@key' => $key, '@error' => $error]);
}
throw new SchemaIncompleteException("Schema errors for $name with the following errors: " . implode(', ', $text_errors));
}
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
index 90bb702..4efcade 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
@@ -267,18 +267,13 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
$entity_type = $this->getEntityType();
- $properties_to_export = $entity_type->getPropertiesToExport();
- if (empty($properties_to_export)) {
+ $id_key = $entity_type->getKey('id');
+ $property_names = $entity_type->getPropertiesToExport($this->id());
+ if (empty($property_names)) {
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
- $definition = $this->getTypedConfig()->getDefinition($config_name);
- if (!isset($definition['mapping'])) {
- throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
- }
- $properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
+ throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
}
-
- $id_key = $entity_type->getKey('id');
- foreach ($properties_to_export as $property_name => $export_name) {
+ foreach ($property_names as $property_name => $export_name) {
// Special handling for IDs so that computed compound IDs work.
// @see \Drupal\Core\Entity\EntityDisplayBase::id()
if ($property_name == $id_key) {
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php
index 248701e..8931f1e 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php
@@ -189,7 +189,7 @@ interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInter
* For example, a default view might not be installable if the base table
* doesn't exist.
*
- * @retun bool
+ * @return bool
* TRUE if the entity is installable, FALSE otherwise.
*/
public function isInstallable();
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
index 62fb13e..257fdf0 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
@@ -3,6 +3,7 @@
namespace Drupal\Core\Config\Entity;
use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Entity\EntityInterface;
@@ -104,9 +105,11 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
+ * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
+ * The memory cache backend.
*/
- public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
- parent::__construct($entity_type);
+ public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache = NULL) {
+ parent::__construct($entity_type, $memory_cache);
$this->configFactory = $config_factory;
$this->uuidService = $uuid_service;
@@ -121,7 +124,8 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
- $container->get('language_manager')
+ $container->get('language_manager'),
+ $container->get('entity.memory_cache')
);
}
@@ -324,43 +328,10 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
}
/**
- * Gets entities from the static cache.
- *
- * @param array $ids
- * If not empty, return entities that match these IDs.
- *
- * @return \Drupal\Core\Entity\EntityInterface[]
- * Array of entities from the entity cache.
- */
- protected function getFromStaticCache(array $ids) {
- $entities = [];
- // Load any available entities from the internal cache.
- if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
- $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
- foreach ($ids as $id) {
- if (!empty($this->entities[$id])) {
- if (isset($this->entities[$id][$config_overrides_key])) {
- $entities[$id] = $this->entities[$id][$config_overrides_key];
- }
- }
- }
- }
- return $entities;
- }
-
- /**
- * Stores entities in the static entity cache.
- *
- * @param \Drupal\Core\Entity\EntityInterface[] $entities
- * Entities to store in the cache.
- */
- protected function setStaticCache(array $entities) {
- if ($this->entityType->isStaticallyCacheable()) {
- $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
- foreach ($entities as $id => $entity) {
- $this->entities[$id][$config_overrides_key] = $entity;
- }
- }
+ * {@inheritdoc}
+ */
+ protected function buildCacheId($id) {
+ return parent::buildCacheId($id) . ':' . ($this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys()));
}
/**
@@ -445,7 +416,7 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
* @param bool $is_syncing
* Is the configuration entity being created as part of a config sync.
*
- * @return ConfigEntityInterface
+ * @return \Drupal\Core\Config\ConfigEntityInterface
* The configuration entity.
*
* @see \Drupal\Core\Config\Entity\ConfigEntityStorageInterface::createFromStorageRecord()
@@ -479,9 +450,27 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
$data = $this->mapFromStorageRecords([$values]);
$updated_entity = current($data);
- foreach (array_keys($values) as $property) {
- $value = $updated_entity->get($property);
- $entity->set($property, $value);
+ /** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
+ $entity_type = $this->getEntityType();
+ $id_key = $entity_type->getKey('id');
+ $properties = $entity_type->getPropertiesToExport($updated_entity->get($id_key));
+
+ if (empty($properties)) {
+ // Fallback to using the provided values. If the properties cannot be
+ // determined for the config entity type annotation or configuration
+ // schema.
+ $properties = array_keys($values);
+ }
+ foreach ($properties as $property) {
+ if ($property === $this->uuidKey) {
+ // During an update the UUID field should not be copied. Under regular
+ // circumstances the values will be equal. If configuration is written
+ // twice during configuration install the updated entity will not have a
+ // UUID.
+ // @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
+ continue;
+ }
+ $entity->set($property, $updated_entity->get($property));
}
return $entity;
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
index 11c9ff1..3f142df 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
@@ -142,30 +142,40 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
/**
* {@inheritdoc}
*/
- public function getPropertiesToExport() {
+ public function getPropertiesToExport($id = NULL) {
+ if (!empty($this->mergedConfigExport)) {
+ return $this->mergedConfigExport;
+ }
if (!empty($this->config_export)) {
- if (empty($this->mergedConfigExport)) {
- // Always add default properties to be exported.
- $this->mergedConfigExport = [
- 'uuid' => 'uuid',
- 'langcode' => 'langcode',
- 'status' => 'status',
- 'dependencies' => 'dependencies',
- 'third_party_settings' => 'third_party_settings',
- '_core' => '_core',
- ];
- foreach ($this->config_export as $property => $name) {
- if (is_numeric($property)) {
- $this->mergedConfigExport[$name] = $name;
- }
- else {
- $this->mergedConfigExport[$property] = $name;
- }
+ // Always add default properties to be exported.
+ $this->mergedConfigExport = [
+ 'uuid' => 'uuid',
+ 'langcode' => 'langcode',
+ 'status' => 'status',
+ 'dependencies' => 'dependencies',
+ 'third_party_settings' => 'third_party_settings',
+ '_core' => '_core',
+ ];
+ foreach ($this->config_export as $property => $name) {
+ if (is_numeric($property)) {
+ $this->mergedConfigExport[$name] = $name;
+ }
+ else {
+ $this->mergedConfigExport[$property] = $name;
}
}
- return $this->mergedConfigExport;
}
- return NULL;
+ else {
+ // @todo https://www.drupal.org/project/drupal/issues/2949021 Deprecate
+ // fallback to schema.
+ $config_name = $this->getConfigPrefix() . '.' . $id;
+ $definition = \Drupal::service('config.typed')->getDefinition($config_name);
+ if (!isset($definition['mapping'])) {
+ return NULL;
+ }
+ $this->mergedConfigExport = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
+ }
+ return $this->mergedConfigExport;
}
/**
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php
index 589a63d..de4b1e1 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php
@@ -65,11 +65,18 @@ interface ConfigEntityTypeInterface extends EntityTypeInterface {
/**
* Gets the config entity properties to export if declared on the annotation.
*
+ * Falls back to determining the properties using configuration schema, if the
+ * config entity properties are not declared.
+ *
+ * @param string $id
+ * The ID of the configuration entity. Used when checking schema instead of
+ * the annotation.
+ *
* @return array|null
* The properties to export or NULL if they can not be determine from the
- * config entity type annotation.
+ * config entity type annotation or the schema.
*/
- public function getPropertiesToExport();
+ public function getPropertiesToExport($id = NULL);
/**
* Gets the keys that are available for fast lookup.
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityUpdater.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityUpdater.php
new file mode 100644
index 0000000..37e5fb1
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityUpdater.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Drupal\Core\Config\Entity;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * A utility class to make updating configuration entities simple.
+ *
+ * Use this in a post update function like so:
+ * @code
+ * // Update the dependencies of all Vocabulary configuration entities.
+ * \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'taxonomy_vocabulary');
+ * @endcode
+ *
+ * The number of entities processed in each batch is determined by the
+ * 'entity_update_batch_size' setting.
+ *
+ * @see default.settings.php
+ */
+class ConfigEntityUpdater implements ContainerInjectionInterface {
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * The number of entities to process in each batch.
+ * @var int
+ */
+ protected $batchSize;
+
+ /**
+ * ConfigEntityUpdater constructor.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param int $batch_size
+ * The number of entities to process in each batch.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, $batch_size) {
+ $this->entityTypeManager = $entity_type_manager;
+ $this->batchSize = $batch_size;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity_type.manager'),
+ $container->get('settings')->get('entity_update_batch_size', 50)
+ );
+ }
+
+ /**
+ * Updates configuration entities as part of a Drupal update.
+ *
+ * @param array $sandbox
+ * Stores information for batch updates.
+ * @param string $entity_type_id
+ * The configuration entity type ID. For example, 'view' or 'vocabulary'.
+ * @param callable $callback
+ * (optional) A callback to determine if a configuration entity should be
+ * saved. The callback will be passed each entity of the provided type that
+ * exists. The callback should not save an entity itself. Return TRUE to
+ * save an entity. The callback can make changes to an entity. Note that all
+ * changes should comply with schema as an entity's data will not be
+ * validated against schema on save to avoid unexpected errors. If a
+ * callback is not provided, the default behaviour is to update the
+ * dependencies if required.
+ *
+ * @see hook_post_update_NAME()
+ *
+ * @api
+ *
+ * @throws \InvalidArgumentException
+ * Thrown when the provided entity type ID is not a configuration entity
+ * type.
+ */
+ public function update(array &$sandbox, $entity_type_id, callable $callback = NULL) {
+ $storage = $this->entityTypeManager->getStorage($entity_type_id);
+ $sandbox_key = 'config_entity_updater:' . $entity_type_id;
+ if (!isset($sandbox[$sandbox_key])) {
+ $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+ if (!($entity_type instanceof ConfigEntityTypeInterface)) {
+ throw new \InvalidArgumentException("The provided entity type ID '$entity_type_id' is not a configuration entity type");
+ }
+ $sandbox[$sandbox_key]['entities'] = $storage->getQuery()->accessCheck(FALSE)->execute();
+ $sandbox[$sandbox_key]['count'] = count($sandbox[$sandbox_key]['entities']);
+ }
+
+ // The default behaviour is to fix dependencies.
+ if ($callback === NULL) {
+ $callback = function ($entity) {
+ /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
+ $original_dependencies = $entity->getDependencies();
+ return $original_dependencies !== $entity->calculateDependencies()->getDependencies();
+ };
+ }
+
+ /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
+ $entities = $storage->loadMultiple(array_splice($sandbox[$sandbox_key]['entities'], 0, $this->batchSize));
+ foreach ($entities as $entity) {
+ if (call_user_func($callback, $entity)) {
+ $entity->trustData();
+ $entity->save();
+ }
+ }
+
+ $sandbox['#finished'] = empty($sandbox[$sandbox_key]['entities']) ? 1 : ($sandbox[$sandbox_key]['count'] - count($sandbox[$sandbox_key]['entities'])) / $sandbox[$sandbox_key]['count'];
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/DraggableListBuilder.php b/core/lib/Drupal/Core/Config/Entity/DraggableListBuilder.php
index f6bf463..d70aee8 100644
--- a/core/lib/Drupal/Core/Config/Entity/DraggableListBuilder.php
+++ b/core/lib/Drupal/Core/Config/Entity/DraggableListBuilder.php
@@ -107,7 +107,7 @@ abstract class DraggableListBuilder extends ConfigEntityListBuilder implements F
$form[$this->entitiesKey] = [
'#type' => 'table',
'#header' => $this->buildHeader(),
- '#empty' => t('There is no @label yet.', ['@label' => $this->entityType->getLabel()]),
+ '#empty' => t('There are no @label yet.', ['@label' => $this->entityType->getPluralLabel()]),
'#tabledrag' => [
[
'action' => 'order',
diff --git a/core/lib/Drupal/Core/Config/Entity/Query/Condition.php b/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
index bd2facd..3410a6e 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
@@ -2,7 +2,6 @@
namespace Drupal\Core\Config\Entity\Query;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Query\ConditionBase;
use Drupal\Core\Entity\Query\ConditionInterface;
use Drupal\Core\Entity\Query\QueryException;
@@ -32,10 +31,10 @@ class Condition extends ConditionBase {
// Lowercase condition value(s) for case-insensitive matches.
if (is_array($condition['value'])) {
- $condition['value'] = array_map('Drupal\Component\Utility\Unicode::strtolower', $condition['value']);
+ $condition['value'] = array_map('mb_strtolower', $condition['value']);
}
elseif (!is_bool($condition['value'])) {
- $condition['value'] = Unicode::strtolower($condition['value']);
+ $condition['value'] = mb_strtolower($condition['value']);
}
$single_conditions[] = $condition;
@@ -164,7 +163,7 @@ class Condition extends ConditionBase {
if (isset($value)) {
// We always want a case-insensitive match.
if (!is_bool($value)) {
- $value = Unicode::strtolower($value);
+ $value = mb_strtolower($value);
}
switch ($condition['operator']) {
diff --git a/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
index f87e618..d784cdb 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
@@ -28,7 +28,7 @@ class QueryFactory implements QueryFactoryInterface, EventSubscriberInterface {
/**
* The config factory used by the config entity query.
*
- * @var \Drupal\Core\Config\ConfigFactoryInterface;
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
@@ -219,7 +219,7 @@ class QueryFactory implements QueryFactoryInterface, EventSubscriberInterface {
/**
* Updates configuration entity in the key store.
*
- * @param ConfigCrudEvent $event
+ * @param \Drupal\Core\Config\ConfigCrudEvent $event
* The configuration event.
*/
public function onConfigSave(ConfigCrudEvent $event) {
diff --git a/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php b/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php
index 7b2ee06..990a5b4 100644
--- a/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php
+++ b/core/lib/Drupal/Core/Config/Entity/ThirdPartySettingsInterface.php
@@ -41,7 +41,6 @@ interface ThirdPartySettingsInterface {
*/
public function getThirdPartySetting($module, $key, $default = NULL);
-
/**
* Gets all third-party settings of a given module.
*
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 84d23d3..32dd929 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -46,7 +46,7 @@ class FileStorage implements StorageInterface {
$this->collection = $collection;
// Use a NULL File Cache backend by default. This will ensure only the
- // internal statc caching of FileCache is used and thus avoids blowing up
+ // internal static caching of FileCache is used and thus avoids blowing up
// the APCu cache.
$this->fileCache = FileCacheFactory::get('config', ['cache_backend_class' => NULL]);
}
diff --git a/core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php b/core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php
new file mode 100644
index 0000000..8aee289
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\Core\Config\Importer;
+
+use Drupal\Core\Config\ConfigImporter;
+
+/**
+ * Methods for running the ConfigImporter in a batch.
+ *
+ * @see \Drupal\Core\Config\ConfigImporter
+ */
+class ConfigImporterBatch {
+
+ /**
+ * Processes the config import batch and persists the importer.
+ *
+ * @param \Drupal\Core\Config\ConfigImporter $config_importer
+ * The batch config importer object to persist.
+ * @param string $sync_step
+ * The synchronization step to do.
+ * @param array $context
+ * The batch context.
+ */
+ public static function process(ConfigImporter $config_importer, $sync_step, &$context) {
+ if (!isset($context['sandbox']['config_importer'])) {
+ $context['sandbox']['config_importer'] = $config_importer;
+ }
+
+ $config_importer = $context['sandbox']['config_importer'];
+ $config_importer->doSyncStep($sync_step, $context);
+ if ($errors = $config_importer->getErrors()) {
+ if (!isset($context['results']['errors'])) {
+ $context['results']['errors'] = [];
+ }
+ $context['results']['errors'] = array_merge($errors, $context['results']['errors']);
+ }
+ }
+
+ /**
+ * Finish batch.
+ *
+ * This function is a static function to avoid serializing the ConfigSync
+ * object unnecessarily.
+ *
+ * @param bool $success
+ * Indicate that the batch API tasks were all completed successfully.
+ * @param array $results
+ * An array of all the results that were updated in update_do_one().
+ * @param array $operations
+ * A list of the operations that had not been completed by the batch API.
+ */
+ public static function finish($success, $results, $operations) {
+ $messenger = \Drupal::messenger();
+ if ($success) {
+ if (!empty($results['errors'])) {
+ $logger = \Drupal::logger('config_sync');
+ foreach ($results['errors'] as $error) {
+ $messenger->addError($error);
+ $logger->error($error);
+ }
+ $messenger->addWarning(t('The configuration was imported with errors.'));
+ }
+ elseif (!drupal_installation_attempted()) {
+ // Display a success message when not installing Drupal.
+ $messenger->addStatus(t('The configuration was imported successfully.'));
+ }
+ }
+ else {
+ // An error occurred.
+ // $operations contains the operations that remained unprocessed.
+ $error_operation = reset($operations);
+ $message = t('An error occurred while processing %error_operation with arguments: @arguments', ['%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)]);
+ $messenger->addError($message);
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Config/PreExistingConfigException.php b/core/lib/Drupal/Core/Config/PreExistingConfigException.php
index 57f54b2..a71604e 100644
--- a/core/lib/Drupal/Core/Config/PreExistingConfigException.php
+++ b/core/lib/Drupal/Core/Config/PreExistingConfigException.php
@@ -2,7 +2,7 @@
namespace Drupal\Core\Config;
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Render\FormattableMarkup;
/**
* An exception thrown if configuration with the same name already exists.
@@ -56,10 +56,10 @@ class PreExistingConfigException extends ConfigException {
* @return \Drupal\Core\Config\PreExistingConfigException
*/
public static function create($extension, array $config_objects) {
- $message = SafeMarkup::format('Configuration objects (@config_names) provided by @extension already exist in active configuration',
+ $message = new FormattableMarkup('Configuration objects (@config_names) provided by @extension already exist in active configuration',
[
'@config_names' => implode(', ', static::flattenConfigObjects($config_objects)),
- '@extension' => $extension
+ '@extension' => $extension,
]
);
$e = new static($message);
diff --git a/core/lib/Drupal/Core/Config/UnmetDependenciesException.php b/core/lib/Drupal/Core/Config/UnmetDependenciesException.php
index 6c37c76..573ec54 100644
--- a/core/lib/Drupal/Core/Config/UnmetDependenciesException.php
+++ b/core/lib/Drupal/Core/Config/UnmetDependenciesException.php
@@ -92,7 +92,7 @@ class UnmetDependenciesException extends ConfigException {
$message = new FormattableMarkup('Configuration objects provided by %extension have unmet dependencies: %config_names',
[
'%config_names' => static::formatConfigObjectList($config_objects),
- '%extension' => $extension
+ '%extension' => $extension,
]
);
$e = new static($message);
diff --git a/core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php b/core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php
new file mode 100644
index 0000000..f5ce9f3
--- /dev/null
+++ b/core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Core\Controller\ArgumentResolver;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
+use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
+
+/**
+ * Yields a PSR7 request object based on the request object passed along.
+ */
+final class Psr7RequestValueResolver implements ArgumentValueResolverInterface {
+
+ /**
+ * The PSR-7 converter.
+ *
+ * @var \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface
+ */
+ protected $httpMessageFactory;
+
+ /**
+ * Constructs a new ControllerResolver.
+ *
+ * @param \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface $http_message_factory
+ * The PSR-7 converter.
+ */
+ public function __construct(HttpMessageFactoryInterface $http_message_factory) {
+ $this->httpMessageFactory = $http_message_factory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports(Request $request, ArgumentMetadata $argument) {
+ return $argument->getType() == ServerRequestInterface::class;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve(Request $request, ArgumentMetadata $argument) {
+ yield $this->httpMessageFactory->createRequest($request);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Controller/ArgumentResolver/RawParameterValueResolver.php b/core/lib/Drupal/Core/Controller/ArgumentResolver/RawParameterValueResolver.php
new file mode 100644
index 0000000..7e2a35a
--- /dev/null
+++ b/core/lib/Drupal/Core/Controller/ArgumentResolver/RawParameterValueResolver.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\Core\Controller\ArgumentResolver;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
+use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
+
+/**
+ * Yields an argument's value from the request's _raw_variables attribute.
+ */
+final class RawParameterValueResolver implements ArgumentValueResolverInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports(Request $request, ArgumentMetadata $argument) {
+ return !$argument->isVariadic() && $request->attributes->has('_raw_variables') && array_key_exists($argument->getName(), $request->attributes->get('_raw_variables'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve(Request $request, ArgumentMetadata $argument) {
+ yield $request->attributes->get('_raw_variables')[$argument->getName()];
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php b/core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php
new file mode 100644
index 0000000..d5846fe
--- /dev/null
+++ b/core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\Core\Controller\ArgumentResolver;
+
+use Drupal\Core\Routing\RouteMatch;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
+use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
+
+/**
+ * Yields a RouteMatch object based on the request object passed along.
+ */
+final class RouteMatchValueResolver implements ArgumentValueResolverInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports(Request $request, ArgumentMetadata $argument) {
+ return $argument->getType() == RouteMatchInterface::class || is_subclass_of($argument->getType(), RouteMatchInterface::class);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve(Request $request, ArgumentMetadata $argument) {
+ yield RouteMatch::createFromRequest($request);
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Controller/ControllerBase.php b/core/lib/Drupal/Core/Controller/ControllerBase.php
index c0d0cdd..68c2655 100644
--- a/core/lib/Drupal/Core/Controller/ControllerBase.php
+++ b/core/lib/Drupal/Core/Controller/ControllerBase.php
@@ -9,6 +9,7 @@ use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\Routing\UrlGeneratorTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Messenger\MessengerTrait;
/**
* Utility base class for thin controllers.
@@ -35,6 +36,7 @@ abstract class ControllerBase implements ContainerInjectionInterface {
use LinkGeneratorTrait;
use LoggerChannelTrait;
+ use MessengerTrait;
use RedirectDestinationTrait;
use StringTranslationTrait;
use UrlGeneratorTrait;
@@ -91,7 +93,7 @@ abstract class ControllerBase implements ContainerInjectionInterface {
/**
* The state service.
*
- * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+ * @var \Drupal\Core\State\StateInterface
*/
protected $stateService;
@@ -220,7 +222,7 @@ abstract class ControllerBase implements ContainerInjectionInterface {
* needs to be the same across development, production, etc. environments
* (for example, the system maintenance message) should use config() instead.
*
- * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+ * @return \Drupal\Core\State\StateInterface
*/
protected function state() {
if (!$this->stateService) {
diff --git a/core/lib/Drupal/Core/Controller/ControllerResolver.php b/core/lib/Drupal/Core/Controller/ControllerResolver.php
index c16a087..35d433a 100644
--- a/core/lib/Drupal/Core/Controller/ControllerResolver.php
+++ b/core/lib/Drupal/Core/Controller/ControllerResolver.php
@@ -80,7 +80,6 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
return $callable;
}
-
/**
* {@inheritdoc}
*/
@@ -129,6 +128,10 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes
* {@inheritdoc}
*/
protected function doGetArguments(Request $request, $controller, array $parameters) {
+ // Note this duplicates the deprecation message of
+ // Symfony\Component\HttpKernel\Controller\ControllerResolver::getArguments()
+ // to ensure it is removed in Drupal 9.
+ @trigger_error(sprintf('%s is deprecated as of 8.6.0 and will be removed in 9.0. Inject the "http_kernel.controller.argument_resolver" service instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED);
$attributes = $request->attributes->all();
$raw_parameters = $request->attributes->has('_raw_variables') ? $request->attributes->get('_raw_variables') : [];
$arguments = [];
diff --git a/core/lib/Drupal/Core/Controller/FormController.php b/core/lib/Drupal/Core/Controller/FormController.php
index 7af86df..acbcaf6 100644
--- a/core/lib/Drupal/Core/Controller/FormController.php
+++ b/core/lib/Drupal/Core/Controller/FormController.php
@@ -7,6 +7,7 @@ use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
/**
* Common base class for form interstitial controllers.
@@ -17,9 +18,23 @@ abstract class FormController {
use DependencySerializationTrait;
/**
+ * The argument resolver.
+ *
+ * @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface
+ */
+ protected $argumentResolver;
+
+ /**
* The controller resolver.
*
* @var \Drupal\Core\Controller\ControllerResolverInterface
+ *
+ * @deprecated
+ * Deprecated property that is only assigned when the 'controller_resolver'
+ * service is used as the first parameter to FormController::__construct().
+ *
+ * @see https://www.drupal.org/node/2959408
+ * @see \Drupal\Core\Controller\FormController::__construct()
*/
protected $controllerResolver;
@@ -33,13 +48,17 @@ abstract class FormController {
/**
* Constructs a new \Drupal\Core\Controller\FormController object.
*
- * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
- * The controller resolver.
+ * @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver
+ * The argument resolver.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
*/
- public function __construct(ControllerResolverInterface $controller_resolver, FormBuilderInterface $form_builder) {
- $this->controllerResolver = $controller_resolver;
+ public function __construct(ArgumentResolverInterface $argument_resolver, FormBuilderInterface $form_builder) {
+ $this->argumentResolver = $argument_resolver;
+ if ($argument_resolver instanceof ControllerResolverInterface) {
+ @trigger_error("Using the 'controller_resolver' service as the first argument is deprecated, use the 'http_kernel.controller.argument_resolver' instead. If your subclass requires the 'controller_resolver' service add it as an additional argument. See https://www.drupal.org/node/2959408.", E_USER_DEPRECATED);
+ $this->controllerResolver = $argument_resolver;
+ }
$this->formBuilder = $form_builder;
}
@@ -63,7 +82,7 @@ abstract class FormController {
$form_state = new FormState();
$request->attributes->set('form', []);
$request->attributes->set('form_state', $form_state);
- $args = $this->controllerResolver->getArguments($request, [$form_object, 'buildForm']);
+ $args = $this->argumentResolver->getArguments($request, [$form_object, 'buildForm']);
$request->attributes->remove('form');
$request->attributes->remove('form_state');
diff --git a/core/lib/Drupal/Core/Controller/HtmlFormController.php b/core/lib/Drupal/Core/Controller/HtmlFormController.php
index fe63038..6dd27ee 100644
--- a/core/lib/Drupal/Core/Controller/HtmlFormController.php
+++ b/core/lib/Drupal/Core/Controller/HtmlFormController.php
@@ -5,6 +5,7 @@ namespace Drupal\Core\Controller;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
/**
* Wrapping controller for forms that serve as the main page body.
@@ -14,22 +15,22 @@ class HtmlFormController extends FormController {
/**
* The class resolver.
*
- * @var \Drupal\Core\DependencyInjection\ClassResolverInterface;
+ * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
*/
protected $classResolver;
/**
* Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
*
- * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
- * The controller resolver.
+ * @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver
+ * The argument resolver.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
*/
- public function __construct(ControllerResolverInterface $controller_resolver, FormBuilderInterface $form_builder, ClassResolverInterface $class_resolver) {
- parent::__construct($controller_resolver, $form_builder);
+ public function __construct(ArgumentResolverInterface $argument_resolver, FormBuilderInterface $form_builder, ClassResolverInterface $class_resolver) {
+ parent::__construct($argument_resolver, $form_builder);
$this->classResolver = $class_resolver;
}
diff --git a/core/lib/Drupal/Core/Controller/TitleResolver.php b/core/lib/Drupal/Core/Controller/TitleResolver.php
index 954835d..85fce77 100644
--- a/core/lib/Drupal/Core/Controller/TitleResolver.php
+++ b/core/lib/Drupal/Core/Controller/TitleResolver.php
@@ -5,6 +5,7 @@ namespace Drupal\Core\Controller;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
use Symfony\Component\Routing\Route;
/**
@@ -21,16 +22,26 @@ class TitleResolver implements TitleResolverInterface {
protected $controllerResolver;
/**
+ * The argument resolver.
+ *
+ * @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface
+ */
+ protected $argumentResolver;
+
+ /**
* Constructs a TitleResolver instance.
*
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* The controller resolver.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
+ * @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver
+ * The argument resolver.
*/
- public function __construct(ControllerResolverInterface $controller_resolver, TranslationInterface $string_translation) {
+ public function __construct(ControllerResolverInterface $controller_resolver, TranslationInterface $string_translation, ArgumentResolverInterface $argument_resolver) {
$this->controllerResolver = $controller_resolver;
$this->stringTranslation = $string_translation;
+ $this->argumentResolver = $argument_resolver;
}
/**
@@ -43,7 +54,7 @@ class TitleResolver implements TitleResolverInterface {
// trying to use empty values.
if ($callback = $route->getDefault('_title_callback')) {
$callable = $this->controllerResolver->getControllerFromDefinition($callback);
- $arguments = $this->controllerResolver->getArguments($request, $callable);
+ $arguments = $this->argumentResolver->getArguments($request, $callable);
$route_title = call_user_func_array($callable, $arguments);
}
elseif ($title = $route->getDefault('_title')) {
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index 100ef68..f451e50 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -808,12 +808,15 @@ abstract class Connection {
* @param string $table
* The table to use for the insert statement.
* @param array $options
- * (optional) An array of options on the query.
+ * (optional) An associative array of options to control how the query is
+ * run. The given options will be merged with
+ * \Drupal\Core\Database\Connection::defaultOptions().
*
* @return \Drupal\Core\Database\Query\Insert
* A new Insert query object.
*
* @see \Drupal\Core\Database\Query\Insert
+ * @see \Drupal\Core\Database\Connection::defaultOptions()
*/
public function insert($table, array $options = []) {
$class = $this->getDriverClass('Insert');
@@ -862,12 +865,15 @@ abstract class Connection {
* @param string $table
* The table to use for the update statement.
* @param array $options
- * (optional) An array of options on the query.
+ * (optional) An associative array of options to control how the query is
+ * run. The given options will be merged with
+ * \Drupal\Core\Database\Connection::defaultOptions().
*
* @return \Drupal\Core\Database\Query\Update
* A new Update query object.
*
* @see \Drupal\Core\Database\Query\Update
+ * @see \Drupal\Core\Database\Connection::defaultOptions()
*/
public function update($table, array $options = []) {
$class = $this->getDriverClass('Update');
@@ -880,12 +886,15 @@ abstract class Connection {
* @param string $table
* The table to use for the delete statement.
* @param array $options
- * (optional) An array of options on the query.
+ * (optional) An associative array of options to control how the query is
+ * run. The given options will be merged with
+ * \Drupal\Core\Database\Connection::defaultOptions().
*
* @return \Drupal\Core\Database\Query\Delete
* A new Delete query object.
*
* @see \Drupal\Core\Database\Query\Delete
+ * @see \Drupal\Core\Database\Connection::defaultOptions()
*/
public function delete($table, array $options = []) {
$class = $this->getDriverClass('Delete');
@@ -1472,4 +1481,116 @@ abstract class Connection {
throw new \LogicException('The database connection is not serializable. This probably means you are serializing an object that has an indirect reference to the database connection. Adjust your code so that is not necessary. Alternatively, look at DependencySerializationTrait as a temporary solution.');
}
+ /**
+ * Creates an array of database connection options from a URL.
+ *
+ * @internal
+ * This method should not be called. Use
+ * \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo() instead.
+ *
+ * @param string $url
+ * The URL.
+ * @param string $root
+ * The root directory of the Drupal installation. Some database drivers,
+ * like for example SQLite, need this information.
+ *
+ * @return array
+ * The connection options.
+ *
+ * @throws \InvalidArgumentException
+ * Exception thrown when the provided URL does not meet the minimum
+ * requirements.
+ *
+ * @see \Drupal\Core\Database\Database::convertDbUrlToConnectionInfo()
+ */
+ public static function createConnectionOptionsFromUrl($url, $root) {
+ $url_components = parse_url($url);
+ if (!isset($url_components['scheme'], $url_components['host'], $url_components['path'])) {
+ throw new \InvalidArgumentException('Minimum requirement: driver://host/database');
+ }
+
+ $url_components += [
+ 'user' => '',
+ 'pass' => '',
+ 'fragment' => '',
+ ];
+
+ // Remove leading slash from the URL path.
+ if ($url_components['path'][0] === '/') {
+ $url_components['path'] = substr($url_components['path'], 1);
+ }
+
+ // Use reflection to get the namespace of the class being called.
+ $reflector = new \ReflectionClass(get_called_class());
+
+ $database = [
+ 'driver' => $url_components['scheme'],
+ 'username' => $url_components['user'],
+ 'password' => $url_components['pass'],
+ 'host' => $url_components['host'],
+ 'database' => $url_components['path'],
+ 'namespace' => $reflector->getNamespaceName(),
+ ];
+
+ if (isset($url_components['port'])) {
+ $database['port'] = $url_components['port'];
+ }
+
+ if (!empty($url_components['fragment'])) {
+ $database['prefix']['default'] = $url_components['fragment'];
+ }
+
+ return $database;
+ }
+
+ /**
+ * Creates a URL from an array of database connection options.
+ *
+ * @internal
+ * This method should not be called. Use
+ * \Drupal\Core\Database\Database::getConnectionInfoAsUrl() instead.
+ *
+ * @param array $connection_options
+ * The array of connection options for a database connection.
+ *
+ * @return string
+ * The connection info as a URL.
+ *
+ * @throws \InvalidArgumentException
+ * Exception thrown when the provided array of connection options does not
+ * meet the minimum requirements.
+ *
+ * @see \Drupal\Core\Database\Database::getConnectionInfoAsUrl()
+ */
+ public static function createUrlFromConnectionOptions(array $connection_options) {
+ if (!isset($connection_options['driver'], $connection_options['database'])) {
+ throw new \InvalidArgumentException("As a minimum, the connection options array must contain at least the 'driver' and 'database' keys");
+ }
+
+ $user = '';
+ if (isset($connection_options['username'])) {
+ $user = $connection_options['username'];
+ if (isset($connection_options['password'])) {
+ $user .= ':' . $connection_options['password'];
+ }
+ $user .= '@';
+ }
+
+ $host = empty($connection_options['host']) ? 'localhost' : $connection_options['host'];
+
+ $db_url = $connection_options['driver'] . '://' . $user . $host;
+
+ if (isset($connection_options['port'])) {
+ $db_url .= ':' . $connection_options['port'];
+ }
+
+ $db_url .= '/' . $connection_options['database'];
+
+ if (isset($connection_options['prefix']['default']) && $connection_options['prefix']['default'] !== '') {
+ $db_url .= '#' . $connection_options['prefix']['default'];
+ }
+
+ return $db_url;
+ }
+
}
diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php
index dd19018..f29b60e 100644
--- a/core/lib/Drupal/Core/Database/Database.php
+++ b/core/lib/Drupal/Core/Database/Database.php
@@ -365,13 +365,8 @@ abstract class Database {
throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
}
- if (!empty(self::$databaseInfo[$key][$target]['namespace'])) {
- $driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection';
- }
- else {
- // Fallback for Drupal 7 settings.php.
- $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
- }
+ $namespace = static::getDatabaseDriverNamespace(self::$databaseInfo[$key][$target]);
+ $driver_class = $namespace . '\\Connection';
$pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
$new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
@@ -455,36 +450,25 @@ abstract class Database {
* requirements.
*/
public static function convertDbUrlToConnectionInfo($url, $root) {
- $info = parse_url($url);
- if (!isset($info['scheme'], $info['host'], $info['path'])) {
- throw new \InvalidArgumentException('Minimum requirement: driver://host/database');
+ // Check that the URL is well formed, starting with 'scheme://', where
+ // 'scheme' is a database driver name.
+ if (preg_match('/^(.*):\/\//', $url, $matches) !== 1) {
+ throw new \InvalidArgumentException("Missing scheme in URL '$url'");
}
- $info += [
- 'user' => '',
- 'pass' => '',
- 'fragment' => '',
- ];
-
- // A SQLite database path with two leading slashes indicates a system path.
- // Otherwise the path is relative to the Drupal root.
- if ($info['path'][0] === '/') {
- $info['path'] = substr($info['path'], 1);
- }
- if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') {
- $info['path'] = $root . '/' . $info['path'];
+ $driver = $matches[1];
+
+ // Discover if the URL has a valid driver scheme. Try with core drivers
+ // first.
+ $connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
+ if (!class_exists($connection_class)) {
+ // If the URL is not relative to a core driver, try with custom ones.
+ $connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
+ if (!class_exists($connection_class)) {
+ throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$connection_class' does not exist");
+ }
}
- $database = [
- 'driver' => $info['scheme'],
- 'username' => $info['user'],
- 'password' => $info['pass'],
- 'host' => $info['host'],
- 'database' => $info['path'],
- ];
- if (isset($info['port'])) {
- $database['port'] = $info['port'];
- }
- return $database;
+ return $connection_class::createConnectionOptionsFromUrl($url, $root);
}
/**
@@ -495,32 +479,36 @@ abstract class Database {
*
* @return string
* The connection info as a URL.
+ *
+ * @throws \RuntimeException
+ * When the database connection is not defined.
*/
public static function getConnectionInfoAsUrl($key = 'default') {
$db_info = static::getConnectionInfo($key);
- if ($db_info['default']['driver'] == 'sqlite') {
- $db_url = 'sqlite://localhost/' . $db_info['default']['database'];
+ if (empty($db_info) || empty($db_info['default'])) {
+ throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
}
- else {
- $user = '';
- if ($db_info['default']['username']) {
- $user = $db_info['default']['username'];
- if ($db_info['default']['password']) {
- $user .= ':' . $db_info['default']['password'];
- }
- $user .= '@';
- }
+ $connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
+ return $connection_class::createUrlFromConnectionOptions($db_info['default']);
+ }
- $db_url = $db_info['default']['driver'] . '://' . $user . $db_info['default']['host'];
- if (isset($db_info['default']['port'])) {
- $db_url .= ':' . $db_info['default']['port'];
- }
- $db_url .= '/' . $db_info['default']['database'];
- }
- if ($db_info['default']['prefix']['default']) {
- $db_url .= '#' . $db_info['default']['prefix']['default'];
+ /**
+ * Gets the PHP namespace of a database driver from the connection info.
+ *
+ * @param array $connection_info
+ * The database connection information, as defined in settings.php. The
+ * structure of this array depends on the database driver it is connecting
+ * to.
+ *
+ * @return string
+ * The PHP namespace of the driver's database.
+ */
+ protected static function getDatabaseDriverNamespace(array $connection_info) {
+ if (isset($connection_info['namespace'])) {
+ return $connection_info['namespace'];
}
- return $db_url;
+ // Fallback for Drupal 7 settings.php.
+ return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
}
}
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
index 164f68a..626be4b 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
@@ -65,6 +65,277 @@ class Connection extends DatabaseConnection {
const MIN_MAX_ALLOWED_PACKET = 1024;
/**
+ * The list of MySQL reserved key words.
+ *
+ * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html
+ */
+ private $reservedKeyWords = [
+ 'accessible',
+ 'add',
+ 'admin',
+ 'all',
+ 'alter',
+ 'analyze',
+ 'and',
+ 'as',
+ 'asc',
+ 'asensitive',
+ 'before',
+ 'between',
+ 'bigint',
+ 'binary',
+ 'blob',
+ 'both',
+ 'by',
+ 'call',
+ 'cascade',
+ 'case',
+ 'change',
+ 'char',
+ 'character',
+ 'check',
+ 'collate',
+ 'column',
+ 'condition',
+ 'constraint',
+ 'continue',
+ 'convert',
+ 'create',
+ 'cross',
+ 'cube',
+ 'cume_dist',
+ 'current_date',
+ 'current_time',
+ 'current_timestamp',
+ 'current_user',
+ 'cursor',
+ 'database',
+ 'databases',
+ 'day_hour',
+ 'day_microsecond',
+ 'day_minute',
+ 'day_second',
+ 'dec',
+ 'decimal',
+ 'declare',
+ 'default',
+ 'delayed',
+ 'delete',
+ 'dense_rank',
+ 'desc',
+ 'describe',
+ 'deterministic',
+ 'distinct',
+ 'distinctrow',
+ 'div',
+ 'double',
+ 'drop',
+ 'dual',
+ 'each',
+ 'else',
+ 'elseif',
+ 'empty',
+ 'enclosed',
+ 'escaped',
+ 'except',
+ 'exists',
+ 'exit',
+ 'explain',
+ 'false',
+ 'fetch',
+ 'first_value',
+ 'float',
+ 'float4',
+ 'float8',
+ 'for',
+ 'force',
+ 'foreign',
+ 'from',
+ 'fulltext',
+ 'function',
+ 'generated',
+ 'get',
+ 'grant',
+ 'group',
+ 'grouping',
+ 'groups',
+ 'having',
+ 'high_priority',
+ 'hour_microsecond',
+ 'hour_minute',
+ 'hour_second',
+ 'if',
+ 'ignore',
+ 'in',
+ 'index',
+ 'infile',
+ 'inner',
+ 'inout',
+ 'insensitive',
+ 'insert',
+ 'int',
+ 'int1',
+ 'int2',
+ 'int3',
+ 'int4',
+ 'int8',
+ 'integer',
+ 'interval',
+ 'into',
+ 'io_after_gtids',
+ 'io_before_gtids',
+ 'is',
+ 'iterate',
+ 'join',
+ 'json_table',
+ 'key',
+ 'keys',
+ 'kill',
+ 'lag',
+ 'last_value',
+ 'lead',
+ 'leading',
+ 'leave',
+ 'left',
+ 'like',
+ 'limit',
+ 'linear',
+ 'lines',
+ 'load',
+ 'localtime',
+ 'localtimestamp',
+ 'lock',
+ 'long',
+ 'longblob',
+ 'longtext',
+ 'loop',
+ 'low_priority',
+ 'master_bind',
+ 'master_ssl_verify_server_cert',
+ 'match',
+ 'maxvalue',
+ 'mediumblob',
+ 'mediumint',
+ 'mediumtext',
+ 'middleint',
+ 'minute_microsecond',
+ 'minute_second',
+ 'mod',
+ 'modifies',
+ 'natural',
+ 'not',
+ 'no_write_to_binlog',
+ 'nth_value',
+ 'ntile',
+ 'null',
+ 'numeric',
+ 'of',
+ 'on',
+ 'optimize',
+ 'optimizer_costs',
+ 'option',
+ 'optionally',
+ 'or',
+ 'order',
+ 'out',
+ 'outer',
+ 'outfile',
+ 'over',
+ 'partition',
+ 'percent_rank',
+ 'persist',
+ 'persist_only',
+ 'precision',
+ 'primary',
+ 'procedure',
+ 'purge',
+ 'range',
+ 'rank',
+ 'read',
+ 'reads',
+ 'read_write',
+ 'real',
+ 'recursive',
+ 'references',
+ 'regexp',
+ 'release',
+ 'rename',
+ 'repeat',
+ 'replace',
+ 'require',
+ 'resignal',
+ 'restrict',
+ 'return',
+ 'revoke',
+ 'right',
+ 'rlike',
+ 'row',
+ 'rows',
+ 'row_number',
+ 'schema',
+ 'schemas',
+ 'second_microsecond',
+ 'select',
+ 'sensitive',
+ 'separator',
+ 'set',
+ 'show',
+ 'signal',
+ 'smallint',
+ 'spatial',
+ 'specific',
+ 'sql',
+ 'sqlexception',
+ 'sqlstate',
+ 'sqlwarning',
+ 'sql_big_result',
+ 'sql_calc_found_rows',
+ 'sql_small_result',
+ 'ssl',
+ 'starting',
+ 'stored',
+ 'straight_join',
+ 'system',
+ 'table',
+ 'terminated',
+ 'then',
+ 'tinyblob',
+ 'tinyint',
+ 'tinytext',
+ 'to',
+ 'trailing',
+ 'trigger',
+ 'true',
+ 'undo',
+ 'union',
+ 'unique',
+ 'unlock',
+ 'unsigned',
+ 'update',
+ 'usage',
+ 'use',
+ 'using',
+ 'utc_date',
+ 'utc_time',
+ 'utc_timestamp',
+ 'values',
+ 'varbinary',
+ 'varchar',
+ 'varcharacter',
+ 'varying',
+ 'virtual',
+ 'when',
+ 'where',
+ 'while',
+ 'window',
+ 'with',
+ 'write',
+ 'xor',
+ 'year_month',
+ 'zerofill',
+ ];
+
+ /**
* Constructs a Connection object.
*/
public function __construct(\PDO $connection, array $connection_options = []) {
@@ -160,7 +431,8 @@ class Connection extends DatabaseConnection {
// Force MySQL to use the UTF-8 character set. Also set the collation, if a
// certain one has been set; otherwise, MySQL defaults to
- // 'utf8mb4_general_ci' for utf8mb4.
+ // 'utf8mb4_general_ci' (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) for
+ // utf8mb4.
if (!empty($connection_options['collation'])) {
$pdo->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
}
@@ -179,9 +451,18 @@ class Connection extends DatabaseConnection {
$connection_options += [
'init_commands' => [],
];
+
+ $sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY';
+ // NO_AUTO_CREATE_USER is removed in MySQL 8.0.11
+ // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html#mysqld-8-0-11-deprecation-removal
+ $version_server = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION);
+ if (version_compare($version_server, '8.0.11', '<')) {
+ $sql_mode .= ',NO_AUTO_CREATE_USER';
+ }
$connection_options['init_commands'] += [
- 'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,ONLY_FULL_GROUP_BY'",
+ 'sql_mode' => "SET sql_mode = '$sql_mode'",
];
+
// Execute initial commands.
foreach ($connection_options['init_commands'] as $sql) {
$pdo->exec($sql);
@@ -193,6 +474,49 @@ class Connection extends DatabaseConnection {
/**
* {@inheritdoc}
*/
+ public function escapeField($field) {
+ $field = parent::escapeField($field);
+ return $this->quoteIdentifier($field);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function escapeAlias($field) {
+ // Quote fields so that MySQL reserved words like 'function' can be used
+ // as aliases.
+ $field = parent::escapeAlias($field);
+ return $this->quoteIdentifier($field);
+ }
+
+ /**
+ * Quotes an identifier if it matches a MySQL reserved keyword.
+ *
+ * @param string $identifier
+ * The field to check.
+ *
+ * @return string
+ * The identifier, quoted if it matches a MySQL reserved keyword.
+ */
+ private function quoteIdentifier($identifier) {
+ // Quote identifiers so that MySQL reserved words like 'function' can be
+ // used as column names. Sometimes the 'table.column_name' format is passed
+ // in. For example,
+ // \Drupal\Core\Entity\Sql\SqlContentEntityStorage::buildQuery() adds a
+ // condition on "base.uid" while loading user entities.
+ if (strpos($identifier, '.') !== FALSE) {
+ list($table, $identifier) = explode('.', $identifier, 2);
+ }
+ if (in_array(strtolower($identifier), $this->reservedKeyWords, TRUE)) {
+ // Quote the string for MySQL reserved keywords.
+ $identifier = '"' . $identifier . '"';
+ }
+ return isset($table) ? $table . '.' . $identifier : $identifier;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function serialize() {
// Cleanup the connection, much like __destruct() does it as well.
if ($this->needsCleanup) {
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php b/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
index 8b7c602e..3d397c5 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
@@ -44,6 +44,10 @@ class Insert extends QueryInsert {
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
+ $insert_fields = array_map(function ($field) {
+ return $this->connection->escapeField($field);
+ }, $insert_fields);
+
// If we're selecting from a SelectQuery, finish building the query and
// pass it back, as any remaining options are irrelevant.
if (!empty($this->fromQuery)) {
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php
index 0b1d7dd..17fc9a3 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php
@@ -59,13 +59,13 @@ class Tasks extends InstallTasks {
protected function connect() {
try {
// This doesn't actually test the connection.
- db_set_active();
+ Database::setActiveConnection();
// Now actually do a check.
try {
Database::getConnection();
}
catch (\Exception $e) {
- // Detect utf8mb4 incompability.
+ // Detect utf8mb4 incompatibility.
if ($e->getCode() == Connection::UNSUPPORTED_CHARSET || ($e->getCode() == Connection::SQLSTATE_SYNTAX_ERROR && $e->errorInfo[1] == Connection::UNKNOWN_CHARSET)) {
$this->fail(t('Your MySQL server and PHP MySQL driver must support utf8mb4 character encoding. Make sure to use a database system that supports this (such as MySQL/MariaDB/Percona 5.5.3 and up), and that the utf8mb4 character set is compiled in. See the <a href=":documentation" target="_blank">MySQL documentation</a> for more information.', [':documentation' => 'https://dev.mysql.com/doc/refman/5.0/en/cannot-initialize-character-set.html']));
$info = Database::getConnectionInfo();
@@ -108,6 +108,16 @@ class Tasks extends InstallTasks {
// Now, attempt the connection again; if it's successful, attempt to
// create the database.
Database::getConnection()->createDatabase($database);
+ Database::closeConnection();
+
+ // Now, restore the database config.
+ Database::removeConnection('default');
+ $connection_info['default']['database'] = $database;
+ Database::addConnectionInfo('default', 'default', $connection_info['default']);
+
+ // Check the database connection.
+ Database::getConnection();
+ $this->pass('Drupal can CONNECT to the database ok.');
}
catch (DatabaseNotFoundException $e) {
// Still no dice; probably a permission issue. Raise the error to the
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
index 878607c..9766cfa 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
@@ -108,6 +108,9 @@ class Schema extends DatabaseSchema {
}
// Process keys & indexes.
+ if (!empty($table['primary key']) && is_array($table['primary key'])) {
+ $this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']);
+ }
$keys = $this->createKeysSql($table);
if (count($keys)) {
$sql .= implode(", \n", $keys) . ", \n";
@@ -118,8 +121,9 @@ class Schema extends DatabaseSchema {
$sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
// By default, MySQL uses the default collation for new tables, which is
- // 'utf8mb4_general_ci' for utf8mb4. If an alternate collation has been
- // set, it needs to be explicitly specified.
+ // 'utf8mb4_general_ci' (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) for
+ // utf8mb4. If an alternate collation has been set, it needs to be
+ // explicitly specified.
// @see \Drupal\Core\Database\Driver\mysql\Schema
if (!empty($info['collation'])) {
$sql .= ' COLLATE ' . $info['collation'];
@@ -151,12 +155,15 @@ class Schema extends DatabaseSchema {
if (isset($spec['length'])) {
$sql .= '(' . $spec['length'] . ')';
}
+ if (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
+ $sql .= ' CHARACTER SET ascii';
+ }
if (!empty($spec['binary'])) {
$sql .= ' BINARY';
}
// Note we check for the "type" key here. "mysql_type" is VARCHAR:
- if (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
- $sql .= ' CHARACTER SET ascii COLLATE ascii_general_ci';
+ elseif (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
+ $sql .= ' COLLATE ascii_general_ci';
}
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
@@ -212,7 +219,7 @@ class Schema extends DatabaseSchema {
// Set the correct database-engine specific datatype.
// In case one is already provided, force it to uppercase.
if (isset($field['mysql_type'])) {
- $field['mysql_type'] = Unicode::strtoupper($field['mysql_type']);
+ $field['mysql_type'] = mb_strtoupper($field['mysql_type']);
}
else {
$map = $this->getFieldTypeMap();
@@ -409,6 +416,9 @@ class Schema extends DatabaseSchema {
// Fields that are part of a PRIMARY KEY must be added as NOT NULL.
$is_primary_key = isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE);
+ if ($is_primary_key) {
+ $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field => $spec]);
+ }
$fixnull = FALSE;
if (!empty($spec['not null']) && !isset($spec['default']) && !$is_primary_key) {
@@ -428,14 +438,22 @@ class Schema extends DatabaseSchema {
$query .= ', ADD ' . implode(', ADD ', $keys_sql);
}
$this->connection->query($query);
- if (isset($spec['initial'])) {
+ if (isset($spec['initial_from_field'])) {
+ if (isset($spec['initial'])) {
+ $expression = 'COALESCE(' . $spec['initial_from_field'] . ', :default_initial_value)';
+ $arguments = [':default_initial_value' => $spec['initial']];
+ }
+ else {
+ $expression = $spec['initial_from_field'];
+ $arguments = [];
+ }
$this->connection->update($table)
- ->fields([$field => $spec['initial']])
+ ->expression($field, $expression, $arguments)
->execute();
}
- if (isset($spec['initial_from_field'])) {
+ elseif (isset($spec['initial'])) {
$this->connection->update($table)
- ->expression($field, $spec['initial_from_field'])
+ ->fields([$field => $spec['initial']])
->execute();
}
if ($fixnull) {
@@ -452,6 +470,18 @@ class Schema extends DatabaseSchema {
return FALSE;
}
+ // When dropping a field that is part of a composite primary key MySQL
+ // automatically removes the field from the primary key, which can leave the
+ // table in an invalid state. MariaDB 10.2.8 requires explicitly dropping
+ // the primary key first for this reason. We perform this deletion
+ // explicitly which also makes the behavior on both MySQL and MariaDB
+ // consistent with PostgreSQL.
+ // @see https://mariadb.com/kb/en/library/alter-table
+ $primary_key = $this->findPrimaryKeyColumns($table);
+ if ((count($primary_key) > 1) && in_array($field, $primary_key, TRUE)) {
+ $this->dropPrimaryKey($table);
+ }
+
$this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`');
return TRUE;
}
@@ -517,6 +547,17 @@ class Schema extends DatabaseSchema {
/**
* {@inheritdoc}
*/
+ protected function findPrimaryKeyColumns($table) {
+ if (!$this->tableExists($table)) {
+ return FALSE;
+ }
+ $result = $this->connection->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
+ return array_keys($result);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function addUniqueKey($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
@@ -579,6 +620,9 @@ class Schema extends DatabaseSchema {
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", ['@table' => $table, '@name' => $field, '@name_new' => $field_new]));
}
+ if (isset($keys_new['primary key']) && in_array($field_new, $keys_new['primary key'], TRUE)) {
+ $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field_new => $spec]);
+ }
$sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
if ($keys_sql = $this->createKeysSql($keys_new)) {
@@ -610,11 +654,11 @@ class Schema extends DatabaseSchema {
$condition->condition('column_name', $column);
$condition->compile($this->connection, $this);
// Don't use {} around information_schema.columns table.
- return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
+ return $this->connection->query("SELECT column_comment as column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
}
$condition->compile($this->connection, $this);
// Don't use {} around information_schema.tables table.
- $comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
+ $comment = $this->connection->query("SELECT table_comment as table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
// Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
return preg_replace('/; InnoDB free:.*$/', '', $comment);
}
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Upsert.php b/core/lib/Drupal/Core/Database/Driver/mysql/Upsert.php
index 6c1af1e..8eda775 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Upsert.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Upsert.php
@@ -18,6 +18,9 @@ class Upsert extends QueryUpsert {
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
+ $insert_fields = array_map(function ($field) {
+ return $this->connection->escapeField($field);
+ }, $insert_fields);
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
index e696f24..80b3089 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
@@ -38,7 +38,7 @@ class Connection extends DatabaseConnection {
/**
* A map of condition operators to PostgreSQL operators.
*
- * In PostgreSQL, 'LIKE' is case-sensitive. ILKE should be used for
+ * In PostgreSQL, 'LIKE' is case-sensitive. ILIKE should be used for
* case-insensitive statements.
*/
protected static $postgresqlConditionOperatorMap = [
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 70d9a33..e3312d2 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php
@@ -58,7 +58,7 @@ class Tasks extends InstallTasks {
protected function connect() {
try {
// This doesn't actually test the connection.
- db_set_active();
+ Database::setActiveConnection();
// Now actually do a check.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
index 9e5e1d9..8d01d5e 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
@@ -2,7 +2,6 @@
namespace Drupal\Core\Database\Driver\pgsql;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Core\Database\Schema as DatabaseSchema;
@@ -250,7 +249,8 @@ EOD;
}
$sql_keys = [];
- if (isset($table['primary key']) && is_array($table['primary key'])) {
+ if (!empty($table['primary key']) && is_array($table['primary key'])) {
+ $this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']);
$sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($name, '', 'pkey') . ' PRIMARY KEY (' . $this->createPrimaryKeySql($table['primary key']) . ')';
}
if (isset($table['unique keys']) && is_array($table['unique keys'])) {
@@ -350,7 +350,7 @@ EOD;
// Set the correct database-engine specific datatype.
// In case one is already provided, force it to lowercase.
if (isset($field['pgsql_type'])) {
- $field['pgsql_type'] = Unicode::strtolower($field['pgsql_type']);
+ $field['pgsql_type'] = mb_strtolower($field['pgsql_type']);
}
else {
$map = $this->getFieldTypeMap();
@@ -358,7 +358,7 @@ EOD;
}
if (!empty($field['unsigned'])) {
- // Unsigned datatypes are not supported in PostgreSQL 9.1. In MySQL,
+ // Unsigned data types are not supported in PostgreSQL 9.1. In MySQL,
// they are used to ensure a positive number is inserted and it also
// doubles the maximum integer size that can be stored in a field.
// The PostgreSQL schema in Drupal creates a check constraint
@@ -552,7 +552,10 @@ EOD;
}
// Fields that are part of a PRIMARY KEY must be added as NOT NULL.
- $is_primary_key = isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE);
+ $is_primary_key = isset($new_keys['primary key']) && in_array($field, $new_keys['primary key'], TRUE);
+ if ($is_primary_key) {
+ $this->ensureNotNullPrimaryKey($new_keys['primary key'], [$field => $spec]);
+ }
$fixnull = FALSE;
if (!empty($spec['not null']) && !isset($spec['default']) && !$is_primary_key) {
@@ -562,14 +565,22 @@ EOD;
$query = 'ALTER TABLE {' . $table . '} ADD COLUMN ';
$query .= $this->createFieldSql($field, $this->processField($spec));
$this->connection->query($query);
- if (isset($spec['initial'])) {
+ if (isset($spec['initial_from_field'])) {
+ if (isset($spec['initial'])) {
+ $expression = 'COALESCE(' . $spec['initial_from_field'] . ', :default_initial_value)';
+ $arguments = [':default_initial_value' => $spec['initial']];
+ }
+ else {
+ $expression = $spec['initial_from_field'];
+ $arguments = [];
+ }
$this->connection->update($table)
- ->fields([$field => $spec['initial']])
+ ->expression($field, $expression, $arguments)
->execute();
}
- if (isset($spec['initial_from_field'])) {
+ elseif (isset($spec['initial'])) {
$this->connection->update($table)
- ->expression($field, $spec['initial_from_field'])
+ ->fields([$field => $spec['initial']])
->execute();
}
if ($fixnull) {
@@ -631,6 +642,15 @@ EOD;
/**
* {@inheritdoc}
*/
+ public function fieldExists($table, $column) {
+ $prefixInfo = $this->getPrefixInfo($table);
+
+ return (bool) $this->connection->query("SELECT 1 FROM pg_attribute WHERE attrelid = :key::regclass AND attname = :column AND NOT attisdropped AND attnum > 0", [':key' => $prefixInfo['schema'] . '.' . $prefixInfo['table'], ':column' => $column])->fetchField();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function indexExists($table, $name) {
// Details http://www.postgresql.org/docs/9.1/interactive/view-pg-indexes.html
$index_name = $this->ensureIdentifiersLength($table, $name, 'idx');
@@ -702,6 +722,29 @@ EOD;
/**
* {@inheritdoc}
*/
+ protected function findPrimaryKeyColumns($table) {
+ if (!$this->tableExists($table)) {
+ return FALSE;
+ }
+
+ // Fetch the 'indkey' column from 'pg_index' to figure out the order of the
+ // primary key.
+ // @todo Use 'array_position()' to be able to perform the ordering in SQL
+ // directly when 9.5 is the minimum PostgreSQL version.
+ $result = $this->connection->query("SELECT a.attname, i.indkey FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '{" . $table . "}'::regclass AND i.indisprimary")->fetchAllKeyed();
+ if (!$result) {
+ return [];
+ }
+
+ $order = explode(' ', reset($result));
+ $columns = array_combine($order, array_keys($result));
+ ksort($columns);
+ return array_values($columns);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function addUniqueKey($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
@@ -765,6 +808,9 @@ EOD;
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", ['@table' => $table, '@name' => $field, '@name_new' => $field_new]));
}
+ if (isset($new_keys['primary key']) && in_array($field_new, $new_keys['primary key'], TRUE)) {
+ $this->ensureNotNullPrimaryKey($new_keys['primary key'], [$field_new => $spec]);
+ }
$spec = $this->processField($spec);
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
index a7c1496..ef0dd86 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
@@ -165,7 +165,6 @@ class Connection extends DatabaseConnection {
return $pdo;
}
-
/**
* Destructor for the SQLite connection.
*
@@ -436,4 +435,50 @@ class Connection extends DatabaseConnection {
return $prefix . $table;
}
+ /**
+ * {@inheritdoc}
+ */
+ public static function createConnectionOptionsFromUrl($url, $root) {
+ $database = parent::createConnectionOptionsFromUrl($url, $root);
+
+ // A SQLite database path with two leading slashes indicates a system path.
+ // Otherwise the path is relative to the Drupal root.
+ $url_components = parse_url($url);
+ if ($url_components['path'][0] === '/') {
+ $url_components['path'] = substr($url_components['path'], 1);
+ }
+ if ($url_components['path'][0] === '/') {
+ $database['database'] = $url_components['path'];
+ }
+ else {
+ $database['database'] = $root . '/' . $url_components['path'];
+ }
+
+ // User credentials and system port are irrelevant for SQLite.
+ unset(
+ $database['username'],
+ $database['password'],
+ $database['port']
+ );
+
+ return $database;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createUrlFromConnectionOptions(array $connection_options) {
+ if (!isset($connection_options['driver'], $connection_options['database'])) {
+ throw new \InvalidArgumentException("As a minimum, the connection options array must contain at least the 'driver' and 'database' keys");
+ }
+
+ $db_url = 'sqlite://localhost/' . $connection_options['database'];
+
+ if (isset($connection_options['prefix']['default']) && $connection_options['prefix']['default'] !== NULL && $connection_options['prefix']['default'] !== '') {
+ $db_url .= '#' . $connection_options['prefix']['default'];
+ }
+
+ return $db_url;
+ }
+
}
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 b0ea188..f5cb289 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
@@ -54,7 +54,7 @@ class Tasks extends InstallTasks {
protected function connect() {
try {
// This doesn't actually test the connection.
- db_set_active();
+ Database::setActiveConnection();
// Now actually do a check.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
index 27b6a58..0f0886c 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
@@ -2,7 +2,6 @@
namespace Drupal\Core\Database\Driver\sqlite;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Core\Database\Schema as DatabaseSchema;
@@ -19,6 +18,8 @@ class Schema extends DatabaseSchema {
/**
* Override DatabaseSchema::$defaultSchema
+ *
+ * @var string
*/
protected $defaultSchema = 'main';
@@ -51,6 +52,10 @@ class Schema extends DatabaseSchema {
* An array of SQL statements to create the table.
*/
public function createTableSql($name, $table) {
+ if (!empty($table['primary key']) && is_array($table['primary key'])) {
+ $this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']);
+ }
+
$sql = [];
$sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumnsSql($name, $table) . "\n)\n";
return array_merge($sql, $this->createIndexSql($name, $table));
@@ -129,7 +134,7 @@ class Schema extends DatabaseSchema {
// Set the correct database-engine specific datatype.
// In case one is already provided, force it to uppercase.
if (isset($field['sqlite_type'])) {
- $field['sqlite_type'] = Unicode::strtoupper($field['sqlite_type']);
+ $field['sqlite_type'] = mb_strtoupper($field['sqlite_type']);
}
else {
$map = $this->getFieldTypeMap();
@@ -314,6 +319,9 @@ class Schema extends DatabaseSchema {
if ($this->fieldExists($table, $field)) {
throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", ['@field' => $field, '@table' => $table]));
}
+ if (isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE)) {
+ $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field => $specification]);
+ }
// SQLite doesn't have a full-featured ALTER TABLE statement. It only
// supports adding new fields to a table, in some simple cases. In most
@@ -325,14 +333,22 @@ class Schema extends DatabaseSchema {
$this->connection->query($query);
// Apply the initial value if set.
- if (isset($specification['initial'])) {
+ if (isset($specification['initial_from_field'])) {
+ if (isset($specification['initial'])) {
+ $expression = 'COALESCE(' . $specification['initial_from_field'] . ', :default_initial_value)';
+ $arguments = [':default_initial_value' => $specification['initial']];
+ }
+ else {
+ $expression = $specification['initial_from_field'];
+ $arguments = [];
+ }
$this->connection->update($table)
- ->fields([$field => $specification['initial']])
+ ->expression($field, $expression, $arguments)
->execute();
}
- if (isset($specification['initial_from_field'])) {
+ elseif (isset($specification['initial'])) {
$this->connection->update($table)
- ->expression($field, $specification['initial_from_field'])
+ ->fields([$field => $specification['initial']])
->execute();
}
}
@@ -347,18 +363,26 @@ class Schema extends DatabaseSchema {
// Build the mapping between the old fields and the new fields.
$mapping = [];
- if (isset($specification['initial'])) {
+ if (isset($specification['initial_from_field'])) {
// If we have a initial value, copy it over.
+ if (isset($specification['initial'])) {
+ $expression = 'COALESCE(' . $specification['initial_from_field'] . ', :default_initial_value)';
+ $arguments = [':default_initial_value' => $specification['initial']];
+ }
+ else {
+ $expression = $specification['initial_from_field'];
+ $arguments = [];
+ }
$mapping[$field] = [
- 'expression' => ':newfieldinitial',
- 'arguments' => [':newfieldinitial' => $specification['initial']],
+ 'expression' => $expression,
+ 'arguments' => $arguments,
];
}
- elseif (isset($specification['initial_from_field'])) {
+ elseif (isset($specification['initial'])) {
// If we have a initial value, copy it over.
$mapping[$field] = [
- 'expression' => $specification['initial_from_field'],
- 'arguments' => [],
+ 'expression' => ':newfieldinitial',
+ 'arguments' => [':newfieldinitial' => $specification['initial']],
];
}
else {
@@ -411,7 +435,7 @@ class Schema extends DatabaseSchema {
// Now add the fields.
foreach ($mapping as $field_alias => $field_source) {
- // Just ignore this field (ie. use it's default value).
+ // Just ignore this field (ie. use its default value).
if (!isset($field_source)) {
continue;
}
@@ -448,7 +472,7 @@ class Schema extends DatabaseSchema {
* Name of the table.
*
* @return
- * An array representing the schema, from drupal_get_schema().
+ * An array representing the schema.
*
* @throws \Exception
* If a column of the table could not be parsed.
@@ -478,20 +502,27 @@ class Schema extends DatabaseSchema {
$schema['fields'][$row->name] = [
'type' => $type,
'size' => $size,
- 'not null' => !empty($row->notnull),
+ 'not null' => !empty($row->notnull) || $row->pk !== "0",
'default' => trim($row->dflt_value, "'"),
];
if ($length) {
$schema['fields'][$row->name]['length'] = $length;
}
+ // $row->pk contains a number that reflects the primary key order. We
+ // use that as the key and sort (by key) below to return the primary key
+ // in the same order that it is stored in.
if ($row->pk) {
- $schema['primary key'][] = $row->name;
+ $schema['primary key'][$row->pk] = $row->name;
}
}
else {
throw new \Exception("Unable to parse the column type " . $row->type);
}
}
+ ksort($schema['primary key']);
+ // Re-key the array because $row->pk starts counting at 1.
+ $schema['primary key'] = array_values($schema['primary key']);
+
$indexes = [];
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')');
foreach ($result as $row) {
@@ -527,9 +558,11 @@ class Schema extends DatabaseSchema {
unset($new_schema['fields'][$field]);
- // Handle possible primary key changes.
- if (isset($new_schema['primary key']) && ($key = array_search($field, $new_schema['primary key'])) !== FALSE) {
- unset($new_schema['primary key'][$key]);
+ // Drop the primary key if the field to drop is part of it. This is
+ // consistent with the behavior on PostgreSQL.
+ // @see \Drupal\Core\Database\Driver\mysql\Schema::dropField()
+ if (isset($new_schema['primary key']) && in_array($field, $new_schema['primary key'], TRUE)) {
+ unset($new_schema['primary key']);
}
// Handle possible index changes.
@@ -558,6 +591,9 @@ class Schema extends DatabaseSchema {
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", ['@table' => $table, '@name' => $field, '@name_new' => $field_new]));
}
+ if (isset($keys_new['primary key']) && in_array($field_new, $keys_new['primary key'], TRUE)) {
+ $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field_new => $spec]);
+ }
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
@@ -609,8 +645,10 @@ class Schema extends DatabaseSchema {
if (is_array($field)) {
$field = &$field[0];
}
- if (isset($mapping[$field])) {
- $field = $mapping[$field];
+
+ $mapped_field = array_search($field, $mapping, TRUE);
+ if ($mapped_field !== FALSE) {
+ $field = $mapped_field;
}
}
return $key_definition;
@@ -705,6 +743,7 @@ class Schema extends DatabaseSchema {
}
$new_schema['primary key'] = $fields;
+ $this->ensureNotNullPrimaryKey($new_schema['primary key'], $new_schema['fields']);
$this->alterTable($table, $old_schema, $new_schema);
}
@@ -727,6 +766,17 @@ class Schema extends DatabaseSchema {
/**
* {@inheritdoc}
*/
+ protected function findPrimaryKeyColumns($table) {
+ if (!$this->tableExists($table)) {
+ return FALSE;
+ }
+ $schema = $this->introspectSchema($table);
+ return $schema['primary key'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function fieldSetDefault($table, $field, $default) {
if (!$this->fieldExists($table, $field)) {
throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", ['@table' => $table, '@field' => $field]));
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Select.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Select.php
index 6d7caab..5eaa515 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Select.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Select.php
@@ -8,6 +8,7 @@ use Drupal\Core\Database\Query\Select as QuerySelect;
* SQLite implementation of \Drupal\Core\Database\Query\Select.
*/
class Select extends QuerySelect {
+
public function forUpdate($set = TRUE) {
// SQLite does not support FOR UPDATE so nothing to do.
return $this;
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php
index 5610d07..d9b422b 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php
@@ -27,7 +27,7 @@ class Statement extends StatementPrefetch implements StatementInterface {
* See http://bugs.php.net/bug.php?id=45259 for more details.
*/
protected function getStatement($query, &$args = []) {
- if (count($args)) {
+ if (is_array($args) && !empty($args)) {
// Check if $args is a simple numeric array.
if (range(0, count($args) - 1) === array_keys($args)) {
// In that case, we have unnamed placeholders.
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php
index c58ff7d..386912f 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php
@@ -11,6 +11,7 @@ use Drupal\Core\Database\Query\Truncate as QueryTruncate;
* exactly the effect (it is implemented by DROPing the table).
*/
class Truncate extends QueryTruncate {
+
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
diff --git a/core/lib/Drupal/Core/Database/Install/Tasks.php b/core/lib/Drupal/Core/Database/Install/Tasks.php
index a2ea41b..44bddea 100644
--- a/core/lib/Drupal/Core/Database/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Install/Tasks.php
@@ -156,7 +156,7 @@ abstract class Tasks {
protected function connect() {
try {
// This doesn't actually test the connection.
- db_set_active();
+ Database::setActiveConnection();
// Now actually do a check.
Database::getConnection();
$this->pass('Drupal can CONNECT to the database ok.');
diff --git a/core/lib/Drupal/Core/Database/Query/Condition.php b/core/lib/Drupal/Core/Database/Query/Condition.php
index 5169357..dd9a730 100644
--- a/core/lib/Drupal/Core/Database/Query/Condition.php
+++ b/core/lib/Drupal/Core/Database/Query/Condition.php
@@ -375,7 +375,8 @@ class Condition implements ConditionInterface, \Countable {
}
else {
// We need to upper case because PHP index matches are case sensitive but
- // do not need the more expensive Unicode::strtoupper() because SQL statements are ASCII.
+ // do not need the more expensive mb_strtoupper() because SQL statements
+ // are ASCII.
$operator = strtoupper($operator);
$return = isset(static::$conditionOperatorMap[$operator]) ? static::$conditionOperatorMap[$operator] : [];
}
diff --git a/core/lib/Drupal/Core/Database/Query/Merge.php b/core/lib/Drupal/Core/Database/Query/Merge.php
index 43188ec..6a4a5ca 100644
--- a/core/lib/Drupal/Core/Database/Query/Merge.php
+++ b/core/lib/Drupal/Core/Database/Query/Merge.php
@@ -174,7 +174,7 @@ class Merge extends Query implements ConditionInterface {
* Specifies fields to be updated as an expression.
*
* Expression fields are cases such as counter = counter + 1. This method
- * takes precedence over MergeQuery::updateFields() and it's wrappers,
+ * takes precedence over MergeQuery::updateFields() and its wrappers,
* MergeQuery::key() and MergeQuery::fields().
*
* @param $field
diff --git a/core/lib/Drupal/Core/Database/Query/Query.php b/core/lib/Drupal/Core/Database/Query/Query.php
index 94f71a5..46c8173 100644
--- a/core/lib/Drupal/Core/Database/Query/Query.php
+++ b/core/lib/Drupal/Core/Database/Query/Query.php
@@ -48,6 +48,8 @@ abstract class Query implements PlaceholderInterface {
/**
* The placeholder counter.
+ *
+ * @var int
*/
protected $nextPlaceholder = 0;
diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php
index ee4ed84..fe88f84 100644
--- a/core/lib/Drupal/Core/Database/Query/Select.php
+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -113,6 +113,8 @@ class Select extends Query implements SelectInterface {
/**
* The FOR UPDATE status
+ *
+ * @var bool
*/
protected $forUpdate = FALSE;
@@ -826,7 +828,7 @@ class Select extends Query implements SelectInterface {
$query .= implode(', ', $fields);
// FROM - We presume all queries have a FROM, as any query that doesn't won't need the query builder anyway.
- $query .= "\nFROM ";
+ $query .= "\nFROM";
foreach ($this->tables as $table) {
$query .= "\n";
if (isset($table['join type'])) {
@@ -912,6 +914,8 @@ class Select extends Query implements SelectInterface {
* {@inheritdoc}
*/
public function __clone() {
+ parent::__clone();
+
// On cloning, also clone the dependent objects. However, we do not
// want to clone the database connection object as that would duplicate the
// connection itself.
@@ -921,6 +925,11 @@ class Select extends Query implements SelectInterface {
foreach ($this->union as $key => $aggregate) {
$this->union[$key]['query'] = clone($aggregate['query']);
}
+ foreach ($this->tables as $alias => $table) {
+ if ($table['table'] instanceof SelectInterface) {
+ $this->tables[$alias]['table'] = clone $table['table'];
+ }
+ }
}
}
diff --git a/core/lib/Drupal/Core/Database/Query/SelectExtender.php b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
index 9082ca8..5a09b89 100644
--- a/core/lib/Drupal/Core/Database/Query/SelectExtender.php
+++ b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
@@ -30,6 +30,8 @@ class SelectExtender implements SelectInterface {
/**
* The placeholder counter.
+ *
+ * @var int
*/
protected $placeholder = 0;
diff --git a/core/lib/Drupal/Core/Database/Query/SelectInterface.php b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
index 59b15d0..e740b74 100644
--- a/core/lib/Drupal/Core/Database/Query/SelectInterface.php
+++ b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
@@ -95,7 +95,7 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
* Note that this method must be called by reference as well:
*
* @code
- * $fields =& $query->getTables();
+ * $tables =& $query->getTables();
* @endcode
*
* @return
@@ -346,6 +346,8 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
* db_query('A')->rightJoin('B') is identical to
* db_query('B')->leftJoin('A'). This functionality has been deprecated
* because SQLite does not support it.
+ *
+ * @see https://www.drupal.org/node/2765249
*/
public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = []);
@@ -402,10 +404,10 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
* on.
*
* Example:
- * <code>
+ * @code
* $query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'order_field');
* $query->orderBy('order_field', 'ASC');
- * </code>
+ * @endcode
* @param $direction
* The direction to sort. Legal values are "ASC" and "DESC". Any other value
* will be converted to "ASC".
diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php
index 80a68f6..4bd0a9b 100644
--- a/core/lib/Drupal/Core/Database/Schema.php
+++ b/core/lib/Drupal/Core/Database/Schema.php
@@ -19,6 +19,8 @@ abstract class Schema implements PlaceholderInterface {
/**
* The placeholder counter.
+ *
+ * @var int
*/
protected $placeholder = 0;
@@ -30,6 +32,8 @@ abstract class Schema implements PlaceholderInterface {
* method.
*
* @see DatabaseSchema::getPrefixInfo()
+ *
+ * @var string
*/
protected $defaultSchema = 'public';
@@ -195,7 +199,7 @@ abstract class Schema implements PlaceholderInterface {
// couldn't use db_select() here because it would prefix
// information_schema.tables and the query would fail.
// Don't use {} around information_schema.tables table.
- $results = $this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());
+ $results = $this->connection->query("SELECT table_name as table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());
foreach ($results as $table) {
// Take into account tables that have an individual prefix.
if (isset($individually_prefixed_tables[$table->table_name])) {
@@ -405,6 +409,26 @@ abstract class Schema implements PlaceholderInterface {
abstract public function dropPrimaryKey($table);
/**
+ * Finds the primary key columns of a table, from the database.
+ *
+ * @param string $table
+ * The name of the table.
+ *
+ * @return string[]|false
+ * A simple array with the names of the columns composing the table's
+ * primary key, or FALSE if the table does not exist.
+ *
+ * @throws \RuntimeException
+ * If the driver does not override this method.
+ */
+ protected function findPrimaryKeyColumns($table) {
+ if (!$this->tableExists($table)) {
+ return FALSE;
+ }
+ throw new \RuntimeException("The '" . $this->connection->driver() . "' database driver does not implement " . __METHOD__);
+ }
+
+ /**
* Add a unique key.
*
* @param $table
@@ -658,4 +682,25 @@ abstract class Schema implements PlaceholderInterface {
return is_string($value) ? $this->connection->quote($value) : $value;
}
+ /**
+ * Ensures that all the primary key fields are correctly defined.
+ *
+ * @param array $primary_key
+ * An array containing the fields that will form the primary key of a table.
+ * @param array $fields
+ * An array containing the field specifications of the table, as per the
+ * schema data structure format.
+ *
+ * @throws \Drupal\Core\Database\SchemaException
+ * Thrown if any primary key field specification does not exist or if they
+ * do not define 'not null' as TRUE.
+ */
+ protected function ensureNotNullPrimaryKey(array $primary_key, array $fields) {
+ foreach (array_intersect($primary_key, array_keys($fields)) as $field_name) {
+ if (!isset($fields[$field_name]['not null']) || $fields[$field_name]['not null'] !== TRUE) {
+ throw new SchemaException("The '$field_name' field specification does not define 'not null' as TRUE.");
+ }
+ }
+ }
+
}
diff --git a/core/lib/Drupal/Core/Database/StatementInterface.php b/core/lib/Drupal/Core/Database/StatementInterface.php
index a97043a..4f4248d 100644
--- a/core/lib/Drupal/Core/Database/StatementInterface.php
+++ b/core/lib/Drupal/Core/Database/StatementInterface.php
@@ -44,7 +44,7 @@ interface StatementInterface extends \Traversable {
*
* @param $args
* An array of values with as many elements as there are bound parameters in
- * the SQL statement being executed.
+ * the SQL statement being executed. This can be NULL.
* @param $options
* An array of options for this query.
*
diff --git a/core/lib/Drupal/Core/Database/StatementPrefetch.php b/core/lib/Drupal/Core/Database/StatementPrefetch.php
index 4e940ea..fb7b0b4 100644
--- a/core/lib/Drupal/Core/Database/StatementPrefetch.php
+++ b/core/lib/Drupal/Core/Database/StatementPrefetch.php
@@ -20,7 +20,7 @@ class StatementPrefetch implements \Iterator, StatementInterface {
/**
* Driver-specific options. Can be used by child classes.
*
- * @var Array
+ * @var array
*/
protected $driverOptions;
@@ -41,14 +41,14 @@ class StatementPrefetch implements \Iterator, StatementInterface {
/**
* Main data store.
*
- * @var Array
+ * @var array
*/
protected $data = [];
/**
* The current row, retrieved in \PDO::FETCH_ASSOC format.
*
- * @var Array
+ * @var array
*/
protected $currentRow = NULL;
@@ -62,7 +62,7 @@ class StatementPrefetch implements \Iterator, StatementInterface {
/**
* The list of column names in this result set.
*
- * @var Array
+ * @var array
*/
protected $columnNames = NULL;
@@ -91,7 +91,7 @@ class StatementPrefetch implements \Iterator, StatementInterface {
/**
* Holds supplementary current fetch options (which will be used by the next fetch).
*
- * @var Array
+ * @var array
*/
protected $fetchOptions = [
'class' => 'stdClass',
@@ -110,7 +110,7 @@ class StatementPrefetch implements \Iterator, StatementInterface {
/**
* Holds supplementary default fetch options.
*
- * @var Array
+ * @var array
*/
protected $defaultFetchOptions = [
'class' => 'stdClass',
@@ -214,8 +214,8 @@ class StatementPrefetch implements \Iterator, StatementInterface {
*
* @param $query
* The query.
- * @param array $args
- * An array of arguments.
+ * @param array|null $args
+ * An array of arguments. This can be NULL.
* @return \PDOStatement
* A PDOStatement object.
*/
diff --git a/core/lib/Drupal/Core/Database/database.api.php b/core/lib/Drupal/Core/Database/database.api.php
index 45a5618..4cf34f8 100644
--- a/core/lib/Drupal/Core/Database/database.api.php
+++ b/core/lib/Drupal/Core/Database/database.api.php
@@ -22,7 +22,7 @@ use Drupal\Core\Database\Query\Condition;
* practices.
*
* For more detailed information on the database abstraction layer, see
- * https://www.drupal.org/developing/api/database.
+ * https://www.drupal.org/docs/8/api/database-api/database-api-overview.
*
* @section sec_entity Querying entities
* Any query on Drupal entities or fields should use the Entity Query API. See
@@ -30,19 +30,20 @@ use Drupal\Core\Database\Query\Condition;
*
* @section sec_simple Simple SELECT database queries
* For simple SELECT queries that do not involve entities, the Drupal database
- * abstraction layer provides the functions db_query() and db_query_range(),
- * which execute SELECT queries (optionally with range limits) and return result
- * sets that you can iterate over using foreach loops. (The result sets are
- * objects implementing the \Drupal\Core\Database\StatementInterface interface.)
+ * abstraction layer provides the functions \Drupal::database()->query() and
+ * \Drupal::database()->queryRange(), which execute SELECT queries (optionally
+ * with range limits) and return result sets that you can iterate over using
+ * foreach loops. (The result sets are objects implementing the
+ * \Drupal\Core\Database\StatementInterface interface.)
* You can use the simple query functions for query strings that are not
* dynamic (except for placeholders, see below), and that you are certain will
* work in any database engine. See @ref sec_dynamic below if you have a more
* complex query, or a query whose syntax would be different in some databases.
*
- * As a note, db_query() and similar functions are wrappers on connection object
- * methods. In most classes, you should use dependency injection and the
- * database connection object instead of these wrappers; See @ref sec_connection
- * below for details.
+ * Note: \Drupal::database() is used here as a shorthand way to get a reference
+ * to the database connection object. In most classes, you should use dependency
+ * injection and inject the 'database' service to perform queries. See
+ * @ref sec_connection below for details.
*
* To use the simple database query functions, you will need to make a couple of
* modifications to your bare SQL query:
@@ -55,7 +56,8 @@ use Drupal\Core\Database\Query\Condition;
* putting variables directly into the query, to protect against SQL
* injection attacks.
* - LIMIT syntax differs between databases, so if you have a ranged query,
- * use db_query_range() instead of db_query().
+ * use \Drupal::database()->queryRange() instead of
+ * \Drupal::database()->query().
*
* For example, if the query you want to run is:
* @code
@@ -64,7 +66,7 @@ use Drupal\Core\Database\Query\Condition;
* @endcode
* you would do it like this:
* @code
- * $result = db_query_range('SELECT e.id, e.title, e.created
+ * $result = \Drupal::database()->queryRange('SELECT e.id, e.title, e.created
* FROM {example} e
* WHERE e.uid = :uid
* ORDER BY e.created DESC',
@@ -91,16 +93,11 @@ use Drupal\Core\Database\Query\Condition;
* fields (see the @link entity_api Entity API topic @endlink for more on
* entity queries).
*
- * As a note, db_select() and similar functions are wrappers on connection
- * object methods. In most classes, you should use dependency injection and the
- * database connection object instead of these wrappers; See @ref sec_connection
- * below for details.
- *
* The dynamic query API lets you build up a query dynamically using method
* calls. As an illustration, the query example from @ref sec_simple above
* would be:
* @code
- * $result = db_select('example', 'e')
+ * $result = \Drupal::database()->select('example', 'e')
* ->fields('e', array('id', 'title', 'created'))
* ->condition('e.uid', $uid)
* ->orderBy('e.created', 'DESC')
@@ -109,7 +106,7 @@ use Drupal\Core\Database\Query\Condition;
* @endcode
*
* There are also methods to join to other tables, add fields with aliases,
- * isNull() to have a @code WHERE e.foo IS NULL @endcode condition, etc. See
+ * isNull() to query for NULL values, etc. See
* https://www.drupal.org/developing/api/database for many more details.
*
* One note on chaining: It is common in the dynamic database API to chain
@@ -123,17 +120,19 @@ use Drupal\Core\Database\Query\Condition;
* returns the query or something else, and only chain methods that return the
* query.
*
- * @section_insert INSERT, UPDATE, and DELETE queries
+ * @section sec_insert INSERT, UPDATE, and DELETE queries
* INSERT, UPDATE, and DELETE queries need special care in order to behave
- * consistently across databases; you should never use db_query() to run
- * an INSERT, UPDATE, or DELETE query. Instead, use functions db_insert(),
- * db_update(), and db_delete() to obtain a base query on your table, and then
- * add dynamic conditions (as illustrated in @ref sec_dynamic above).
- *
- * As a note, db_insert() and similar functions are wrappers on connection
- * object methods. In most classes, you should use dependency injection and the
- * database connection object instead of these wrappers; See @ref sec_connection
- * below for details.
+ * consistently across databases; you should never use
+ * \Drupal::database()->query() to run an INSERT, UPDATE, or DELETE query.
+ * Instead, use functions \Drupal::database()->insert(),
+ * \Drupal::database()->update(), and \Drupal::database()->delete() to obtain
+ * a base query on your table, and then add dynamic conditions (as illustrated
+ * in @ref sec_dynamic above).
+ *
+ * Note: \Drupal::database() is used here as a shorthand way to get a reference
+ * to the database connection object. In most classes, you should use dependency
+ * injection and inject the 'database' service to perform queries. See
+ * @ref sec_connection below for details.
*
* For example, if your query is:
* @code
@@ -142,7 +141,7 @@ use Drupal\Core\Database\Query\Condition;
* You can execute it via:
* @code
* $fields = array('id' => 1, 'uid' => 2, 'path' => 'path', 'name' => 'Name');
- * db_insert('example')
+ * \Drupal::database()->insert('example')
* ->fields($fields)
* ->execute();
* @endcode
@@ -150,21 +149,26 @@ use Drupal\Core\Database\Query\Condition;
* @section sec_transaction Transactions
* Drupal supports transactions, including a transparent fallback for
* databases that do not support transactions. To start a new transaction,
- * call @code $txn = db_transaction(); @endcode The transaction will
- * remain open for as long as the variable $txn remains in scope; when $txn is
- * destroyed, the transaction will be committed. If your transaction is nested
- * inside of another then Drupal will track each transaction and only commit
- * the outer-most transaction when the last transaction object goes out out of
- * scope (when all relevant queries have completed successfully).
+ * call startTransaction(), like this:
+ * @code
+ * $transaction = \Drupal::database()->startTransaction();
+ * @endcode
+ * The transaction will remain open for as long as the variable $transaction
+ * remains in scope; when $transaction is destroyed, the transaction will be
+ * committed. If your transaction is nested inside of another then Drupal will
+ * track each transaction and only commit the outer-most transaction when the
+ * last transaction object goes out out of scope (when all relevant queries have
+ * completed successfully).
*
* Example:
* @code
* function my_transaction_function() {
+ * $connection = \Drupal::database();
* // The transaction opens here.
- * $txn = db_transaction();
+ * $transaction = $connection->startTransaction();
*
* try {
- * $id = db_insert('example')
+ * $id = $connection->insert('example')
* ->fields(array(
* 'field1' => 'mystring',
* 'field2' => 5,
@@ -177,20 +181,21 @@ use Drupal\Core\Database\Query\Condition;
* }
* catch (Exception $e) {
* // Something went wrong somewhere, so roll back now.
- * $txn->rollBack();
+ * $transaction->rollBack();
* // Log the exception to watchdog.
* watchdog_exception('type', $e);
* }
*
- * // $txn goes out of scope here. Unless the transaction was rolled back, it
- * // gets automatically committed here.
+ * // $transaction goes out of scope here. Unless the transaction was rolled
+ * // back, it gets automatically committed here.
* }
*
* function my_other_function($id) {
+ * $connection = \Drupal::database();
* // The transaction is still open here.
*
* if ($id % 2 == 0) {
- * db_update('example')
+ * $connection->update('example')
* ->condition('id', $id)
* ->fields(array('field2' => 10))
* ->execute();
@@ -199,18 +204,19 @@ use Drupal\Core\Database\Query\Condition;
* @endcode
*
* @section sec_connection Database connection objects
- * The examples here all use functions like db_select() and db_query(), which
- * can be called from any Drupal method or function code. In some classes, you
- * may already have a database connection object in a member variable, or it may
- * be passed into a class constructor via dependency injection. If that is the
- * case, you can look at the code for db_select() and the other functions to see
- * how to get a query object from your connection variable. For example:
+ * The examples here all use functions like \Drupal::database()->select() and
+ * \Drupal::database()->query(), which can be called from any Drupal method or
+ * function code. In some classes, you may already have a database connection
+ * object in a member variable, or it may be passed into a class constructor
+ * via dependency injection. If that is the case, you can look at the code for
+ * \Drupal::database()->select() and the other functions to see how to get a
+ * query object from your connection variable. For example:
* @code
* $query = $connection->select('example', 'e');
* @endcode
* would be the equivalent of
* @code
- * $query = db_select('example', 'e');
+ * $query = \Drupal::database()->select('example', 'e');
* @endcode
* if you had a connection object variable $connection available to use. See
* also the @link container Services and Dependency Injection topic. @endlink
@@ -256,7 +262,7 @@ use Drupal\Core\Database\Query\Condition;
* recent) {node_field_revision}.vid value for this nid."
* - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int',
* 'float', 'numeric', or 'serial'. Most types just map to the according
- * database engine specific datatypes. Use 'serial' for auto incrementing
+ * database engine specific data types. Use 'serial' for auto incrementing
* fields. This will expand to 'INT auto_increment' on MySQL.
* A special 'varchar_ascii' type is also available for limiting machine
* name field to US ASCII characters.
@@ -272,7 +278,7 @@ use Drupal\Core\Database\Query\Condition;
* - 'size': The data size: 'tiny', 'small', 'medium', 'normal',
* 'big'. This is a hint about the largest value the field will
* store and determines which of the database engine specific
- * datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT).
+ * data types will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT).
* 'normal', the default, selects the base type (e.g. on MySQL,
* INT, VARCHAR, BLOB, etc.).
* Not all sizes are available for all data types. See
diff --git a/core/lib/Drupal/Core/Datetime/DateHelper.php b/core/lib/Drupal/Core/Datetime/DateHelper.php
index b3a8ce7..168ef62 100644
--- a/core/lib/Drupal/Core/Datetime/DateHelper.php
+++ b/core/lib/Drupal/Core/Datetime/DateHelper.php
@@ -334,7 +334,6 @@ class DateHelper {
return !$required ? $none + $range : $range;
}
-
/**
* Constructs an array of hours.
*
diff --git a/core/lib/Drupal/Core/Datetime/DrupalDateTime.php b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
index bb7b821..f921b45 100644
--- a/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
+++ b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
@@ -38,7 +38,7 @@ class DrupalDateTime extends DateTimePlus {
* timezone are ignored when the $time parameter either is a UNIX timestamp
* (e.g. @946684800) or specifies a timezone
* (e.g. 2010-01-28T15:00:00+02:00).
- * @see http://php.net/manual/en/datetime.construct.php
+ * @see http://php.net/manual/datetime.construct.php
* @param array $settings
* - validate_format: (optional) Boolean choice to validate the
* created date using the input format. The format used in
diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
index e238ff5..bfdaa40 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datetime.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
@@ -70,8 +70,8 @@ class Datetime extends DateElementBase {
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input !== FALSE) {
- $date_input = $element['#date_date_element'] != 'none' && !empty($input['date']) ? $input['date'] : '';
- $time_input = $element['#date_time_element'] != 'none' && !empty($input['time']) ? $input['time'] : '';
+ $date_input = $element['#date_date_element'] != 'none' && !empty($input['date']) ? $input['date'] : '';
+ $time_input = $element['#date_time_element'] != 'none' && !empty($input['time']) ? $input['time'] : '';
$date_format = $element['#date_date_element'] != 'none' ? static::getHtml5DateFormat($element) : '';
$time_format = $element['#date_time_element'] != 'none' ? static::getHtml5TimeFormat($element) : '';
$timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL;
@@ -181,9 +181,13 @@ class Datetime extends DateElementBase {
* dynamic value that is that many years earlier or later than the current
* year at the time the form is displayed. Used in jQueryUI datepicker year
* range and HTML5 min/max date settings. Defaults to '1900:2050'.
- * - #date_increment: The increment to use for minutes and seconds, i.e.
- * '15' would show only :00, :15, :30 and :45. Used for HTML5 step values and
- * jQueryUI datepicker settings. Defaults to 1 to show every minute.
+ * - #date_increment: The interval (step) to use when incrementing or
+ * decrementing time, in seconds. For example, if this value is set to 30,
+ * time increases (or decreases) in steps of 30 seconds (00:00:00,
+ * 00:00:30, 00:01:00, and so on.) If this value is a multiple of 60, the
+ * "seconds"-component will not be shown in the input. Used for HTML5 step
+ * values and jQueryUI datepicker settings. Defaults to 1 to show every
+ * second.
* - #date_timezone: The local timezone to use when creating dates. Generally
* this should be left empty and it will be set correctly for the user using
* the form. Useful if the default value is empty to designate a desired
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php
index 207e094..3e0344a 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/CorsCompilerPass.php
@@ -22,7 +22,7 @@ class CorsCompilerPass implements CompilerPassInterface {
$enabled = !empty($cors_config['enabled']);
}
- // Remove the CORS middleware completly in case it was not enabled.
+ // Remove the CORS middleware completely in case it was not enabled.
if (!$enabled) {
$container->removeDefinition('http_middleware.cors');
}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php
index 73ee17b..0f2bdb0 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php
@@ -9,8 +9,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Adds the twig_extension_hash parameter to the container.
*
- * twig_extension_hash is a hash of all extension mtimes for Twig template
- * invalidation.
+ * Parameter twig_extension_hash is a hash of all extension mtimes for Twig
+ * template invalidation.
*/
class TwigExtensionPass implements CompilerPassInterface {
diff --git a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
index e0becc7..9f51078 100644
--- a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
+++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
@@ -27,8 +27,8 @@ class ContainerBuilder extends SymfonyContainerBuilder {
* {@inheritdoc}
*/
public function __construct(ParameterBagInterface $parameterBag = NULL) {
- $this->setResourceTracking(FALSE);
parent::__construct($parameterBag);
+ $this->setResourceTracking(FALSE);
}
/**
@@ -46,9 +46,12 @@ class ContainerBuilder extends SymfonyContainerBuilder {
}
/**
- * {@inheritdoc}
+ * A 1to1 copy of parent::shareService.
+ *
+ * @todo https://www.drupal.org/project/drupal/issues/2937010 Since Symfony
+ * 3.4 this is not a 1to1 copy.
*/
- protected function shareService(Definition $definition, $service, $id)
+ protected function shareService(Definition $definition, $service, $id, array &$inlineServices)
{
if ($definition->isShared()) {
$this->services[$lowerId = strtolower($id)] = $service;
@@ -91,6 +94,32 @@ class ContainerBuilder extends SymfonyContainerBuilder {
/**
* {@inheritdoc}
*/
+ public function setAlias($alias, $id) {
+ $alias = parent::setAlias($alias, $id);
+ // As of Symfony 3.4 all aliases are private by default.
+ $alias->setPublic(TRUE);
+ return $alias;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDefinition($id, Definition $definition) {
+ $definition = parent::setDefinition($id, $definition);
+ // As of Symfony 3.4 all definitions are private by default.
+ // \Symfony\Component\DependencyInjection\Compiler\ResolvePrivatesPassOnly
+ // removes services marked as private from the container even if they are
+ // also marked as public. Drupal requires services that are public to
+ // remain in the container and not be removed.
+ if ($definition->isPublic()) {
+ $definition->setPrivate(FALSE);
+ }
+ return $definition;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function setParameter($name, $value) {
if (strtolower($name) !== $name) {
throw new \InvalidArgumentException("Parameter names must be lowercase: $name");
@@ -100,8 +129,11 @@ class ContainerBuilder extends SymfonyContainerBuilder {
/**
* A 1to1 copy of parent::callMethod.
+ *
+ * @todo https://www.drupal.org/project/drupal/issues/2937010 Since Symfony
+ * 3.4 this is not a 1to1 copy.
*/
- protected function callMethod($service, $call) {
+ protected function callMethod($service, $call, array &$inlineServices = array()) {
$services = self::getServiceConditionals($call[1]);
foreach ($services as $s) {
diff --git a/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
index 37e7f82..b151a3b 100644
--- a/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
+++ b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\DependencyInjection;
+use Drupal\Core\Entity\EntityStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -17,6 +18,13 @@ trait DependencySerializationTrait {
protected $_serviceIds = [];
/**
+ * An array of entity type IDs keyed by the property name of their storages.
+ *
+ * @var array
+ */
+ protected $_entityStorages = [];
+
+ /**
* {@inheritdoc}
*/
public function __sleep() {
@@ -35,6 +43,17 @@ trait DependencySerializationTrait {
$this->_serviceIds[$key] = 'service_container';
unset($vars[$key]);
}
+ elseif ($value instanceof EntityStorageInterface) {
+ // If a class member is an entity storage, only store the entity type ID
+ // the storage is for so it can be used to get a fresh object on
+ // unserialization. By doing this we prevent possible memory leaks when
+ // the storage is serialized when it contains a static cache of entity
+ // objects and additionally we ensure that we'll not have multiple
+ // storage objects for the same entity type and therefore prevent
+ // returning different references for the same entity.
+ $this->_entityStorages[$key] = $value->getEntityTypeId();
+ unset($vars[$key]);
+ }
}
return array_keys($vars);
@@ -61,6 +80,19 @@ trait DependencySerializationTrait {
$this->$key = $container->get($service_id);
}
$this->_serviceIds = [];
+
+ // In rare cases, when test data is serialized in the parent process, there
+ // is a service container but it doesn't contain all expected services. To
+ // avoid fatal errors during the wrap-up of failing tests, we check for this
+ // case, too.
+ if ($this->_entityStorages && (!$phpunit_bootstrap || $container->has('entity_type.manager'))) {
+ /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
+ $entity_type_manager = $container->get('entity_type.manager');
+ foreach ($this->_entityStorages as $key => $entity_type_id) {
+ $this->$key = $entity_type_manager->getStorage($entity_type_id);
+ }
+ }
+ $this->_entityStorages = [];
}
}
diff --git a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php
index d999450..e6741fc 100644
--- a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php
+++ b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php
@@ -8,7 +8,7 @@ use Drupal\Core\Serialization\Yaml;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
-use Symfony\Component\DependencyInjection\DefinitionDecorator;
+use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -155,7 +155,7 @@ class YamlFileLoader
}
if (isset($service['parent'])) {
- $definition = new DefinitionDecorator($service['parent']);
+ $definition = new ChildDefinition($service['parent']);
} else {
$definition = new Definition();
}
diff --git a/core/lib/Drupal/Core/Diff/DiffFormatter.php b/core/lib/Drupal/Core/Diff/DiffFormatter.php
index 1b3604d..52f5550 100644
--- a/core/lib/Drupal/Core/Diff/DiffFormatter.php
+++ b/core/lib/Drupal/Core/Diff/DiffFormatter.php
@@ -57,7 +57,7 @@ class DiffFormatter extends DiffFormatterBase {
[
'data' => $ybeg + $this->line_stats['offset']['y'],
'colspan' => 2,
- ]
+ ],
];
}
@@ -94,7 +94,7 @@ class DiffFormatter extends DiffFormatterBase {
[
'data' => ['#markup' => $line],
'class' => 'diff-context diff-addedline',
- ]
+ ],
];
}
@@ -116,7 +116,7 @@ class DiffFormatter extends DiffFormatterBase {
[
'data' => ['#markup' => $line],
'class' => 'diff-context diff-deletedline',
- ]
+ ],
];
}
@@ -135,7 +135,7 @@ class DiffFormatter extends DiffFormatterBase {
[
'data' => ['#markup' => $line],
'class' => 'diff-context',
- ]
+ ],
];
}
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 217d4dc..a099327 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -5,7 +5,6 @@ namespace Drupal\Core;
use Composer\Autoload\ClassLoader;
use Drupal\Component\Assertion\Handle;
use Drupal\Component\FileCache\FileCacheFactory;
-use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\DatabaseBackend;
use Drupal\Core\Config\BootstrapConfigStorageFactory;
@@ -20,6 +19,7 @@ use Drupal\Core\File\MimeType\MimeTypeGuesser;
use Drupal\Core\Http\TrustedHostsRequestFactory;
use Drupal\Core\Installer\InstallerRedirectTrait;
use Drupal\Core\Language\Language;
+use Drupal\Core\Security\RequestSanitizer;
use Drupal\Core\Site\Settings;
use Drupal\Core\Test\TestDatabase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -298,12 +298,16 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
}
/**
- * Determine the application root directory based on assumptions.
+ * Determine the application root directory based on this file's location.
*
* @return string
* The application root.
*/
protected static function guessApplicationRoot() {
+ // Determine the application root by:
+ // - Removing the namespace directories from the path.
+ // - Getting the path to the directory two levels up from the path
+ // determined in the previous step.
return dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
}
@@ -317,6 +321,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* for bootstrap level configuration, file configuration stores, public file
* storage and site specific modules and themes.
*
+ * A file named sites.php must be present in the sites directory for
+ * multisite. If it doesn't exist, then 'sites/default' will be used.
+ *
* Finds a matching site directory file by stripping the website's hostname
* from left to right and pathname from right to left. By default, the
* directory must contain a 'settings.php' file for it to match. If the
@@ -327,9 +334,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* default.settings.php for examples on how the URL is converted to a
* directory.
*
- * If a file named sites.php is present in the sites directory, it will be
- * loaded prior to scanning for directories. That file can define aliases in
- * an associative array named $sites. The array is written in the format
+ * The sites.php file in the sites directory can define aliases in an
+ * associative array named $sites. The array is written in the format
* '<port>.<domain>.<path>' => 'directory'. As an example, to create a
* directory alias for https://www.drupal.org:8080/mysite/test whose
* configuration file is in sites/example.com, the array should be defined as:
@@ -542,6 +548,12 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* {@inheritdoc}
*/
public function preHandle(Request $request) {
+ // Sanitize the request.
+ $request = RequestSanitizer::sanitize(
+ $request,
+ (array) Settings::get(RequestSanitizer::SANITIZE_WHITELIST, []),
+ (bool) Settings::get(RequestSanitizer::SANITIZE_LOG, FALSE)
+ );
$this->loadLegacyIncludes();
@@ -676,13 +688,13 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
*
* @param \Exception $e
* An exception
- * @param Request $request
+ * @param \Symfony\Component\HttpFoundation\Request $request
* A Request instance
* @param int $type
* The type of the request (one of HttpKernelInterface::MASTER_REQUEST or
* HttpKernelInterface::SUB_REQUEST)
*
- * @return Response
+ * @return \Symfony\Component\HttpFoundation\Response
* A Response instance
*
* @throws \Exception
@@ -974,23 +986,26 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
// sites/default/default.settings.php contains more runtime settings.
// The .htaccess file contains settings that cannot be changed at runtime.
- // Use session cookies, not transparent sessions that puts the session id in
- // the query string.
- ini_set('session.use_cookies', '1');
- ini_set('session.use_only_cookies', '1');
- ini_set('session.use_trans_sid', '0');
- // Don't send HTTP headers using PHP's session handler.
- // Send an empty string to disable the cache limiter.
- ini_set('session.cache_limiter', '');
- // Use httponly session cookies.
- ini_set('session.cookie_httponly', '1');
+ if (PHP_SAPI !== 'cli') {
+ // Use session cookies, not transparent sessions that puts the session id
+ // in the query string.
+ ini_set('session.use_cookies', '1');
+ ini_set('session.use_only_cookies', '1');
+ ini_set('session.use_trans_sid', '0');
+ // Don't send HTTP headers using PHP's session handler.
+ // Send an empty string to disable the cache limiter.
+ ini_set('session.cache_limiter', '');
+ // Use httponly session cookies.
+ ini_set('session.cookie_httponly', '1');
+ }
// Set sane locale settings, to ensure consistent string, dates, times and
// numbers handling.
setlocale(LC_ALL, 'C');
- // Detect string handling method.
- Unicode::check();
+ // Set appropriate configuration for multi-byte strings.
+ mb_internal_encoding('utf-8');
+ mb_language('uni');
// Indicate that code is operating in a test child site.
if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) {
@@ -1080,7 +1095,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
// misses.
$old_loader = $this->classLoader;
$this->classLoader = $loader;
- // Our class loaders are preprended to ensure they come first like the
+ // Our class loaders are prepended to ensure they come first like the
// class loader they are replacing.
$old_loader->register(TRUE);
$loader->register(TRUE);
@@ -1187,10 +1202,10 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
/**
* Attach synthetic values on to kernel.
*
- * @param ContainerInterface $container
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* Container object
*
- * @return ContainerInterface
+ * @return \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected function attachSynthetic(ContainerInterface $container) {
$persist = [];
@@ -1213,7 +1228,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
/**
* Compiles a new service container.
*
- * @return ContainerBuilder The compiled service container
+ * @return \Drupal\Core\DependencyInjection\ContainerBuilder The compiled service container
*/
protected function compileContainer() {
// We are forcing a container build so it is reasonable to assume that the
@@ -1334,7 +1349,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
/**
* Gets a new ContainerBuilder instance used to build the service container.
*
- * @return ContainerBuilder
+ * @return \Drupal\Core\DependencyInjection\ContainerBuilder
*/
protected function getContainerBuilder() {
return new ContainerBuilder(new ParameterBag($this->getKernelParameters()));
@@ -1593,13 +1608,14 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
*/
protected function getInstallProfile() {
$config = $this->getConfigStorage()->read('core.extension');
- if (!empty($config['profile'])) {
+ if (isset($config['profile'])) {
$install_profile = $config['profile'];
}
// @todo https://www.drupal.org/node/2831065 remove the BC layer.
else {
// If system_update_8300() has not yet run fallback to using settings.
- $install_profile = Settings::get('install_profile');
+ $settings = Settings::getAll();
+ $install_profile = isset($settings['install_profile']) ? $settings['install_profile'] : NULL;
}
// Normalize an empty string to a NULL value.
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php b/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php
index 96bc6b1..88f86db 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php
@@ -63,7 +63,7 @@ class EntityReferenceSelection extends Plugin {
public $entity_types = [];
/**
- * The weight of the plugin in it's group.
+ * The weight of the plugin in its group.
*
* @var int
*/
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index 99335ae..9d14bed 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -33,6 +33,8 @@ class EntityType extends Plugin {
/**
* The group machine name.
+ *
+ * @var string
*/
public $group = 'default';
diff --git a/core/lib/Drupal/Core/Entity/BundleEntityFormBase.php b/core/lib/Drupal/Core/Entity/BundleEntityFormBase.php
index f51dd36..aa51cbc 100644
--- a/core/lib/Drupal/Core/Entity/BundleEntityFormBase.php
+++ b/core/lib/Drupal/Core/Entity/BundleEntityFormBase.php
@@ -22,7 +22,7 @@ class BundleEntityFormBase extends EntityForm {
protected function protectBundleIdElement(array $form) {
$entity = $this->getEntity();
$id_key = $entity->getEntityType()->getKey('id');
- assert('isset($form[$id_key])');
+ assert(isset($form[$id_key]));
$element = &$form[$id_key];
// Make sure the element is not accidentally re-enabled if it has already
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 2c62589..2bf1372 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -2,10 +2,9 @@
namespace Drupal\Core\Entity;
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Entity\Plugin\DataType\EntityReference;
use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\Field\ChangedFieldItemList;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
@@ -20,6 +19,10 @@ use Drupal\Core\TypedData\TypedDataInterface;
*/
abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface {
+ use EntityChangesDetectionTrait {
+ getFieldsToSkipFromTranslationChangesCheck as traitGetFieldsToSkipFromTranslationChangesCheck;
+ }
+
/**
* The plain data values of the contained fields.
*
@@ -157,6 +160,29 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
protected $loadedRevisionId;
/**
+ * The revision translation affected entity key.
+ *
+ * @var string
+ */
+ protected $revisionTranslationAffectedKey;
+
+ /**
+ * Whether the revision translation affected flag has been enforced.
+ *
+ * An array, keyed by the translation language code.
+ *
+ * @var bool[]
+ */
+ protected $enforceRevisionTranslationAffected = [];
+
+ /**
+ * Local cache for fields to skip from the checking for translation changes.
+ *
+ * @var array
+ */
+ protected static $fieldsToSkipFromTranslationChangesCheck = [];
+
+ /**
* {@inheritdoc}
*/
public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = []) {
@@ -164,6 +190,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$this->entityKeys['bundle'] = $bundle ? $bundle : $this->entityTypeId;
$this->langcodeKey = $this->getEntityType()->getKey('langcode');
$this->defaultLangcodeKey = $this->getEntityType()->getKey('default_langcode');
+ $this->revisionTranslationAffectedKey = $this->getEntityType()->getKey('revision_translation_affected');
foreach ($values as $key => $value) {
// If the key matches an existing property set the value to the property
@@ -269,15 +296,11 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// When saving a new revision, set any existing revision ID to NULL so as
// to ensure that a new revision will actually be created.
$this->set($this->getEntityType()->getKey('revision'), NULL);
-
- // Make sure that the flag tracking which translations are affected by the
- // current revision is reset.
- foreach ($this->translations as $langcode => $data) {
- // But skip removed translations.
- if ($this->hasTranslation($langcode)) {
- $this->getTranslation($langcode)->setRevisionTranslationAffected(NULL);
- }
- }
+ }
+ elseif (!$value && $this->newRevision) {
+ // If ::setNewRevision(FALSE) is called after ::setNewRevision(TRUE) we
+ // have to restore the loaded revision ID.
+ $this->set($this->getEntityType()->getKey('revision'), $this->getLoadedRevisionId());
}
$this->newRevision = $value;
@@ -321,18 +344,51 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
/**
* {@inheritdoc}
*/
+ public function wasDefaultRevision() {
+ /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
+ $entity_type = $this->getEntityType();
+ if (!$entity_type->isRevisionable()) {
+ return TRUE;
+ }
+
+ $revision_default_key = $entity_type->getRevisionMetadataKey('revision_default');
+ $value = $this->isNew() || $this->get($revision_default_key)->value;
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isLatestRevision() {
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+ $storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId());
+
+ return $this->getLoadedRevisionId() == $storage->getLatestRevisionId($this->id());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isLatestTranslationAffectedRevision() {
+ /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+ $storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId());
+
+ return $this->getLoadedRevisionId() == $storage->getLatestTranslationAffectedRevisionId($this->id(), $this->language()->getId());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function isRevisionTranslationAffected() {
- $field_name = $this->getEntityType()->getKey('revision_translation_affected');
- return $this->hasField($field_name) ? $this->get($field_name)->value : TRUE;
+ return $this->hasField($this->revisionTranslationAffectedKey) ? $this->get($this->revisionTranslationAffectedKey)->value : TRUE;
}
/**
* {@inheritdoc}
*/
public function setRevisionTranslationAffected($affected) {
- $field_name = $this->getEntityType()->getKey('revision_translation_affected');
- if ($this->hasField($field_name)) {
- $this->set($field_name, $affected);
+ if ($this->hasField($this->revisionTranslationAffectedKey)) {
+ $this->set($this->revisionTranslationAffectedKey, $affected);
}
return $this;
}
@@ -340,6 +396,21 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
/**
* {@inheritdoc}
*/
+ public function isRevisionTranslationAffectedEnforced() {
+ return !empty($this->enforceRevisionTranslationAffected[$this->activeLangcode]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setRevisionTranslationAffectedEnforced($enforced) {
+ $this->enforceRevisionTranslationAffected[$this->activeLangcode] = $enforced;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function isDefaultTranslation() {
return $this->activeLangcode === LanguageInterface::LANGCODE_DEFAULT;
}
@@ -355,8 +426,8 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
* {@inheritdoc}
*/
public function isTranslatable() {
- // Check that the bundle is translatable, the entity has a language defined
- // and if we have more than one language on the site.
+ // Check the bundle is translatable, the entity has a language defined, and
+ // the site has more than one language.
$bundles = $this->entityManager()->getBundleInfo($this->entityTypeId);
return !empty($bundles[$this->bundle()]['translatable']) && !$this->getUntranslated()->language()->isLocked() && $this->languageManager()->isMultilingual();
}
@@ -401,6 +472,12 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
}
}
$this->translations = array_diff_key($this->translations, $removed);
+
+ // Reset the new revision flag.
+ $this->newRevision = FALSE;
+
+ // Reset the enforcement of the revision translation affected flag.
+ $this->enforceRevisionTranslationAffected = [];
}
/**
@@ -701,11 +778,11 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
* {@inheritdoc}
*/
public function onChange($name) {
- // Check if the changed name is the value of an entity key and if the value
- // of that is currently cached, if so, reset it. Exclude the bundle from
- // that check, as it ready only and must not change, unsetting it could
+ // Check if the changed name is the value of any entity keys and if any of
+ // those values are currently cached, if so, reset it. Exclude the bundle
+ // from that check, as it ready only and must not change, unsetting it could
// lead to recursions.
- if ($key = array_search($name, $this->getEntityType()->getKeys())) {
+ foreach (array_keys($this->getEntityType()->getKeys(), $name, TRUE) as $key) {
if ($key != 'bundle') {
if (isset($this->entityKeys[$key])) {
unset($this->entityKeys[$key]);
@@ -713,6 +790,12 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
elseif (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) {
unset($this->translatableEntityKeys[$key][$this->activeLangcode]);
}
+ // If the revision identifier field is being populated with the original
+ // value, we need to make sure the "new revision" flag is reset
+ // accordingly.
+ if ($key === 'revision' && $this->getRevisionId() == $this->getLoadedRevisionId() && !$this->isNew()) {
+ $this->newRevision = FALSE;
+ }
}
}
@@ -722,7 +805,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// Update the default internal language cache.
$this->setDefaultLangcode();
if (isset($this->translations[$this->defaultLangcode])) {
- $message = SafeMarkup::format('A translation already exists for the specified language (@langcode).', ['@langcode' => $this->defaultLangcode]);
+ $message = new FormattableMarkup('A translation already exists for the specified language (@langcode).', ['@langcode' => $this->defaultLangcode]);
throw new \InvalidArgumentException($message);
}
$this->updateFieldLangcodes($this->defaultLangcode);
@@ -733,7 +816,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$items = $this->get($this->langcodeKey);
if ($items->value != $this->activeLangcode) {
$items->setValue($this->activeLangcode, FALSE);
- $message = SafeMarkup::format('The translation language cannot be changed (@langcode).', ['@langcode' => $this->activeLangcode]);
+ $message = new FormattableMarkup('The translation language cannot be changed (@langcode).', ['@langcode' => $this->activeLangcode]);
throw new \LogicException($message);
}
}
@@ -744,10 +827,16 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// read-only. See https://www.drupal.org/node/2443991.
if (isset($this->values[$this->defaultLangcodeKey]) && $this->get($this->defaultLangcodeKey)->value != $this->isDefaultTranslation()) {
$this->get($this->defaultLangcodeKey)->setValue($this->isDefaultTranslation(), FALSE);
- $message = SafeMarkup::format('The default translation flag cannot be changed (@langcode).', ['@langcode' => $this->activeLangcode]);
+ $message = new FormattableMarkup('The default translation flag cannot be changed (@langcode).', ['@langcode' => $this->activeLangcode]);
throw new \LogicException($message);
}
break;
+
+ case $this->revisionTranslationAffectedKey:
+ // If the revision translation affected flag is being set then enforce
+ // its value.
+ $this->setRevisionTranslationAffectedEnforced(TRUE);
+ break;
}
}
@@ -763,8 +852,8 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// Populate entity translation object cache so it will be available for all
// translation objects.
- if ($langcode == $this->activeLangcode) {
- $this->translations[$langcode]['entity'] = $this;
+ if (!isset($this->translations[$this->activeLangcode]['entity'])) {
+ $this->translations[$this->activeLangcode]['entity'] = $this;
}
// If we already have a translation object for the specified language we can
@@ -831,6 +920,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$translation->typedData = NULL;
$translation->loadedRevisionId = &$this->loadedRevisionId;
$translation->isDefaultRevision = &$this->isDefaultRevision;
+ $translation->enforceRevisionTranslationAffected = &$this->enforceRevisionTranslationAffected;
return $translation;
}
@@ -1043,7 +1133,9 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$duplicate = clone $this;
$entity_type = $this->getEntityType();
- $duplicate->{$entity_type->getKey('id')}->value = NULL;
+ if ($entity_type->hasKey('id')) {
+ $duplicate->{$entity_type->getKey('id')}->value = NULL;
+ }
$duplicate->enforceIsNew();
// Check if the entity type supports UUIDs and generate a new one if so.
@@ -1098,7 +1190,8 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// Ensure that the following properties are actually cloned by
// overwriting the original references with ones pointing to copies of
// them: enforceIsNew, newRevision, loadedRevisionId, fields, entityKeys,
- // translatableEntityKeys, values and isDefaultRevision.
+ // translatableEntityKeys, values, isDefaultRevision and
+ // enforceRevisionTranslationAffected.
$enforce_is_new = $this->enforceIsNew;
$this->enforceIsNew = &$enforce_is_new;
@@ -1123,6 +1216,9 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$default_revision = $this->isDefaultRevision;
$this->isDefaultRevision = &$default_revision;
+ $is_revision_translation_affected_enforced = $this->enforceRevisionTranslationAffected;
+ $this->enforceRevisionTranslationAffected = &$is_revision_translation_affected_enforced;
+
foreach ($this->fields as $name => $fields_by_langcode) {
$this->fields[$name] = [];
// Untranslatable fields may have multiple references for the same field
@@ -1287,17 +1383,11 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
* An array of field names.
*/
protected function getFieldsToSkipFromTranslationChangesCheck() {
- /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
- $entity_type = $this->getEntityType();
- // A list of known revision metadata fields which should be skipped from
- // the comparision.
- $fields = [
- $entity_type->getKey('revision'),
- 'revision_translation_affected',
- ];
- $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
-
- return $fields;
+ $bundle = $this->bundle();
+ if (!isset(static::$fieldsToSkipFromTranslationChangesCheck[$this->entityTypeId][$bundle])) {
+ static::$fieldsToSkipFromTranslationChangesCheck[$this->entityTypeId][$bundle] = $this->traitGetFieldsToSkipFromTranslationChangesCheck($this);
+ }
+ return static::$fieldsToSkipFromTranslationChangesCheck[$this->entityTypeId][$bundle];
}
/**
@@ -1333,32 +1423,40 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// possible or be meaningless.
/** @var \Drupal\Core\Entity\ContentEntityBase $translation */
$translation = $original->getTranslation($this->activeLangcode);
+ $langcode = $this->language()->getId();
// The list of fields to skip from the comparision.
$skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck();
+ // We also check untranslatable fields, so that a change to those will mark
+ // all translations as affected, unless they are configured to only affect
+ // the default translation.
+ $skip_untranslatable_fields = !$this->isDefaultTranslation() && $this->isDefaultTranslationAffectedOnly();
+
foreach ($this->getFieldDefinitions() as $field_name => $definition) {
// @todo Avoid special-casing the following fields. See
// https://www.drupal.org/node/2329253.
- if (in_array($field_name, $skip_fields, TRUE)) {
+ if (in_array($field_name, $skip_fields, TRUE) || ($skip_untranslatable_fields && !$definition->isTranslatable())) {
continue;
}
- $field = $this->get($field_name);
- // When saving entities in the user interface, the changed timestamp is
- // automatically incremented by ContentEntityForm::submitForm() even if
- // nothing was actually changed. Thus, the changed time needs to be
- // ignored when determining whether there are any actual changes in the
- // entity.
- if (!($field instanceof ChangedFieldItemList) && !$definition->isComputed()) {
- $items = $field->filterEmptyItems();
- $original_items = $translation->get($field_name)->filterEmptyItems();
- if (!$items->equals($original_items)) {
- return TRUE;
- }
+ $items = $this->get($field_name)->filterEmptyItems();
+ $original_items = $translation->get($field_name)->filterEmptyItems();
+ if ($items->hasAffectingChanges($original_items, $langcode)) {
+ return TRUE;
}
}
return FALSE;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function isDefaultTranslationAffectedOnly() {
+ $bundle_name = $this->bundle();
+ $bundle_info = \Drupal::service('entity_type.bundle.info')
+ ->getBundleInfo($this->getEntityTypeId());
+ return !empty($bundle_info[$bundle_name]['untranslatable_fields.default_translation_affected']);
+ }
+
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php b/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php
index 32c48d8..27dd2b7 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php
@@ -39,7 +39,7 @@ class ContentEntityDeleteForm extends ContentEntityConfirmFormBase {
$form['deleted_translations'] = [
'#theme' => 'item_list',
'#title' => $this->t('The following @entity-type translations will be deleted:', [
- '@entity-type' => $entity->getEntityType()->getLowercaseLabel()
+ '@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
]),
'#items' => $languages,
];
@@ -60,6 +60,7 @@ class ContentEntityDeleteForm extends ContentEntityConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
+ $message = $this->getDeletionMessage();
// Make sure that deleting a translation does not delete the whole entity.
if (!$entity->isDefaultTranslation()) {
@@ -73,7 +74,7 @@ class ContentEntityDeleteForm extends ContentEntityConfirmFormBase {
$form_state->setRedirectUrl($this->getRedirectUrl());
}
- drupal_set_message($this->getDeletionMessage());
+ $this->messenger()->addStatus($message);
$this->logDeletionMessage();
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
index 92bbbae..75bde9f 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -16,13 +16,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class ContentEntityForm extends EntityForm implements ContentEntityFormInterface {
/**
- * The entity manager.
- *
- * @var \Drupal\Core\Entity\EntityManagerInterface
- */
- protected $entityManager;
-
- /**
* The entity being used by this form.
*
* @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface
@@ -44,18 +37,28 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
protected $time;
/**
+ * The entity repository service.
+ *
+ * @var \Drupal\Core\Entity\EntityRepositoryInterface
+ */
+ protected $entityRepository;
+
+ /**
* Constructs a ContentEntityForm object.
*
- * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
- * The entity manager.
+ * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
+ * The entity repository service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
*/
- public function __construct(EntityManagerInterface $entity_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
- $this->entityManager = $entity_manager;
-
+ public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
+ if ($entity_repository instanceof EntityManagerInterface) {
+ @trigger_error('Passing the entity.manager service to ContentEntityForm::__construct() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Pass the entity.repository service instead. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
+ $this->entityManager = $entity_repository;
+ }
+ $this->entityRepository = $entity_repository;
$this->entityTypeBundleInfo = $entity_type_bundle_info ?: \Drupal::service('entity_type.bundle.info');
$this->time = $time ?: \Drupal::service('datetime.time');
}
@@ -65,7 +68,7 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
*/
public static function create(ContainerInterface $container) {
return new static(
- $container->get('entity.manager'),
+ $container->get('entity.repository'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time')
);
@@ -131,7 +134,7 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
'#type' => 'container',
'#weight' => 99,
'#attributes' => [
- 'class' => ['entity-content-form-footer']
+ 'class' => ['entity-content-form-footer'],
],
'#optional' => TRUE,
];
@@ -307,7 +310,7 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
// Imply a 'view' operation to ensure users edit entities in the same
// language they are displayed. This allows to keep contextual editing
// working also for multilingual entities.
- $form_state->set('langcode', $this->entityManager->getTranslationFromContext($this->entity)->language()->getId());
+ $form_state->set('langcode', $this->entityRepository->getTranslationFromContext($this->entity)->language()->getId());
}
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
index d045152..56cf139 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
@@ -2,73 +2,25 @@
namespace Drupal\Core\Entity;
-use Drupal\Core\TypedData\TranslatableInterface;
-
/**
* Defines a common interface for all content entity objects.
*
- * Content entities use fields for all their entity properties and are
- * translatable and revisionable, while translations and revisions can be
- * enabled per entity type. It's best practice to always implement
- * ContentEntityInterface for content-like entities that should be stored in
- * some database, and enable/disable revisions and translations as desired.
+ * Content entities use fields for all their entity properties and can be
+ * translatable and revisionable. Translations and revisions can be
+ * enabled per entity type through annotation and using entity type hooks.
+ *
+ * It's best practice to always implement ContentEntityInterface for
+ * content-like entities that should be stored in some database, and
+ * enable/disable revisions and translations as desired.
*
* When implementing this interface which extends Traversable, make sure to list
* IteratorAggregate or Iterator before this interface in the implements clause.
*
* @see \Drupal\Core\Entity\ContentEntityBase
+ * @see \Drupal\Core\Entity\EntityTypeInterface
*
* @ingroup entity_api
*/
-interface ContentEntityInterface extends \Traversable, FieldableEntityInterface, RevisionableInterface, TranslatableInterface {
-
- /**
- * Determines if the current translation of the entity has unsaved changes.
- *
- * @return bool
- * TRUE if the current translation of the entity has changes.
- */
- public function hasTranslationChanges();
-
- /**
- * Marks the current revision translation as affected.
- *
- * @param bool|null $affected
- * The flag value. A NULL value can be specified to reset the current value
- * and make sure a new value will be computed by the system.
- *
- * @return $this
- */
- public function setRevisionTranslationAffected($affected);
-
- /**
- * Checks whether the current translation is affected by the current revision.
- *
- * @return bool
- * TRUE if the entity object is affected by the current revision, FALSE
- * otherwise.
- */
- public function isRevisionTranslationAffected();
-
- /**
- * Gets the loaded Revision ID of the entity.
- *
- * @return int
- * The loaded Revision identifier of the entity, or NULL if the entity
- * does not have a revision identifier.
- */
- public function getLoadedRevisionId();
-
- /**
- * Updates the loaded Revision ID with the revision ID.
- *
- * This method should not be used, it could unintentionally cause the original
- * revision ID property value to be lost.
- *
- * @internal
- *
- * @return $this
- */
- public function updateLoadedRevisionId();
+interface ContentEntityInterface extends \Traversable, FieldableEntityInterface, TranslatableRevisionableInterface {
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
index 3dc00c9..94efc11 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
@@ -41,6 +41,13 @@ class ContentEntityNullStorage extends ContentEntityStorageBase {
/**
* {@inheritdoc}
*/
+ public function loadMultipleRevisions(array $revision_ids) {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function deleteRevision($revision_id) {
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index 874a341..d38244b 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -4,8 +4,11 @@ namespace Drupal\Core\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\TypedData\TranslationStatusInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -35,6 +38,13 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
protected $cacheBackend;
/**
+ * Stores the latest revision IDs for entities.
+ *
+ * @var array
+ */
+ protected $latestRevisionIds = [];
+
+ /**
* Constructs a ContentEntityStorageBase object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
@@ -43,9 +53,11 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
* The entity manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend to be used.
+ * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
+ * The memory cache backend.
*/
- public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache) {
- parent::__construct($entity_type);
+ public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, MemoryCacheInterface $memory_cache = NULL) {
+ parent::__construct($entity_type, $memory_cache);
$this->bundleKey = $this->entityType->getKey('bundle');
$this->entityManager = $entity_manager;
$this->cacheBackend = $cache;
@@ -58,7 +70,8 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
return new static(
$entity_type,
$container->get('entity.manager'),
- $container->get('cache.entity')
+ $container->get('cache.entity'),
+ $container->get('entity.memory_cache')
);
}
@@ -149,6 +162,69 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
}
/**
+ * Checks whether any entity revision is translated.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\TranslatableInterface $entity
+ * The entity object to be checked.
+ *
+ * @return bool
+ * TRUE if the entity has at least one translation in any revision, FALSE
+ * otherwise.
+ *
+ * @see \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages()
+ * @see \Drupal\Core\Entity\ContentEntityStorageBase::isAnyStoredRevisionTranslated()
+ */
+ protected function isAnyRevisionTranslated(TranslatableInterface $entity) {
+ return $entity->getTranslationLanguages(FALSE) || $this->isAnyStoredRevisionTranslated($entity);
+ }
+
+ /**
+ * Checks whether any stored entity revision is translated.
+ *
+ * A revisionable entity can have translations in a pending revision, hence
+ * the default revision may appear as not translated. This determines whether
+ * the entity has any translation in the storage and thus should be considered
+ * as multilingual.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\TranslatableInterface $entity
+ * The entity object to be checked.
+ *
+ * @return bool
+ * TRUE if the entity has at least one translation in any revision, FALSE
+ * otherwise.
+ *
+ * @see \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages()
+ * @see \Drupal\Core\Entity\ContentEntityStorageBase::isAnyRevisionTranslated()
+ */
+ protected function isAnyStoredRevisionTranslated(TranslatableInterface $entity) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+ if ($entity->isNew()) {
+ return FALSE;
+ }
+
+ if ($entity instanceof TranslationStatusInterface) {
+ foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
+ if ($entity->getTranslationStatus($langcode) === TranslationStatusInterface::TRANSLATION_EXISTING) {
+ return TRUE;
+ }
+ }
+ }
+
+ $query = $this->getQuery()
+ ->condition($this->entityType->getKey('id'), $entity->id())
+ ->condition($this->entityType->getKey('default_langcode'), 0)
+ ->accessCheck(FALSE)
+ ->range(0, 1);
+
+ if ($entity->getEntityType()->isRevisionable()) {
+ $query->allRevisions();
+ }
+
+ $result = $query->execute();
+ return !empty($result);
+ }
+
+ /**
* {@inheritdoc}
*/
public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []) {
@@ -169,6 +245,162 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
/**
* {@inheritdoc}
*/
+ public function createRevision(RevisionableInterface $entity, $default = TRUE, $keep_untranslatable_fields = NULL) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+ $new_revision = clone $entity;
+
+ $original_keep_untranslatable_fields = $keep_untranslatable_fields;
+
+ // For translatable entities, create a merged revision of the active
+ // translation and the other translations in the default revision. This
+ // permits the creation of pending revisions that can always be saved as the
+ // new default revision without reverting changes in other languages.
+ if (!$entity->isNew() && !$entity->isDefaultRevision() && $entity->isTranslatable() && $this->isAnyRevisionTranslated($entity)) {
+ $active_langcode = $entity->language()->getId();
+ $skipped_field_names = array_flip($this->getRevisionTranslationMergeSkippedFieldNames());
+
+ // By default we copy untranslatable field values from the default
+ // revision, unless they are configured to affect only the default
+ // translation. This way we can ensure we always have only one affected
+ // translation in pending revisions. This constraint is enforced by
+ // EntityUntranslatableFieldsConstraintValidator.
+ if (!isset($keep_untranslatable_fields)) {
+ $keep_untranslatable_fields = $entity->isDefaultTranslation() && $entity->isDefaultTranslationAffectedOnly();
+ }
+
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
+ $default_revision = $this->load($entity->id());
+ $translation_languages = $default_revision->getTranslationLanguages();
+ foreach ($translation_languages as $langcode => $language) {
+ if ($langcode == $active_langcode) {
+ continue;
+ }
+
+ $default_revision_translation = $default_revision->getTranslation($langcode);
+ $new_revision_translation = $new_revision->hasTranslation($langcode) ?
+ $new_revision->getTranslation($langcode) : $new_revision->addTranslation($langcode);
+
+ /** @var \Drupal\Core\Field\FieldItemListInterface[] $sync_items */
+ $sync_items = array_diff_key(
+ $keep_untranslatable_fields ? $default_revision_translation->getTranslatableFields() : $default_revision_translation->getFields(),
+ $skipped_field_names
+ );
+ foreach ($sync_items as $field_name => $items) {
+ $new_revision_translation->set($field_name, $items->getValue());
+ }
+
+ // Make sure the "revision_translation_affected" flag is recalculated.
+ $new_revision_translation->setRevisionTranslationAffected(NULL);
+
+ // No need to copy untranslatable field values more than once.
+ $keep_untranslatable_fields = TRUE;
+ }
+
+ // Make sure we do not inadvertently recreate removed translations.
+ foreach (array_diff_key($new_revision->getTranslationLanguages(), $translation_languages) as $langcode => $language) {
+ // Allow a new revision to be created for the active language.
+ if ($langcode !== $active_langcode) {
+ $new_revision->removeTranslation($langcode);
+ }
+ }
+
+ // The "original" property is used in various places to detect changes in
+ // field values with respect to the stored ones. If the property is not
+ // defined, the stored version is loaded explicitly. Since the merged
+ // revision generated here is not stored anywhere, we need to populate the
+ // "original" property manually, so that changes can be properly detected.
+ $new_revision->original = clone $new_revision;
+ }
+
+ // Eventually mark the new revision as such.
+ $new_revision->setNewRevision();
+ $new_revision->isDefaultRevision($default);
+
+ // Actually make sure the current translation is marked as affected, even if
+ // there are no explicit changes, to be sure this revision can be related
+ // to the correct translation.
+ $new_revision->setRevisionTranslationAffected(TRUE);
+
+ // Notify modules about the new revision.
+ $arguments = [$new_revision, $entity, $original_keep_untranslatable_fields];
+ $this->moduleHandler()->invokeAll($this->entityTypeId . '_revision_create', $arguments);
+ $this->moduleHandler()->invokeAll('entity_revision_create', $arguments);
+
+ return $new_revision;
+ }
+
+ /**
+ * Returns an array of field names to skip when merging revision translations.
+ *
+ * @return array
+ * An array of field names.
+ */
+ protected function getRevisionTranslationMergeSkippedFieldNames() {
+ /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
+ $entity_type = $this->getEntityType();
+
+ // A list of known revision metadata fields which should be skipped from
+ // the comparision.
+ $field_names = [
+ $entity_type->getKey('revision'),
+ $entity_type->getKey('revision_translation_affected'),
+ ];
+ $field_names = array_merge($field_names, array_values($entity_type->getRevisionMetadataKeys()));
+
+ return $field_names;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLatestRevisionId($entity_id) {
+ if (!$this->entityType->isRevisionable()) {
+ return NULL;
+ }
+
+ if (!isset($this->latestRevisionIds[$entity_id][LanguageInterface::LANGCODE_DEFAULT])) {
+ $result = $this->getQuery()
+ ->latestRevision()
+ ->condition($this->entityType->getKey('id'), $entity_id)
+ ->accessCheck(FALSE)
+ ->execute();
+
+ $this->latestRevisionIds[$entity_id][LanguageInterface::LANGCODE_DEFAULT] = key($result);
+ }
+
+ return $this->latestRevisionIds[$entity_id][LanguageInterface::LANGCODE_DEFAULT];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLatestTranslationAffectedRevisionId($entity_id, $langcode) {
+ if (!$this->entityType->isRevisionable()) {
+ return NULL;
+ }
+
+ if (!$this->entityType->isTranslatable()) {
+ return $this->getLatestRevisionId($entity_id);
+ }
+
+ if (!isset($this->latestRevisionIds[$entity_id][$langcode])) {
+ $result = $this->getQuery()
+ ->allRevisions()
+ ->condition($this->entityType->getKey('id'), $entity_id)
+ ->condition($this->entityType->getKey('revision_translation_affected'), 1, '=', $langcode)
+ ->range(0, 1)
+ ->sort($this->entityType->getKey('revision'), 'DESC')
+ ->accessCheck(FALSE)
+ ->execute();
+
+ $this->latestRevisionIds[$entity_id][$langcode] = key($result);
+ }
+ return $this->latestRevisionIds[$entity_id][$langcode];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {}
/**
@@ -244,15 +476,37 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
* {@inheritdoc}
*/
public function loadRevision($revision_id) {
- $revision = $this->doLoadRevisionFieldItems($revision_id);
+ $revisions = $this->loadMultipleRevisions([$revision_id]);
+
+ return isset($revisions[$revision_id]) ? $revisions[$revision_id] : NULL;
+ }
- if ($revision) {
- $entities = [$revision->id() => $revision];
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMultipleRevisions(array $revision_ids) {
+ $revisions = $this->doLoadMultipleRevisionsFieldItems($revision_ids);
+
+ // The hooks are executed with an array of entities keyed by the entity ID.
+ // As we could load multiple revisions for the same entity ID at once we
+ // have to build groups of entities where the same entity ID is present only
+ // once.
+ $entity_groups = [];
+ $entity_group_mapping = [];
+ foreach ($revisions as $revision) {
+ $entity_id = $revision->id();
+ $entity_group_key = isset($entity_group_mapping[$entity_id]) ? $entity_group_mapping[$entity_id] + 1 : 0;
+ $entity_group_mapping[$entity_id] = $entity_group_key;
+ $entity_groups[$entity_group_key][$entity_id] = $revision;
+ }
+
+ // Invoke the entity hooks for each group.
+ foreach ($entity_groups as $entities) {
$this->invokeStorageLoadHook($entities);
$this->postLoad($entities);
}
- return $revision;
+ return $revisions;
}
/**
@@ -263,10 +517,34 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The specified entity revision or NULL if not found.
+ *
+ * @deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0.
+ * \Drupal\Core\Entity\ContentEntityStorageBase::doLoadMultipleRevisionsFieldItems()
+ * should be implemented instead.
+ *
+ * @see https://www.drupal.org/node/2924915
*/
abstract protected function doLoadRevisionFieldItems($revision_id);
/**
+ * Actually loads revision field item values from the storage.
+ *
+ * @param array $revision_ids
+ * An array of revision identifiers.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface[]
+ * The specified entity revisions or an empty array if none are found.
+ */
+ protected function doLoadMultipleRevisionsFieldItems($revision_ids) {
+ $revisions = [];
+ foreach ($revision_ids as $revision_id) {
+ $revisions[] = $this->doLoadRevisionFieldItems($revision_id);
+ }
+
+ return $revisions;
+ }
+
+ /**
* {@inheritdoc}
*/
protected function doSave($id, EntityInterface $entity) {
@@ -288,6 +566,15 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
}
$this->populateAffectedRevisionTranslations($entity);
+
+ // Populate the "revision_default" flag. We skip this when we are resaving
+ // the revision because this is only allowed for default revisions, and
+ // these cannot be made non-default.
+ if ($this->entityType->isRevisionable() && $entity->isNewRevision()) {
+ $revision_default_key = $this->entityType->getRevisionMetadataKey('revision_default');
+ $entity->set($revision_default_key, $entity->isDefaultRevision());
+ }
+
$this->doSaveFieldItems($entity);
return $return;
@@ -587,32 +874,41 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
$languages = $entity->getTranslationLanguages();
foreach ($languages as $langcode => $language) {
$translation = $entity->getTranslation($langcode);
- // Avoid populating the value if it was already manually set.
- $affected = $translation->isRevisionTranslationAffected();
- if (!isset($affected) && $translation->hasTranslationChanges()) {
- $translation->setRevisionTranslationAffected(TRUE);
+ $current_affected = $translation->isRevisionTranslationAffected();
+ if (!isset($current_affected) || ($entity->isNewRevision() && !$translation->isRevisionTranslationAffectedEnforced())) {
+ // When setting the revision translation affected flag we have to
+ // explicitly set it to not be enforced. By default it will be
+ // enforced automatically when being set, which allows us to determine
+ // if the flag has been already set outside the storage in which case
+ // we should not recompute it.
+ // @see \Drupal\Core\Entity\ContentEntityBase::setRevisionTranslationAffected().
+ $new_affected = $translation->hasTranslationChanges() ? TRUE : NULL;
+ $translation->setRevisionTranslationAffected($new_affected);
+ $translation->setRevisionTranslationAffectedEnforced(FALSE);
}
}
}
}
/**
- * Ensures integer entity IDs are valid.
+ * Ensures integer entity key values are valid.
*
* The identifier sanitization provided by this method has been introduced
* as Drupal used to rely on the database to facilitate this, which worked
* correctly with MySQL but led to errors with other DBMS such as PostgreSQL.
*
* @param array $ids
- * The entity IDs to verify.
+ * The entity key values to verify.
+ * @param string $entity_key
+ * (optional) The entity key to sanitise values for. Defaults to 'id'.
*
* @return array
- * The sanitized list of entity IDs.
+ * The sanitized list of entity key values.
*/
- protected function cleanIds(array $ids) {
+ protected function cleanIds(array $ids, $entity_key = 'id') {
$definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId);
- $id_definition = $definitions[$this->entityType->getKey('id')];
- if ($id_definition->getType() == 'integer') {
+ $field_name = $this->entityType->getKey($entity_key);
+ if ($field_name && $definitions[$field_name]->getType() == 'integer') {
$ids = array_filter($ids, function ($id) {
return is_numeric($id) && $id == (int) $id;
});
@@ -720,34 +1016,23 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
*/
public function resetCache(array $ids = NULL) {
if ($ids) {
- $cids = [];
- foreach ($ids as $id) {
- unset($this->entities[$id]);
- $cids[] = $this->buildCacheId($id);
- }
+ parent::resetCache($ids);
if ($this->entityType->isPersistentlyCacheable()) {
+ $cids = [];
+ foreach ($ids as $id) {
+ unset($this->latestRevisionIds[$id]);
+ $cids[] = $this->buildCacheId($id);
+ }
$this->cacheBackend->deleteMultiple($cids);
}
}
else {
- $this->entities = [];
+ parent::resetCache();
if ($this->entityType->isPersistentlyCacheable()) {
Cache::invalidateTags([$this->entityTypeId . '_values']);
}
+ $this->latestRevisionIds = [];
}
}
- /**
- * Builds the cache ID for the passed in entity ID.
- *
- * @param int $id
- * Entity ID for which the cache ID should be built.
- *
- * @return string
- * Cache ID that can be passed to the cache backend.
- */
- protected function buildCacheId($id) {
- return "values:{$this->entityTypeId}:$id";
- }
-
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
index eb979c7..9b35a84 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
@@ -5,24 +5,7 @@ namespace Drupal\Core\Entity;
/**
* A storage that supports content entity types.
*/
-interface ContentEntityStorageInterface extends EntityStorageInterface {
-
- /**
- * Constructs a new entity translation object, without permanently saving it.
- *
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
- * The entity object being translated.
- * @param string $langcode
- * The translation language code.
- * @param array $values
- * (optional) An associative array of initial field values keyed by field
- * name. If none is provided default values will be applied.
- *
- * @return \Drupal\Core\Entity\ContentEntityInterface
- * A new entity translation object.
- */
- public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []);
-
+interface ContentEntityStorageInterface extends EntityStorageInterface, TranslatableRevisionableStorageInterface {
/**
* Creates an entity with sample field values.
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityType.php b/core/lib/Drupal/Core/Entity/ContentEntityType.php
index 0e26c3b..3871405 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityType.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityType.php
@@ -15,14 +15,45 @@ class ContentEntityType extends EntityType implements ContentEntityTypeInterface
protected $revision_metadata_keys = [];
/**
+ * The required revision metadata keys.
+ *
+ * This property should only be filled in the constructor. This ensures that
+ * only new instances get newly added required revision metadata keys.
+ * Unserialized objects will only retrieve the keys that they already have
+ * been cached with.
+ *
+ * @var array
+ */
+ protected $requiredRevisionMetadataKeys = [];
+
+ /**
* {@inheritdoc}
*/
public function __construct($definition) {
parent::__construct($definition);
+
$this->handlers += [
'storage' => 'Drupal\Core\Entity\Sql\SqlContentEntityStorage',
'view_builder' => 'Drupal\Core\Entity\EntityViewBuilder',
];
+
+ // Only new instances should provide the required revision metadata keys.
+ // The cached instances should return only what already has been stored
+ // under the property $revision_metadata_keys. The BC layer in
+ // ::getRevisionMetadataKeys() has to detect if the revision metadata keys
+ // have been provided by the entity type annotation, therefore we add keys
+ // to the property $requiredRevisionMetadataKeys only if those keys aren't
+ // set in the entity type annotation.
+ if (!isset($this->revision_metadata_keys['revision_default'])) {
+ $this->requiredRevisionMetadataKeys['revision_default'] = 'revision_default';
+ }
+
+ // Add the required revision metadata fields here instead in the getter
+ // method, so that they are serialized as part of the object even if the
+ // getter method doesn't get called. This allows the list to be further
+ // extended. Only new instances of the class will contain the new list,
+ // while the cached instances contain the previous version of the list.
+ $this->revision_metadata_keys += $this->requiredRevisionMetadataKeys;
}
/**
@@ -54,7 +85,7 @@ class ContentEntityType extends EntityType implements ContentEntityTypeInterface
public function getRevisionMetadataKeys($include_backwards_compatibility_field_names = TRUE) {
// Provide backwards compatibility in case the revision metadata keys are
// not defined in the entity annotation.
- if (!$this->revision_metadata_keys && $include_backwards_compatibility_field_names) {
+ if ((!$this->revision_metadata_keys || ($this->revision_metadata_keys == $this->requiredRevisionMetadataKeys)) && $include_backwards_compatibility_field_names) {
$base_fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($this->id());
if ((isset($base_fields['revision_uid']) && $revision_user = 'revision_uid') || (isset($base_fields['revision_user']) && $revision_user = 'revision_user')) {
@trigger_error('The revision_user revision metadata key is not set.', E_USER_DEPRECATED);
diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityListController.php b/core/lib/Drupal/Core/Entity/Controller/EntityListController.php
index d8a1ea1..ee35467 100644
--- a/core/lib/Drupal/Core/Entity/Controller/EntityListController.php
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityListController.php
@@ -16,7 +16,8 @@ class EntityListController extends ControllerBase {
* The entity type to render.
*
* @return array
- * A render array as expected by drupal_render().
+ * A render array as expected by
+ * \Drupal\Core\Render\RendererInterface::render().
*/
public function listing($entity_type) {
return $this->entityManager()->getListBuilder($entity_type)->render();
diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
index 983eec8..7922ef4 100644
--- a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
@@ -88,7 +88,8 @@ class EntityViewController implements ContainerInjectionInterface {
* Defaults to 'full'.
*
* @return array
- * A render array as expected by drupal_render().
+ * A render array as expected by
+ * \Drupal\Core\Render\RendererInterface::render().
*/
public function view(EntityInterface $_entity, $view_mode = 'full') {
$page = $this->entityManager
diff --git a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
index 04d1b1e..cf303a2 100644
--- a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
@@ -2,9 +2,7 @@
namespace Drupal\Core\Entity;
-use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldDefinitionListenerInterface;
-use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
/**
@@ -18,27 +16,4 @@ use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
*/
interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface {
- /**
- * Purges a batch of field data.
- *
- * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
- * The deleted field whose data is being purged.
- * @param $batch_size
- * The maximum number of field data records to purge before returning,
- * relating to the count of field data records returned by
- * \Drupal\Core\Entity\FieldableEntityStorageInterface::countFieldData().
- *
- * @return int
- * The number of field data records that have been purged.
- */
- public function purgeFieldData(FieldDefinitionInterface $field_definition, $batch_size);
-
- /**
- * Performs final cleanup after all data of a field has been purged.
- *
- * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
- * The field being purged.
- */
- public function finalizePurge(FieldStorageDefinitionInterface $storage_definition);
-
}
diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
index e82ed81..0691239 100644
--- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
+++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
@@ -39,7 +39,7 @@ class EntityAutocomplete extends Textfield {
$info['#validate_reference'] = TRUE;
// IMPORTANT! This should only be set to FALSE if the #default_value
// property is processed at another level (e.g. by a Field API widget) and
- // it's value is properly checked for access.
+ // its value is properly checked for access.
$info['#process_default_value'] = TRUE;
$info['#element_validate'] = [[$class, 'validateEntityAutocomplete']];
@@ -251,8 +251,8 @@ class EntityAutocomplete extends Textfield {
/**
* Finds an entity from an autocomplete input without an explicit ID.
*
- * The method will return an entity ID if one single entity unambuguously
- * matches the incoming input, and sill assign form errors otherwise.
+ * The method will return an entity ID if one single entity unambiguously
+ * matches the incoming input, and assign form errors otherwise.
*
* @param \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler
* Entity reference selection plugin.
diff --git a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
index 850d404..f383fbc 100644
--- a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
+++ b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
@@ -68,7 +68,6 @@ class EntityRouteEnhancer implements EnhancerInterface {
return $defaults;
}
-
/**
* Update defaults for an entity list.
*
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 4fe3786..84991bc 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -5,7 +5,6 @@ namespace Drupal\Core\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
use Drupal\Core\Language\Language;
@@ -13,6 +12,7 @@ use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
+use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
@@ -344,6 +344,9 @@ abstract class Entity implements EntityInterface {
catch (RouteNotFoundException $e) {
return FALSE;
}
+ catch (MissingMandatoryParametersException $e) {
+ return FALSE;
+ }
return TRUE;
});
}
@@ -427,7 +430,7 @@ abstract class Entity implements EntityInterface {
// Check if this is an entity bundle.
if ($this->getEntityType()->getBundleOf()) {
// Throw an exception if the bundle ID is longer than 32 characters.
- if (Unicode::strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) {
+ if (mb_strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) {
throw new ConfigEntityIdLengthException("Attempt to create a bundle with an ID longer than " . EntityTypeInterface::BUNDLE_MAX_LENGTH . " characters: $this->id().");
}
}
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
index 6c31df5..183e428 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
@@ -144,7 +144,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn
'form_mode' => $this->originalMode,
// No need to prepare, defaults have been merged in setComponent().
'prepare' => FALSE,
- 'configuration' => $configuration
+ 'configuration' => $configuration,
]);
}
else {
@@ -330,7 +330,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn
}
return [
- 'widgets' => new EntityDisplayPluginCollection($this->pluginManager, $configurations)
+ 'widgets' => new EntityDisplayPluginCollection($this->pluginManager, $configurations),
];
}
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
index 1604e31..cd36418 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
@@ -8,7 +8,7 @@ use Drupal\Core\Entity\EntityDisplayPluginCollection;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityDisplayBase;
-use Drupal\Core\TypedData\TranslatableInterface;
+use Drupal\Core\TypedData\TranslatableInterface as TranslatableDataInterface;
/**
* Configuration entity that contains display options for all components of a
@@ -201,7 +201,7 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
'view_mode' => $this->originalMode,
// No need to prepare, defaults have been merged in setComponent().
'prepare' => FALSE,
- 'configuration' => $configuration
+ 'configuration' => $configuration,
]);
}
else {
@@ -253,7 +253,7 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
// those values using:
// - the entity language if the entity is translatable,
// - the current "content language" otherwise.
- if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) {
+ if ($entity instanceof TranslatableDataInterface && $entity->isTranslatable()) {
$view_langcode = $entity->language()->getId();
}
else {
@@ -301,7 +301,7 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
}
return [
- 'formatters' => new EntityDisplayPluginCollection($this->pluginManager, $configurations)
+ 'formatters' => new EntityDisplayPluginCollection($this->pluginManager, $configurations),
];
}
diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php
index ac36411..0a2bb19 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php
@@ -158,7 +158,7 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce
return AccessResult::forbidden()->addCacheableDependency($entity);
}
if ($admin_permission = $this->entityType->getAdminPermission()) {
- return AccessResult::allowedIfHasPermission($account, $this->entityType->getAdminPermission());
+ return AccessResult::allowedIfHasPermission($account, $admin_permission);
}
else {
// No opinion.
@@ -316,14 +316,18 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce
$default = $items ? $items->defaultAccess($operation, $account) : AccessResult::allowed();
// Explicitly disallow changing the entity ID and entity UUID.
- if ($operation === 'edit') {
+ $entity = $items ? $items->getEntity() : NULL;
+ if ($operation === 'edit' && $entity) {
if ($field_definition->getName() === $this->entityType->getKey('id')) {
- return $return_as_object ? AccessResult::forbidden('The entity ID cannot be changed') : FALSE;
+ // String IDs can be set when creating the entity.
+ if (!($entity->isNew() && $field_definition->getType() === 'string')) {
+ return $return_as_object ? AccessResult::forbidden('The entity ID cannot be changed.')->addCacheableDependency($entity) : FALSE;
+ }
}
elseif ($field_definition->getName() === $this->entityType->getKey('uuid')) {
// UUIDs can be set when creating an entity.
- if ($items && ($entity = $items->getEntity()) && !$entity->isNew()) {
- return $return_as_object ? AccessResult::forbidden('The entity UUID cannot be changed')->addCacheableDependency($entity) : FALSE;
+ if (!$entity->isNew()) {
+ return $return_as_object ? AccessResult::forbidden('The entity UUID cannot be changed.')->addCacheableDependency($entity) : FALSE;
}
}
}
diff --git a/core/lib/Drupal/Core/Entity/EntityBundleListener.php b/core/lib/Drupal/Core/Entity/EntityBundleListener.php
index e3b219b..e37317a 100644
--- a/core/lib/Drupal/Core/Entity/EntityBundleListener.php
+++ b/core/lib/Drupal/Core/Entity/EntityBundleListener.php
@@ -68,6 +68,7 @@ class EntityBundleListener implements EntityBundleListenerInterface {
}
// Invoke hook_entity_bundle_create() hook.
$this->moduleHandler->invokeAll('entity_bundle_create', [$entity_type_id, $bundle]);
+ $this->entityFieldManager->clearCachedFieldDefinitions();
}
/**
diff --git a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
index 7475717..2c92b0c 100644
--- a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
@@ -37,6 +37,11 @@ interface EntityChangedInterface {
/**
* Gets the timestamp of the last entity change across all translations.
*
+ * This method will return the highest timestamp across all translations. To
+ * check that no translation is older than in another version of the entity
+ * (e.g. to avoid overwriting newer translations with old data), compare each
+ * translation to the other version individually.
+ *
* @return int
* The timestamp of the last entity save operation across all
* translations.
diff --git a/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php b/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php
new file mode 100644
index 0000000..cde4d83
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides helper methods to detect changes in an entity object.
+ *
+ * @internal This may be replaced by a proper entity comparison handler.
+ */
+trait EntityChangesDetectionTrait {
+
+ /**
+ * Returns an array of field names to skip when checking for changes.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * A content entity object.
+ *
+ * @return string[]
+ * An array of field names.
+ */
+ protected function getFieldsToSkipFromTranslationChangesCheck(ContentEntityInterface $entity) {
+ /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
+ $entity_type = $entity->getEntityType();
+
+ // A list of known revision metadata fields which should be skipped from
+ // the comparision.
+ $fields = [
+ $entity_type->getKey('revision'),
+ $entity_type->getKey('revision_translation_affected'),
+ ];
+ $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
+
+ // Computed fields should be skipped by the check for translation changes.
+ foreach (array_diff_key($entity->getFieldDefinitions(), array_flip($fields)) as $field_name => $field_definition) {
+ if ($field_definition->isComputed()) {
+ $fields[] = $field_name;
+ }
+ }
+
+ return $fields;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
index 9ceac52..b3c12a8 100644
--- a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
+++ b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
@@ -62,7 +62,7 @@ class EntityCreateAccessCheck implements AccessInterface {
}
// If we were unable to replace all placeholders, deny access.
if (strpos($bundle, '{') !== FALSE) {
- return AccessResult::neutral();
+ return AccessResult::neutral(sprintf("Could not find '%s' request argument, therefore cannot check create access.", $bundle));
}
}
return $this->entityManager->getAccessControlHandler($entity_type)->createAccess($bundle, $account, [], TRUE);
diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
index 70dec2c..c5d0abd 100644
--- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
@@ -22,13 +22,28 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
protected $entityManager;
/**
+ * The last installed schema repository.
+ *
+ * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
+ */
+ protected $entityLastInstalledSchemaRepository;
+
+ /**
* Constructs a new EntityDefinitionUpdateManager.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
+ * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository
+ * The last installed schema repository service.
*/
- public function __construct(EntityManagerInterface $entity_manager) {
+ public function __construct(EntityManagerInterface $entity_manager, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository = NULL) {
$this->entityManager = $entity_manager;
+
+ if (!isset($entity_last_installed_schema_repository)) {
+ @trigger_error('The $entity_last_installed_schema_repository parameter was added in Drupal 8.6.x and will be required in 9.0.0. See https://www.drupal.org/node/2973262.', E_USER_DEPRECATED);
+ $entity_last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
+ }
+ $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository;
}
/**
@@ -63,7 +78,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
// Process field storage definition changes.
if (!empty($change_list['field_storage_definitions'])) {
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
- $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
switch ($change) {
@@ -108,7 +123,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
// Process field storage definition changes.
if (!empty($change_list['field_storage_definitions'])) {
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
- $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
$storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
@@ -123,13 +138,20 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
* {@inheritdoc}
*/
public function getEntityType($entity_type_id) {
- $entity_type = $this->entityManager->getLastInstalledDefinition($entity_type_id);
+ $entity_type = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
return $entity_type ? clone $entity_type : NULL;
}
/**
* {@inheritdoc}
*/
+ public function getEntityTypes() {
+ return $this->entityLastInstalledSchemaRepository->getLastInstalledDefinitions();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function installEntityType(EntityTypeInterface $entity_type) {
$this->entityManager->clearCachedDefinitions();
$this->entityManager->onEntityTypeCreate($entity_type);
@@ -173,7 +195,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
* {@inheritdoc}
*/
public function getFieldStorageDefinition($name, $entity_type_id) {
- $storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ $storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL;
}
@@ -211,7 +233,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
break;
case static::DEFINITION_UPDATED:
- $original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
+ $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
$this->entityManager->onEntityTypeUpdate($entity_type, $original);
break;
}
@@ -262,7 +284,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
$change_list = [];
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
- $original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
+ $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
// @todo Support non-storage-schema-changing definition updates too:
// https://www.drupal.org/node/2336895.
@@ -277,7 +299,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) {
$field_changes = [];
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
- $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
// Detect created field storage definitions.
foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
@@ -311,7 +333,8 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
}
// @todo Support deleting entity definitions when we support base field
- // purging. See https://www.drupal.org/node/2282119.
+ // purging.
+ // @see https://www.drupal.org/node/2907779
$this->entityManager->useCaches(TRUE);
diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php
index 7754170..a219f3c 100644
--- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php
@@ -109,6 +109,18 @@ interface EntityDefinitionUpdateManagerInterface {
public function getEntityType($entity_type_id);
/**
+ * Returns all the entity type definitions, ready to be manipulated.
+ *
+ * When needing to apply updates to existing entity type definitions, this
+ * method should always be used to retrieve all the definitions ready to be
+ * manipulated.
+ *
+ * @return \Drupal\Core\Entity\EntityTypeInterface[]
+ * The last installed entity type definitions, keyed by the entity type ID.
+ */
+ public function getEntityTypes();
+
+ /**
* Installs a new entity type definition.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php b/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php
index 2faf45c..9761190 100644
--- a/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php
+++ b/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php
@@ -120,7 +120,7 @@ trait EntityDeleteFormTrait {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->getEntity()->delete();
- drupal_set_message($this->getDeletionMessage());
+ $this->messenger()->addStatus($this->getDeletionMessage());
$form_state->setRedirectUrl($this->getCancelUrl());
$this->logDeletionMessage();
}
diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php
new file mode 100644
index 0000000..470c1b0
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Checks if the current user has delete access to the items of the tempstore.
+ */
+class EntityDeleteMultipleAccessCheck implements AccessInterface {
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * The tempstore service.
+ *
+ * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
+ */
+ protected $tempStore;
+
+ /**
+ * Request stack service.
+ *
+ * @var \Symfony\Component\HttpFoundation\RequestStack
+ */
+ protected $requestStack;
+
+ /**
+ * Constructs a new EntityDeleteMultipleAccessCheck.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
+ * The tempstore service.
+ * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+ * The request stack service.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, RequestStack $request_stack) {
+ $this->entityTypeManager = $entity_type_manager;
+ $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
+ $this->requestStack = $request_stack;
+ }
+
+ /**
+ * Checks if the user has delete access for at least one item of the store.
+ *
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * Run access checks for this account.
+ * @param string $entity_type_id
+ * Entity type ID.
+ *
+ * @return \Drupal\Core\Access\AccessResult
+ * Allowed or forbidden, neutral if tempstore is empty.
+ */
+ public function access(AccountInterface $account, $entity_type_id) {
+ if (!$this->requestStack->getCurrentRequest()->