From 507b45a0ed6101dc011ae61429a4ca1e3f4ffe54 Mon Sep 17 00:00:00 2001 From: Pantheon Automation Date: Wed, 2 Nov 2016 11:43:31 -0700 Subject: [PATCH] Update to Drupal 8.2.2. For more information, see https://www.drupal.org/project/drupal/releases/8.2.2 --- core/.eslintrc | 2 +- core/MAINTAINERS.txt | 17 +- core/includes/form.inc | 2 - core/includes/install.inc | 34 +- core/lib/Drupal.php | 2 +- core/lib/Drupal/Component/Utility/Random.php | 1 - .../Access/CsrfRequestHeaderAccessCheck.php | 5 +- .../Drupal/Core/Ajax/OpenDialogCommand.php | 3 + core/lib/Drupal/Core/Asset/AssetResolver.php | 2 +- .../Cache/Context/SessionCacheContext.php | 5 +- .../Core/Database/Driver/mysql/Schema.php | 2 +- .../Core/Database/Driver/pgsql/Schema.php | 4 +- .../Core/Database/StatementPrefetch.php | 2 +- .../Entity/Controller/EntityController.php | 2 +- .../Core/Entity/Entity/EntityFormDisplay.php | 2 +- .../Core/Entity/EntityResolverManager.php | 5 +- core/lib/Drupal/Core/Entity/entity.api.php | 45 +- .../EventSubscriber/PathRootsSubscriber.php | 4 +- .../Core/Field/FieldItemListInterface.php | 2 + .../Field/FieldType/EntityReferenceItem.php | 21 +- .../EntityReferenceAutocompleteWidget.php | 5 +- core/lib/Drupal/Core/File/FileSystem.php | 6 +- .../MimeType/ExtensionMimeTypeGuesser.php | 2 +- core/lib/Drupal/Core/Form/FormBuilder.php | 6 +- core/lib/Drupal/Core/Render/Element/Link.php | 2 +- core/lib/Drupal/Core/Render/Element/Table.php | 8 +- .../Core/Routing/ContentTypeHeaderMatcher.php | 7 +- .../Drupal/Core/Session/SessionManager.php | 3 +- .../Drupal/Core/Template/TwigExtension.php | 6 + core/lib/Drupal/Core/Test/TestStatus.php | 63 +++ .../Core/Utility/LinkGeneratorInterface.php | 2 +- core/misc/displace.js | 2 +- .../Plugin/migrate/source/ActionTest.php | 62 +++ .../Unit/Plugin/migrate/source/ActionTest.php | 68 --- .../migrate/source/AggregatorFeedTest.php | 60 +++ .../migrate/source/AggregatorItemTest.php | 44 ++ .../migrate/source/AggregatorItemTest.php | 44 -- .../migrate/source/d6/AggregatorFeedTest.php | 60 --- .../migrate/source/d7/AggregatorFeedTest.php | 62 --- .../migrate/source/d7/BlockedIpsTest.php | 40 ++ .../migrate/source/d7/BlockedIpsTest.php | 43 -- core/modules/block/src/BlockForm.php | 2 +- .../src/Controller/BlockLibraryController.php | 20 +- core/modules/block/src/Tests/BlockTest.php | 43 -- core/modules/block/src/Tests/BlockUiTest.php | 7 +- .../Plugin/migrate/source/BlockTest.php | 127 +++++ .../Unit/Plugin/migrate/source/BlockTest.php | 144 ------ .../Plugin/migrate/source/d6/BoxTest.php | 46 ++ .../migrate/source/d7/BlockCustomTest.php | 40 ++ .../Unit/Plugin/migrate/source/d6/BoxTest.php | 46 -- .../migrate/source/d7/BlockCustomTest.php | 40 -- core/modules/block_place/block_place.module | 2 +- .../src/Plugin/Block/BookNavigationBlock.php | 9 +- core/modules/book/src/Tests/BookTest.php | 53 +- .../BookJavascriptTest.php | 160 ++++++ .../Plugin/migrate/source/d6/BookTest.php | 84 ++++ .../Plugin/migrate/source/d6/BookTest.php | 85 ---- .../src/Plugin/CKEditorPlugin/Internal.php | 3 +- .../config/schema/ckeditor_test.schema.yml | 9 + .../src/Plugin/Filter/TestAttributeFilter.php | 38 ++ .../Plugin/CKEditorPlugin/InternalTest.php | 140 ++++++ .../migration_templates/contact_category.yml | 4 +- core/modules/contact/src/MailHandler.php | 9 +- .../contact/src/Tests/ContactSitewideTest.php | 28 +- .../Migrate/d6/MigrateContactCategoryTest.php | 23 + .../migrate/source/ContactCategoryTest.php | 57 +++ .../migrate/source/d6/ContactSettingsTest.php | 54 ++ .../migrate/source/ContactCategoryTest.php | 54 -- .../migrate/source/d6/ContactSettingsTest.php | 55 -- .../src/Entity/ContentModerationState.php | 3 - .../src/EntityOperations.php | 31 +- .../src/ModerationInformation.php | 16 +- .../src/ModerationInformationInterface.php | 13 - .../Field/ModerationStateFieldItemList.php | 24 +- .../ModerationStateConstraintValidator.php | 4 +- .../ModerationStateFieldItemListTest.php | 78 +++ .../src/Unit/ModerationInformationTest.php | 3 + .../Field/FieldType/DateTimeFieldItemList.php | 18 +- .../datetime/src/Tests/DateTimeFieldTest.php | 142 +++--- .../config/schema/datetime_range.schema.yml | 9 +- .../datetime_range.post_update.php | 22 + .../DateRangeDefaultFormatter.php | 13 +- .../src/Tests/DateRangeFieldTest.php | 30 ++ .../src/Kernel/SeparatorTranslationTest.php | 131 +++++ core/modules/editor/editor.module | 72 +++ .../EditorPrivateFileReferenceFilterTest.php | 100 ++++ .../install/editor.editor.private_images.yml | 34 ++ .../install/filter.format.private_images.yml | 23 + .../editor_private_test.info.yml | 9 + .../field/migration_templates/d6_field.yml | 195 ++++--- .../source/d6/FieldInstancePerFormDisplay.php | 5 +- .../field/src/Tests/NestedFormTest.php | 26 + .../modules/field_test/field_test.routing.yml | 14 + .../src/Form/NestedEntityTestForm.php | 28 +- .../Migrate/d7/MigrateFieldInstanceTest.php | 17 + .../d6/FieldInstancePerFormDisplayTest.php | 80 +++ .../d6/FieldInstancePerViewModeTest.php | 101 ++++ .../migrate/source/d6/FieldInstanceTest.php | 96 ++++ .../Plugin/migrate/source/d6/FieldTest.php | 79 +++ .../d7/FieldInstancePerFormDisplayTest.php | 161 +++--- .../d7/FieldInstancePerViewModeTest.php | 75 +++ .../migrate/source/d7/FieldInstanceTest.php | 113 +++-- .../Plugin/migrate/source/d7/FieldTest.php | 191 +++---- .../Plugin/migrate/source/d7/ViewModeTest.php | 99 ++-- .../d6/FieldInstancePerFormDisplayTest.php | 75 --- .../d6/FieldInstancePerViewModeTest.php | 96 ---- .../migrate/source/d6/FieldInstanceTest.php | 91 ---- .../Plugin/migrate/source/d6/FieldTest.php | 72 --- .../d7/FieldInstancePerViewModeTest.php | 68 --- .../src/Form/EntityDisplayFormBase.php | 8 +- .../src/Plugin/migrate/process/d6/CckFile.php | 6 +- .../Kernel/Migrate/process/d6/CckFileTest.php | 32 ++ .../Plugin/migrate/source/d6/FileTest.php | 57 +++ .../migrate/source/d6/UploadInstanceTest.php | 68 +++ .../Plugin/migrate/source/d6/UploadTest.php | 78 +++ .../Plugin/migrate/source/d7/FileTest.php | 112 +++++ .../Plugin/migrate/process/d6/FileUriTest.php | 2 +- .../Plugin/migrate/source/d6/FileTest.php | 54 -- .../migrate/source/d6/UploadInstanceTest.php | 65 --- .../Plugin/migrate/source/d6/UploadTest.php | 75 --- .../Plugin/migrate/source/d7/FileTest.php | 146 ------ .../Plugin/migrate/process/FilterIdTest.php | 2 +- .../migrate/source/d6/FilterFormatTest.php | 174 +++++++ .../migrate/source/d7/FilterFormatTest.php | 120 +++++ .../migrate/source/d6/FilterFormatTest.php | 114 ----- .../migrate/source/d7/FilterFormatTest.php | 84 ---- core/modules/forum/forum.module | 3 +- .../Normalizer/ContentEntityNormalizer.php | 16 +- .../Kernel/EntityTranslationNormalizeTest.php | 89 ++++ .../templates/image-crop-summary.html.twig | 6 +- .../templates/image-resize-summary.html.twig | 6 +- .../templates/image-rotate-summary.html.twig | 3 +- .../templates/image-scale-summary.html.twig | 6 +- .../source/d6/ImageCachePresetTest.php | 69 +++ .../migrate/source/d7/ImageStylesTest.php | 66 +++ .../source/d6/ImageCachePresetTest.php | 67 --- .../source/d7/MigrateImageStylesTest.php | 56 --- .../optional/tour.tour.language-add.yml | 2 +- .../optional/tour.tour.language-edit.yml | 2 +- .../config/optional/tour.tour.language.yml | 2 +- .../migration_templates/default_language.yml | 17 +- .../language/src/Form/NegotiationUrlForm.php | 2 +- .../Migrate/MigrateDefaultLanguageTrait.php | 28 +- .../Migrate/d6/MigrateDefaultLanguageTest.php | 7 + .../Migrate/d7/MigrateDefaultLanguageTest.php | 7 + .../Plugin/migrate/cckfield/d7/LinkField.php | 18 + .../config/optional/tour.tour.locale.yml | 4 +- .../{menu_links.yml => d6_menu_links.yml} | 10 +- .../migration_templates/d7_menu_links.yml | 52 ++ .../Plugin/migrate/process/d7/InternalUri.php | 43 ++ .../Kernel/Migrate/d6/MigrateMenuLinkTest.php | 2 +- .../Kernel/Migrate/d7/MigrateMenuLinkTest.php | 53 +- .../Plugin/migrate/source/MenuLinkTest.php | 225 +++++++++ .../migrate/process/d7/InternalUriTest.php | 73 +++ .../migrate/source/MenuLinkSourceTest.php | 217 -------- .../modules/migrate/src/MigrateExecutable.php | 32 +- .../Plugin/MigrateDestinationInterface.php | 56 ++- .../src/Plugin/MigratePluginManager.php | 3 +- .../src/Plugin/MigrateSourceInterface.php | 51 +- .../migrate/destination/EntityContentBase.php | 36 +- .../migrate/destination/EntityRevision.php | 3 +- .../migrate/src/Plugin/migrate/id_map/Sql.php | 32 +- .../src/Plugin/migrate/process/Download.php | 117 +++++ .../src/Plugin/migrate/process/Extract.php | 3 +- .../src/Plugin/migrate/process/FileCopy.php | 48 +- .../migrate/source/SourcePluginBase.php | 3 + core/modules/migrate/src/Row.php | 2 +- .../migrate_entity_test.info.yml | 6 + .../src/Entity/StringIdEntityTest.php | 36 ++ .../migrate_prepare_row_test.module | 4 +- .../migrate/process/TestSkipRowProcess.php | 34 ++ .../Kernel/MigrateEntityContentBaseTest.php | 50 +- .../tests/src/Kernel/MigrateSkipRowTest.php | 35 ++ .../tests/src/Kernel/process/CopyFileTest.php | 4 +- .../tests/src/Kernel/process/DownloadTest.php | 128 +++++ .../tests/src/Kernel/process/ExtractTest.php | 111 ++++ .../tests/src/Kernel/process/FileCopyTest.php | 180 +++++++ .../tests/src/Unit/MigrateExecutableTest.php | 4 +- .../tests/src/Unit/MigrateSourceTest.php | 8 +- .../src/Unit/MigrateSqlSourceTestCase.php | 2 +- .../destination/EntityContentBaseTest.php | 30 +- .../migrate/tests/src/Unit/RowTest.php | 2 +- .../Unit/destination/EntityRevisionTest.php | 2 +- .../PerComponentEntityDisplayTest.php | 2 +- .../PerComponentEntityFormDisplayTest.php | 2 +- .../tests/src/Unit/process/IteratorTest.php | 2 +- .../tests/src/Unit/process/UrlEncodeTest.php | 2 +- .../src/Plugin/migrate/source/d8/Config.php | 59 +++ .../migrate_drupal/tests/fixtures/drupal6.php | 16 +- .../migrate_drupal/tests/fixtures/drupal7.php | 93 +++- .../Plugin/migrate/source/d8/ConfigTest.php | 115 +++++ .../src/Form/MigrateUpgradeForm.php | 22 +- .../src/Tests/d7/MigrateUpgrade7Test.php | 12 +- .../migrate/source/d6/NodeByNodeTypeTest.php | 185 +++++++ .../source/d6/NodeRevisionByNodeTypeTest.php | 160 +++--- .../migrate/source/d6/NodeRevisionTest.php | 157 +++--- .../Plugin/migrate/source/d6/NodeTest.php | 328 ++++++++++++ .../migrate/source/d6/NodeTranslationTest.php | 65 +++ .../Plugin/migrate/source/d6/NodeTypeTest.php | 67 +++ .../Plugin/migrate/source/d6/ViewModeTest.php | 71 +++ .../Plugin/migrate/source/d7/NodeTest.php | 232 +++++++++ .../Plugin/migrate/source/d7/NodeTypeTest.php | 87 ++++ .../migrate/source/d6/NodeByNodeTypeTest.php | 135 ----- .../Plugin/migrate/source/d6/NodeTest.php | 122 ----- .../Plugin/migrate/source/d6/NodeTestBase.php | 179 ------- .../migrate/source/d6/NodeTranslationTest.php | 46 -- .../Plugin/migrate/source/d6/NodeTypeTest.php | 70 --- .../Plugin/migrate/source/d6/ViewModeTest.php | 73 --- .../Plugin/migrate/source/d7/NodeTest.php | 147 ------ .../Plugin/migrate/source/d7/NodeTypeTest.php | 84 ---- core/modules/options/options.api.php | 4 +- .../Plugin/Field/FieldType/ListItemBase.php | 6 +- core/modules/outside_in/js/outside_in.js | 8 +- core/modules/outside_in/outside_in.module | 1 + .../src/Ajax/OpenOffCanvasDialogCommand.php | 19 +- .../src/Tests/Ajax/OffCanvasDialogTest.php | 2 +- .../outside-in-page-wrapper.html.twig | 12 +- .../src/Controller/TestController.php | 4 + .../FunctionalJavascript/OffCanvasTest.php | 5 +- .../page_cache/src/Tests/PageCacheTest.php | 27 + .../Plugin/migrate/source/d6/UrlAliasTest.php | 48 ++ .../Plugin/migrate/source/d7/UrlAliasTest.php | 48 ++ .../src/Unit/Migrate/UrlAliasTestBase.php | 20 - .../src/Unit/Migrate/d6/UrlAliasTest.php | 38 -- .../src/Unit/Migrate/d7/UrlAliasTest.php | 38 -- .../responsive_image/responsive_image.module | 2 +- .../Plugin/rest/resource/EntityResource.php | 2 +- core/modules/rest/src/Tests/CreateTest.php | 12 +- core/modules/rest/src/Tests/DeleteTest.php | 13 +- core/modules/rest/src/Tests/RESTTestBase.php | 29 +- core/modules/rest/src/Tests/UpdateTest.php | 10 +- .../d6_search_settings.yml | 2 - .../src/Normalizer/ListNormalizer.php | 2 +- .../Unit/Normalizer/ListNormalizerTest.php | 26 +- .../migration_templates/d7_shortcut.yml | 2 +- core/modules/shortcut/shortcut.module | 3 +- .../shortcut/src/Tests/ShortcutLinksTest.php | 71 ++- .../Migrate/d7/MigrateShortcutSetTest.php | 4 +- .../d7/MigrateShortcutSetUsersTest.php | 4 +- .../Kernel/Migrate/d7/MigrateShortcutTest.php | 4 +- .../migrate/source/d7/ShortcutSetTest.php | 41 ++ .../source/d7/ShortcutSetUsersTest.php | 41 ++ .../Plugin/migrate/source/d7/ShortcutTest.php | 72 +++ .../migrate/source/d7/ShortcutSetTest.php | 38 -- .../source/d7/ShortcutSetUsersTest.php | 38 -- .../Plugin/migrate/source/d7/ShortcutTest.php | 68 --- core/modules/simpletest/simpletest.module | 28 +- core/modules/simpletest/src/TestBase.php | 6 +- .../src/Tests/SimpleTestBrowserTest.php | 3 +- .../simpletest_phpunit_run_command_test.php | 17 +- .../tests/src/Functional/ThroughUITest.php | 23 + .../Unit/SimpletestPhpunitRunCommandTest.php | 93 +++- ...s_settings.yml => statistics_settings.yml} | 3 +- .../d6/MigrateStatisticsConfigsTest.php | 2 +- .../d7/MigrateStatisticsConfigsTest.php | 41 ++ .../{menu.yml => d6_menu.yml} | 3 +- .../system/migration_templates/d7_menu.yml | 20 + .../system/src/Tests/Ajax/DialogTest.php | 11 +- .../src/Tests/Common/FormatDateTest.php | 42 -- .../src/Tests/Render/AjaxPageStateTest.php | 32 +- .../system/src/Tests/Theme/EngineTwigTest.php | 5 + core/modules/system/system.install | 2 +- core/modules/system/system.libraries.yml | 1 + core/modules/system/system.module | 21 +- core/modules/system/templates/html.html.twig | 2 +- .../src/Controller/AjaxTestController.php | 2 +- .../ajax_test/src/Form/AjaxTestDialogForm.php | 2 +- .../Condition/ConditionTestNoExistingType.php | 34 ++ .../modules/entity_test/entity_test.module | 13 +- .../modules/form_test/form_test.routing.yml | 8 + .../src/Form/FormTestMachineNameForm.php | 63 +++ .../twig_theme_test.link_generator.html.twig | 4 + .../src/Kernel/Form/FormElementLabelTest.php | 36 ++ .../InstallerMissingDependenciesTest.php | 39 ++ .../Migrate/{ => d6}/MigrateMenuTest.php | 14 +- .../src/Kernel/Migrate/d7/MigrateMenuTest.php | 68 +++ .../Kernel/Plugin/migrate/source/MenuTest.php | 47 ++ .../Unit/Plugin/migrate/source/MenuTest.php | 44 -- .../taxonomy/src/Entity/Vocabulary.php | 8 +- .../taxonomy/src/Form/OverviewTerms.php | 12 +- .../TermSelection.php | 1 + core/modules/taxonomy/src/TermForm.php | 6 +- .../src/Tests/TermAutocompleteTest.php | 204 ++++++++ .../taxonomy/src/VocabularyInterface.php | 21 +- core/modules/taxonomy/taxonomy.module | 30 +- .../src/VocabularyResponse.php | 19 + .../VocabularySerializationTestController.php | 15 + .../vocabulary_serialization_test.info.yml | 7 + .../vocabulary_serialization_test.routing.yml | 6 + .../VocabularySerializationTest.php | 47 ++ .../d7/MigrateTaxonomyVocabularyTest.php | 6 +- .../TermSourceWithVocabularyFilterTest.php | 56 +++ .../Kernel/Plugin/migrate/source/TermTest.php | 157 ++++++ .../migrate/source}/d6/TermNodeTest.php | 98 ++-- .../migrate/source/d6/VocabularyTest.php | 108 ++++ .../migrate/source/d7/VocabularyTest.php | 54 ++ .../TermSourceWithVocabularyFilterTest.php | 25 - .../tests/src/Unit/Migrate/TermTest.php | 12 - .../tests/src/Unit/Migrate/TermTestBase.php | 89 ---- .../src/Unit/Migrate/d6/VocabularyTest.php | 74 --- .../src/Unit/Migrate/d7/VocabularyTest.php | 52 -- .../migrate/source/d7/TrackerNodeTest.php | 42 ++ .../migrate/source/d7/TrackerUserTest.php | 43 ++ .../migrate/source/d7/TrackerNodeTest.php | 39 -- .../migrate/source/d7/TrackerUserTest.php | 40 -- .../user/migration_templates/d6_user.yml | 15 +- .../user/migration_templates/d6_user_role.yml | 23 +- .../user/migration_templates/d7_user.yml | 17 +- .../user/migration_templates/d7_user_role.yml | 23 +- core/modules/user/src/Entity/User.php | 3 + .../Plugin/migrate/process/UserLangcode.php | 86 ++++ .../migrate/source/d6/ProfileFieldValues.php | 2 +- .../src/Plugin/migrate/source/d6/Role.php | 10 +- .../src/Plugin/migrate/source/d6/User.php | 1 + .../d6/MigrateUserProfileValuesTest.php | 6 + .../Kernel/Migrate/d6/MigrateUserRoleTest.php | 152 ++++-- .../src/Kernel/Migrate/d6/MigrateUserTest.php | 51 +- .../Kernel/Migrate/d7/MigrateUserRoleTest.php | 44 ++ .../src/Kernel/Migrate/d7/MigrateUserTest.php | 130 +++-- .../migrate/source/ProfileFieldTest.php | 129 +++++ .../source/UserPictureInstanceTest.php | 55 ++ .../source}/d6/ProfileFieldValuesTest.php | 77 ++- .../Plugin/migrate/source/d6/RoleTest.php | 87 ++++ .../migrate/source/d6/UserPictureFileTest.php | 49 ++ .../migrate/source/d6/UserPictureTest.php | 46 ++ .../Plugin/migrate/source/d6/UserTest.php | 83 +++ .../Plugin/migrate/source/d7/UserTest.php | 120 +++++ .../src/Unit/Migrate/ProfileFieldTest.php | 82 --- .../Unit/Migrate/UserPictureInstanceTest.php | 54 -- .../tests/src/Unit/Migrate/d6/RoleTest.php | 75 --- .../Unit/Migrate/d6/UserPictureFileTest.php | 48 -- .../src/Unit/Migrate/d6/UserPictureTest.php | 44 -- .../tests/src/Unit/Migrate/d6/UserTest.php | 83 --- .../Plugin/migrate/source/d7/UserTest.php | 112 ----- core/modules/views/js/base.js | 6 +- core/modules/views/src/ManyToOneHelper.php | 2 +- .../Plugin/views/field/FieldPluginBase.php | 15 +- .../views/src/Plugin/views/filter/Combine.php | 35 ++ .../src/Plugin/views/filter/StringFilter.php | 7 +- .../views/src/Plugin/views/pager/Mini.php | 40 +- .../views/src/Tests/Plugin/MiniPagerTest.php | 12 + .../views/src/Tests/TaxonomyGlossaryTest.php | 58 +++ .../test_views/views.view.test_glossary.yml | 476 ++++++++++++++++-- .../views.view.test_invalid_tokens.yml | 53 ++ .../test_views/views.view.test_mini_pager.yml | 23 + .../views.view.test_taxonomy_glossary.yml | 191 +++++++ .../FunctionalJavascript/GlossaryViewTest.php | 129 +++++ .../views/Handler/ContextualFilterTest.php | 75 +++ .../src/Kernel/Handler/FilterCombineTest.php | 95 ++++ .../tests/src/Kernel/TokenReplaceTest.php | 21 +- .../Unit/Plugin/field/FieldPluginBaseTest.php | 40 +- core/modules/views/views.tokens.inc | 12 +- .../views_ui/src/Tests/DisplayCRUDTest.php | 10 + .../views_ui/src/Tests/FieldUITest.php | 18 + core/modules/views_ui/src/ViewEditForm.php | 2 +- core/phpunit.xml.dist | 4 +- .../testing_missing_dependencies.info.yml | 10 + .../Ajax/AjaxTest.php | 85 ++++ .../Ajax/AjaxThemeTest.php | 45 -- .../Core/MachineNameTest.php | 116 +++++ .../EntityReferenceAutocompleteWidgetTest.php | 102 ++++ .../FunctionalJavascriptTests/JSWebAssert.php | 11 + .../KernelTests/Core/Database/FetchTest.php | 13 + .../Core/Datetime/FormatDateTest.php | 92 ++++ .../KernelTests/Core/File/DirectoryTest.php | 13 + core/tests/Drupal/Tests/BrowserTestBase.php | 9 +- .../Core/Asset/LibraryDiscoveryParserTest.php | 2 +- .../Cache/Context/SessionCacheContextTest.php | 85 ++++ .../Core/Entity/EntityResolverManagerTest.php | 21 + .../PathRootsSubscriberTest.php | 5 + .../Tests/Core/Form/FormBuilderTest.php | 23 + .../Routing/ContentTypeHeaderMatcherTest.php | 25 +- .../classy/templates/layout/html.html.twig | 2 +- .../admin/image-crop-summary.html.twig | 6 +- .../admin/image-resize-summary.html.twig | 6 +- .../admin/image-rotate-summary.html.twig | 3 +- .../admin/image-scale-summary.html.twig | 6 +- .../stable/templates/layout/html.html.twig | 2 +- 378 files changed, 11434 insertions(+), 5542 deletions(-) create mode 100644 core/lib/Drupal/Core/Test/TestStatus.php create mode 100644 core/modules/action/tests/src/Kernel/Plugin/migrate/source/ActionTest.php delete mode 100644 core/modules/action/tests/src/Unit/Plugin/migrate/source/ActionTest.php create mode 100644 core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorFeedTest.php create mode 100644 core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorItemTest.php delete mode 100644 core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/AggregatorItemTest.php delete mode 100644 core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d6/AggregatorFeedTest.php delete mode 100644 core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d7/AggregatorFeedTest.php create mode 100644 core/modules/ban/tests/src/Kernel/Plugin/migrate/source/d7/BlockedIpsTest.php delete mode 100644 core/modules/ban/tests/src/Unit/Plugin/migrate/source/d7/BlockedIpsTest.php create mode 100644 core/modules/block/tests/src/Kernel/Plugin/migrate/source/BlockTest.php delete mode 100644 core/modules/block/tests/src/Unit/Plugin/migrate/source/BlockTest.php create mode 100644 core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d6/BoxTest.php create mode 100644 core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d7/BlockCustomTest.php delete mode 100644 core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d6/BoxTest.php delete mode 100644 core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d7/BlockCustomTest.php create mode 100644 core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php create mode 100644 core/modules/book/tests/src/Kernel/Plugin/migrate/source/d6/BookTest.php delete mode 100644 core/modules/book/tests/src/Unit/Plugin/migrate/source/d6/BookTest.php create mode 100644 core/modules/ckeditor/tests/modules/src/Plugin/Filter/TestAttributeFilter.php create mode 100644 core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php create mode 100644 core/modules/contact/tests/src/Kernel/Plugin/migrate/source/ContactCategoryTest.php create mode 100644 core/modules/contact/tests/src/Kernel/Plugin/migrate/source/d6/ContactSettingsTest.php delete mode 100644 core/modules/contact/tests/src/Unit/Plugin/migrate/source/ContactCategoryTest.php delete mode 100644 core/modules/contact/tests/src/Unit/Plugin/migrate/source/d6/ContactSettingsTest.php create mode 100644 core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php create mode 100644 core/modules/datetime_range/datetime_range.post_update.php create mode 100644 core/modules/datetime_range/tests/src/Kernel/SeparatorTranslationTest.php create mode 100644 core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php create mode 100644 core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml create mode 100644 core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml create mode 100644 core/modules/editor/tests/editor_private_test/editor_private_test.info.yml create mode 100644 core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php create mode 100644 core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php create mode 100644 core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstanceTest.php create mode 100644 core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldTest.php rename core/modules/field/tests/src/{Unit => Kernel}/Plugin/migrate/source/d7/FieldInstancePerFormDisplayTest.php (79%) create mode 100644 core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php rename core/modules/field/tests/src/{Unit => Kernel}/Plugin/migrate/source/d7/FieldInstanceTest.php (51%) rename core/modules/field/tests/src/{Unit => Kernel}/Plugin/migrate/source/d7/FieldTest.php (52%) rename core/modules/field/tests/src/{Unit => Kernel}/Plugin/migrate/source/d7/ViewModeTest.php (69%) delete mode 100644 core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php delete mode 100644 core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php delete mode 100644 core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstanceTest.php delete mode 100644 core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldTest.php delete mode 100644 core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php create mode 100644 core/modules/file/tests/src/Kernel/Migrate/process/d6/CckFileTest.php create mode 100644 core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/FileTest.php create mode 100644 core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadInstanceTest.php create mode 100644 core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadTest.php create mode 100644 core/modules/file/tests/src/Kernel/Plugin/migrate/source/d7/FileTest.php delete mode 100644 core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/FileTest.php delete mode 100644 core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadInstanceTest.php delete mode 100644 core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadTest.php delete mode 100644 core/modules/file/tests/src/Unit/Plugin/migrate/source/d7/FileTest.php create mode 100644 core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d6/FilterFormatTest.php create mode 100644 core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d7/FilterFormatTest.php delete mode 100644 core/modules/filter/tests/src/Unit/Plugin/migrate/source/d6/FilterFormatTest.php delete mode 100644 core/modules/filter/tests/src/Unit/Plugin/migrate/source/d7/FilterFormatTest.php create mode 100644 core/modules/hal/tests/src/Kernel/EntityTranslationNormalizeTest.php create mode 100644 core/modules/image/tests/src/Kernel/Plugin/migrate/source/d6/ImageCachePresetTest.php create mode 100644 core/modules/image/tests/src/Kernel/Plugin/migrate/source/d7/ImageStylesTest.php delete mode 100644 core/modules/image/tests/src/Unit/Plugin/migrate/source/d6/ImageCachePresetTest.php delete mode 100644 core/modules/image/tests/src/Unit/Plugin/migrate/source/d7/MigrateImageStylesTest.php rename core/modules/menu_link_content/migration_templates/{menu_links.yml => d6_menu_links.yml} (90%) create mode 100644 core/modules/menu_link_content/migration_templates/d7_menu_links.yml create mode 100644 core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php create mode 100644 core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/source/MenuLinkTest.php create mode 100644 core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/d7/InternalUriTest.php delete mode 100644 core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/MenuLinkSourceTest.php create mode 100644 core/modules/migrate/src/Plugin/migrate/process/Download.php create mode 100644 core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml create mode 100644 core/modules/migrate/tests/modules/migrate_entity_test/src/Entity/StringIdEntityTest.php create mode 100644 core/modules/migrate/tests/modules/migrate_prepare_row_test/src/Plugin/migrate/process/TestSkipRowProcess.php create mode 100644 core/modules/migrate/tests/src/Kernel/process/DownloadTest.php create mode 100644 core/modules/migrate/tests/src/Kernel/process/ExtractTest.php create mode 100644 core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php create mode 100644 core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php create mode 100644 core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d8/ConfigTest.php create mode 100644 core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeByNodeTypeTest.php rename core/modules/node/tests/src/{Unit => Kernel}/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php (50%) rename core/modules/node/tests/src/{Unit => Kernel}/Plugin/migrate/source/d6/NodeRevisionTest.php (50%) create mode 100644 core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php create mode 100644 core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTranslationTest.php create mode 100644 core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTypeTest.php create mode 100644 core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/ViewModeTest.php create mode 100644 core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php create mode 100644 core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTypeTest.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTestBase.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTranslationTest.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTypeTest.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/ViewModeTest.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTest.php delete mode 100644 core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTypeTest.php create mode 100644 core/modules/path/tests/src/Kernel/Plugin/migrate/source/d6/UrlAliasTest.php create mode 100644 core/modules/path/tests/src/Kernel/Plugin/migrate/source/d7/UrlAliasTest.php delete mode 100644 core/modules/path/tests/src/Unit/Migrate/UrlAliasTestBase.php delete mode 100644 core/modules/path/tests/src/Unit/Migrate/d6/UrlAliasTest.php delete mode 100644 core/modules/path/tests/src/Unit/Migrate/d7/UrlAliasTest.php create mode 100644 core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetTest.php create mode 100644 core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetUsersTest.php create mode 100644 core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutTest.php delete mode 100644 core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetTest.php delete mode 100644 core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetUsersTest.php delete mode 100644 core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutTest.php create mode 100644 core/modules/simpletest/tests/src/Functional/ThroughUITest.php rename core/modules/statistics/migration_templates/{d6_statistics_settings.yml => statistics_settings.yml} (92%) create mode 100644 core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php rename core/modules/system/migration_templates/{menu.yml => d6_menu.yml} (91%) create mode 100644 core/modules/system/migration_templates/d7_menu.yml create mode 100644 core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php create mode 100644 core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameForm.php create mode 100644 core/modules/system/tests/src/Kernel/Form/FormElementLabelTest.php create mode 100644 core/modules/system/tests/src/Kernel/Installer/InstallerMissingDependenciesTest.php rename core/modules/system/tests/src/Kernel/Migrate/{ => d6}/MigrateMenuTest.php (75%) create mode 100644 core/modules/system/tests/src/Kernel/Migrate/d7/MigrateMenuTest.php create mode 100644 core/modules/system/tests/src/Kernel/Plugin/migrate/source/MenuTest.php delete mode 100644 core/modules/system/tests/src/Unit/Plugin/migrate/source/MenuTest.php create mode 100644 core/modules/taxonomy/src/Tests/TermAutocompleteTest.php create mode 100644 core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php create mode 100644 core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php create mode 100644 core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml create mode 100644 core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml create mode 100644 core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php create mode 100644 core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php create mode 100644 core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php rename core/modules/taxonomy/tests/src/{Unit/Migrate => Kernel/Plugin/migrate/source}/d6/TermNodeTest.php (54%) create mode 100644 core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/VocabularyTest.php create mode 100644 core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/VocabularyTest.php delete mode 100644 core/modules/taxonomy/tests/src/Unit/Migrate/TermSourceWithVocabularyFilterTest.php delete mode 100644 core/modules/taxonomy/tests/src/Unit/Migrate/TermTest.php delete mode 100644 core/modules/taxonomy/tests/src/Unit/Migrate/TermTestBase.php delete mode 100644 core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php delete mode 100644 core/modules/taxonomy/tests/src/Unit/Migrate/d7/VocabularyTest.php create mode 100644 core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerNodeTest.php create mode 100644 core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerUserTest.php delete mode 100644 core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerNodeTest.php delete mode 100644 core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerUserTest.php create mode 100644 core/modules/user/src/Plugin/migrate/process/UserLangcode.php create mode 100644 core/modules/user/tests/src/Kernel/Plugin/migrate/source/ProfileFieldTest.php create mode 100644 core/modules/user/tests/src/Kernel/Plugin/migrate/source/UserPictureInstanceTest.php rename core/modules/user/tests/src/{Unit/Migrate => Kernel/Plugin/migrate/source}/d6/ProfileFieldValuesTest.php (55%) create mode 100644 core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/RoleTest.php create mode 100644 core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureFileTest.php create mode 100644 core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureTest.php create mode 100644 core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserTest.php create mode 100644 core/modules/user/tests/src/Kernel/Plugin/migrate/source/d7/UserTest.php delete mode 100644 core/modules/user/tests/src/Unit/Migrate/ProfileFieldTest.php delete mode 100644 core/modules/user/tests/src/Unit/Migrate/UserPictureInstanceTest.php delete mode 100644 core/modules/user/tests/src/Unit/Migrate/d6/RoleTest.php delete mode 100644 core/modules/user/tests/src/Unit/Migrate/d6/UserPictureFileTest.php delete mode 100644 core/modules/user/tests/src/Unit/Migrate/d6/UserPictureTest.php delete mode 100644 core/modules/user/tests/src/Unit/Migrate/d6/UserTest.php delete mode 100644 core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php create mode 100644 core/modules/views/src/Tests/TaxonomyGlossaryTest.php create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.test_invalid_tokens.yml create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.test_taxonomy_glossary.yml create mode 100644 core/modules/views/tests/src/FunctionalJavascript/GlossaryViewTest.php create mode 100644 core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/ContextualFilterTest.php create mode 100644 core/profiles/testing_missing_dependencies/testing_missing_dependencies.info.yml create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php delete mode 100644 core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxThemeTest.php create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php create mode 100644 core/tests/Drupal/KernelTests/Core/Datetime/FormatDateTest.php create mode 100644 core/tests/Drupal/Tests/Core/Cache/Context/SessionCacheContextTest.php diff --git a/core/.eslintrc b/core/.eslintrc index 7e88b38d0..9b34952fd 100644 --- a/core/.eslintrc +++ b/core/.eslintrc @@ -80,7 +80,7 @@ "space-infix-ops": 2, "space-unary-ops": [2, { "words": true, "nonwords": false }], "spaced-comment": [2, "always"], - "strict": 2, + "strict": [2, "function"], "yoda": [2, "never"], // Warnings. "max-nested-callbacks": [1, 3], diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index fa89b9d37..c3d5f5622 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -206,6 +206,8 @@ Entity API - Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago - Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch - Sascha Grossenbacher 'Berdir' https://www.drupal.org/u/berdir +- Francesco Placella 'plach' https://www.drupal.org/u/plach +- Tobias Stöckler 'tstoeckler' https://www.drupal.org/u/tstoeckler Extension API - ? @@ -277,11 +279,13 @@ Migrate - Ben Dougherty 'benjy' https://www.drupal.org/u/benjy - Michael Anello 'ultimike' https://www.drupal.org/u/ultimike - Mike Ryan 'mikeryan' https://www.drupal.org/u/mikeryan +- Vicki Spagnolo 'quietone' https://www.drupal.org/u/quietone Migrate (Drupal) - Ben Dougherty 'benjy' https://www.drupal.org/u/benjy - Michael Anello 'ultimike' https://www.drupal.org/u/ultimike - Mike Ryan 'mikeryan' https://www.drupal.org/u/mikeryan +- Vicki Spagnolo 'quietone' https://www.drupal.org/u/quietone Menu - Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner @@ -366,11 +370,6 @@ Shortcut - Tobias Stöckler 'tstoeckler' https://www.drupal.org/u/tstoeckler - Jibran Ijaz 'jibran' https://www.drupal.org/u/jibran -Simpletest -- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun -- Sascha Grossenbacher 'Berdir' https://www.drupal.org/u/berdir -- Alex Pott 'alexpott' https://www.drupal.org/u/alexpott - Stable - Scott Reeves 'Cottser' https://www.drupal.org/u/cottser @@ -394,12 +393,17 @@ Taxonomy Telephone - Dave Reid 'dave-reid' https://www.drupal.org/u/dave-reid +Testing framework +- Alex Pott 'alexpott' https://www.drupal.org/u/alexpott +- Sascha Grossenbacher 'Berdir' https://www.drupal.org/u/berdir +- Klaus Purer 'klausi' https://www.drupal.org/u/klausi +- Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner + Text Field - ? Theme API - Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia -- Scott Reeves 'Cottser' https://www.drupal.org/u/cottser - Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx - Joël Pittet 'joelpittet' https://www.drupal.org/u/joelpittet - Lauri Eskola 'lauriii' https://www.drupal.org/u/lauriii @@ -523,7 +527,6 @@ participate in mentoring. - Lucas Hedding 'heddn' https://www.drupal.org/u/heddn - Valery Lourie 'valthebald' https://www.drupal.org/u/valthebald - Alina Mackenzie 'alimac' https://www.drupal.org/u/alimac -- Chris McCafferty 'cilefen' https://www.drupal.org/u/cilefen - Jess Myrbo 'xjm' https://www.drupal.org/u/xjm - Andrea Soper 'ZenDoodles' https://www.drupal.org/u/zendoodles - Cathy Theys 'YesCT' https://www.drupal.org/u/yesct diff --git a/core/includes/form.inc b/core/includes/form.inc index d44f404cc..7db0ff10d 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -511,8 +511,6 @@ function template_preprocess_form_element_label(&$variables) { $variables['title'] = ['#markup' => $element['#title']]; } - $variables['attributes'] = array(); - // Pass elements title_display to template. $variables['title_display'] = $element['#title_display']; diff --git a/core/includes/install.inc b/core/includes/install.inc index 3a9c2bce7..e243c184f 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -930,27 +930,35 @@ function drupal_requirements_url($severity) { */ function drupal_check_profile($profile) { $info = install_profile_info($profile); - // Collect requirement testing results. $requirements = array(); - // Performs an ExtensionDiscovery scan as the system module is unavailable and // we don't yet know where all the modules are located. // @todo Remove as part of https://www.drupal.org/node/2186491 - $listing = new ExtensionDiscovery(\Drupal::root()); - $module_list = $listing->scan('module'); - foreach ($info['dependencies'] as $module) { - $file = \Drupal::root() . '/' . $module_list[$module]->getPath() . "/$module.install"; - if (is_file($file)) { - require_once $file; - } - $function = $module . '_requirements'; + $drupal_root = \Drupal::root(); + $module_list = (new ExtensionDiscovery($drupal_root))->scan('module'); - drupal_classloader_register($module, $module_list[$module]->getPath()); - if (function_exists($function)) { - $requirements = array_merge($requirements, $function('install')); + foreach ($info['dependencies'] as $module) { + // If the module is in the module list we know it exists and we can continue + // including and registering it. + // @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory() + if (isset($module_list[$module])) { + $function = $module . '_requirements'; + $module_path = $module_list[$module]->getPath(); + $install_file = "$drupal_root/$module_path/$module.install"; + + if (is_file($install_file)) { + require_once $install_file; + } + + drupal_classloader_register($module, $module_path); + + if (function_exists($function)) { + $requirements = array_merge($requirements, $function('install')); + } } } + return $requirements; } diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 005275b48..092266182 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -81,7 +81,7 @@ class Drupal { /** * The current system version. */ - const VERSION = '8.2.1'; + const VERSION = '8.2.2'; /** * Core API compatibility. diff --git a/core/lib/Drupal/Component/Utility/Random.php b/core/lib/Drupal/Component/Utility/Random.php index 3a0083909..1aeeb958d 100644 --- a/core/lib/Drupal/Component/Utility/Random.php +++ b/core/lib/Drupal/Component/Utility/Random.php @@ -288,7 +288,6 @@ class Random { // Make a perfect circle in the image middle. $color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255)); $smaller_dimension = min($width, $height); - $smaller_dimension = ($smaller_dimension % 2) ? $smaller_dimension : $smaller_dimension; imageellipse($im, $width / 2, $height / 2, $smaller_dimension, $smaller_dimension, $color); $save_function = 'image' . ($extension == 'jpg' ? 'jpeg' : $extension); diff --git a/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php index 53f326d08..5548c7f90 100644 --- a/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php +++ b/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php @@ -97,12 +97,15 @@ class CsrfRequestHeaderAccessCheck implements AccessCheckInterface { && $account->isAuthenticated() && $this->sessionConfiguration->hasSession($request) ) { + if (!$request->headers->has('X-CSRF-Token')) { + return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0); + } $csrf_token = $request->headers->get('X-CSRF-Token'); // @todo Remove validate call using 'rest' in 8.3. // Kept here for sessions active during update. if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY) && !$this->csrfToken->validate($csrf_token, 'rest')) { - return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0); + return AccessResult::forbidden()->setReason('X-CSRF-Token request header is invalid')->setCacheMaxAge(0); } } // Let other access checkers decide if the request is legit. diff --git a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php index 5c5be4cfb..30b65fac4 100644 --- a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php @@ -2,6 +2,8 @@ namespace Drupal\Core\Ajax; +use Drupal\Component\Render\PlainTextOutput; + /** * Defines an AJAX command to open certain content in a dialog. * @@ -69,6 +71,7 @@ class OpenDialogCommand implements CommandInterface, CommandWithAttachedAssetsIn * populated automatically from the current request. */ public function __construct($selector, $title, $content, array $dialog_options = array(), $settings = NULL) { + $title = PlainTextOutput::renderFromHtml($title); $dialog_options += array('title' => $title); $this->selector = $selector; $this->content = $content; diff --git a/core/lib/Drupal/Core/Asset/AssetResolver.php b/core/lib/Drupal/Core/Asset/AssetResolver.php index 9e1bcce2b..863f97183 100644 --- a/core/lib/Drupal/Core/Asset/AssetResolver.php +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php @@ -214,7 +214,7 @@ class AssetResolver implements AssetResolverInterface { // hook_library_info_alter(). Additionally add the current language to // support translation of JavaScript files via hook_js_alter(). $libraries_to_load = $this->getLibrariesToLoad($assets); - $cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load) . serialize($assets->getLibraries())) . (int) (count($assets->getSettings()) > 0) . (int) $optimize; + $cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize; if ($cached = $this->cache->get($cid)) { list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data; diff --git a/core/lib/Drupal/Core/Cache/Context/SessionCacheContext.php b/core/lib/Drupal/Core/Cache/Context/SessionCacheContext.php index f93e3a5f9..c8b102717 100644 --- a/core/lib/Drupal/Core/Cache/Context/SessionCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/SessionCacheContext.php @@ -2,6 +2,8 @@ namespace Drupal\Core\Cache\Context; +use Drupal\Component\Utility\Crypt; + /** * Defines the SessionCacheContext service, for "per session" caching. * @@ -20,7 +22,8 @@ class SessionCacheContext extends RequestStackCacheContextBase { * {@inheritdoc} */ public function getContext() { - return $this->requestStack->getCurrentRequest()->getSession()->getId(); + $sid = $this->requestStack->getCurrentRequest()->getSession()->getId(); + return Crypt::hashBase64($sid); } } diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php index 6fd999e28..c952630d9 100644 --- a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php @@ -120,7 +120,7 @@ class Schema extends DatabaseSchema { // 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. - // @see DatabaseConnection_mysql + // @see \Drupal\Core\Database\Driver\mysql\Schema if (!empty($info['collation'])) { $sql .= ' COLLATE ' . $info['collation']; } diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php index 72f35da8d..9b987546d 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php @@ -20,8 +20,8 @@ class Schema extends DatabaseSchema { /** * A cache of information about blob columns and sequences of tables. * - * This is collected by DatabaseConnection_pgsql->queryTableInformation(), - * by introspecting the database. + * This is collected by Schema::queryTableInformation(), by introspecting the + * database. * * @see \Drupal\Core\Database\Driver\pgsql\Schema::queryTableInformation() * @var array diff --git a/core/lib/Drupal/Core/Database/StatementPrefetch.php b/core/lib/Drupal/Core/Database/StatementPrefetch.php index ab6236a19..6864f00ef 100644 --- a/core/lib/Drupal/Core/Database/StatementPrefetch.php +++ b/core/lib/Drupal/Core/Database/StatementPrefetch.php @@ -306,7 +306,7 @@ class StatementPrefetch implements \Iterator, StatementInterface { return $this->fetchOptions['object']; case \PDO::FETCH_COLUMN: if (isset($this->columnNames[$this->fetchOptions['column']])) { - return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]]; + return $this->currentRow[$this->columnNames[$this->fetchOptions['column']]]; } else { return; diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityController.php b/core/lib/Drupal/Core/Entity/Controller/EntityController.php index 4ca97e43e..d65166e3a 100644 --- a/core/lib/Drupal/Core/Entity/Controller/EntityController.php +++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php @@ -301,7 +301,7 @@ class EntityController implements ContainerInjectionInterface { * @param array $bundles * An array of bundle information. * @param \Drupal\Core\Entity\EntityTypeInterface $bundle_entity_type - * The ID of the bundle entity type. + * The bundle entity type definition. * * @return array * The expanded array of bundle information. diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php index b2f64df77..cd90ecce0 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php @@ -236,7 +236,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn // Flag entity level violations. foreach ($violations->getEntityViolations() as $violation) { /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */ - $form_state->setErrorByName('', $violation->getMessage()); + $form_state->setError($form, $violation->getMessage()); } $this->flagWidgetsErrorsFromViolations($violations, $form, $form_state); diff --git a/core/lib/Drupal/Core/Entity/EntityResolverManager.php b/core/lib/Drupal/Core/Entity/EntityResolverManager.php index 79e62d1c3..847a480e4 100644 --- a/core/lib/Drupal/Core/Entity/EntityResolverManager.php +++ b/core/lib/Drupal/Core/Entity/EntityResolverManager.php @@ -166,7 +166,10 @@ class EntityResolverManager { list($entity_type) = explode('.', $entity_form, 2); } - if (isset($entity_type) && isset($this->getEntityTypes()[$entity_type])) { + // Do not add parameter information if the route does not declare a + // parameter in the first place. This is the case for add forms, for + // example. + if (isset($entity_type) && isset($this->getEntityTypes()[$entity_type]) && (strpos($route->getPath(), '{' . $entity_type . '}') !== FALSE)) { $parameter_definitions = $route->getOption('parameters') ?: array(); // First try to figure out whether there is already a parameter upcasting diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php index 60ae7413f..85e205aac 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -358,11 +358,18 @@ use Drupal\node\Entity\NodeType; * \Drupal\Core\Entity\EntityType. * * @section sec_routes Entity routes - * Entity routes, like other routes, are defined in *.routing.yml files; see - * the @link routing Routing API @endlink topic for more information. Entities - * may alternatively use an auto route provider class; there is an example of - * this at the end of this section. If providing routes directly, here is a - * typical entry, for the block configure form: + * Entity routes can be defined in *.routing.yml files, like any other route: + * see the @link routing Routing API @endlink topic for more information. + * Another option for entity routes is to use a route provider class, and + * reference it in the annotations on the entity class: see the end of this + * section for an example. + * + * It's possible to use both a YAML file and a provider class for entity + * routes, at the same time. Avoid duplicating route names between the two: + * if a duplicate route name is found in both locations, the one in the YAML + * file takes precedence; regardless, such duplication can be confusing. + * + * Here's an example YAML route specification, for the block configure form: * @code * entity.block.edit_form: * path: '/admin/structure/block/manage/{block}' @@ -372,7 +379,7 @@ use Drupal\node\Entity\NodeType; * requirements: * _entity_access: 'block.update' * @endcode - * Some notes: + * Some notes on this example: * - path: The {block} in the path is a placeholder, which (for an entity) must * always take the form of {machine_name_of_entity_type}. In the URL, the * placeholder value will be the ID of an entity item. When the route is used, @@ -389,19 +396,21 @@ use Drupal\node\Entity\NodeType; * "form" = { * "default" = "Drupal\block\BlockForm", * @endcode - * - Instead of putting the routes for your entity in a *.routing.yml file, you - * can instead use a route provider class. - * \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider provides canonical, - * edit-form, and delete-form routes; - * \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider provides the same + * If instead of YAML you want to use a route provider class: + * - \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider provides canonical, + * edit-form, and delete-form routes. + * - \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider provides the same * routes, set up to use the administrative theme for edit and delete pages. - * You can also create your own class. To use a route provider class, add - * lines like the following to your entity annotation: - * @code - * handlers = { - * "route_provider" = { - * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", - * @endcode + * - You can also create your own class, extending one of these two classes if + * you only want to modify their behaviour slightly. + * + * To register any route provider class, add lines like the following to your + * entity class annotation: + * @code + * handlers = { + * "route_provider" = { + * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", + * @endcode * * @section bundle Defining a content entity bundle * For entity types that use bundles, such as Node (bundles are content types) diff --git a/core/lib/Drupal/Core/EventSubscriber/PathRootsSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathRootsSubscriber.php index 824c4e89b..a0bd7b064 100644 --- a/core/lib/Drupal/Core/EventSubscriber/PathRootsSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/PathRootsSubscriber.php @@ -20,7 +20,7 @@ class PathRootsSubscriber implements EventSubscriberInterface { * * @var array */ - protected $pathRoots; + protected $pathRoots = []; /** * The state key value store. @@ -58,7 +58,7 @@ class PathRootsSubscriber implements EventSubscriberInterface { */ public function onRouteFinished() { $this->state->set('router.path_roots', array_keys($this->pathRoots)); - unset($this->pathRoots); + $this->pathRoots = []; } /** diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php index f901cb748..4e77202b6 100644 --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php @@ -22,6 +22,8 @@ use Drupal\Core\TypedData\ListInterface; * * When implementing this interface which extends Traversable, make sure to list * IteratorAggregate or Iterator before this interface in the implements clause. + * + * @see \Drupal\Core\Field\FieldItemInterface */ interface FieldItemListInterface extends ListInterface, AccessibleInterface { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index 398d26088..63bd90d42 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -268,7 +268,26 @@ class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterf */ public static function generateSampleValue(FieldDefinitionInterface $field_definition) { $manager = \Drupal::service('plugin.manager.entity_reference_selection'); - if ($referenceable = $manager->getSelectionHandler($field_definition)->getReferenceableEntities()) { + + // Instead of calling $manager->getSelectionHandler($field_definition) + // replicate the behavior to be able to override the sorting settings. + $options = array( + 'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'), + 'handler' => $field_definition->getSetting('handler'), + 'handler_settings' => $field_definition->getSetting('handler_settings') ?: array(), + 'entity' => NULL, + ); + + $entity_type = \Drupal::entityManager()->getDefinition($options['target_type']); + $options['handler_settings']['sort'] = [ + 'field' => $entity_type->getKey('id'), + 'direction' => 'DESC', + ]; + $selection_handler = $manager->getInstance($options); + + // Select a random number of references between the last 50 referenceable + // entities created. + if ($referenceable = $selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 50)) { $group = array_rand($referenceable); $values['target_id'] = array_rand($referenceable[$group]); return $values; diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php index b5e16c14d..3824810a9 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php @@ -87,11 +87,14 @@ class EntityReferenceAutocompleteWidget extends WidgetBase { $entity = $items->getEntity(); $referenced_entities = $items->referencedEntities(); + // Append the match operation to the selection settings. + $selection_settings = $this->getFieldSetting('handler_settings') + ['match_operator' => $this->getSetting('match_operator')]; + $element += array( '#type' => 'entity_autocomplete', '#target_type' => $this->getFieldSetting('target_type'), '#selection_handler' => $this->getFieldSetting('handler'), - '#selection_settings' => $this->getFieldSetting('handler_settings'), + '#selection_settings' => $selection_settings, // Entity reference field items are handling validation themselves via // the 'ValidReference' constraint. '#validate_reference' => FALSE, diff --git a/core/lib/Drupal/Core/File/FileSystem.php b/core/lib/Drupal/Core/File/FileSystem.php index 076bc066d..a2fafbb60 100644 --- a/core/lib/Drupal/Core/File/FileSystem.php +++ b/core/lib/Drupal/Core/File/FileSystem.php @@ -181,8 +181,10 @@ class FileSystem implements FileSystemInterface { // If recursive, create each missing component of the parent directory // individually and set the mode explicitly to override the umask. if ($recursive) { - // Ensure the path is using DIRECTORY_SEPARATOR. - $uri = str_replace('/', DIRECTORY_SEPARATOR, $uri); + // Ensure the path is using DIRECTORY_SEPARATOR, and trim off any trailing + // slashes because they can throw off the loop when creating the parent + // directories. + $uri = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $uri), DIRECTORY_SEPARATOR); // Determine the components of the path. $components = explode(DIRECTORY_SEPARATOR, $uri); // If the filepath is absolute the first component will be empty as there diff --git a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php index 5e05ec05c..8e5c95a2c 100644 --- a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php +++ b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php @@ -152,7 +152,7 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface { 129 => 'application/x-iphone', 130 => 'application/x-iso9660-image', 131 => 'application/x-java-jnlp-file', - 132 => 'application/x-javascript', + 132 => 'application/javascript', 133 => 'application/x-jmol', 134 => 'application/x-kchart', 135 => 'application/x-killustrator', diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 544ee94b1..878967b9c 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -316,7 +316,7 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS // In case the post request exceeds the configured allowed size // (post_max_size), the post request is potentially broken. Add some // protection against that and at the same time have a nice error message. - if ($ajax_form_request && !isset($form_state->getUserInput()['form_id'])) { + if ($ajax_form_request && !$request->request->has('form_id')) { throw new BrokenPostRequestException($this->getFileUploadMaxSize()); } @@ -327,7 +327,9 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS // then passed through // \Drupal\Core\Form\FormAjaxResponseBuilderInterface::buildResponse() to // build a proper AJAX response. - if ($ajax_form_request && $form_state->isProcessingInput()) { + // Only do this when the form ID matches, since there is no guarantee from + // $ajax_form_request that it's an AJAX request for this particular form. + if ($ajax_form_request && $form_state->isProcessingInput() && $request->request->get('form_id') == $form_id) { throw new FormAjaxException($form, $form_state); } diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php index 535d9bb51..3888c7938 100644 --- a/core/lib/Drupal/Core/Render/Element/Link.php +++ b/core/lib/Drupal/Core/Render/Element/Link.php @@ -92,7 +92,7 @@ class Link extends RenderElement { /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */ $link_generator = \Drupal::service('link_generator'); $generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options)); - $element['#markup'] = $generated_link->getGeneratedLink(); + $element['#markup'] = $generated_link; $generated_link->merge(BubbleableMetadata::createFromRenderArray($element)) ->applyTo($element); } diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php index 2da6ef96d..75bcab554 100644 --- a/core/lib/Drupal/Core/Render/Element/Table.php +++ b/core/lib/Drupal/Core/Render/Element/Table.php @@ -33,7 +33,8 @@ use Drupal\Component\Utility\Html as HtmlUtility; * '#header' => array($this->t('Name'), $this->t('Phone')), * ); * - * for ($i=1; $i<=4; $i++) { + * for ($i = 1; $i <= 4; $i++) { + * $form['contacts'][$i]['#attributes'] = array('class' => array('foo', 'baz')); * $form['contacts'][$i]['name'] = array( * '#type' => 'textfield', * '#title' => $this->t('Name'), @@ -46,6 +47,11 @@ use Drupal\Component\Utility\Html as HtmlUtility; * '#title_display' => 'invisible', * ); * } + * + * $form['contacts'][]['colspan_example'] = array( + * '#plain_text' => 'Colspan Example', + * '#wrapper_attributes' => array('colspan' => 2, 'class' => array('foo', 'bar')), + * ); * @endcode * @see \Drupal\Core\Render\Element\Tableselect * diff --git a/core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php b/core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php index e7efcdd67..13e18f35a 100644 --- a/core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php +++ b/core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php @@ -42,7 +42,12 @@ class ContentTypeHeaderMatcher implements RouteFilterInterface { // We do not throw a // \Symfony\Component\Routing\Exception\ResourceNotFoundException here // because we don't want to return a 404 status code, but rather a 415. - throw new UnsupportedMediaTypeHttpException('No route found that matches "Content-Type: ' . $request->headers->get('Content-Type') . '"'); + if (!$request->headers->has('Content-Type')) { + throw new UnsupportedMediaTypeHttpException('No "Content-Type" request header specified'); + } + else { + throw new UnsupportedMediaTypeHttpException('No route found that matches "Content-Type: ' . $request->headers->get('Content-Type') . '"'); + } } /** diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php index fcadf32db..5f986021c 100644 --- a/core/lib/Drupal/Core/Session/SessionManager.php +++ b/core/lib/Drupal/Core/Session/SessionManager.php @@ -257,7 +257,8 @@ class SessionManager extends NativeSessionStorage implements SessionManagerInter // Unset the session cookies. $session_name = $this->getName(); $cookies = $this->requestStack->getCurrentRequest()->cookies; - if ($cookies->has($session_name)) { + // setcookie() can only be called when headers are not yet sent. + if ($cookies->has($session_name) && !headers_sent()) { $params = session_get_cookie_params(); setcookie($session_name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']); $cookies->remove($session_name); diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 3ebe01b36..8270c66d6 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -8,6 +8,7 @@ use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\Render\AttachmentsInterface; use Drupal\Core\Render\BubbleableMetadata; +use Drupal\Core\Render\Markup; use Drupal\Core\Render\RenderableInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\UrlGeneratorInterface; @@ -273,6 +274,11 @@ class TwigExtension extends \Twig_Extension { } $url->setOption('attributes', $attributes); } + // The text has been processed by twig already, convert it to a safe object + // for the render system. + if ($text instanceof \Twig_Markup) { + $text = Markup::create($text); + } $build = [ '#type' => 'link', '#title' => $text, diff --git a/core/lib/Drupal/Core/Test/TestStatus.php b/core/lib/Drupal/Core/Test/TestStatus.php new file mode 100644 index 000000000..b70dcdd1d --- /dev/null +++ b/core/lib/Drupal/Core/Test/TestStatus.php @@ -0,0 +1,63 @@ +2 indicates segfault timeout, or other type of system + * failure. + */ +class TestStatus { + + /** + * Signify that the test result was a passed test. + */ + const PASS = 0; + + /** + * Signify that the test result was a failed test. + */ + const FAIL = 1; + + /** + * Signify that the test result was an exception or code error. + * + * This means that the test runner was able to exit and report an error. + */ + const EXCEPTION = 2; + + /** + * Signify a system error where the test runner was unable to complete. + * + * Note that SYSTEM actually represents the lowest value of system errors, and + * the returned value could be as high as 127. Since that's the case, this + * constant should be used for range comparisons, and not just for equality. + * + * @see http://php.net/manual/en/pcntl.constants.php + */ + const SYSTEM = 3; + + /** + * Turns a status code into a human-readable string. + * + * @param int $status + * A test runner return code. + * + * @return string + * The human-readable version of the status code. + */ + public static function label($status) { + $statusMap = [ + static::PASS => 'pass', + static::FAIL => 'fail', + static::EXCEPTION => 'exception', + static::SYSTEM => 'error', + ]; + // For status 3 and higher, we want 'error.' + $label = $statusMap[$status > static::SYSTEM ? static::SYSTEM : $status]; + return $label; + } + +} diff --git a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php index cab7e7ce7..83aa4f2e0 100644 --- a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php +++ b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php @@ -31,7 +31,7 @@ interface LinkGeneratorInterface { * This keeps the context of the link title ('settings' in the example) for * translators. * - * @param string|array $text + * @param string|array|\Drupal\Component\Render\MarkupInterface $text * The link text for the anchor tag as a translated string or render array. * Strings will be sanitized automatically. If you need to output HTML in * the link text, use a render array or an already sanitized string such as diff --git a/core/misc/displace.js b/core/misc/displace.js index 3315f2ea5..3e89c563e 100644 --- a/core/misc/displace.js +++ b/core/misc/displace.js @@ -165,7 +165,7 @@ var placement = $el.offset()[horizontal ? 'left' : 'top']; // Subtract scroll distance from placement to get the distance // to the edge of the viewport. - placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal) ? 'Left' : 'Top'] || 0; + placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0; // Find the displacement value according to the edge. switch (edge) { // Left and top elements displace as a sum of their own offset value diff --git a/core/modules/action/tests/src/Kernel/Plugin/migrate/source/ActionTest.php b/core/modules/action/tests/src/Kernel/Plugin/migrate/source/ActionTest.php new file mode 100644 index 000000000..7f7b7ef8c --- /dev/null +++ b/core/modules/action/tests/src/Kernel/Plugin/migrate/source/ActionTest.php @@ -0,0 +1,62 @@ + 'Redirect to node list page', + 'type' => 'system', + 'callback' => 'system_goto_action', + 'parameters' => 'a:1:{s:3:"url";s:4:"node";}', + 'description' => 'Redirect to node list page', + ], + [ + 'aid' => 'Test notice email', + 'type' => 'system', + 'callback' => 'system_send_email_action', + 'parameters' => 'a:3:{s:9:"recipient";s:7:"%author";s:7:"subject";s:4:"Test";s:7:"message";s:4:"Test', + 'description' => 'Test notice email', + ], + [ + 'aid' => 'comment_publish_action', + 'type' => 'comment', + 'callback' => 'comment_publish_action', + 'parameters' => NULL, + 'description' => NULL, + ], + [ + 'aid' => 'node_publish_action', + 'type' => 'comment', + 'callback' => 'node_publish_action', + 'parameters' => NULL, + 'description' => NULL, + ], + ]; + // The expected results are identical to the source data. + $tests[0][1] = $tests[0][0]['actions']; + + return $tests; + } + +} diff --git a/core/modules/action/tests/src/Unit/Plugin/migrate/source/ActionTest.php b/core/modules/action/tests/src/Unit/Plugin/migrate/source/ActionTest.php deleted file mode 100644 index 3ef553e82..000000000 --- a/core/modules/action/tests/src/Unit/Plugin/migrate/source/ActionTest.php +++ /dev/null @@ -1,68 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'action', - ), - ); - - // We need to set up the database contents; it's easier to do that below. - - protected $expectedResults = array( - array( - 'aid' => 'Redirect to node list page', - 'type' => 'system', - 'callback' => 'system_goto_action', - 'parameters' => 'a:1:{s:3:"url";s:4:"node";}', - 'description' => 'Redirect to node list page', - ), - array( - 'aid' => 'Test notice email', - 'type' => 'system', - 'callback' => 'system_send_email_action', - 'parameters' => 'a:3:{s:9:"recipient";s:7:"%author";s:7:"subject";s:4:"Test";s:7:"message";s:4:"Test', - 'description' => 'Test notice email', - ), - array( - 'aid' => 'comment_publish_action', - 'type' => 'comment', - 'callback' => 'comment_publish_action', - 'parameters' => NULL, - 'description' => NULL, - ), - array( - 'aid' => 'node_publish_action', - 'type' => 'comment', - 'callback' => 'node_publish_action', - 'parameters' => NULL, - 'description' => NULL, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['actions'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorFeedTest.php b/core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorFeedTest.php new file mode 100644 index 000000000..d41f4f4b3 --- /dev/null +++ b/core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorFeedTest.php @@ -0,0 +1,60 @@ + 1, + 'title' => 'feed title 1', + 'url' => 'http://example.com/feed.rss', + 'refresh' => 900, + 'checked' => 0, + 'link' => 'http://example.com', + 'description' => 'A vague description', + 'image' => '', + 'etag' => '', + 'modified' => 0, + 'block' => 5, + ], + [ + 'fid' => 2, + 'title' => 'feed title 2', + 'url' => 'http://example.net/news.rss', + 'refresh' => 1800, + 'checked' => 0, + 'link' => 'http://example.net', + 'description' => 'An even more vague description', + 'image' => '', + 'etag' => '', + 'modified' => 0, + 'block' => 5, + ], + ]; + // The expected results are identical to the source data. + $tests[0]['expected_results'] = $tests[0]['database']['aggregator_feed']; + + return $tests; + } + +} diff --git a/core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorItemTest.php b/core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorItemTest.php new file mode 100644 index 000000000..09101f7cd --- /dev/null +++ b/core/modules/aggregator/tests/src/Kernel/Plugin/migrate/source/AggregatorItemTest.php @@ -0,0 +1,44 @@ + 1, + 'fid' => 1, + 'title' => 'This (three) weeks in Drupal Core - January 10th 2014', + 'link' => 'https://groups.drupal.org/node/395218', + 'author' => 'larowlan', + 'description' => "

What's new with Drupal 8?

", + 'timestamp' => 1389297196, + 'guid' => '395218 at https://groups.drupal.org', + ], + ]; + // The expected results are identical to the source data. + $tests[0]['expected_results'] = $tests[0]['database']['aggregator_item']; + + return $tests; + } + +} diff --git a/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/AggregatorItemTest.php b/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/AggregatorItemTest.php deleted file mode 100644 index edae6d673..000000000 --- a/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/AggregatorItemTest.php +++ /dev/null @@ -1,44 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'aggregator_item', - ), - ); - - protected $expectedResults = array( - array( - 'iid' => 1, - 'fid' => 1, - 'title' => 'This (three) weeks in Drupal Core - January 10th 2014', - 'link' => 'https://groups.drupal.org/node/395218', - 'author' => 'larowlan', - 'description' => "

What's new with Drupal 8?

", - 'timestamp' => 1389297196, - 'guid' => '395218 at https://groups.drupal.org', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['aggregator_item'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d6/AggregatorFeedTest.php b/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d6/AggregatorFeedTest.php deleted file mode 100644 index 85eaf8eeb..000000000 --- a/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d6/AggregatorFeedTest.php +++ /dev/null @@ -1,60 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_aggregator_feed', - ), - ); - - protected $expectedResults = array( - array( - 'fid' => 1, - 'title' => 'feed title 1', - 'url' => 'http://example.com/feed.rss', - 'refresh' => 900, - 'checked' => 0, - 'link' => 'http://example.com', - 'description' => 'A vague description', - 'image' => '', - 'etag' => '', - 'modified' => 0, - 'block' => 5, - ), - array( - 'fid' => 2, - 'title' => 'feed title 2', - 'url' => 'http://example.net/news.rss', - 'refresh' => 1800, - 'checked' => 0, - 'link' => 'http://example.net', - 'description' => 'An even more vague description', - 'image' => '', - 'etag' => '', - 'modified' => 0, - 'block' => 5, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['aggregator_feed'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d7/AggregatorFeedTest.php b/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d7/AggregatorFeedTest.php deleted file mode 100644 index e80b461a5..000000000 --- a/core/modules/aggregator/tests/src/Unit/Plugin/migrate/source/d7/AggregatorFeedTest.php +++ /dev/null @@ -1,62 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd7_aggregator_feed', - ), - ); - - protected $expectedResults = array( - array( - 'fid' => 1, - 'title' => 'feed title 1', - 'url' => 'http://example.com/feed.rss', - 'refresh' => 900, - 'checked' => 0, - 'queued' => 0, - 'link' => 'http://example.com', - 'description' => 'A vague description', - 'image' => '', - 'etag' => '', - 'modified' => 0, - 'block' => 5, - ), - array( - 'fid' => 2, - 'title' => 'feed title 2', - 'url' => 'http://example.net/news.rss', - 'refresh' => 1800, - 'checked' => 0, - 'queued' => 0, - 'link' => 'http://example.net', - 'description' => 'An even more vague description', - 'image' => '', - 'etag' => '', - 'modified' => 0, - 'block' => 5, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['aggregator_feed'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/ban/tests/src/Kernel/Plugin/migrate/source/d7/BlockedIpsTest.php b/core/modules/ban/tests/src/Kernel/Plugin/migrate/source/d7/BlockedIpsTest.php new file mode 100644 index 000000000..267cc3447 --- /dev/null +++ b/core/modules/ban/tests/src/Kernel/Plugin/migrate/source/d7/BlockedIpsTest.php @@ -0,0 +1,40 @@ + 1, + 'ip' => '127.0.0.1', + ] + ]; + $tests[0]['expected_data'] = [ + [ + 'ip' => '127.0.0.1', + ], + ]; + return $tests; + } + +} diff --git a/core/modules/ban/tests/src/Unit/Plugin/migrate/source/d7/BlockedIpsTest.php b/core/modules/ban/tests/src/Unit/Plugin/migrate/source/d7/BlockedIpsTest.php deleted file mode 100644 index 8ca8f58ee..000000000 --- a/core/modules/ban/tests/src/Unit/Plugin/migrate/source/d7/BlockedIpsTest.php +++ /dev/null @@ -1,43 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_blocked_ips', - ], - ]; - - protected $expectedResults = [ - [ - 'ip' => '127.0.0.1', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['blocked_ips'] = [ - [ - 'iid' => 1, - 'ip' => '127.0.0.1', - ] - ]; - parent::setUp(); - } - -} diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php index 3f3f92ce6..bece32dd0 100644 --- a/core/modules/block/src/BlockForm.php +++ b/core/modules/block/src/BlockForm.php @@ -237,7 +237,7 @@ class BlockForm extends EntityForm { // @todo Allow list of conditions to be configured in // https://www.drupal.org/node/2284687. $visibility = $this->entity->getVisibility(); - foreach ($this->manager->getDefinitions() as $condition_id => $definition) { + foreach ($this->manager->getDefinitionsForContexts($form_state->getTemporaryValue('gathered_contexts')) as $condition_id => $definition) { // Don't display the current theme condition. if ($condition_id == 'current_theme') { continue; diff --git a/core/modules/block/src/Controller/BlockLibraryController.php b/core/modules/block/src/Controller/BlockLibraryController.php index 57862fcc6..79d6eff8c 100644 --- a/core/modules/block/src/Controller/BlockLibraryController.php +++ b/core/modules/block/src/Controller/BlockLibraryController.php @@ -8,7 +8,6 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\Menu\LocalActionManagerInterface; use Drupal\Core\Plugin\Context\LazyContextRepository; -use Drupal\Core\Routing\RedirectDestinationInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -47,13 +46,6 @@ class BlockLibraryController extends ControllerBase { */ protected $localActionManager; - /** - * The redirect destination. - * - * @var \Drupal\Core\Routing\RedirectDestinationInterface - */ - protected $redirectDestination; - /** * Constructs a BlockLibraryController object. * @@ -65,15 +57,12 @@ class BlockLibraryController extends ControllerBase { * The current route match. * @param \Drupal\Core\Menu\LocalActionManagerInterface $local_action_manager * The local action manager. - * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination - * The redirect destination. */ - public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager, RedirectDestinationInterface $redirect_destination) { + public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager) { $this->blockManager = $block_manager; $this->routeMatch = $route_match; $this->localActionManager = $local_action_manager; $this->contextRepository = $context_repository; - $this->redirectDestination = $redirect_destination; } /** @@ -84,8 +73,7 @@ class BlockLibraryController extends ControllerBase { $container->get('plugin.manager.block'), $container->get('context.repository'), $container->get('current_route_match'), - $container->get('plugin.manager.menu.local_action'), - $container->get('redirect.destination') + $container->get('plugin.manager.menu.local_action') ); } @@ -148,10 +136,6 @@ class BlockLibraryController extends ControllerBase { if (isset($weight)) { $links['add']['query']['weight'] = $weight; } - $destination = $this->redirectDestination->get(); - if ($destination) { - $links['add']['query']['destination'] = $destination; - } $row['operations']['data'] = [ '#type' => 'operations', '#links' => $links, diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php index 41039dd1e..2427a71c4 100644 --- a/core/modules/block/src/Tests/BlockTest.php +++ b/core/modules/block/src/Tests/BlockTest.php @@ -128,49 +128,6 @@ class BlockTest extends BlockTestBase { $this->assertNoText($title, 'Block was not displayed to anonymous users on the front page.'); } - /** - * Tests adding a block from the library page with a destination query string. - */ - public function testAddBlockFromLibrary() { - $default_theme = $this->config('system.theme')->get('default'); - $help_url = Url::fromRoute('help.page', ['name' => 'block']); - // Set up the request so we land on the block help page after creation. - $options = [ - 'query' => [ - 'region' => 'sidebar_first', - 'destination' => $help_url->toString(), - ], - ]; - $this->drupalGet(Url::fromRoute('block.admin_library', ['theme' => $default_theme], $options)); - - $block_name = 'system_powered_by_block'; - $add_url = Url::fromRoute('block.admin_add', ['plugin_id' => $block_name, 'theme' => $default_theme]); - $links = $this->xpath('//a[contains(@href, :href)]', [':href' => $add_url->toString()]); - $this->assertEqual(1, count($links), 'Found one matching link'); - - list($path, $query_string) = explode('?', $links[0]['href'], 2); - parse_str($query_string, $query_parts); - $this->assertEqual(t('Place block'), (string) $links[0]); - $this->assertEqual($help_url->toString(), $query_parts['destination'], 'Expected destination query string is in href'); - - // Create a random title for the block. - $title = $this->randomMachineName(8); - $block_id = strtolower($this->randomMachineName(8)); - $edit = [ - 'id' => $block_id, - 'settings[label]' => $title, - ]; - // Create the block using the link parsed from the library page. - $this->drupalPostForm($this->getAbsoluteUrl($links[0]['href']), $edit, t('Save block')); - // Verify that we are redirected according to the original request. - $this->assertUrl($help_url); - - // Ensure that the block was created. - /** @var \Drupal\block\BlockInterface $block */ - $block = Block::load($block_id); - $this->assertEqual($title, $block->label(), 'Found the block with expected title.'); - } - /** * Tests adding a block from the library page with a weight query string. */ diff --git a/core/modules/block/src/Tests/BlockUiTest.php b/core/modules/block/src/Tests/BlockUiTest.php index ec720e9c9..2ba766386 100644 --- a/core/modules/block/src/Tests/BlockUiTest.php +++ b/core/modules/block/src/Tests/BlockUiTest.php @@ -17,7 +17,7 @@ class BlockUiTest extends WebTestBase { * * @var array */ - public static $modules = array('block', 'block_test', 'help'); + public static $modules = array('block', 'block_test', 'help', 'condition_test'); protected $regions; @@ -248,6 +248,11 @@ class BlockUiTest extends WebTestBase { $this->drupalGet(''); $this->assertText('No context mapping selected.'); $this->assertNoText('User context found.'); + + // Tests that conditions with missing context are not displayed. + $this->drupalGet('admin/structure/block/manage/testcontextawareblock'); + $this->assertNoRaw('No existing type'); + $this->assertNoFieldByXPath('//*[@name="visibility[condition_test_no_existing_type][negate]"]'); } /** diff --git a/core/modules/block/tests/src/Kernel/Plugin/migrate/source/BlockTest.php b/core/modules/block/tests/src/Kernel/Plugin/migrate/source/BlockTest.php new file mode 100644 index 000000000..d37f8c796 --- /dev/null +++ b/core/modules/block/tests/src/Kernel/Plugin/migrate/source/BlockTest.php @@ -0,0 +1,127 @@ + 1, + 'module' => 'block', + 'delta' => '1', + 'theme' => 'garland', + 'status' => 1, + 'weight' => 0, + 'region' => 'left', + 'visibility' => 0, + 'pages' => '', + 'title' => 'Test Title 01', + 'cache' => -1, + ], + [ + 'bid' => 2, + 'module' => 'block', + 'delta' => '2', + 'theme' => 'garland', + 'status' => 1, + 'weight' => 5, + 'region' => 'right', + 'visibility' => 0, + 'pages' => '', + 'title' => 'Test Title 02', + 'cache' => -1, + ], + ]; + $tests[0]['source_data']['blocks_roles'] = [ + [ + 'module' => 'block', + 'delta' => 1, + 'rid' => 2, + ], + [ + 'module' => 'block', + 'delta' => 2, + 'rid' => 2, + ], + [ + 'module' => 'block', + 'delta' => 2, + 'rid' => 100, + ], + ]; + $tests[0]['source_data']['role'] = [ + [ + 'rid' => 2, + 'name' => 'authenticated user', + ], + ]; + $tests[0]['source_data']['system'] = [ + [ + 'filename' => 'modules/system/system.module', + 'name' => 'system', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '6055', + 'weight' => '0', + 'info' => 'a:0:{}', + ] + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'bid' => 1, + 'module' => 'block', + 'delta' => '1', + 'theme' => 'garland', + 'status' => 1, + 'weight' => 0, + 'region' => 'left', + 'visibility' => 0, + 'pages' => '', + 'title' => 'Test Title 01', + 'cache' => -1, + 'roles' => [2] + ], + [ + 'bid' => 2, + 'module' => 'block', + 'delta' => '2', + 'theme' => 'garland', + 'status' => 1, + 'weight' => 5, + 'region' => 'right', + 'visibility' => 0, + 'pages' => '', + 'title' => 'Test Title 02', + 'cache' => -1, + 'roles' => [2] + ], + ]; + return $tests; + } + +} diff --git a/core/modules/block/tests/src/Unit/Plugin/migrate/source/BlockTest.php b/core/modules/block/tests/src/Unit/Plugin/migrate/source/BlockTest.php deleted file mode 100644 index 4a60bd19a..000000000 --- a/core/modules/block/tests/src/Unit/Plugin/migrate/source/BlockTest.php +++ /dev/null @@ -1,144 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'block', - ), - ); - - /** - * Sample block instance query results from the source. - */ - protected $expectedResults = array( - array( - 'bid' => 1, - 'module' => 'block', - 'delta' => '1', - 'theme' => 'garland', - 'status' => 1, - 'weight' => 0, - 'region' => 'left', - 'visibility' => 0, - 'pages' => '', - 'title' => 'Test Title 01', - 'cache' => -1, - 'roles' => [2] - ), - array( - 'bid' => 2, - 'module' => 'block', - 'delta' => '2', - 'theme' => 'garland', - 'status' => 1, - 'weight' => 5, - 'region' => 'right', - 'visibility' => 0, - 'pages' => '', - 'title' => 'Test Title 02', - 'cache' => -1, - 'roles' => [2] - ), - ); - /** - * Sample block table. - */ - protected $expectedBlocks = array( - array( - 'bid' => 1, - 'module' => 'block', - 'delta' => '1', - 'theme' => 'garland', - 'status' => 1, - 'weight' => 0, - 'region' => 'left', - 'visibility' => 0, - 'pages' => '', - 'title' => 'Test Title 01', - 'cache' => -1, - ), - array( - 'bid' => 2, - 'module' => 'block', - 'delta' => '2', - 'theme' => 'garland', - 'status' => 1, - 'weight' => 5, - 'region' => 'right', - 'visibility' => 0, - 'pages' => '', - 'title' => 'Test Title 02', - 'cache' => -1, - ), - ); - - /** - * Sample block roles table. - */ - protected $expectedBlocksRoles = array( - array( - 'module' => 'block', - 'delta' => 1, - 'rid' => 2, - ), - array( - 'module' => 'block', - 'delta' => 2, - 'rid' => 2, - ), - array( - 'module' => 'block', - 'delta' => 2, - 'rid' => 100, - ), - ); - - /** - * Sample role table. - */ - protected $expectedRole = array( - array( - 'rid' => 2, - 'name' => 'authenticated user', - ), - ); - - /** - * Prepopulate database contents. - */ - protected function setUp() { - $this->databaseContents['blocks'] = $this->expectedBlocks; - $this->databaseContents['blocks_roles'] = $this->expectedBlocksRoles; - $this->databaseContents['role'] = $this->expectedRole; - $this->databaseContents['system'] = array( - array( - 'filename' => 'modules/system/system.module', - 'name' => 'system', - 'type' => 'module', - 'owner' => '', - 'status' => '1', - 'throttle' => '0', - 'bootstrap' => '0', - 'schema_version' => '6055', - 'weight' => '0', - 'info' => 'a:0:{}', - ) - ); - parent::setUp(); - } - -} diff --git a/core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d6/BoxTest.php b/core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d6/BoxTest.php new file mode 100644 index 000000000..22ab03c1f --- /dev/null +++ b/core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d6/BoxTest.php @@ -0,0 +1,46 @@ + 1, + 'body' => '

I made some custom content.

', + 'info' => 'Static Block', + 'format' => 1, + ], + [ + 'bid' => 2, + 'body' => '

I made some more custom content.

', + 'info' => 'Test Content', + 'format' => 1, + ], + ]; + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['boxes']; + + return $tests; + } + +} diff --git a/core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d7/BlockCustomTest.php b/core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d7/BlockCustomTest.php new file mode 100644 index 000000000..55419e387 --- /dev/null +++ b/core/modules/block_content/tests/src/Kernel/Plugin/migrate/source/d7/BlockCustomTest.php @@ -0,0 +1,40 @@ + '1', + 'body' => "I don't feel creative enough to write anything clever here.", + 'info' => 'Meh', + 'format' => 'filtered_html', + ], + ]; + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['block_custom']; + + return $tests; + } + +} diff --git a/core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d6/BoxTest.php b/core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d6/BoxTest.php deleted file mode 100644 index bb4a1ac4d..000000000 --- a/core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d6/BoxTest.php +++ /dev/null @@ -1,46 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_boxes', - ), - ); - - protected $expectedResults = array( - array( - 'bid' => 1, - 'body' => '

I made some custom content.

', - 'info' => 'Static Block', - 'format' => 1, - ), - array( - 'bid' => 2, - 'body' => '

I made some more custom content.

', - 'info' => 'Test Content', - 'format' => 1, - ), - ); - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->databaseContents['boxes'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d7/BlockCustomTest.php b/core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d7/BlockCustomTest.php deleted file mode 100644 index f2132967b..000000000 --- a/core/modules/block_content/tests/src/Unit/Plugin/migrate/source/d7/BlockCustomTest.php +++ /dev/null @@ -1,40 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd7_block_custom', - ), - ); - - protected $expectedResults = array( - array( - 'bid' => '1', - 'body' => "I don't feel creative enough to write anything clever here.", - 'info' => 'Meh', - 'format' => 'filtered_html', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['block_custom'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/block_place/block_place.module b/core/modules/block_place/block_place.module index 685de11f4..7fe86a6c7 100644 --- a/core/modules/block_place/block_place.module +++ b/core/modules/block_place/block_place.module @@ -17,7 +17,7 @@ function block_place_help($route_name, RouteMatchInterface $route_match) { $output = '

' . t('About') . '

'; $output .= '

' . t('The Place Blocks module allows you to place blocks from every page. For more information, see the online documentation for the Place Blocks module.', [':blocks-documentation' => 'https://www.drupal.org/documentation/modules/block_place/']) . '

'; $output .= '

' . t('Uses') . '

'; - $output .= '

' . t('Block placement is specific to each theme on your site. This module allows you to place blocks in the context of your content pages') . '

'; + $output .= '

' . t('Block placement is specific to each theme on your site. This module allows you to place blocks in the context of your content pages.') . '

'; return $output; } } diff --git a/core/modules/book/src/Plugin/Block/BookNavigationBlock.php b/core/modules/book/src/Plugin/Block/BookNavigationBlock.php index b232fbf41..6967077f3 100644 --- a/core/modules/book/src/Plugin/Block/BookNavigationBlock.php +++ b/core/modules/book/src/Plugin/Block/BookNavigationBlock.php @@ -156,9 +156,12 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt } } elseif ($current_bid) { - // Only display this block when the user is browsing a book. - $query = \Drupal::entityQuery('node'); - $nid = $query->condition('nid', $node->book['bid'], '=')->execute(); + // Only display this block when the user is browsing a book and do + // not show unpublished books. + $nid = \Drupal::entityQuery('node') + ->condition('nid', $node->book['bid'], '=') + ->condition('status', NODE_PUBLISHED) + ->execute(); // Only show the block if the user has view access for the top-level node. if ($nid) { diff --git a/core/modules/book/src/Tests/BookTest.php b/core/modules/book/src/Tests/BookTest.php index 015899295..cdd61c60a 100644 --- a/core/modules/book/src/Tests/BookTest.php +++ b/core/modules/book/src/Tests/BookTest.php @@ -570,34 +570,6 @@ class BookTest extends WebTestBase { $this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.'); } - /** - * Tests re-ordering of books. - */ - public function testBookOrdering() { - // Create new book. - $this->createBook(); - $book = $this->book; - - $this->drupalLogin($this->adminUser); - $node1 = $this->createBookNode($book->id()); - $node2 = $this->createBookNode($book->id()); - $pid = $node1->book['nid']; - - // Head to admin screen and attempt to re-order. - $this->drupalGet('admin/structure/book/' . $book->id()); - $edit = array( - "table[book-admin-{$node1->id()}][weight]" => 1, - "table[book-admin-{$node2->id()}][weight]" => 2, - // Put node 2 under node 1. - "table[book-admin-{$node2->id()}][pid]" => $pid, - ); - $this->drupalPostForm(NULL, $edit, t('Save book pages')); - // Verify weight was updated. - $this->assertFieldByName("table[book-admin-{$node1->id()}][weight]", 1); - $this->assertFieldByName("table[book-admin-{$node2->id()}][weight]", 2); - $this->assertFieldByName("table[book-admin-{$node2->id()}][pid]", $pid); - } - /** * Tests outline of a book. */ @@ -752,4 +724,29 @@ class BookTest extends WebTestBase { $this->assertEqual($book_node->book['bid'], $this->book->id()); } + /** + * Tests the book navigation block when book is unpublished. + * + * There was a fatal error with "Show block only on book pages" block mode. + */ + public function testBookNavigationBlockOnUnpublishedBook() { + // Create a new book. + $this->createBook(); + + // Create administrator user. + $administratorUser = $this->drupalCreateUser(['administer blocks', 'administer nodes', 'bypass node access']); + $this->drupalLogin($administratorUser); + + // Enable the block with "Show block only on book pages" mode. + $this->drupalPlaceBlock('book_navigation', ['block_mode' => 'book pages']); + + // Unpublish book node. + $edit = []; + $this->drupalPostForm('node/' . $this->book->id() . '/edit', $edit, t('Save and unpublish')); + + // Test node page. + $this->drupalGet('node/' . $this->book->id()); + $this->assertText($this->book->label(), 'Unpublished book with "Show block only on book pages" book navigation settings.'); + } + } diff --git a/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php new file mode 100644 index 000000000..e895b6824 --- /dev/null +++ b/core/modules/book/tests/src/FunctionalJavascript/BookJavascriptTest.php @@ -0,0 +1,160 @@ + 'book', + 'title' => 'Book', + 'book' => ['bid' => 'new'], + ]); + $book->save(); + $page1 = Node::create([ + 'type' => 'book', + 'title' => '1st page', + 'book' => ['bid' => $book->id(), 'pid' => $book->id(), 'weight' => 0], + ]); + $page1->save(); + $page2 = Node::create([ + 'type' => 'book', + 'title' => '2nd page', + 'book' => ['bid' => $book->id(), 'pid' => $book->id(), 'weight' => 1], + ]); + $page2->save(); + + // Head to admin screen and attempt to re-order. + $this->drupalLogin($this->drupalCreateUser(['administer book outlines'])); + $this->drupalGet('admin/structure/book/' . $book->id()); + + $page = $this->getSession()->getPage(); + + $weight_select1 = $page->findField("table[book-admin-{$page1->id()}][weight]"); + $weight_select2 = $page->findField("table[book-admin-{$page2->id()}][weight]"); + + // Check that rows weight selects are hidden. + $this->assertFalse($weight_select1->isVisible()); + $this->assertFalse($weight_select2->isVisible()); + + // Check that '2nd page' row is heavier than '1st page' row. + $this->assertGreaterThan($weight_select1->getValue(), $weight_select2->getValue()); + + // Check that '1st page' precedes the '2nd page'. + $this->assertOrderInPage(['1st page', '2nd page']); + + // Check that the 'unsaved changes' text is not present in the message area. + $this->assertSession()->pageTextNotContains('You have unsaved changes.'); + + // Drag and drop the '1st page' row over the '2nd page' row. + // @todo: Test also the reverse, '2nd page' over '1st page', when + // https://www.drupal.org/node/2769825 is fixed. + // @see https://www.drupal.org/node/2769825 + $dragged = $this->xpath("//tr[@data-drupal-selector='edit-table-book-admin-{$page1->id()}']//a[@class='tabledrag-handle']")[0]; + $target = $this->xpath("//tr[@data-drupal-selector='edit-table-book-admin-{$page2->id()}']//a[@class='tabledrag-handle']")[0]; + $dragged->dragTo($target); + + // Give javascript some time to manipulate the DOM. + $this->getSession()->wait(1000, 'jQuery(".tabledrag-changed-warning").is(":visible")'); + + // Check that the 'unsaved changes' text appeared in the message area. + $this->assertSession()->pageTextContains('You have unsaved changes.'); + + // Check that '2nd page' page precedes the '1st page'. + $this->assertOrderInPage(['2nd page', '1st page']); + + $this->submitForm([], 'Save book pages'); + $this->assertSession()->pageTextContains(new FormattableMarkup('Updated book @book.', ['@book' => $book->getTitle()])); + + // Check that page reordering was done in the backend for drag-n-drop. + $page1 = Node::load($page1->id()); + $page2 = Node::load($page2->id()); + $this->assertGreaterThan($page2->book['weight'], $page1->book['weight']); + + // Check again that '2nd page' is on top after form submit in the UI. + $this->assertOrderInPage(['2nd page', '1st page']); + + // Toggle row weight selects as visible. + $page->findButton('Show row weights')->click(); + + // Check that rows weight selects are visible. + $this->assertTrue($weight_select1->isVisible()); + $this->assertTrue($weight_select2->isVisible()); + + // Check that '1st page' row became heavier than '2nd page' row. + $this->assertGreaterThan($weight_select2->getValue(), $weight_select1->getValue()); + + // Reverse again using the weight fields. Use the current values so the test + // doesn't rely on knowing the values in the select boxes. + $value1 = $weight_select1->getValue(); + $value2 = $weight_select2->getValue(); + $weight_select1->setValue($value2); + $weight_select2->setValue($value1); + + // Toggle row weight selects back to hidden. + $page->findButton('Hide row weights')->click(); + + // Check that rows weight selects are hidden again. + $this->assertFalse($weight_select1->isVisible()); + $this->assertFalse($weight_select2->isVisible()); + + $this->submitForm([], 'Save book pages'); + $this->assertSession()->pageTextContains(new FormattableMarkup('Updated book @book.', ['@book' => $book->getTitle()])); + + // Check that the '1st page' is first again. + $this->assertOrderInPage(['1st page', '2nd page']); + + // Check that page reordering was done in the backend for manual weight + // field usage. + $page1 = Node::load($page1->id()); + $page2 = Node::load($page2->id()); + $this->assertGreaterThan($page2->book['weight'], $page1->book['weight']); + } + + /** + * Asserts that several pieces of markup are in a given order in the page. + * + * @param string[] $items + * An ordered list of strings. + * + * @throws \Behat\Mink\Exception\ExpectationException + * When any of the given string is not found. + * + * @todo Remove this once https://www.drupal.org/node/2817657 is committed. + */ + protected function assertOrderInPage(array $items) { + $session = $this->getSession(); + $text = $session->getPage()->getHtml(); + $strings = []; + foreach ($items as $item) { + if (($pos = strpos($text, $item)) === FALSE) { + throw new ExpectationException("Cannot find '$item' in the page", $session->getDriver()); + } + $strings[$pos] = $item; + } + ksort($strings); + $ordered = implode(', ', array_map(function ($item) { + return "'$item'"; + }, $items)); + $this->assertSame($items, array_values($strings), "Found strings, ordered as: $ordered."); + } + +} diff --git a/core/modules/book/tests/src/Kernel/Plugin/migrate/source/d6/BookTest.php b/core/modules/book/tests/src/Kernel/Plugin/migrate/source/d6/BookTest.php new file mode 100644 index 000000000..feece1f7b --- /dev/null +++ b/core/modules/book/tests/src/Kernel/Plugin/migrate/source/d6/BookTest.php @@ -0,0 +1,84 @@ + '1', + 'nid' => '4', + 'bid' => '4', + ], + ]; + $tests[0]['source_data']['menu_links'] = [ + [ + 'menu_name' => 'book-toc-1', + 'mlid' => '1', + 'plid' => '0', + 'link_path' => 'node/4', + 'router_path' => 'node/%', + 'link_title' => 'Test top book title', + 'options' => 'a:0:{}', + 'module' => 'book', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '-10', + 'depth' => '1', + 'customized' => '0', + 'p1' => '1', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'nid' => '4', + 'bid' => '4', + 'mlid' => '1', + 'plid' => '0', + 'weight' => '-10', + 'p1' => '1', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + ], + ]; + return $tests; + } + +} diff --git a/core/modules/book/tests/src/Unit/Plugin/migrate/source/d6/BookTest.php b/core/modules/book/tests/src/Unit/Plugin/migrate/source/d6/BookTest.php deleted file mode 100644 index 6433cadcd..000000000 --- a/core/modules/book/tests/src/Unit/Plugin/migrate/source/d6/BookTest.php +++ /dev/null @@ -1,85 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_book', - ), - ); - - protected $expectedResults = array( - array( - 'nid' => '4', - 'bid' => '4', - 'mlid' => '1', - 'plid' => '0', - 'weight' => '-10', - 'p1' => '1', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['book'] = array( - array( - 'mlid' => '1', - 'nid' => '4', - 'bid' => '4', - ), - ); - $this->databaseContents['menu_links'] = array( - array( - 'menu_name' => 'book-toc-1', - 'mlid' => '1', - 'plid' => '0', - 'link_path' => 'node/4', - 'router_path' => 'node/%', - 'link_title' => 'Test top book title', - 'options' => 'a:0:{}', - 'module' => 'book', - 'hidden' => '0', - 'external' => '0', - 'has_children' => '1', - 'expanded' => '0', - 'weight' => '-10', - 'depth' => '1', - 'customized' => '0', - 'p1' => '1', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php index 3728790ca..605dc56a7 100644 --- a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php +++ b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php @@ -5,6 +5,7 @@ namespace Drupal\ckeditor\Plugin\CKEditorPlugin; use Drupal\ckeditor\CKEditorPluginBase; use Drupal\ckeditor\CKEditorPluginContextualInterface; use Drupal\ckeditor\CKEditorPluginManager; +use Drupal\Component\Utility\Html; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -369,7 +370,7 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter foreach ($possible_format_tags as $tag) { $input = '<' . $tag . '>TEST'; $output = trim(check_markup($input, $editor->id())); - if ($input == $output) { + if (Html::load($output)->getElementsByTagName($tag)->length !== 0) { $format_tags[] = $tag; } } diff --git a/core/modules/ckeditor/tests/modules/config/schema/ckeditor_test.schema.yml b/core/modules/ckeditor/tests/modules/config/schema/ckeditor_test.schema.yml index e6734a7c5..e7305a291 100644 --- a/core/modules/ckeditor/tests/modules/config/schema/ckeditor_test.schema.yml +++ b/core/modules/ckeditor/tests/modules/config/schema/ckeditor_test.schema.yml @@ -5,3 +5,12 @@ ckeditor.plugin.llama_contextual_and_button: ultra_llama_mode: type: boolean label: 'Ultra llama mode' + +filter_settings.test_attribute_filter: + type: filter + label: 'Test Attribute Filter' + mapping: + tags: + type: sequence + sequence: + type: string diff --git a/core/modules/ckeditor/tests/modules/src/Plugin/Filter/TestAttributeFilter.php b/core/modules/ckeditor/tests/modules/src/Plugin/Filter/TestAttributeFilter.php new file mode 100644 index 000000000..885877b4b --- /dev/null +++ b/core/modules/ckeditor/tests/modules/src/Plugin/Filter/TestAttributeFilter.php @@ -0,0 +1,38 @@ +settings['tags'] as $tag) { + $tag_elements = $document->getElementsByTagName($tag); + foreach ($tag_elements as $tag_element) { + $tag_element->setAttribute('test_attribute', 'test attribute value'); + } + } + return new FilterProcessResult(Html::serialize($document)); + } + +} diff --git a/core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php b/core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php new file mode 100644 index 000000000..aecc22802 --- /dev/null +++ b/core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php @@ -0,0 +1,140 @@ +installEntitySchema('editor'); + $this->installEntitySchema('filter_format'); + + $this->format = FilterFormat::create([ + 'format' => 'test_format', + 'name' => $this->randomMachineName(), + ]); + $this->format->save(); + + $this->editor = Editor::create([ + 'editor' => 'ckeditor', + 'format' => 'test_format', + 'settings' => [ + 'toolbar' => [ + 'rows' => [ + [ + [ + 'name' => 'Enabled Buttons', + 'items' => [ + 'Format', + ], + ], + ], + ], + ], + ], + ]); + $this->editor->save(); + + $this->ckeditorPluginManager = $this->container->get('plugin.manager.ckeditor.plugin'); + } + + /** + * Test the format tags settings. + * + * @dataProvider formatTagsSettingsTestCases + */ + public function testFormatTagsSettings($filter_plugins, $expected_format_tags) { + foreach ($filter_plugins as $filter_plugin_id => $filter_plugin_settings) { + $this->format->setFilterConfig($filter_plugin_id, $filter_plugin_settings); + } + $this->format->save(); + + $internal_plugin = $this->ckeditorPluginManager->createInstance('internal', []); + $plugin_config = $internal_plugin->getConfig($this->editor); + $this->assertEquals($expected_format_tags, explode(';', $plugin_config['format_tags'])); + } + + /** + * A data provider for testFormatTagsSettings. + */ + public function formatTagsSettingsTestCases() { + $all_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre']; + + return [ + 'No filter plugins enabled (all tags allowed)' => [ + [], + $all_tags, + ], + 'HTML filter plugin enabled (some tags filtered out)' => [ + [ + 'filter_html' => [ + 'status' => 1, + 'settings' => [ + 'allowed_html' => '

', + 'filter_html_help' => 1, + 'filter_html_nofollow' => 0, + ], + ], + ], + ['p', 'h1', 'h2'], + ], + 'Test attribute filter enabled (all tags allowed)' => [ + [ + 'test_attribute_filter' => [ + 'status' => 1, + 'settings' => [ + 'tags' => ['h1', 'h2'], + ], + ], + ], + $all_tags, + ], + ]; + } + +} diff --git a/core/modules/contact/migration_templates/contact_category.yml b/core/modules/contact/migration_templates/contact_category.yml index ce7ce9931..0174de491 100644 --- a/core/modules/contact/migration_templates/contact_category.yml +++ b/core/modules/contact/migration_templates/contact_category.yml @@ -12,8 +12,8 @@ process: source: category - plugin: dedupe_entity - entity_type: user_role - field: cid + entity_type: contact_form + field: id length: 32 label: category recipients: recipients diff --git a/core/modules/contact/src/MailHandler.php b/core/modules/contact/src/MailHandler.php index bac7659ac..bb3b95e8d 100644 --- a/core/modules/contact/src/MailHandler.php +++ b/core/modules/contact/src/MailHandler.php @@ -122,7 +122,14 @@ class MailHandler implements MailHandlerInterface { if (!$message->isPersonal() && $contact_form->getReply()) { // User contact forms do not support an auto-reply message, so this // message always originates from the site. - $this->mailManager->mail('contact', 'page_autoreply', $sender_cloned->getEmail(), $current_langcode, $params); + if (!$sender_cloned->getEmail()) { + $this->logger->error('Error sending auto-reply, missing sender e-mail address in %contact_form', [ + '%contact_form' => $contact_form->label(), + ]); + } + else { + $this->mailManager->mail('contact', 'page_autoreply', $sender_cloned->getEmail(), $current_langcode, $params); + } } if (!$message->isPersonal()) { diff --git a/core/modules/contact/src/Tests/ContactSitewideTest.php b/core/modules/contact/src/Tests/ContactSitewideTest.php index c14871503..c00ad3ddb 100644 --- a/core/modules/contact/src/Tests/ContactSitewideTest.php +++ b/core/modules/contact/src/Tests/ContactSitewideTest.php @@ -27,7 +27,7 @@ class ContactSitewideTest extends WebTestBase { * * @var array */ - public static $modules = array('text', 'contact', 'field_ui', 'contact_test', 'block'); + public static $modules = ['text', 'contact', 'field_ui', 'contact_test', 'block', 'error_service_test', 'dblog']; /** * {@inheritdoc} @@ -44,13 +44,13 @@ class ContactSitewideTest extends WebTestBase { */ function testSiteWideContact() { // Create and log in administrative user. - $admin_user = $this->drupalCreateUser(array( + $admin_user = $this->drupalCreateUser([ 'access site-wide contact form', 'administer contact forms', 'administer users', 'administer account settings', 'administer contact_message fields', - )); + ]); $this->drupalLogin($admin_user); // Check the presence of expected cache tags. @@ -346,7 +346,13 @@ class ContactSitewideTest extends WebTestBase { */ function testAutoReply() { // Create and log in administrative user. - $admin_user = $this->drupalCreateUser(array('access site-wide contact form', 'administer contact forms', 'administer permissions', 'administer users')); + $admin_user = $this->drupalCreateUser([ + 'access site-wide contact form', + 'administer contact forms', + 'administer permissions', + 'administer users', + 'access site reports' + ]); $this->drupalLogin($admin_user); // Set up three forms, 2 with an auto-reply and one without. @@ -384,6 +390,20 @@ class ContactSitewideTest extends WebTestBase { $this->submitContact($this->randomMachineName(16), $email, $this->randomString(64), 'no_autoreply', $this->randomString(128)); $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email)); $this->assertEqual(count($captured_emails), 0); + + // Verify that the current error message doesn't show, that the auto-reply + // doesn't get sent and the correct silent error gets logged. + $email = ''; + entity_get_form_display('contact_message', 'foo', 'default') + ->removeComponent('mail') + ->save(); + $this->submitContact($this->randomMachineName(16), $email, $this->randomString(64), 'foo', $this->randomString(128)); + $this->assertNoText('Unable to send email. Contact the site administrator if the problem persists.'); + $captured_emails = $this->drupalGetMails(['id' => 'contact_page_autoreply', 'to' => $email]); + $this->assertEqual(count($captured_emails), 0); + $this->drupalLogin($admin_user); + $this->drupalGet('admin/reports/dblog'); + $this->assertRaw('Error sending auto-reply, missing sender e-mail address in foo'); } /** diff --git a/core/modules/contact/tests/src/Kernel/Migrate/d6/MigrateContactCategoryTest.php b/core/modules/contact/tests/src/Kernel/Migrate/d6/MigrateContactCategoryTest.php index d613cacdf..e40f0bdd4 100644 --- a/core/modules/contact/tests/src/Kernel/Migrate/d6/MigrateContactCategoryTest.php +++ b/core/modules/contact/tests/src/Kernel/Migrate/d6/MigrateContactCategoryTest.php @@ -47,6 +47,29 @@ class MigrateContactCategoryTest extends MigrateDrupal6TestBase { $this->assertIdentical(array('fortyninechars@example.com'), $contact_form->getRecipients()); $this->assertIdentical('', $contact_form->getReply()); $this->assertIdentical(2, $contact_form->getWeight()); + + // Test there are no duplicated roles. + $contact_forms = [ + 'website_feedback1', + 'some_other_category1', + 'a_category_much_longer_than_thir1', + ]; + $this->assertEmpty(ContactForm::loadMultiple($contact_forms)); + + /* + * Remove the map row for the Website feedback contact form so that it + * can be migrated again. + */ + $id_map = $this->getMigration('contact_category')->getIdMap(); + $id_map->delete(['cid' => '1']); + $this->executeMigration('contact_category'); + + // Test there is a duplicate Website feedback form. + $contact_form = ContactForm::load('website_feedback1'); + $this->assertSame('Website feedback', $contact_form->label()); + $this->assertSame(array('admin@example.com'), $contact_form->getRecipients()); + $this->assertSame('', $contact_form->getReply()); + $this->assertSame(0, $contact_form->getWeight()); } } diff --git a/core/modules/contact/tests/src/Kernel/Plugin/migrate/source/ContactCategoryTest.php b/core/modules/contact/tests/src/Kernel/Plugin/migrate/source/ContactCategoryTest.php new file mode 100644 index 000000000..619e0cd10 --- /dev/null +++ b/core/modules/contact/tests/src/Kernel/Plugin/migrate/source/ContactCategoryTest.php @@ -0,0 +1,57 @@ + [], + 'expected_data' => [], + ], + ]; + + $tests[0]['expected_data'] = [ + [ + 'cid' => 1, + 'category' => 'contact category value 1', + 'recipients' => ['admin@example.com', 'user@example.com'], + 'reply' => 'auto reply value 1', + 'weight' => 0, + 'selected' => 0, + ], + [ + 'cid' => 2, + 'category' => 'contact category value 2', + 'recipients' => ['admin@example.com', 'user@example.com'], + 'reply' => 'auto reply value 2', + 'weight' => 0, + 'selected' => 0, + ], + ]; + + foreach ($tests[0]['expected_data'] as $k => $row) { + $row['recipients'] = implode(',', $row['recipients']); + $tests[0]['source_data']['contact'][$k] = $row; + } + return $tests; + } + +} diff --git a/core/modules/contact/tests/src/Kernel/Plugin/migrate/source/d6/ContactSettingsTest.php b/core/modules/contact/tests/src/Kernel/Plugin/migrate/source/d6/ContactSettingsTest.php new file mode 100644 index 000000000..72ea7f269 --- /dev/null +++ b/core/modules/contact/tests/src/Kernel/Plugin/migrate/source/d6/ContactSettingsTest.php @@ -0,0 +1,54 @@ + 'site_name', + 'value' => serialize('Blorf!'), + ], + ]; + $tests[0]['source_data']['contact'] = [ + [ + 'cid' => '1', + 'category' => 'Website feedback', + 'recipients' => 'admin@example.com', + 'reply' => '', + 'weight' => '0', + 'selected' => '1', + ] + ]; + $tests[0]['expected_data'] = [ + [ + 'default_category' => '1', + 'site_name' => 'Blorf!', + ], + ]; + $tests[0]['expected_count'] = NULL; + $tests[0]['configuration']['variables'] = ['site_name']; + + return $tests; + } + +} diff --git a/core/modules/contact/tests/src/Unit/Plugin/migrate/source/ContactCategoryTest.php b/core/modules/contact/tests/src/Unit/Plugin/migrate/source/ContactCategoryTest.php deleted file mode 100644 index cbdd68845..000000000 --- a/core/modules/contact/tests/src/Unit/Plugin/migrate/source/ContactCategoryTest.php +++ /dev/null @@ -1,54 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'contact_category', - ), - ); - - protected $expectedResults = array( - array( - 'cid' => 1, - 'category' => 'contact category value 1', - 'recipients' => array('admin@example.com', 'user@example.com'), - 'reply' => 'auto reply value 1', - 'weight' => 0, - 'selected' => 0, - ), - array( - 'cid' => 2, - 'category' => 'contact category value 2', - 'recipients' => array('admin@example.com', 'user@example.com'), - 'reply' => 'auto reply value 2', - 'weight' => 0, - 'selected' => 0, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $row) { - $this->databaseContents['contact'][$k] = $row; - $this->databaseContents['contact'][$k]['recipients'] = implode(',', $row['recipients']); - } - parent::setUp(); - } - -} diff --git a/core/modules/contact/tests/src/Unit/Plugin/migrate/source/d6/ContactSettingsTest.php b/core/modules/contact/tests/src/Unit/Plugin/migrate/source/d6/ContactSettingsTest.php deleted file mode 100644 index 95850cdae..000000000 --- a/core/modules/contact/tests/src/Unit/Plugin/migrate/source/d6/ContactSettingsTest.php +++ /dev/null @@ -1,55 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_contact_settings', - 'variables' => array('site_name'), - ), - ); - - protected $expectedResults = array( - array( - 'default_category' => '1', - 'site_name' => 'Blorf!', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['variable'] = array( - array( - 'name' => 'site_name', - 'value' => serialize('Blorf!'), - ), - ); - $this->databaseContents['contact'] = array( - array( - 'cid' => '1', - 'category' => 'Website feedback', - 'recipients' => 'admin@example.com', - 'reply' => '', - 'weight' => '0', - 'selected' => '1', - ) - ); - parent::setUp(); - } - -} diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php index 1ff7f2a88..978408fdd 100644 --- a/core/modules/content_moderation/src/Entity/ContentModerationState.php +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php @@ -4,7 +4,6 @@ namespace Drupal\content_moderation\Entity; use Drupal\content_moderation\ContentModerationStateInterface; use Drupal\Core\Entity\ContentEntityBase; -use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\TypedData\TranslatableInterface; @@ -42,8 +41,6 @@ use Drupal\user\UserInterface; */ class ContentModerationState extends ContentEntityBase implements ContentModerationStateInterface { - use EntityChangedTrait; - /** * {@inheritdoc} */ diff --git a/core/modules/content_moderation/src/EntityOperations.php b/core/modules/content_moderation/src/EntityOperations.php index 3685b0dea..51ad7d685 100644 --- a/core/modules/content_moderation/src/EntityOperations.php +++ b/core/modules/content_moderation/src/EntityOperations.php @@ -4,7 +4,6 @@ namespace Drupal\content_moderation; use Drupal\content_moderation\Entity\ContentModerationState; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -77,32 +76,6 @@ class EntityOperations implements ContainerInjectionInterface { ); } - /** - * Determines the default moderation state on load for an entity. - * - * This method is only applicable when an entity is loaded that has - * no moderation state on it, but should. In those cases, failing to set - * one may result in NULL references elsewhere when other code tries to check - * the moderation state of the entity. - * - * The amount of indirection here makes performance a concern, but - * given how Entity API works I don't know how else to do it. - * This reliably gets us *A* valid state. However, that state may be - * not the ideal one. Suggestions on how to better select the default - * state here are welcome. - * - * @param \Drupal\Core\Entity\ContentEntityInterface $entity - * The entity for which we want a default state. - * - * @return string - * The default state for the given entity. - */ - protected function getDefaultLoadStateId(ContentEntityInterface $entity) { - return $this->moderationInfo - ->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle()) - ->getThirdPartySetting('content_moderation', 'default_moderation_state'); - } - /** * Acts on an entity and set published status based on the moderation state. * @@ -170,8 +143,8 @@ class EntityOperations implements ContainerInjectionInterface { $moderation_state = $entity->moderation_state->target_id; /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ if (!$moderation_state) { - $moderation_state = $this->moderationInfo - ->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle()) + $moderation_state = $this->entityTypeManager + ->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle()) ->getThirdPartySetting('content_moderation', 'default_moderation_state'); } diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php index 0f5b406cc..ed33902a5 100644 --- a/core/modules/content_moderation/src/ModerationInformation.php +++ b/core/modules/content_moderation/src/ModerationInformation.php @@ -49,21 +49,15 @@ class ModerationInformation implements ModerationInformationInterface { return $entity_type->hasHandlerClass('moderation'); } - /** - * {@inheritdoc} - */ - public function loadBundleEntity($bundle_entity_type_id, $bundle_id) { - if ($bundle_entity_type_id) { - return $this->entityTypeManager->getStorage($bundle_entity_type_id)->load($bundle_id); - } - } - /** * {@inheritdoc} */ public function shouldModerateEntitiesOfBundle(EntityTypeInterface $entity_type, $bundle) { - if ($bundle_entity = $this->loadBundleEntity($entity_type->getBundleEntityType(), $bundle)) { - return $bundle_entity->getThirdPartySetting('content_moderation', 'enabled', FALSE); + if ($this->canModerateEntitiesOfEntityType($entity_type)) { + $bundle_entity = $this->entityTypeManager->getStorage($entity_type->getBundleEntityType())->load($bundle); + if ($bundle_entity) { + return $bundle_entity->getThirdPartySetting('content_moderation', 'enabled', FALSE); + } } return FALSE; } diff --git a/core/modules/content_moderation/src/ModerationInformationInterface.php b/core/modules/content_moderation/src/ModerationInformationInterface.php index e7fa15d2f..95a658e65 100644 --- a/core/modules/content_moderation/src/ModerationInformationInterface.php +++ b/core/modules/content_moderation/src/ModerationInformationInterface.php @@ -11,19 +11,6 @@ use Drupal\Core\Entity\EntityTypeInterface; */ interface ModerationInformationInterface { - /** - * Loads a specific bundle entity. - * - * @param string $bundle_entity_type_id - * The bundle entity type ID. - * @param string $bundle_id - * The bundle ID. - * - * @return \Drupal\Core\Config\Entity\ConfigEntityInterface|null - * The bundle entity. - */ - public function loadBundleEntity($bundle_entity_type_id, $bundle_id); - /** * Determines if an entity is moderated. * diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php index 27afd30c9..80820b4b4 100644 --- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php +++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php @@ -57,8 +57,9 @@ class ModerationStateFieldItemList extends EntityReferenceFieldItemList { // It is possible that the bundle does not exist at this point. For example, // the node type form creates a fake Node entity to get default values. // @see \Drupal\node\NodeTypeForm::form() - $bundle_entity = \Drupal::service('content_moderation.moderation_information') - ->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle()); + $bundle_entity = \Drupal::entityTypeManager() + ->getStorage($entity->getEntityType()->getBundleEntityType()) + ->load($entity->bundle()); if ($bundle_entity && ($default = $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state'))) { return ModerationState::load($default); } @@ -71,7 +72,24 @@ class ModerationStateFieldItemList extends EntityReferenceFieldItemList { if ($index !== 0) { throw new \InvalidArgumentException('An entity can not have multiple moderation states at the same time.'); } + $this->computeModerationFieldItemList(); + return isset($this->list[$index]) ? $this->list[$index] : NULL; + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + $this->computeModerationFieldItemList(); + return parent::getIterator(); + } + + /** + * Recalculate the moderation field item list. + */ + protected function computeModerationFieldItemList() { // Compute the value of the moderation state. + $index = 0; if (!isset($this->list[$index]) || $this->list[$index]->isEmpty()) { $moderation_state = $this->getModerationState(); // Do not store NULL values in the static cache. @@ -79,8 +97,6 @@ class ModerationStateFieldItemList extends EntityReferenceFieldItemList { $this->list[$index] = $this->createItem($index, ['entity' => $moderation_state]); } } - - return isset($this->list[$index]) ? $this->list[$index] : NULL; } } diff --git a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php index f70537c6f..ca75604a8 100644 --- a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php +++ b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php @@ -97,8 +97,8 @@ class ModerationStateConstraintValidator extends ConstraintValidator implements $new_state_id = $entity->moderation_state->target_id; } else { - $new_state_id = $default = $this->moderationInformation - ->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle()) + $new_state_id = $default = $this->entityTypeManager + ->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle()) ->getThirdPartySetting('content_moderation', 'default_moderation_state'); } if ($new_state_id) { diff --git a/core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php b/core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php new file mode 100644 index 000000000..c57963e07 --- /dev/null +++ b/core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php @@ -0,0 +1,78 @@ +installSchema('node', 'node_access'); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installEntitySchema('content_moderation_state'); + $this->installConfig('content_moderation'); + + $node_type = NodeType::create([ + 'type' => 'example', + ]); + $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE); + $node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft']); + $node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft'); + $node_type->save(); + $this->testNode = Node::create([ + 'type' => 'example', + 'title' => 'Test title', + ]); + $this->testNode->save(); + \Drupal::entityTypeManager()->getStorage('node')->resetCache(); + $this->testNode = Node::load($this->testNode->id()); + } + + /** + * Test the field item list when accessing an index. + */ + public function testArrayIndex() { + $this->assertEquals('draft', $this->testNode->moderation_state[0]->entity->id()); + } + + /** + * Test the field item list when iterating. + */ + public function testArrayIteration() { + $states = []; + foreach ($this->testNode->moderation_state as $item) { + $states[] = $item->entity->id(); + } + $this->assertEquals(['draft'], $states); + } + +} diff --git a/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php b/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php index 2b2e6f232..ff46e4158 100644 --- a/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php +++ b/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\content_moderation\Unit; +use Drupal\content_moderation\Entity\Handler\ModerationHandler; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityType; @@ -70,6 +71,7 @@ class ModerationInformationTest extends \PHPUnit_Framework_TestCase { $entity_type = new ContentEntityType([ 'id' => 'test_entity_type', 'bundle_entity_type' => 'entity_test_bundle', + 'handlers' => ['moderation' => ModerationHandler::class], ]); $entity = $this->prophesize(ContentEntityInterface::class); $entity->getEntityType()->willReturn($entity_type); @@ -104,6 +106,7 @@ class ModerationInformationTest extends \PHPUnit_Framework_TestCase { $entity_type = new ContentEntityType([ 'id' => 'test_entity_type', 'bundle_entity_type' => 'entity_test_bundle', + 'handlers' => ['moderation' => ModerationHandler::class], ]); $moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser()); diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php index 0a171de8e..d246011ae 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php +++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php @@ -92,11 +92,19 @@ class DateTimeFieldItemList extends FieldItemList { $default_value = parent::processDefaultValue($default_value, $entity, $definition); if (isset($default_value[0]['default_date_type'])) { - // A default value should be in the format and timezone used for date - // storage. - $date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_STORAGE_TIMEZONE); - $storage_format = $definition->getSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT; - $value = $date->format($storage_format); + if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) { + // A default date only value should be in the format used for date + // storage but in the user's local timezone. + $date = new DrupalDateTime($default_value[0]['default_date'], drupal_get_user_timezone()); + $format = DATETIME_DATE_STORAGE_FORMAT; + } + else { + // A default date+time value should be in the format and timezone used + // for date storage. + $date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_STORAGE_TIMEZONE); + $format = DATETIME_DATETIME_STORAGE_FORMAT; + } + $value = $date->format($format); // We only provide a default value for the first item, as do all fields. // Otherwise, there is no way to clear out unwanted values on multiple value // fields. diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php index c729b232a..f7aeae819 100644 --- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php +++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php @@ -594,83 +594,103 @@ class DateTimeFieldTest extends DateTestBase { ]); $field->save(); - // Set now as default_value. - $field_edit = array( - 'default_value_input[default_date_type]' => 'now', - ); - $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + // Loop through defined timezones to test that date-only defaults work at + // the extremes. + foreach (static::$timezones as $timezone) { - // Check that default value is selected in default value form. - $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); - $this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default value is selected in instance settings page'); - $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page'); + $this->setSiteTimezone($timezone); - // Check if default_date has been stored successfully. - $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get(); - $this->assertEqual($config_entity['default_value'][0], array('default_date_type' => 'now', 'default_date' => 'now'), 'Default value has been stored successfully'); + // Set now as default_value. + $field_edit = array( + 'default_value_input[default_date_type]' => 'now', + ); + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); - // Clear field cache in order to avoid stale cache values. - \Drupal::entityManager()->clearCachedFieldDefinitions(); + // Check that default value is selected in default value form. + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page'); - // Create a new node to check that datetime field default value is today. - $new_node = Node::create(['type' => 'date_content']); - $expected_date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE); - $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT)); + // Check if default_date has been stored successfully. + $config_entity = $this->config('field.field.node.date_content.' . $field_name) + ->get(); + $this->assertEqual($config_entity['default_value'][0], array( + 'default_date_type' => 'now', + 'default_date' => 'now', + ), 'Default value has been stored successfully'); - // Set an invalid relative default_value to test validation. - $field_edit = array( - 'default_value_input[default_date_type]' => 'relative', - 'default_value_input[default_date]' => 'invalid date', - ); - $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + // Clear field cache in order to avoid stale cache values. + \Drupal::entityManager()->clearCachedFieldDefinitions(); - $this->assertText('The relative date value entered is invalid.'); + // Create a new node to check that datetime field default value is today. + $new_node = Node::create(['type' => 'date_content']); + $expected_date = new DrupalDateTime('now', drupal_get_user_timezone()); + $this->assertEqual($new_node->get($field_name) + ->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT)); - // Set a relative default_value. - $field_edit = array( - 'default_value_input[default_date_type]' => 'relative', - 'default_value_input[default_date]' => '+90 days', - ); - $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + // Set an invalid relative default_value to test validation. + $field_edit = array( + 'default_value_input[default_date_type]' => 'relative', + 'default_value_input[default_date]' => 'invalid date', + ); + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); - // Check that default value is selected in default value form. - $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); - $this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default value is selected in instance settings page'); - $this->assertFieldByName('default_value_input[default_date]', '+90 days', 'The relative default value is displayed in instance settings page'); + $this->assertText('The relative date value entered is invalid.'); - // Check if default_date has been stored successfully. - $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get(); - $this->assertEqual($config_entity['default_value'][0], array('default_date_type' => 'relative', 'default_date' => '+90 days'), 'Default value has been stored successfully'); + // Set a relative default_value. + $field_edit = array( + 'default_value_input[default_date_type]' => 'relative', + 'default_value_input[default_date]' => '+90 days', + ); + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); - // Clear field cache in order to avoid stale cache values. - \Drupal::entityManager()->clearCachedFieldDefinitions(); + // Check that default value is selected in default value form. + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_date]', '+90 days', 'The relative default value is displayed in instance settings page'); - // Create a new node to check that datetime field default value is +90 days. - $new_node = Node::create(['type' => 'date_content']); - $expected_date = new DrupalDateTime('+90 days', DATETIME_STORAGE_TIMEZONE); - $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT)); + // Check if default_date has been stored successfully. + $config_entity = $this->config('field.field.node.date_content.' . $field_name) + ->get(); + $this->assertEqual($config_entity['default_value'][0], array( + 'default_date_type' => 'relative', + 'default_date' => '+90 days', + ), 'Default value has been stored successfully'); - // Remove default value. - $field_edit = array( - 'default_value_input[default_date_type]' => '', - ); - $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); + // Clear field cache in order to avoid stale cache values. + \Drupal::entityManager()->clearCachedFieldDefinitions(); - // Check that default value is selected in default value form. - $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); - $this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default value is selected in instance settings page'); - $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page'); + // Create a new node to check that datetime field default value is +90 + // days. + $new_node = Node::create(['type' => 'date_content']); + $expected_date = new DrupalDateTime('+90 days', drupal_get_user_timezone()); + $this->assertEqual($new_node->get($field_name) + ->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT)); - // Check if default_date has been stored successfully. - $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get(); - $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully'); + // Remove default value. + $field_edit = array( + 'default_value_input[default_date_type]' => '', + ); + $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings')); - // Clear field cache in order to avoid stale cache values. - \Drupal::entityManager()->clearCachedFieldDefinitions(); + // Check that default value is selected in default value form. + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default value is selected in instance settings page'); + $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page'); - // Create a new node to check that datetime field default value is not set. - $new_node = Node::create(['type' => 'date_content']); - $this->assertNull($new_node->get($field_name)->value, 'Default value is not set'); + // Check if default_date has been stored successfully. + $config_entity = $this->config('field.field.node.date_content.' . $field_name) + ->get(); + $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully'); + + // Clear field cache in order to avoid stale cache values. + \Drupal::entityManager()->clearCachedFieldDefinitions(); + + // Create a new node to check that datetime field default value is not + // set. + $new_node = Node::create(['type' => 'date_content']); + $this->assertNull($new_node->get($field_name)->value, 'Default value is not set'); + } } /** diff --git a/core/modules/datetime_range/config/schema/datetime_range.schema.yml b/core/modules/datetime_range/config/schema/datetime_range.schema.yml index 90bbd38b2..f0f93259d 100644 --- a/core/modules/datetime_range/config/schema/datetime_range.schema.yml +++ b/core/modules/datetime_range/config/schema/datetime_range.schema.yml @@ -32,24 +32,27 @@ field.formatter.settings.daterange_default: label: 'Date range default display format settings' mapping: separator: - type: string + type: label label: 'Separator' + translation context: 'Date range separator' field.formatter.settings.daterange_plain: type: field.formatter.settings.datetime_plain label: 'Date range plain display format settings' mapping: separator: - type: string + type: label label: 'Separator' + translation context: 'Date range separator' field.formatter.settings.daterange_custom: type: field.formatter.settings.datetime_custom label: 'Date range custom display format settings' mapping: separator: - type: string + type: label label: 'Separator' + translation context: 'Date range separator' field.widget.settings.daterange_datelist: type: mapping diff --git a/core/modules/datetime_range/datetime_range.post_update.php b/core/modules/datetime_range/datetime_range.post_update.php new file mode 100644 index 000000000..48a866bac --- /dev/null +++ b/core/modules/datetime_range/datetime_range.post_update.php @@ -0,0 +1,22 @@ +buildDateWithIsoAttribute($start_date); - } - - if (!empty($item->_attributes)) { - $elements[$delta]['#attributes'] += $item->_attributes; - // Unset field item attributes since they have been included in the - // formatter output and should not be rendered in the field template. - unset($item->_attributes); + if (!empty($item->_attributes)) { + $elements[$delta]['#attributes'] += $item->_attributes; + // Unset field item attributes since they have been included in the + // formatter output and should not be rendered in the field template. + unset($item->_attributes); + } } } } diff --git a/core/modules/datetime_range/src/Tests/DateRangeFieldTest.php b/core/modules/datetime_range/src/Tests/DateRangeFieldTest.php index 2afb5f5ec..759352e8d 100644 --- a/core/modules/datetime_range/src/Tests/DateRangeFieldTest.php +++ b/core/modules/datetime_range/src/Tests/DateRangeFieldTest.php @@ -140,6 +140,11 @@ class DateRangeFieldTest extends DateTestBase { ])); $this->assertText(' THESEPARATOR ', 'Found proper separator'); + // Verify that hook_entity_prepare_view can add attributes. + // @see entity_test_entity_prepare_view() + $this->drupalGet('entity_test/' . $id); + $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]'); + // Verify that the plain formatter works. $this->displayOptions['type'] = 'daterange_plain'; $this->displayOptions['settings'] = $this->defaultSettings; @@ -203,6 +208,11 @@ class DateRangeFieldTest extends DateTestBase { ])); $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + // Verify that hook_entity_prepare_view can add attributes. + // @see entity_test_entity_prepare_view() + $this->drupalGet('entity_test/' . $id); + $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]'); + $this->displayOptions['type'] = 'daterange_plain'; $this->displayOptions['settings'] = $this->defaultSettings; entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') @@ -289,6 +299,11 @@ class DateRangeFieldTest extends DateTestBase { $this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso])); $this->assertText(' THESEPARATOR ', 'Found proper separator'); + // Verify that hook_entity_prepare_view can add attributes. + // @see entity_test_entity_prepare_view() + $this->drupalGet('entity_test/' . $id); + $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]'); + // Verify that the plain formatter works. $this->displayOptions['type'] = 'daterange_plain'; $this->displayOptions['settings'] = $this->defaultSettings; @@ -360,6 +375,11 @@ class DateRangeFieldTest extends DateTestBase { $this->assertFieldByXPath('//time[@datetime="' . $start_expected_iso . '"]', $start_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); $this->assertNoText(' THESEPARATOR ', 'Separator not found on page'); + // Verify that hook_entity_prepare_view can add attributes. + // @see entity_test_entity_prepare_view() + $this->drupalGet('entity_test/' . $id); + $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]'); + $this->displayOptions['type'] = 'daterange_plain'; $this->displayOptions['settings'] = $this->defaultSettings; entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') @@ -440,6 +460,11 @@ class DateRangeFieldTest extends DateTestBase { $this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso])); $this->assertText(' THESEPARATOR ', 'Found proper separator'); + // Verify that hook_entity_prepare_view can add attributes. + // @see entity_test_entity_prepare_view() + $this->drupalGet('entity_test/' . $id); + $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]'); + // Verify that the plain formatter works. $this->displayOptions['type'] = 'daterange_plain'; $this->displayOptions['settings'] = $this->defaultSettings; @@ -513,6 +538,11 @@ class DateRangeFieldTest extends DateTestBase { $this->assertFieldByXPath('//time[@datetime="' . $end_expected_iso . '"]', $end_expected, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso])); $this->assertText(' THESEPARATOR ', 'Found proper separator'); + // Verify that hook_entity_prepare_view can add attributes. + // @see entity_test_entity_prepare_view() + $this->drupalGet('entity_test/' . $id); + $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]'); + $this->displayOptions['type'] = 'daterange_plain'; entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full') ->setComponent($field_name, $this->displayOptions) diff --git a/core/modules/datetime_range/tests/src/Kernel/SeparatorTranslationTest.php b/core/modules/datetime_range/tests/src/Kernel/SeparatorTranslationTest.php new file mode 100644 index 000000000..155bb61d1 --- /dev/null +++ b/core/modules/datetime_range/tests/src/Kernel/SeparatorTranslationTest.php @@ -0,0 +1,131 @@ +installEntitySchema('entity_test'); + $this->installEntitySchema('user'); + $this->installConfig(['system']); + $this->installSchema('system', ['sequences', 'key_value']); + + // Add a datetime range field. + $this->fieldStorage = FieldStorageConfig::create([ + 'field_name' => Unicode::strtolower($this->randomMachineName()), + 'entity_type' => 'entity_test', + 'type' => 'daterange', + 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATE], + ]); + $this->fieldStorage->save(); + + $this->field = FieldConfig::create([ + 'field_storage' => $this->fieldStorage, + 'bundle' => 'entity_test', + 'required' => TRUE, + ]); + $this->field->save(); + + $display_options = [ + 'type' => 'daterange_default', + 'label' => 'hidden', + 'settings' => [ + 'format_type' => 'fallback', + 'separator' => 'UNTRANSLATED', + ], + ]; + EntityViewDisplay::create([ + 'targetEntityType' => $this->field->getTargetEntityTypeId(), + 'bundle' => $this->field->getTargetBundle(), + 'mode' => 'default', + 'status' => TRUE, + ])->setComponent($this->fieldStorage->getName(), $display_options) + ->save(); + } + + /** + * Tests the translation of the range separator. + */ + public function testSeparatorTranslation() { + // Create an entity. + $entity = EntityTest::create([ + 'name' => $this->randomString(), + $this->fieldStorage->getName() => [ + 'value' => '2016-09-20', + 'end_value' => '2016-09-21', + ], + ]); + + // Verify the untranslated separator. + $display = EntityViewDisplay::collectRenderDisplay($entity, 'default'); + $build = $display->build($entity); + $output = $this->container->get('renderer')->renderRoot($build); + $this->verbose($output); + $this->assertContains('UNTRANSLATED', (string) $output); + + // Translate the separator. + ConfigurableLanguage::createFromLangcode('nl')->save(); + /** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */ + $language_manager = $this->container->get('language_manager'); + $language_manager->getLanguageConfigOverride('nl', 'core.entity_view_display.entity_test.entity_test.default') + ->set('content.' . $this->fieldStorage->getName() . '.settings.separator', 'NL_TRANSLATED!') + ->save(); + + $this->container->get('language.config_factory_override') + ->setLanguage(new Language(['id' => 'nl'])); + $this->container->get('cache_tags.invalidator')->invalidateTags($entity->getCacheTags()); + $display = EntityViewDisplay::collectRenderDisplay($entity, 'default'); + $build = $display->build($entity); + $output = $this->container->get('renderer')->renderRoot($build); + $this->verbose($output); + $this->assertContains('NL_TRANSLATED!', (string) $output); + } + +} diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index d6fa16c5c..010177974 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -467,6 +467,78 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count } } +/** + * Implements hook_file_download(). + * + * @see file_file_download() + * @see file_get_file_references() + */ +function editor_file_download($uri) { + // Get the file record based on the URI. If not in the database just return. + /** @var \Drupal\file\FileInterface[] $files */ + $files = \Drupal::entityTypeManager() + ->getStorage('file') + ->loadByProperties(['uri' => $uri]); + if (count($files)) { + foreach ($files as $item) { + // Since some database servers sometimes use a case-insensitive comparison + // by default, double check that the filename is an exact match. + if ($item->getFileUri() === $uri) { + $file = $item; + break; + } + } + } + if (!isset($file)) { + return; + } + + // Temporary files are handled by file_file_download(), so nothing to do here + // about them. + // @see file_file_download() + + // Find out if any editor-backed field contains the file. + $usage_list = \Drupal::service('file.usage')->listUsage($file); + + // Stop processing if there are no references in order to avoid returning + // headers for files controlled by other modules. Make an exception for + // temporary files where the host entity has not yet been saved (for example, + // an image preview on a node creation form) in which case, allow download by + // the file's owner. + if (empty($usage_list['editor']) && ($file->isPermanent() || $file->getOwnerId() != \Drupal::currentUser()->id())) { + return; + } + + // Editor.module MUST NOT call $file->access() here (like file_file_download() + // does) as checking the 'download' access to a file entity would end up in + // FileAccessControlHandler->checkAccess() and ->getFileReferences(), which + // calls file_get_file_references(). This latter one would allow downloading + // files only handled by the file.module, which is exactly not the case right + // here. So instead we must check if the current user is allowed to view any + // of the entities that reference the image using the 'editor' module. + if ($file->isPermanent()) { + $referencing_entity_is_accessible = FALSE; + $references = empty($usage_list['editor']) ? [] : $usage_list['editor']; + foreach ($references as $entity_type => $entity_ids) { + $referencing_entities = entity_load_multiple($entity_type, $entity_ids); + /** @var \Drupal\Core\Entity\EntityInterface $referencing_entity */ + foreach ($referencing_entities as $referencing_entity) { + if ($referencing_entity->access('view', NULL, TRUE)->isAllowed()) { + $referencing_entity_is_accessible = TRUE; + break 2; + } + } + } + if (!$referencing_entity_is_accessible) { + return -1; + } + } + + // Access is granted. + $headers = file_get_content_headers($file); + return $headers; +} + /** * Finds all files referenced (data-entity-uuid) by formatted text fields. * diff --git a/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php new file mode 100644 index 000000000..5f689b3f0 --- /dev/null +++ b/core/modules/editor/src/Tests/EditorPrivateFileReferenceFilterTest.php @@ -0,0 +1,100 @@ +drupalCreateUser(); + $this->drupalLogin($author); + + // Create a content type with a body field. + $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']); + + // Create a file in the 'private:// ' stream. + $filename = 'test.png'; + $src = '/system/files/' . $filename; + /** @var \Drupal\file\FileInterface $file */ + $file = File::create([ + 'uri' => 'private://' . $filename, + ]); + $file->setTemporary(); + $file->setOwner($author); + // Create the file itself. + file_put_contents($file->getFileUri(), $this->randomString()); + $file->save(); + + // The image should be visible for its author. + $this->drupalGet($src); + $this->assertSession()->statusCodeEquals(200); + // The not-yet-permanent image should NOT be visible for anonymous. + $this->drupalLogout(); + $this->drupalGet($src); + $this->assertSession()->statusCodeEquals(403); + + // Resave the file to be permanent. + $file->setPermanent(); + $file->save(); + + // Create a node with its body field properly pointing to the just-created + // file. + $node = $this->drupalCreateNode([ + 'type' => 'page', + 'body' => [ + 'value' => 'alt', + 'format' => 'private_images', + ], + 'uid' => $author->id(), + ]); + + // Do the actual test. The image should be visible for anonymous users, + // because they can view the referencing entity. + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet($src); + $this->assertSession()->statusCodeEquals(200); + + // Disallow anonymous users to view the entity, which then should also + // disallow them to view the image. + Role::load(RoleInterface::ANONYMOUS_ID) + ->revokePermission('access content') + ->save(); + $this->drupalGet($node->toUrl()); + $this->assertSession()->statusCodeEquals(403); + $this->drupalGet($src); + $this->assertSession()->statusCodeEquals(403); + } + +} diff --git a/core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml b/core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml new file mode 100644 index 000000000..2de126a1c --- /dev/null +++ b/core/modules/editor/tests/editor_private_test/config/install/editor.editor.private_images.yml @@ -0,0 +1,34 @@ +format: private_images +status: true +langcode: en +editor: ckeditor +settings: + toolbar: + rows: + - + - + name: Media + items: + - DrupalImage + - + name: Tools + items: + - Source + plugins: + language: + language_list: un + stylescombo: + styles: '' +image_upload: + status: true + scheme: private + directory: '' + max_size: '' + max_dimensions: + width: null + height: null +dependencies: + config: + - filter.format.private_images + module: + - ckeditor diff --git a/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml b/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml new file mode 100644 index 000000000..261bd9017 --- /dev/null +++ b/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml @@ -0,0 +1,23 @@ +format: private_images +name: 'Private images' +status: true +langcode: en +filters: + editor_file_reference: + id: editor_file_reference + provider: editor + status: true + weight: 0 + settings: { } + filter_html: + id: filter_html + provider: filter + status: false + weight: -10 + settings: + allowed_html: '' + filter_html_help: true + filter_html_nofollow: false +dependencies: + module: + - editor diff --git a/core/modules/editor/tests/editor_private_test/editor_private_test.info.yml b/core/modules/editor/tests/editor_private_test/editor_private_test.info.yml new file mode 100644 index 000000000..474fda672 --- /dev/null +++ b/core/modules/editor/tests/editor_private_test/editor_private_test.info.yml @@ -0,0 +1,9 @@ +name: 'Text Editor Private test' +type: module +description: 'Support module for the Text Editor Private module tests.' +core: 8.x +package: Testing +version: VERSION +dependencies: + - filter + - ckeditor diff --git a/core/modules/field/migration_templates/d6_field.yml b/core/modules/field/migration_templates/d6_field.yml index 531bc132d..3157808f5 100644 --- a/core/modules/field/migration_templates/d6_field.yml +++ b/core/modules/field/migration_templates/d6_field.yml @@ -15,105 +15,101 @@ process: langcode: 'constants/langcode' field_name: field_name type: - - - plugin: field_type - source: - - type - - widget_type - map: - number_integer: - number: integer - optionwidgets_select: list_integer - optionwidgets_buttons: list_integer - optionwidgets_onoff: boolean - number_decimal: - number: decimal - optionwidgets_select: list_float - optionwidgets_buttons: list_float - optionwidgets_onoff: boolean - number_float: - number: float - optionwidgets_select: list_float - optionwidgets_buttons: list_float - optionwidgets_onoff: boolean - email: - email_textfield: email - filefield: - imagefield_widget: image - filefield_widget: file - date: - date_select: datetime - datestamp: - date_select: datetime - datetime: - date_select: datetime - fr_phone: - phone_textfield: telephone - be_phone: - phone_textfield: telephone - it_phone: - phone_textfield: telephone - el_phone: - phone_textfield: telephone - ch_phone: - phone_textfield: telephone - ca_phone: - phone_textfield: telephone - cr_phone: - phone_textfield: telephone - pa_phone: - phone_textfield: telephone - gb_phone: - phone_textfield: telephone - ru_phone: - phone_textfield: telephone - ua_phone: - phone_textfield: telephone - es_phone: - phone_textfield: telephone - au_phone: - phone_textfield: telephone - cs_phone: - phone_textfield: telephone - hu_phone: - phone_textfield: telephone - pl_phone: - phone_textfield: telephone - nl_phone: - phone_textfield: telephone - se_phone: - phone_textfield: telephone - za_phone: - phone_textfield: telephone - il_phone: - phone_textfield: telephone - nz_phone: - phone_textfield: telephone - br_phone: - phone_textfield: telephone - cl_phone: - phone_textfield: telephone - cn_phone: - phone_textfield: telephone - hk_phone: - phone_textfield: telephone - mo_phone: - phone_textfield: telephone - ph_phone: - phone_textfield: telephone - sg_phone: - phone_textfield: telephone - jo_phone: - phone_textfield: telephone - eg_phone: - phone_textfield: telephone - pk_phone: - phone_textfield: telephone - int_phone: - phone_textfield: telephone - - - plugin: skip_on_empty - method: row + plugin: field_type + source: + - type + - widget_type + map: + number_integer: + number: integer + optionwidgets_select: list_integer + optionwidgets_buttons: list_integer + optionwidgets_onoff: boolean + number_decimal: + number: decimal + optionwidgets_select: list_float + optionwidgets_buttons: list_float + optionwidgets_onoff: boolean + number_float: + number: float + optionwidgets_select: list_float + optionwidgets_buttons: list_float + optionwidgets_onoff: boolean + email: + email_textfield: email + filefield: + imagefield_widget: image + filefield_widget: file + date: + date_select: datetime + datestamp: + date_select: datetime + datetime: + date_select: datetime + fr_phone: + phone_textfield: telephone + be_phone: + phone_textfield: telephone + it_phone: + phone_textfield: telephone + el_phone: + phone_textfield: telephone + ch_phone: + phone_textfield: telephone + ca_phone: + phone_textfield: telephone + cr_phone: + phone_textfield: telephone + pa_phone: + phone_textfield: telephone + gb_phone: + phone_textfield: telephone + ru_phone: + phone_textfield: telephone + ua_phone: + phone_textfield: telephone + es_phone: + phone_textfield: telephone + au_phone: + phone_textfield: telephone + cs_phone: + phone_textfield: telephone + hu_phone: + phone_textfield: telephone + pl_phone: + phone_textfield: telephone + nl_phone: + phone_textfield: telephone + se_phone: + phone_textfield: telephone + za_phone: + phone_textfield: telephone + il_phone: + phone_textfield: telephone + nz_phone: + phone_textfield: telephone + br_phone: + phone_textfield: telephone + cl_phone: + phone_textfield: telephone + cn_phone: + phone_textfield: telephone + hk_phone: + phone_textfield: telephone + mo_phone: + phone_textfield: telephone + ph_phone: + phone_textfield: telephone + sg_phone: + phone_textfield: telephone + jo_phone: + phone_textfield: telephone + eg_phone: + phone_textfield: telephone + pk_phone: + phone_textfield: telephone + int_phone: + phone_textfield: telephone cardinality: plugin: static_map bypass: true @@ -126,6 +122,5 @@ process: source: - '@type' - global_settings - destination: plugin: md_entity:field_storage_config diff --git a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php index 8c2f31940..44bad618e 100644 --- a/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php +++ b/core/modules/field/src/Plugin/migrate/source/d6/FieldInstancePerFormDisplay.php @@ -21,8 +21,6 @@ class FieldInstancePerFormDisplay extends DrupalSqlBase { $rows = array(); $result = $this->prepareQuery()->execute(); while ($field_row = $result->fetchAssoc()) { - $field_row['display_settings'] = unserialize($field_row['display_settings']); - $field_row['widget_settings'] = unserialize($field_row['widget_settings']); $bundle = $field_row['type_name']; $field_name = $field_row['field_name']; @@ -34,7 +32,8 @@ class FieldInstancePerFormDisplay extends DrupalSqlBase { $rows[$index]['module'] = $field_row['module']; $rows[$index]['weight'] = $field_row['weight']; $rows[$index]['widget_type'] = $field_row['widget_type']; - $rows[$index]['widget_settings'] = $field_row['widget_settings']; + $rows[$index]['widget_settings'] = unserialize($field_row['widget_settings']); + $rows[$index]['display_settings'] = unserialize($field_row['display_settings']); } return new \ArrayIterator($rows); diff --git a/core/modules/field/src/Tests/NestedFormTest.php b/core/modules/field/src/Tests/NestedFormTest.php index 28dbe55ff..8b3402d58 100644 --- a/core/modules/field/src/Tests/NestedFormTest.php +++ b/core/modules/field/src/Tests/NestedFormTest.php @@ -165,4 +165,30 @@ class NestedFormTest extends FieldTestBase { $this->assertFieldValues($entity_2, 'field_unlimited', array(13, 14, 15)); } + /** + * Tests entity level validation within subforms. + */ + public function testNestedEntityFormEntityLevelValidation() { + // Create two entities. + $storage = $this->container->get('entity_type.manager') + ->getStorage('entity_test_constraints'); + + $entity_1 = $storage->create(); + $entity_1->save(); + + $entity_2 = $storage->create(); + $entity_2->save(); + + // Display the 'combined form'. + $this->drupalGet("test-entity-constraints/nested/{$entity_1->id()}/{$entity_2->id()}"); + $this->assertFieldByName('entity_2[changed]', 0, 'Entity 2: changed value appears correctly in the form.'); + + // Submit the form and check that the entities are updated accordingly. + $edit = ['entity_2[changed]' => REQUEST_TIME - 86400]; + $this->drupalPostForm(NULL, $edit, t('Save')); + + $elements = $this->cssSelect('.entity-2.error'); + $this->assertEqual(1, count($elements), 'The whole nested entity form has been correctly flagged with an error class.'); + } + } diff --git a/core/modules/field/tests/modules/field_test/field_test.routing.yml b/core/modules/field/tests/modules/field_test/field_test.routing.yml index cfd60faf4..bcacdc4ba 100644 --- a/core/modules/field/tests/modules/field_test/field_test.routing.yml +++ b/core/modules/field/tests/modules/field_test/field_test.routing.yml @@ -11,3 +11,17 @@ field_test.entity_nested_form: type: 'entity:entity_test' requirements: _permission: 'administer entity_test content' + +field_test.entity_constraints_nested_form: + path: '/test-entity-constraints/nested/{entity_1}/{entity_2}' + defaults: + _title: 'Nested entity form' + _form: '\Drupal\field_test\Form\NestedEntityTestForm' + options: + parameters: + entity_1: + type: 'entity:entity_test_constraints' + entity_2: + type: 'entity:entity_test_constraints' + requirements: + _permission: 'administer entity_test content' diff --git a/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php b/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php index a83182693..d54c6836f 100644 --- a/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php +++ b/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php @@ -2,6 +2,7 @@ namespace Drupal\field_test\Form; +use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; @@ -33,16 +34,26 @@ class NestedEntityTestForm extends FormBase { $form_state->set('entity_2', $entity_2); $form_display_2 = EntityFormDisplay::collectRenderDisplay($entity_2, 'default'); $form_state->set('form_display_2', $form_display_2); - $form['entity_2'] = array( + $form['entity_2'] = [ '#type' => 'details', '#title' => t('Second entity'), '#tree' => TRUE, - '#parents' => array('entity_2'), + '#parents' => ['entity_2'], '#weight' => 50, - ); + '#attributes' => ['class' => ['entity-2']] + ]; $form_display_2->buildForm($entity_2, $form['entity_2'], $form_state); + if ($entity_2 instanceof EntityChangedInterface) { + // Changed must be sent to the client, for later overwrite error checking. + // @see Drupal\field\Tests\NestedFormTest::testNestedEntityFormEntityLevelValidation() + $form['entity_2']['changed'] = [ + '#type' => 'hidden', + '#default_value' => $entity_1->getChangedTime(), + ]; + } + $form['save'] = array( '#type' => 'submit', '#value' => t('Save'), @@ -65,7 +76,16 @@ class NestedEntityTestForm extends FormBase { $entity_2 = $form_state->get('entity_2'); /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_2 */ $form_display_2 = $form_state->get('form_display_2'); - $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state); + $extracted = $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state); + // Extract the values of fields that are not rendered through widgets, by + // simply copying from top-level form values. This leaves the fields that + // are not being edited within this form untouched. + // @see Drupal\field\Tests\NestedFormTest::testNestedEntityFormEntityLevelValidation() + foreach ($form_state->getValues()['entity_2'] as $name => $values) { + if ($entity_2->hasField($name) && !isset($extracted[$name])) { + $entity_2->set($name, $values); + } + } $form_display_2->validateFormValues($entity_2, $form['entity_2'], $form_state); } diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php index b41311ae5..2fa6d5cc0 100644 --- a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php +++ b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php @@ -96,6 +96,19 @@ class MigrateFieldInstanceTest extends MigrateDrupal7TestBase { $this->assertIdentical($expected_entity_type . '.' . $expected_name, $field->getFieldStorageDefinition()->id()); } + /** + * Asserts the settings of a link field config entity. + * + * @param $id + * The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME. + * @param $title_setting + * The expected title setting. + */ + protected function assertLinkFields($id, $title_setting) { + $field = FieldConfig::load($id); + $this->assertSame($title_setting, $field->getSetting('title')); + } + /** * Tests migrating D7 field instances to field_config entities. */ @@ -131,6 +144,10 @@ class MigrateFieldInstanceTest extends MigrateDrupal7TestBase { $this->assertEntity('node.test_content_type.field_text', 'Text', 'text', FALSE); $this->assertEntity('comment.comment_node_test_content_type.field_integer', 'Integer', 'integer', FALSE); $this->assertEntity('user.user.field_file', 'File', 'file', FALSE); + + $this->assertLinkFields('node.test_content_type.field_link', DRUPAL_OPTIONAL); + $this->assertLinkFields('node.article.field_link', DRUPAL_DISABLED); + $this->assertLinkFields('node.blog.field_link', DRUPAL_REQUIRED); } } diff --git a/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php new file mode 100644 index 000000000..a4ef2f6e6 --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php @@ -0,0 +1,80 @@ + [], + 'expected_data' => [], + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'display_settings' => [], + 'widget_settings' => [], + 'type_name' => 'story', + 'widget_active' => TRUE, + 'field_name' => 'field_test_filefield', + 'type' => 'filefield', + 'module' => 'filefield', + 'weight' => '8', + 'widget_type' => 'filefield_widget', + ], + ]; + + // The source data. + $empty_array = serialize([]); + $tests[0]['source_data']['content_node_field'] = [ + [ + 'field_name' => 'field_test_filefield', + 'type' => 'filefield', + 'global_settings' => $empty_array, + 'required' => '0', + 'multiple' => '0', + 'db_storage' => '1', + 'module' => 'filefield', + 'db_columns' => $empty_array, + 'active' => '1', + 'locked' => '0', + ] + ]; + $tests[0]['source_data']['content_node_field_instance'] = [ + [ + 'field_name' => 'field_test_filefield', + 'type_name' => 'story', + 'weight' => '8', + 'label' => 'File Field', + 'widget_type' => 'filefield_widget', + 'widget_settings' => $empty_array, + 'display_settings' => $empty_array, + 'description' => 'An example image field.', + 'widget_module' => 'filefield', + 'widget_active' => '1', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php new file mode 100644 index 000000000..baea562ee --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php @@ -0,0 +1,101 @@ + [], + 'expected_data' => [], + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'entity_type' => 'node', + 'view_mode' => 4, + 'type_name' => 'article', + 'field_name' => 'field_test', + 'type' => 'text', + 'module' => 'text', + 'weight' => 1, + 'label' => 'above', + 'display_settings' => [ + 'weight' => 1, + 'parent' => '', + 'label' => [ + 'format' => 'above', + ], + 4 => [ + 'format' => 'trimmed', + 'exclude' => 0, + ], + ], + 'widget_settings' => [], + ], + [ + 'entity_type' => 'node', + 'view_mode' => 'teaser', + 'type_name' => 'story', + 'field_name' => 'field_test', + 'type' => 'text', + 'module' => 'text', + 'weight' => 2, + 'label' => 'above', + 'display_settings' => [ + 'weight' => 1, + 'parent' => '', + 'label' => [ + 'format' => 'above', + ], + 'teaser' => [ + 'format' => 'trimmed', + 'exclude' => 0, + ], + ], + 'widget_settings' => [], + ], + ]; + + // The source data. + foreach ($tests[0]['expected_data'] as $k => $field_view_mode) { + // These are stored as serialized strings. + $field_view_mode['display_settings'] = serialize($field_view_mode['display_settings']); + $field_view_mode['widget_settings'] = serialize($field_view_mode['widget_settings']); + + $tests[0]['source_data']['content_node_field'][] = [ + 'field_name' => $field_view_mode['field_name'], + 'type' => $field_view_mode['type'], + 'module' => $field_view_mode['module'], + ]; + unset($field_view_mode['type'], $field_view_mode['module']); + + $tests[0]['source_data']['content_node_field_instance'][] = $field_view_mode; + + // Update the expected display settings. + $tests[0]['expected_data'][$k]['display_settings'] = $tests[0]['expected_data'][$k]['display_settings'][$field_view_mode['view_mode']]; + } + + return $tests; + } + +} diff --git a/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstanceTest.php new file mode 100644 index 000000000..bfbddcaef --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldInstanceTest.php @@ -0,0 +1,96 @@ + [], + 'expected_data' => [], + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'field_name' => 'field_body', + 'type_name' => 'page', + 'weight' => 1, + 'label' => 'body', + 'widget_type' => 'text_textarea', + 'description' => '', + 'widget_module' => 'text', + 'widget_active' => 1, + 'required' => 1, + 'active' => 1, + 'global_settings' => [], + 'widget_settings' => [ + 'rows' => 5, + 'size' => 60, + 'default_value' => [ + [ + 'value' => '', + '_error_element' => 'default_value_widget][field_body][0][value', + 'default_value_php' => '', + ], + ], + ], + 'display_settings' => [ + 'label' => [ + 'format' => 'above', + 'exclude' => 0, + ], + 'teaser' => [ + 'format' => 'default', + 'exclude' => 0, + ], + 'full' => [ + 'format' => 'default', + 'exclude' => 0, + ], + ], + ], + ]; + + // The source data. + $tests[0]['source_data']['content_node_field_instance'] = array_map( + function (array $row) { + $row['widget_settings'] = serialize($row['widget_settings']); + $row['display_settings'] = serialize($row['display_settings']); + $row['global_settings'] = serialize($row['global_settings']); + return $row; + }, + $tests[0]['expected_data'] + ); + $tests[0]['source_data']['content_node_field'] = [ + [ + 'field_name' => 'field_body', + 'required' => 1, + 'type' => 'text', + 'active' => 1, + 'global_settings' => serialize([]), + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldTest.php new file mode 100644 index 000000000..06026414a --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d6/FieldTest.php @@ -0,0 +1,79 @@ + [], + 'expected_data' => [], + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'field_name' => 'field_body', + 'type' => 'text', + 'global_settings' => [ + 'text_processing' => 0, + 'max_length' => '', + 'allowed_values' => '', + 'allowed_values_php' => '', + ], + 'required' => 0, + 'multiple' => 0, + 'db_storage' => 1, + 'module' => 'text', + 'db_columns' => [ + 'value' => [ + 'type' => 'text', + 'size' => 'big', + 'not null' => '', + 'sortable' => 1, + 'views' => 1, + ], + ], + 'active' => 1, + 'locked' => 0, + ], + ]; + + // The source data. + $tests[0]['source_data']['content_node_field'] = array_map( + function (array $row) { + $row['global_settings'] = serialize($row['global_settings']); + $row['db_columns'] = serialize($row['db_columns']); + return $row; + }, + $tests[0]['expected_data'] + ); + $tests[0]['source_data']['content_node_field_instance'] = [ + [ + 'widget_type' => 'text_textarea', + 'field_name' => 'field_body', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstancePerFormDisplayTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstancePerFormDisplayTest.php similarity index 79% rename from core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstancePerFormDisplayTest.php rename to core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstancePerFormDisplayTest.php index 92c10a0c3..e2de4fda4 100644 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstancePerFormDisplayTest.php +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstancePerFormDisplayTest.php @@ -1,84 +1,36 @@ 'test_fieldinstance', - 'source' => array( - 'plugin' => 'd7_field_instance_per_form_display', - ), - ); - - // We need to set up the database contents; it's easier to do that below. - // These are sample result queries. - protected $expectedResults = array( - array( - 'field_name' => 'body', - 'entity_type' => 'node', - 'bundle' => 'page', - 'widget_settings' => array( - ), - 'display_settings' => array( - ), - 'description' => '', - 'required' => FALSE, - 'global_settings' => array(), - ), - array( - 'field_name' => 'field_file', - 'entity_type' => 'user', - 'bundle' => 'user', - 'widget_settings' => array( - ), - 'display_settings' => array( - ), - 'description' => '', - 'required' => FALSE, - 'global_settings' => array(), - ), - array( - 'field_name' => 'field_integer', - 'entity_type' => 'comment', - 'bundle' => 'comment_node_test_content_type', - 'widget_settings' => array( - ), - 'display_settings' => array( - ), - 'description' => '', - 'required' => FALSE, - 'global_settings' => array(), - ), - array( - 'field_name' => 'field_link', - 'entity_type' => 'taxonomy_term', - 'bundle' => 'test_vocabulary', - 'widget_settings' => array( - ), - 'display_settings' => array( - ), - 'description' => '', - 'required' => FALSE, - 'global_settings' => array(), - ), - ); +class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestBase { /** - * Prepopulate contents with results. + * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['field_config_instance'] = array( - array( + public static $modules = ['field', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = [ + [ + 'source_data' => [], + 'expected_data' => [], + ], + ]; + + // The source data. + $tests[0]['source_data']['field_config_instance'] = [ + [ 'id' => '2', 'field_id' => '2', 'field_name' => 'body', @@ -86,8 +38,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'bundle' => 'page', 'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}', 'deleted' => '0', - ), - array( + ], + [ 'id' => '33', 'field_id' => '11', 'field_name' => 'field_file', @@ -95,8 +47,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'bundle' => 'user', 'data' => 'a:6:{s:5:"label";s:4:"File";s:6:"widget";a:5:{s:6:"weight";s:1:"8";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:3:"txt";s:12:"max_filesize";s:0:"";s:17:"description_field";i:0;s:18:"user_register_form";i:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:8:"settings";a:0:{}s:6:"module";s:4:"file";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";}', 'deleted' => '0', - ), - array( + ], + [ 'id' => '32', 'field_id' => '14', 'field_name' => 'field_integer', @@ -104,8 +56,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'bundle' => 'comment_node_test_content_type', 'data' => 'a:7:{s:5:"label";s:7:"Integer";s:6:"widget";a:5:{s:6:"weight";s:1:"2";s:4:"type";s:6:"number";s:6:"module";s:6:"number";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:5:{s:3:"min";s:0:"";s:3:"max";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:14:"number_integer";s:8:"settings";a:4:{s:18:"thousand_separator";s:1:" ";s:17:"decimal_separator";s:1:".";s:5:"scale";i:0;s:13:"prefix_suffix";b:1;}s:6:"module";s:6:"number";s:6:"weight";i:1;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', 'deleted' => '0', - ), - array( + ], + [ 'id' => '25', 'field_id' => '15', 'field_name' => 'field_link', @@ -113,10 +65,10 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'bundle' => 'test_vocabulary', 'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:2:"10";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:12:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"optional";s:11:"title_value";s:19:"Unused Static Title";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"81";}s:10:"attributes";a:6:{s:6:"target";s:6:"_blank";s:3:"rel";s:8:"nofollow";s:18:"configurable_class";i:0;s:5:"class";s:7:"classes";s:18:"configurable_title";i:1;s:5:"title";s:0:"";}s:10:"rel_remove";s:19:"rel_remove_external";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:6:"weight";s:1:"9";s:8:"settings";a:0:{}s:6:"module";s:4:"link";}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', 'deleted' => '0', - ), - ); - $this->databaseContents['field_config'] = array( - array( + ], + ]; + $tests[0]['source_data']['field_config'] = [ + [ 'id' => '2', 'field_name' => 'body', 'type' => 'text_with_summary', @@ -130,8 +82,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', - ), - array( + ], + [ 'id' => '11', 'field_name' => 'field_file', 'type' => 'file', @@ -145,8 +97,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', - ), - array( + ], + [ 'id' => '14', 'field_name' => 'field_integer', 'type' => 'number_integer', @@ -160,8 +112,8 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', - ), - array( + ], + [ 'id' => '15', 'field_name' => 'field_link', 'type' => 'link_field', @@ -175,10 +127,43 @@ class FieldInstancePerFormDisplayTest extends MigrateSqlSourceTestCase { 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', - ), - ); + ], + ]; - parent::setUp(); + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'field_name' => 'body', + 'entity_type' => 'node', + 'bundle' => 'page', + 'widget_settings' => [ + 'rows' => 20, + 'summary_rows' => 5, + ], + ], + [ + 'field_name' => 'field_file', + 'entity_type' => 'user', + 'bundle' => 'user', + 'widget_settings' => [ + 'progress_indicator' => 'throbber', + ], + ], + [ + 'field_name' => 'field_integer', + 'entity_type' => 'comment', + 'bundle' => 'comment_node_test_content_type', + 'widget_settings' => [], + ], + [ + 'field_name' => 'field_link', + 'entity_type' => 'taxonomy_term', + 'bundle' => 'test_vocabulary', + 'widget_settings' => [], + ], + ]; + + return $tests; } } diff --git a/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php new file mode 100644 index 000000000..6ed72e43c --- /dev/null +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php @@ -0,0 +1,75 @@ + [], + 'expected_data' => [], + ], + ]; + + // The source data. + $tests[0]['source_data']['field_config_instance'] = [ + [ + 'id' => '2', + 'field_id' => '2', + 'field_name' => 'body', + 'entity_type' => 'node', + 'bundle' => 'page', + 'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}', + 'deleted' => '0', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'field_name' => 'body', + 'label' => 'hidden', + 'type' => 'text_default', + 'settings' => [], + 'module' => 'text', + 'weight' => 0, + 'view_mode' => 'default', + ], + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'field_name' => 'body', + 'label' => 'hidden', + 'type' => 'text_summary_or_trimmed', + 'settings' => [ + 'trim_length' => 600, + ], + 'module' => 'text', + 'weight' => 0, + 'view_mode' => 'teaser', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstanceTest.php similarity index 51% rename from core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstanceTest.php rename to core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstanceTest.php index eca7f27f5..a0b8ec1db 100644 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstanceTest.php +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstanceTest.php @@ -1,54 +1,36 @@ 'test_fieldinstance', - 'source' => array( - 'plugin' => 'd7_field_instance', - ), - ); - - protected $expectedResults = array( - array( - 'field_name' => 'body', - 'entity_type' => 'node', - 'bundle' => 'page', - 'label' => 'Body', - 'widget_settings' => array( - 'module' => 'text', - 'settings' => array( - 'rows' => 20, - 'summary_rows' => 5, - ), - 'type' => 'text_textarea_with_summary', - 'weight' => -4, - ), - 'display_settings' => array( - ), - 'description' => '', - 'required' => FALSE, - 'global_settings' => array(), - ), - ); +class FieldInstanceTest extends MigrateSqlSourceTestBase { /** - * Prepopulate contents with results. + * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['field_config_instance'] = array( - array( + public static $modules = ['field', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = [ + [ + 'source_data' => [], + 'expected_data' => [], + ], + ]; + + // The source data. + $tests[0]['source_data']['field_config_instance'] = [ + [ 'id' => '2', 'field_id' => '2', 'field_name' => 'body', @@ -56,10 +38,10 @@ class FieldInstanceTest extends MigrateSqlSourceTestCase { 'bundle' => 'page', 'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}', 'deleted' => '0', - ), - ); - $this->databaseContents['field_config'] = array( - array( + ], + ]; + $tests[0]['source_data']['field_config'] = [ + [ 'id' => '2', 'field_name' => 'body', 'type' => 'text_with_summary', @@ -73,10 +55,49 @@ class FieldInstanceTest extends MigrateSqlSourceTestCase { 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', - ), - ); + ], + ]; - parent::setUp(); + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'field_name' => 'body', + 'entity_type' => 'node', + 'bundle' => 'page', + 'label' => 'Body', + 'widget_settings' => [ + 'module' => 'text', + 'settings' => [ + 'rows' => 20, + 'summary_rows' => 5, + ], + 'type' => 'text_textarea_with_summary', + 'weight' => -4, + ], + 'display_settings' => [ + 'default' => [ + 'label' => 'hidden', + 'type' => 'text_default', + 'settings' => [], + 'module' => 'text', + 'weight' => 0, + ], + 'teaser' => [ + 'label' => 'hidden', + 'type' => 'text_summary_or_trimmed', + 'settings' => [ + 'trim_length' => 600, + ], + 'module' => 'text', + 'weight' => 0, + ], + ], + 'description' => '', + 'required' => FALSE, + ], + ]; + + return $tests; } } diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldTest.php similarity index 52% rename from core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldTest.php rename to core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldTest.php index a440f39f7..707d624c1 100644 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldTest.php +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldTest.php @@ -1,100 +1,36 @@ 'test_field', - 'source' => array( - 'plugin' => 'd7_field', - ), - ); - - protected $expectedResults = array( - array( - 'field_name' => 'field_file', - 'type' => 'file', - 'global_settings' => '', - 'storage' => array( - 'active' => 1, - 'details' => array( - 'sql' => array( - 'FIELD_LOAD_CURRENT' => array( - 'field_data_field_file' => array( - 'description' => 'field_file_description', - 'display' => 'field_file_display', - 'fid' => 'field_file_fid', - ), - ), - 'FIELD_LOAD_REVISION' => array( - 'field_revision_field_file' => array( - 'description' => 'field_file_description', - 'display' => 'field_file_display', - 'fid' => 'field_file_fid', - ), - ), - ), - ), - 'module' => 'field_sql_storage', - 'settings' => array(), - 'type' => 'field_sql_storage', - ), - 'module' => 'file', - 'db_columns' => '', - 'locked' => 0, - 'entity_type' => 'node', - ), - array( - 'field_name' => 'field_file', - 'type' => 'file', - 'global_settings' => '', - 'storage' => array( - 'active' => 1, - 'details' => array( - 'sql' => array( - 'FIELD_LOAD_CURRENT' => array( - 'field_data_field_file' => array( - 'description' => 'field_file_description', - 'display' => 'field_file_display', - 'fid' => 'field_file_fid', - ), - ), - 'FIELD_LOAD_REVISION' => array( - 'field_revision_field_file' => array( - 'description' => 'field_file_description', - 'display' => 'field_file_display', - 'fid' => 'field_file_fid', - ), - ), - ), - ), - 'module' => 'field_sql_storage', - 'settings' => array(), - 'type' => 'field_sql_storage', - ), - 'module' => 'file', - 'db_columns' => '', - 'locked' => 0, - 'entity_type' => 'user', - ), - ); +class FieldTest extends MigrateSqlSourceTestBase { /** - * Prepopulate contents with results. + * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['field_config'] = array( - array( + public static $modules = ['field', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = [ + [ + 'source_data' => [], + 'expected_data' => [], + ], + ]; + + // The source data. + $tests[0]['source_data']['field_config'] = [ + [ 'id' => '11', 'field_name' => 'field_file', 'type' => 'file', @@ -108,10 +44,10 @@ class FieldTest extends MigrateSqlSourceTestCase { 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', - ), - ); - $this->databaseContents['field_config_instance'] = array( - array( + ], + ]; + $tests[0]['source_data']['field_config_instance'] = [ + [ 'id' => '33', 'field_id' => '11', 'field_name' => 'field_file', @@ -119,8 +55,8 @@ class FieldTest extends MigrateSqlSourceTestCase { 'bundle' => 'user', 'data' => 'a:6:{s:5:"label";s:4:"File";s:6:"widget";a:5:{s:6:"weight";s:1:"8";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:3:"txt";s:12:"max_filesize";s:0:"";s:17:"description_field";i:0;s:18:"user_register_form";i:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:8:"settings";a:0:{}s:6:"module";s:4:"file";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";}', 'deleted' => '0', - ), - array( + ], + [ 'id' => '21', 'field_id' => '11', 'field_name' => 'field_file', @@ -128,9 +64,76 @@ class FieldTest extends MigrateSqlSourceTestCase { 'bundle' => 'test_content_type', 'data' => 'a:6:{s:5:"label";s:4:"File";s:6:"widget";a:5:{s:6:"weight";s:1:"5";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:15:"txt pdf ods odf";s:12:"max_filesize";s:5:"10 MB";s:17:"description_field";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:6:"weight";s:1:"5";s:8:"settings";a:0:{}s:6:"module";s:4:"file";}}s:8:"required";i:0;s:11:"description";s:0:"";}', 'deleted' => '0', - ), - ); - parent::setUp(); + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'field_name' => 'field_file', + 'type' => 'file', + 'storage' => [ + 'active' => 1, + 'details' => [ + 'sql' => [ + 'FIELD_LOAD_CURRENT' => [ + 'field_data_field_file' => [ + 'description' => 'field_file_description', + 'display' => 'field_file_display', + 'fid' => 'field_file_fid', + ], + ], + 'FIELD_LOAD_REVISION' => [ + 'field_revision_field_file' => [ + 'description' => 'field_file_description', + 'display' => 'field_file_display', + 'fid' => 'field_file_fid', + ], + ], + ], + ], + 'module' => 'field_sql_storage', + 'settings' => [], + 'type' => 'field_sql_storage', + ], + 'module' => 'file', + 'locked' => 0, + 'entity_type' => 'node', + ], + [ + 'field_name' => 'field_file', + 'type' => 'file', + 'storage' => [ + 'active' => 1, + 'details' => [ + 'sql' => [ + 'FIELD_LOAD_CURRENT' => [ + 'field_data_field_file' => [ + 'description' => 'field_file_description', + 'display' => 'field_file_display', + 'fid' => 'field_file_fid', + ], + ], + 'FIELD_LOAD_REVISION' => [ + 'field_revision_field_file' => [ + 'description' => 'field_file_description', + 'display' => 'field_file_display', + 'fid' => 'field_file_fid', + ], + ], + ], + ], + 'module' => 'field_sql_storage', + 'settings' => [], + 'type' => 'field_sql_storage', + ], + 'module' => 'file', + 'locked' => 0, + 'entity_type' => 'user', + ], + ]; + + return $tests; } } diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/ViewModeTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/ViewModeTest.php similarity index 69% rename from core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/ViewModeTest.php rename to core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/ViewModeTest.php index c3e0385e0..42c7bce9f 100644 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/ViewModeTest.php +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/ViewModeTest.php @@ -1,54 +1,36 @@ 'test', - 'source' => array( - 'plugin' => 'd7_view_mode', - ), - ); - - protected $expectedResults = array( - array( - 'entity_type' => 'node', - 'view_mode' => 'default', - ), - array( - 'entity_type' => 'node', - 'view_mode' => 'teaser', - ), - array( - 'entity_type' => 'node', - 'view_mode' => 'custom', - ), - array( - 'entity_type' => 'user', - 'view_mode' => 'default', - ), - array( - 'entity_type' => 'comment', - 'view_mode' => 'default', - ), - ); +class ViewModeTest extends MigrateSqlSourceTestBase { /** - * Prepopulate contents with results. + * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['field_config_instance'] = array( - array( + public static $modules = ['field', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = [ + [ + 'source_data' => [], + 'expected_data' => [], + ], + ]; + + // The source data. + $tests[0]['source_data']['field_config_instance'] = [ + [ 'id' => '13', 'field_id' => '2', 'field_name' => 'body', @@ -56,8 +38,8 @@ class ViewModeTest extends MigrateSqlSourceTestCase { 'bundle' => 'forum', 'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:1;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:3:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:11;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:11;}s:6:"custom";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:11;}}s:8:"required";b:0;s:11:"description";s:0:"";}', 'deleted' => '0', - ), - array( + ], + [ 'id' => '33', 'field_id' => '11', 'field_name' => 'field_file', @@ -65,8 +47,8 @@ class ViewModeTest extends MigrateSqlSourceTestCase { 'bundle' => 'user', 'data' => 'a:6:{s:5:"label";s:4:"File";s:6:"widget";a:5:{s:6:"weight";s:1:"8";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:3:"txt";s:12:"max_filesize";s:0:"";s:17:"description_field";i:0;s:18:"user_register_form";i:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:8:"settings";a:0:{}s:6:"module";s:4:"file";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";}', 'deleted' => '0', - ), - array( + ], + [ 'id' => '12', 'field_id' => '1', 'field_name' => 'comment_body', @@ -74,9 +56,34 @@ class ViewModeTest extends MigrateSqlSourceTestCase { 'bundle' => 'comment_node_forum', 'data' => 'a:6:{s:5:"label";s:7:"Comment";s:8:"settings";a:2:{s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:8:"required";b:1;s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:6:"weight";i:0;s:8:"settings";a:0:{}s:6:"module";s:4:"text";}}s:6:"widget";a:4:{s:4:"type";s:13:"text_textarea";s:8:"settings";a:1:{s:4:"rows";i:5;}s:6:"weight";i:0;s:6:"module";s:4:"text";}s:11:"description";s:0:"";}', 'deleted' => '0', - ), - ); - parent::setUp(); + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'entity_type' => 'node', + 'view_mode' => 'default', + ], + [ + 'entity_type' => 'node', + 'view_mode' => 'teaser', + ], + [ + 'entity_type' => 'node', + 'view_mode' => 'custom', + ], + [ + 'entity_type' => 'user', + 'view_mode' => 'default', + ], + [ + 'entity_type' => 'comment', + 'view_mode' => 'default', + ], + ]; + + return $tests; } } diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php b/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php deleted file mode 100644 index d6184b5bf..000000000 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerFormDisplayTest.php +++ /dev/null @@ -1,75 +0,0 @@ - 'view_mode_test', - 'source' => array( - 'plugin' => 'd6_field_instance_per_form_display', - ), - ); - - protected $expectedResults = array( - array( - 'display_settings' => array(), - 'widget_settings' => array(), - 'type_name' => 'story', - 'widget_active' => TRUE, - 'field_name' => 'field_test_filefield', - 'type' => 'filefield', - 'module' => 'filefield', - 'weight' => '8', - 'widget_type' => 'filefield_widget', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $empty_array = serialize([]); - - $this->databaseContents['content_node_field'] = array( - array( - 'field_name' => 'field_test_filefield', - 'type' => 'filefield', - 'global_settings' => $empty_array, - 'required' => '0', - 'multiple' => '0', - 'db_storage' => '1', - 'module' => 'filefield', - 'db_columns' => $empty_array, - 'active' => '1', - 'locked' => '0', - ) - ); - $this->databaseContents['content_node_field_instance'] = array( - array( - 'field_name' => 'field_test_filefield', - 'type_name' => 'story', - 'weight' => '8', - 'label' => 'File Field', - 'widget_type' => 'filefield_widget', - 'widget_settings' => $empty_array, - 'display_settings' => $empty_array, - 'description' => 'An example image field.', - 'widget_module' => 'filefield', - 'widget_active' => '1', - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php b/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php deleted file mode 100644 index d0865b8a5..000000000 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstancePerViewModeTest.php +++ /dev/null @@ -1,96 +0,0 @@ - 'view_mode_test', - 'source' => array( - 'plugin' => 'd6_field_instance_per_view_mode', - ), - ); - - protected $expectedResults = array( - array( - 'entity_type' => 'node', - 'view_mode' => 4, - 'type_name' => 'article', - 'field_name' => 'field_test', - 'type' => 'text', - 'module' => 'text', - 'weight' => 1, - 'label' => 'above', - 'display_settings' => array( - 'weight' => 1, - 'parent' => '', - 'label' => array( - 'format' => 'above', - ), - 4 => array( - 'format' => 'trimmed', - 'exclude' => 0, - ), - ), - 'widget_settings' => array(), - ), - array( - 'entity_type' => 'node', - 'view_mode' => 'teaser', - 'type_name' => 'story', - 'field_name' => 'field_test', - 'type' => 'text', - 'module' => 'text', - 'weight' => 2, - 'label' => 'above', - 'display_settings' => array( - 'weight' => 1, - 'parent' => '', - 'label' => array( - 'format' => 'above', - ), - 'teaser' => array( - 'format' => 'trimmed', - 'exclude' => 0, - ), - ), - 'widget_settings' => array(), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $field_view_mode) { - // These are stored as serialized strings. - $field_view_mode['display_settings'] = serialize($field_view_mode['display_settings']); - $field_view_mode['widget_settings'] = serialize($field_view_mode['widget_settings']); - - $this->databaseContents['content_node_field'][] = array( - 'field_name' => $field_view_mode['field_name'], - 'type' => $field_view_mode['type'], - 'module' => $field_view_mode['module'], - ); - unset($field_view_mode['type']); - unset($field_view_mode['module']); - - $this->databaseContents['content_node_field_instance'][] = $field_view_mode; - - // Update the expected display settings. - $this->expectedResults[$k]['display_settings'] = $this->expectedResults[$k]['display_settings'][$field_view_mode['view_mode']]; - - } - parent::setUp(); - } - -} diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstanceTest.php b/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstanceTest.php deleted file mode 100644 index 80e6e9c6b..000000000 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldInstanceTest.php +++ /dev/null @@ -1,91 +0,0 @@ - 'test_fieldinstance', - 'source' => [ - 'plugin' => 'd6_field_instance', - ], - ]; - - // We need to set up the database contents; it's easier to do that below. - // These are sample result queries. - protected $expectedResults = [ - [ - 'field_name' => 'field_body', - 'type_name' => 'page', - 'weight' => 1, - 'label' => 'body', - 'widget_type' => 'text_textarea', - 'widget_settings' => '', - 'display_settings' => '', - 'description' => '', - 'widget_module' => 'text', - 'widget_active' => 1, - 'required' => 1, - 'active' => 1, - 'global_settings' => [], - ], - ]; - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->expectedResults[0]['widget_settings'] = [ - 'rows' => 5, - 'size' => 60, - 'default_value' => [ - [ - 'value' => '', - '_error_element' => 'default_value_widget][field_body][0][value', - 'default_value_php' => '', - ], - ], - ]; - $this->expectedResults[0]['display_settings'] = [ - 'label' => [ - 'format' => 'above', - 'exclude' => 0, - ], - 'teaser' => [ - 'format' => 'default', - 'exclude' => 0, - ], - 'full' => [ - 'format' => 'default', - 'exclude' => 0, - ], - ]; - $this->databaseContents['content_node_field_instance'] = $this->expectedResults; - $this->databaseContents['content_node_field_instance'][0]['widget_settings'] = serialize($this->expectedResults[0]['widget_settings']); - $this->databaseContents['content_node_field_instance'][0]['display_settings'] = serialize($this->expectedResults[0]['display_settings']); - $this->databaseContents['content_node_field_instance'][0]['global_settings'] = 'a:0:{}'; - - $this->databaseContents['content_node_field'][0] = [ - 'field_name' => 'field_body', - 'required' => 1, - 'type' => 'text', - 'active' => 1, - 'global_settings' => serialize([]), - ]; - parent::setUp(); - } - -} diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldTest.php b/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldTest.php deleted file mode 100644 index 365746671..000000000 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d6/FieldTest.php +++ /dev/null @@ -1,72 +0,0 @@ - 'test_field', - 'source' => array( - 'plugin' => 'd6_field', - ), - ); - - // We need to set up the database contents; it's easier to do that below. - // These are sample result queries. - protected $expectedResults = array( - array( - 'field_name' => 'field_body', - 'type' => 'text', - 'global_settings' => '', - 'required' => 0, - 'multiple' => 0, - 'db_storage' => 1, - 'module' => 'text', - 'db_columns' => '', - 'active' => 1, - 'locked' => 0, - ), - ); - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->expectedResults[0]['global_settings'] = array( - 'text_processing' => 0, - 'max_length' => '', - 'allowed_values' => '', - 'allowed_values_php' => '', - ); - $this->expectedResults[0]['db_columns'] = array( - 'value' => array( - 'type' => 'text', - 'size' => 'big', - 'not null' => '', - 'sortable' => 1, - 'views' => 1, - ), - ); - $this->databaseContents['content_node_field'] = $this->expectedResults; - $this->databaseContents['content_node_field'][0]['global_settings'] = serialize($this->databaseContents['content_node_field'][0]['global_settings']); - $this->databaseContents['content_node_field'][0]['db_columns'] = serialize($this->databaseContents['content_node_field'][0]['db_columns']); - - $this->databaseContents['content_node_field_instance'][0]['widget_type'] = 'text_textarea'; - $this->databaseContents['content_node_field_instance'][0]['field_name'] = 'field_body'; - parent::setUp(); - } - -} diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php b/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php deleted file mode 100644 index ffcbea1f9..000000000 --- a/core/modules/field/tests/src/Unit/Plugin/migrate/source/d7/FieldInstancePerViewModeTest.php +++ /dev/null @@ -1,68 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd7_field_instance_per_view_mode', - ), - ); - - protected $expectedResults = array( - array( - 'entity_type' => 'node', - 'bundle' => 'page', - 'field_name' => 'body', - 'label' => 'hidden', - 'type' => 'text_default', - 'settings' => array(), - 'module' => 'text', - 'weight' => 0, - 'view_mode' => 'default', - ), - array( - 'entity_type' => 'node', - 'bundle' => 'page', - 'field_name' => 'body', - 'label' => 'hidden', - 'type' => 'text_summary_or_trimmed', - 'settings' => array( - 'trim_length' => 600, - ), - 'module' => 'text', - 'weight' => 0, - 'view_mode' => 'teaser', - ), - ); - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->databaseContents['field_config_instance'] = array( - array( - 'id' => '2', - 'field_id' => '2', - 'field_name' => 'body', - 'entity_type' => 'node', - 'bundle' => 'page', - 'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}', - 'deleted' => '0', - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php index 20e1487b0..586f1ef39 100644 --- a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php +++ b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php @@ -876,8 +876,12 @@ abstract class EntityDisplayFormBase extends EntityForm { protected function saveDisplayStatuses($display_statuses) { $displays = $this->getDisplays(); foreach ($displays as $display) { - $display->set('status', $display_statuses[$display->get('mode')]); - $display->save(); + // Only update the display if the status is changing. + $new_status = $display_statuses[$display->get('mode')]; + if ($new_status !== $display->status()) { + $display->set('status', $new_status); + $display->save(); + } } } diff --git a/core/modules/file/src/Plugin/migrate/process/d6/CckFile.php b/core/modules/file/src/Plugin/migrate/process/d6/CckFile.php index fd2f42c3f..0415c7ffb 100644 --- a/core/modules/file/src/Plugin/migrate/process/d6/CckFile.php +++ b/core/modules/file/src/Plugin/migrate/process/d6/CckFile.php @@ -50,10 +50,10 @@ class CckFile extends ProcessPluginBase implements ContainerFactoryPluginInterfa */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { // Configure the migration process plugin to look up migrated IDs from - // the d6_file migration. - $migration_plugin_configuration = [ - 'source' => ['fid'], + // a d6 file migration. + $migration_plugin_configuration = $configuration + [ 'migration' => 'd6_file', + 'source' => ['fid'], ]; return new static( diff --git a/core/modules/file/tests/src/Kernel/Migrate/process/d6/CckFileTest.php b/core/modules/file/tests/src/Kernel/Migrate/process/d6/CckFileTest.php new file mode 100644 index 000000000..1c60c95d2 --- /dev/null +++ b/core/modules/file/tests/src/Kernel/Migrate/process/d6/CckFileTest.php @@ -0,0 +1,32 @@ +container, [], 'custom_migration', []); + $cck_file_migration = CckFile::create($this->container, ['migration' => 'custom_file'], 'custom_file', [], $migration); + $migration_plugin = $this->readAttribute($cck_file_migration, 'migrationPlugin'); + $config = $this->readAttribute($migration_plugin, 'configuration'); + + $this->assertEquals($config['migration'], 'custom_file'); + } + +} diff --git a/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/FileTest.php b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/FileTest.php new file mode 100644 index 000000000..cc8c9b36f --- /dev/null +++ b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/FileTest.php @@ -0,0 +1,57 @@ + 1, + 'uid' => 1, + 'filename' => 'migrate-test-file-1.pdf', + 'filepath' => 'sites/default/files/migrate-test-file-1.pdf', + 'filemime' => 'application/pdf', + 'filesize' => 890404, + 'status' => 1, + 'timestamp' => 1382255613, + ], + [ + 'fid' => 2, + 'uid' => 1, + 'filename' => 'migrate-test-file-2.pdf', + 'filepath' => 'sites/default/files/migrate-test-file-2.pdf', + 'filemime' => 'application/pdf', + 'filesize' => 204124, + 'status' => 1, + 'timestamp' => 1382255662, + ], + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['files']; + + return $tests; + } + +} diff --git a/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadInstanceTest.php b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadInstanceTest.php new file mode 100644 index 000000000..711b36fec --- /dev/null +++ b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadInstanceTest.php @@ -0,0 +1,68 @@ + 'article', + ], + [ + 'type' => 'company', + ], + ]; + + $tests[0]['source_data']['variable'] = [ + [ + 'name' => 'upload_article', + 'value' => serialize(TRUE), + ], + [ + 'name' => 'upload_company', + 'value' => serialize(FALSE), + ], + [ + 'name' => 'upload_uploadsize_default', + 'value' => serialize(16), + ], + [ + 'name' => 'upload_extensions_default', + 'value' => serialize('txt pdf'), + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'node_type' => 'article', + 'max_filesize' => '16MB', + 'file_extensions' => 'txt pdf', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadTest.php b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadTest.php new file mode 100644 index 000000000..96389d5dd --- /dev/null +++ b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d6/UploadTest.php @@ -0,0 +1,78 @@ + '1', + 'nid' => '1', + 'vid' => '1', + 'description' => 'file 1-1-1', + 'list' => '0', + 'weight' => '-1', + ], + ]; + + $tests[0]['source_data']['node'] = [ + [ + 'nid' => '1', + 'vid' => '1', + 'type' => 'story', + 'language' => '', + 'title' => 'Test title', + 'uid' => '1', + 'status' => '1', + 'created' => '1388271197', + 'changed' => '1420861423', + 'comment' => '0', + 'promote' => '0', + 'moderate' => '0', + 'sticky' => '0', + 'tnid' => '0', + 'translate' => '0', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'upload' => [ + [ + 'fid' => '1', + 'description' => 'file 1-1-1', + 'list' => '0', + ], + ], + 'nid' => '1', + 'vid' => '1', + 'type' => 'story', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d7/FileTest.php b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d7/FileTest.php new file mode 100644 index 000000000..8ae0ec41f --- /dev/null +++ b/core/modules/file/tests/src/Kernel/Plugin/migrate/source/d7/FileTest.php @@ -0,0 +1,112 @@ + '1', + 'uid' => '1', + 'filename' => 'cube.jpeg', + 'uri' => 'public://cube.jpeg', + 'filemime' => 'image/jpeg', + 'filesize' => '3620', + 'status' => '1', + 'timestamp' => '1421727515', + ], + // A private file. + [ + 'fid' => '1', + 'uid' => '1', + 'filename' => 'cube.jpeg', + 'uri' => 'private://cube.jpeg', + 'filemime' => 'image/jpeg', + 'filesize' => '3620', + 'status' => '1', + 'timestamp' => '1421727515', + ], + // A temporary file. + [ + 'fid' => '1', + 'uid' => '1', + 'filename' => 'cube.jpeg', + 'uri' => 'temporary://cube.jpeg', + 'filemime' => 'image/jpeg', + 'filesize' => '3620', + 'status' => '1', + 'timestamp' => '1421727515', + ], + // A file with a URI scheme that will be filtered out. + [ + 'fid' => '1', + 'uid' => '1', + 'filename' => 'cube.jpeg', + 'uri' => 'null://cube.jpeg', + 'filemime' => 'image/jpeg', + 'filesize' => '3620', + 'status' => '1', + 'timestamp' => '1421727515', + ], + ]; + $tests[0]['source_data']['variable'] = [ + [ + 'name' => 'file_public_path', + 'value' => serialize('sites/default/files'), + ], + [ + 'name' => 'file_private_path', + 'value' => serialize('/path/to/private/files'), + ], + [ + 'name' => 'file_temporary_path', + 'value' => serialize('/tmp'), + ], + ]; + + // The expected results will include only the first three files, since we + // are configuring the plugin to filter out the file with the null URI + // scheme. + $tests[0]['expected_data'] = array_slice($tests[0]['source_data']['file_managed'], 0, 3); + + // The filepath property will vary by URI scheme. + $tests[0]['expected_data'][0]['filepath'] = 'sites/default/files/cube.jpeg'; + $tests[0]['expected_data'][1]['filepath'] = '/path/to/private/files/cube.jpeg'; + $tests[0]['expected_data'][2]['filepath'] = '/tmp/cube.jpeg'; + + // Do an automatic count. + $tests[0]['expected_count'] = NULL; + + // Set up plugin configuration. + $tests[0]['configuration'] = [ + 'constants' => [ + 'source_base_path' => '/path/to/files', + ], + // Only return files which use one of these URI schemes. + 'scheme' => ['public', 'private', 'temporary'], + ]; + + return $tests; + } + +} diff --git a/core/modules/file/tests/src/Unit/Plugin/migrate/process/d6/FileUriTest.php b/core/modules/file/tests/src/Unit/Plugin/migrate/process/d6/FileUriTest.php index bfbdc325c..78d5082f6 100644 --- a/core/modules/file/tests/src/Unit/Plugin/migrate/process/d6/FileUriTest.php +++ b/core/modules/file/tests/src/Unit/Plugin/migrate/process/d6/FileUriTest.php @@ -70,7 +70,7 @@ class FileUriTest extends MigrateTestCase { protected function doTransform(array $value) { $executable = new MigrateExecutable($this->getMigration(), new MigrateMessage()); - $row = new Row([], []); + $row = new Row(); return (new FileUri([], 'file_uri', [])) ->transform($value, $executable, $row, 'foobaz'); diff --git a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/FileTest.php b/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/FileTest.php deleted file mode 100644 index 55437e23d..000000000 --- a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/FileTest.php +++ /dev/null @@ -1,54 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_file', - ), - ); - - protected $expectedResults = array( - array( - 'fid' => 1, - 'uid' => 1, - 'filename' => 'migrate-test-file-1.pdf', - 'filepath' => 'sites/default/files/migrate-test-file-1.pdf', - 'filemime' => 'application/pdf', - 'filesize' => 890404, - 'status' => 1, - 'timestamp' => 1382255613, - ), - array( - 'fid' => 2, - 'uid' => 1, - 'filename' => 'migrate-test-file-2.pdf', - 'filepath' => 'sites/default/files/migrate-test-file-2.pdf', - 'filemime' => 'application/pdf', - 'filesize' => 204124, - 'status' => 1, - 'timestamp' => 1382255662, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['files'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadInstanceTest.php b/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadInstanceTest.php deleted file mode 100644 index 623268002..000000000 --- a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadInstanceTest.php +++ /dev/null @@ -1,65 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_upload_instance', - ), - ); - - protected $expectedResults = array( - array( - 'node_type' => 'article', - 'max_filesize' => '16MB', - 'file_extensions' => 'txt pdf', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['node_type'] = array( - array( - 'type' => 'article', - ), - array( - 'type' => 'company', - ), - ); - $this->databaseContents['variable'] = array( - array( - 'name' => 'upload_article', - 'value' => serialize(TRUE), - ), - array( - 'name' => 'upload_company', - 'value' => serialize(FALSE), - ), - array( - 'name' => 'upload_uploadsize_default', - 'value' => serialize(16), - ), - array( - 'name' => 'upload_extensions_default', - 'value' => serialize('txt pdf'), - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadTest.php b/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadTest.php deleted file mode 100644 index 026cc35dc..000000000 --- a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d6/UploadTest.php +++ /dev/null @@ -1,75 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_upload', - ), - ); - - protected $expectedResults = array( - array( - 'upload' => array( - array( - 'fid' => '1', - 'description' => 'file 1-1-1', - 'list' => '0', - ), - ), - 'nid' => '1', - 'vid' => '1', - 'type' => 'story', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['upload'] = array( - array( - 'fid' => '1', - 'nid' => '1', - 'vid' => '1', - 'description' => 'file 1-1-1', - 'list' => '0', - 'weight' => '-1', - ), - ); - $this->databaseContents['node'] = array( - array( - 'nid' => '1', - 'vid' => '1', - 'type' => 'story', - 'language' => '', - 'title' => 'Test title', - 'uid' => '1', - 'status' => '1', - 'created' => '1388271197', - 'changed' => '1420861423', - 'comment' => '0', - 'promote' => '0', - 'moderate' => '0', - 'sticky' => '0', - 'tnid' => '0', - 'translate' => '0', - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d7/FileTest.php b/core/modules/file/tests/src/Unit/Plugin/migrate/source/d7/FileTest.php deleted file mode 100644 index 6a97e7398..000000000 --- a/core/modules/file/tests/src/Unit/Plugin/migrate/source/d7/FileTest.php +++ /dev/null @@ -1,146 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd7_file', - 'constants' => array( - 'source_base_path' => '/path/to/files', - ), - // Used by testFilteringByScheme(). - 'scheme' => array( - 'public', - 'private', - ), - ), - 'destination' => array( - 'plugin' => 'entity:file', - ), - ); - - protected $expectedResults = [ - [ - 'fid' => '1', - 'uid' => '1', - 'filename' => 'cube.jpeg', - 'uri' => 'public://cube.jpeg', - 'filemime' => 'image/jpeg', - 'filesize' => '3620', - 'status' => '1', - 'timestamp' => '1421727515', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['file_managed'] = $this->expectedResults; - parent::setUp(); - } - - /** - * Tests that public file URIs are properly transformed by prepareRow(). - */ - public function testPublicUri() { - $this->source->publicPath = 'sites/default/files'; - $row = new Row(['uri' => 'public://foo.png'], ['uri' => []]); - $this->source->prepareRow($row); - $this->assertEquals('sites/default/files/foo.png', - $row->getSourceProperty('filepath')); - } - - /** - * Tests that private file URIs are properly transformed by prepareRow(). - */ - public function testPrivateUri() { - $this->source->privatePath = '/path/to/private/files'; - $row = new Row(['uri' => 'private://baz.jpeg'], ['uri' => []]); - $this->source->prepareRow($row); - $this->assertEquals('/path/to/private/files/baz.jpeg', - $row->getSourceProperty('filepath')); - } - - /** - * Tests that temporary file URIs are property transformed by prepareRow(). - */ - public function testTemporaryUri() { - $this->source->temporaryPath = '/tmp'; - $row = new Row(['uri' => 'temporary://camelot/lancelot.gif'], - ['uri' => []]); - $this->source->prepareRow($row); - $this->assertEquals('/tmp/camelot/lancelot.gif', - $row->getSourceProperty('filepath')); - } - - /** - * Tests that it's possible to filter files by scheme. - */ - public function testFilteringByScheme() { - $query_conditions = $this->source->query()->conditions(); - $scheme_condition = end($query_conditions); - - $this->assertInstanceOf(ConditionInterface::class, $scheme_condition['field']); - $conditions = $scheme_condition['field']->conditions(); - - $this->assertSame('uri', $conditions[0]['field']); - $this->assertSame('LIKE', $conditions[0]['operator']); - $this->assertSame('public://%', $conditions[0]['value']); - - $this->assertSame('uri', $conditions[1]['field']); - $this->assertSame('LIKE', $conditions[1]['operator']); - $this->assertSame('private://%', $conditions[1]['value']); - } - -} - -/** - * Testing version of \Drupal\file\Plugin\migrate\source\d7\File. - * - * Exposes inaccessible properties. - */ -class TestFile extends File { - - /** - * The public file directory path. - * - * @var string - */ - public $publicPath; - - /** - * The private file directory path, if any. - * - * @var string - */ - public $privatePath; - - /** - * The temporary file directory path. - * - * @var string - */ - public $temporaryPath; - -} diff --git a/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php index 39e4037c1..1206734ec 100644 --- a/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php +++ b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php @@ -71,7 +71,7 @@ class FilterIdTest extends KernelTestBase { ); } - $row = new Row([], []); + $row = new Row(); $output_value = $plugin->transform($value, $this->executable, $row, 'foo'); $this->assertSame($expected_value, $output_value); diff --git a/core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d6/FilterFormatTest.php b/core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d6/FilterFormatTest.php new file mode 100644 index 000000000..a3e708b7b --- /dev/null +++ b/core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d6/FilterFormatTest.php @@ -0,0 +1,174 @@ + 1, + 'name' => 'Filtered HTML', + 'roles' => ',1,2,', + 'cache' => 1, + ], + [ + 'format' => 2, + 'name' => 'Full HTML', + 'roles' => '', + 'cache' => 1, + ], + [ + 'format' => 4, + 'name' => 'Example Custom Format', + 'roles' => '4', + 'cache' => 1, + ], + ]; + $tests[0]['source_data']['filters'] = [ + [ + 'fid' => 1, + 'format' => 1, + 'module' => 'filter', + 'delta' => 2, + 'weight' => 0, + ], + [ + 'fid' => 2, + 'format' => 1, + 'module' => 'filter', + 'delta' => 0, + 'weight' => 1, + ], + [ + 'fid' => 3, + 'format' => 1, + 'module' => 'filter', + 'delta' => 1, + 'weight' => 2, + ], + [ + 'fid' => 4, + 'format' => 2, + 'module' => 'filter', + 'delta' => 2, + 'weight' => 0, + ], + [ + 'fid' => 5, + 'format' => 2, + 'module' => 'filter', + 'delta' => 1, + 'weight' => 1, + ], + [ + 'fid' => 6, + 'format' => 2, + 'module' => 'filter', + 'delta' => 3, + 'weight' => 10, + ], + [ + 'fid' => 7, + 'format' => 4, + 'module' => 'markdown', + 'delta' => 1, + 'weight' => 10, + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'format' => 1, + 'name' => 'Filtered HTML', + 'roles' => [1, 2], + 'cache' => 1, + 'filters' => [ + [ + 'module' => 'filter', + 'delta' => 2, + 'weight' => 0, + 'settings' => [], + ], + [ + 'module' => 'filter', + 'delta' => 0, + 'weight' => 1, + 'settings' => [], + ], + [ + 'module' => 'filter', + 'delta' => 1, + 'weight' => 2, + 'settings' => [], + ], + ], + ], + [ + 'format' => 2, + 'name' => 'Full HTML', + 'roles' => [], + 'cache' => 1, + 'filters' => [ + [ + 'module' => 'filter', + 'delta' => 2, + 'weight' => 0, + 'settings' => [], + ], + [ + 'module' => 'filter', + 'delta' => 1, + 'weight' => 1, + 'settings' => [], + ], + [ + 'module' => 'filter', + 'delta' => 3, + 'weight' => 10, + 'settings' => [], + ], + ], + ], + [ + 'format' => 4, + 'name' => 'Example Custom Format', + 'roles' => [4], + 'cache' => 1, + 'filters' => [ + // This custom format uses a filter defined by a contrib module. + [ + 'module' => 'markdown', + 'delta' => 1, + 'weight' => 10, + 'settings' => [], + ], + ], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d7/FilterFormatTest.php b/core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d7/FilterFormatTest.php new file mode 100644 index 000000000..f68b93579 --- /dev/null +++ b/core/modules/filter/tests/src/Kernel/Plugin/migrate/source/d7/FilterFormatTest.php @@ -0,0 +1,120 @@ + 'custom_text_format', + 'name' => 'Custom Text format', + 'cache' => 1, + 'status' => 1, + 'weight' => 0, + ], + [ + 'format' => 'full_html', + 'name' => 'Full HTML', + 'cache' => 1, + 'status' => 1, + 'weight' => 1, + ], + ]; + $tests[0]['source_data']['filter'] = [ + [ + 'format' => 'custom_text_format', + 'module' => 'filter', + 'name' => 'filter_autop', + 'weight' => 0, + 'status' => 1, + 'settings' => serialize(array()), + ], + [ + 'format' => 'custom_text_format', + 'module' => 'filter', + 'name' => 'filter_html', + 'weight' => 1, + 'status' => 1, + 'settings' => serialize(array()), + ], + [ + 'format' => 'full_html', + 'module' => 'filter', + 'name' => 'filter_url', + 'weight' => 0, + 'status' => 1, + 'settings' => serialize(array()), + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'format' => 'custom_text_format', + 'name' => 'Custom Text format', + 'cache' => 1, + 'status' => 1, + 'weight' => 0, + 'filters' => [ + 'filter_autop' => [ + 'format' => 'custom_text_format', + 'module' => 'filter', + 'name' => 'filter_autop', + 'weight' => 0, + 'status' => 1, + 'settings' => [], + ], + 'filter_html' => [ + 'format' => 'custom_text_format', + 'module' => 'filter', + 'name' => 'filter_html', + 'weight' => 1, + 'status' => 1, + 'settings' => [], + ], + ], + ], + [ + 'format' => 'full_html', + 'name' => 'Full HTML', + 'cache' => 1, + 'status' => 1, + 'weight' => 1, + 'filters' => [ + 'filter_url' => [ + 'format' => 'full_html', + 'module' => 'filter', + 'name' => 'filter_url', + 'weight' => 0, + 'status' => 1, + 'settings' => [], + ], + ], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/filter/tests/src/Unit/Plugin/migrate/source/d6/FilterFormatTest.php b/core/modules/filter/tests/src/Unit/Plugin/migrate/source/d6/FilterFormatTest.php deleted file mode 100644 index 6b2861321..000000000 --- a/core/modules/filter/tests/src/Unit/Plugin/migrate/source/d6/FilterFormatTest.php +++ /dev/null @@ -1,114 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_filter_formats', - ), - ); - - protected $expectedResults = array( - array( - 'format' => 1, - 'name' => 'Filtered HTML', - 'roles' => array(1, 2), - 'cache' => 1, - 'filters' => array( - array( - 'module' => 'filter', - 'delta' => 2, - 'weight' => 0, - 'settings' => array(), - ), - array( - 'module' => 'filter', - 'delta' => 0, - 'weight' => 1, - 'settings' => array(), - ), - array( - 'module' => 'filter', - 'delta' => 1, - 'weight' => 2, - 'settings' => array(), - ), - ), - ), - array( - 'format' => 2, - 'name' => 'Full HTML', - 'roles' => array(), - 'cache' => 1, - 'filters' => array( - array( - 'module' => 'filter', - 'delta' => 2, - 'weight' => 0, - 'settings' => array(), - ), - array( - 'module' => 'filter', - 'delta' => 1, - 'weight' => 1, - 'settings' => array(), - ), - array( - 'module' => 'filter', - 'delta' => 3, - 'weight' => 10, - 'settings' => array(), - ), - ), - ), - array( - 'format' => 4, - 'name' => 'Example Custom Format', - 'roles' => array(4), - 'cache' => 1, - 'filters' => array( - // This custom format uses a filter defined by a contrib module. - array( - 'module' => 'markdown', - 'delta' => 1, - 'weight' => 10, - 'settings' => array(), - ), - ), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $fid = 1; - $empty_array = serialize(array()); - foreach ($this->expectedResults as $k => $row) { - $row['roles'] = ',' . implode(',', $row['roles']) . ','; - foreach ($row['filters'] as $filter) { - $filter['settings'] = $empty_array; - $this->databaseContents['filters'][$fid] = $filter; - $this->databaseContents['filters'][$fid]['format'] = $row['format']; - $this->databaseContents['filters'][$fid]['fid'] = $fid; - $fid++; - } - unset($row['filters']); - $this->databaseContents['filter_formats'][$k] = $row; - } - parent::setUp(); - } - -} diff --git a/core/modules/filter/tests/src/Unit/Plugin/migrate/source/d7/FilterFormatTest.php b/core/modules/filter/tests/src/Unit/Plugin/migrate/source/d7/FilterFormatTest.php deleted file mode 100644 index 2616ad1c9..000000000 --- a/core/modules/filter/tests/src/Unit/Plugin/migrate/source/d7/FilterFormatTest.php +++ /dev/null @@ -1,84 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_filter_formats', - ), - ); - - protected $expectedResults = array( - array( - 'format' => 'custom_text_format', - 'name' => 'Custom Text format', - 'cache' => 1, - 'status' => 1, - 'weight' => 0, - 'filters' => array( - 'filter_autop' => array( - 'format' => 'custom_text_format', - 'module' => 'filter', - 'name' => 'filter_autop', - 'weight' => 0, - 'status' => 1, - 'settings' => array(), - ), - 'filter_html' => array( - 'format' => 'custom_text_format', - 'module' => 'filter', - 'name' => 'filter_html', - 'weight' => 1, - 'status' => 1, - 'settings' => array(), - ), - ), - ), - array( - 'format' => 'full_html', - 'name' => 'Full HTML', - 'cache' => 1, - 'status' => 1, - 'weight' => 1, - 'filters' => array( - 'filter_url' => array( - 'format' => 'full_html', - 'module' => 'filter', - 'name' => 'filter_url', - 'weight' => 0, - 'status' => 1, - 'settings' => array(), - ), - ), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $row) { - foreach ($row['filters'] as $filter) { - $filter['format'] = $row['format']; - $filter['settings'] = serialize($filter['settings']); - $this->databaseContents['filter'][] = $filter; - } - unset($row['filters']); - $this->databaseContents['filter_format'][] = $row; - } - parent::setUp(); - } - -} diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 330688756..8d4a9f459 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Url; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\taxonomy\VocabularyInterface; use Drupal\user\Entity\User; /** @@ -295,7 +296,7 @@ function forum_form_taxonomy_vocabulary_form_alter(&$form, FormStateInterface $f // Forum's vocabulary always has single hierarchy. Forums and containers // have only one parent or no parent for root items. By default this value // is 0. - $form['hierarchy']['#value'] = TAXONOMY_HIERARCHY_SINGLE; + $form['hierarchy']['#value'] = VocabularyInterface::HIERARCHY_SINGLE; // Do not allow to delete forum's vocabulary. $form['actions']['delete']['#access'] = FALSE; // Do not allow to change a vid of forum's vocabulary. diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php index 712d52cbd..e1efc207d 100644 --- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php +++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php @@ -129,14 +129,22 @@ class ContentEntityNormalizer extends NormalizerBase { // Create the entity. $typed_data_ids = $this->getTypedDataIds($data['_links']['type'], $context); $entity_type = $this->entityManager->getDefinition($typed_data_ids['entity_type']); + $default_langcode_key = $entity_type->getKey('default_langcode'); $langcode_key = $entity_type->getKey('langcode'); $values = array(); // Figure out the language to use. - if (isset($data[$langcode_key])) { - $values[$langcode_key] = $data[$langcode_key][0]['value']; - // Remove the langcode so it does not get iterated over below. - unset($data[$langcode_key]); + if (isset($data[$default_langcode_key])) { + // Find the field item for which the default_lancode value is set to 1 and + // set the langcode the right default language. + foreach ($data[$default_langcode_key] as $item) { + if (!empty($item['value']) && isset($item['lang'])) { + $values[$langcode_key] = $item['lang']; + break; + } + } + // Remove the default langcode so it does not get iterated over below. + unset($data[$default_langcode_key]); } if ($entity_type->hasKey('bundle')) { diff --git a/core/modules/hal/tests/src/Kernel/EntityTranslationNormalizeTest.php b/core/modules/hal/tests/src/Kernel/EntityTranslationNormalizeTest.php new file mode 100644 index 000000000..35e038f84 --- /dev/null +++ b/core/modules/hal/tests/src/Kernel/EntityTranslationNormalizeTest.php @@ -0,0 +1,89 @@ +installSchema('system', array('sequences')); + $this->installConfig(['node', 'content_translation']); + } + + /** + * Tests the normalization of node translations. + */ + public function testNodeTranslation() { + $node_type = NodeType::create(['type' => 'example_type']); + $node_type->save(); + $this->container->get('content_translation.manager')->setEnabled('node', 'example_type', TRUE); + + $user = User::create(['name' => $this->randomMachineName()]); + $user->save(); + + $node = Node::create([ + 'title' => $this->randomMachineName(), + 'uid' => $user->id(), + 'type' => $node_type->id(), + 'status' => NODE_PUBLISHED, + 'langcode' => 'en', + 'promote' => 1, + 'sticky' => 0, + 'body' => [ + 'value' => $this->randomMachineName(), + 'format' => $this->randomMachineName() + ], + 'revision_log' => $this->randomString(), + ]); + $node->addTranslation('de', [ + 'title' => 'German title', + 'body' => [ + 'value' => $this->randomMachineName(), + 'format' => $this->randomMachineName() + ], + ]); + $node->save(); + + $original_values = $node->toArray(); + $translation = $node->getTranslation('de'); + $original_translation_values = $node->getTranslation('en')->toArray(); + + $normalized = $this->serializer->normalize($node, $this->format); + + $this->assertContains(['lang' => 'en', 'value' => $node->getTitle()], $normalized['title'], 'Original language title has been normalized.'); + $this->assertContains(['lang' => 'de', 'value' => $translation->getTitle()], $normalized['title'], 'Translation language title has been normalized.'); + + /** @var \Drupal\node\NodeInterface $denormalized_node */ + $denormalized_node = $this->serializer->denormalize($normalized, 'Drupal\node\Entity\Node', $this->format); + + $this->assertSame($denormalized_node->language()->getId(), $denormalized_node->getUntranslated()->language()->getId(), 'Untranslated object is returned from serializer.'); + $this->assertSame('en', $denormalized_node->language()->getId()); + $this->assertTrue($denormalized_node->hasTranslation('de')); + + $this->assertSame($node->getTitle(), $denormalized_node->getTitle()); + $this->assertSame($translation->getTitle(), $denormalized_node->getTranslation('de')->getTitle()); + + $this->assertEquals($original_values, $denormalized_node->toArray(), 'Node values are restored after normalizing and denormalizing.'); + $this->assertEquals($original_translation_values, $denormalized_node->getTranslation('en')->toArray(), 'Node values are restored after normalizing and denormalizing.'); + } + +} diff --git a/core/modules/image/templates/image-crop-summary.html.twig b/core/modules/image/templates/image-crop-summary.html.twig index fc991b65e..7abcc0779 100644 --- a/core/modules/image/templates/image-crop-summary.html.twig +++ b/core/modules/image/templates/image-crop-summary.html.twig @@ -18,15 +18,15 @@ */ #} {% if data.width and data.height -%} - {{ data.width|e }}×{{ data.height|e }} + {{ data.width }}×{{ data.height }} {%- else -%} {% if data.width %} {% trans %} - width {{ data.width|e }} + width {{ data.width }} {% endtrans %} {% elseif data.height %} {% trans %} - height {{ data.height|e }} + height {{ data.height }} {% endtrans %} {% endif %} {%- endif %} diff --git a/core/modules/image/templates/image-resize-summary.html.twig b/core/modules/image/templates/image-resize-summary.html.twig index f4084ef51..71c7ab335 100644 --- a/core/modules/image/templates/image-resize-summary.html.twig +++ b/core/modules/image/templates/image-resize-summary.html.twig @@ -16,15 +16,15 @@ */ #} {% if data.width and data.height -%} - {{ data.width|e }}×{{ data.height|e }} + {{ data.width }}×{{ data.height }} {%- else -%} {% if data.width %} {% trans %} - width {{ data.width|e }} + width {{ data.width }} {% endtrans %} {% elseif data.height %} {% trans %} - height {{ data.height|e }} + height {{ data.height }} {% endtrans %} {% endif %} {%- endif %} diff --git a/core/modules/image/templates/image-rotate-summary.html.twig b/core/modules/image/templates/image-rotate-summary.html.twig index 705a0eb03..f837efa1a 100644 --- a/core/modules/image/templates/image-rotate-summary.html.twig +++ b/core/modules/image/templates/image-rotate-summary.html.twig @@ -19,8 +19,9 @@ */ #} {% if data.random %} + {% set degrees = data.degrees|abs %} {% trans %} - random between -{{ data.degrees|abs }}° and {{ data.degrees|abs }}° + random between -{{ degrees }}° and {{ degrees }}° {% endtrans %} {% else %} {{ data.degrees }}° diff --git a/core/modules/image/templates/image-scale-summary.html.twig b/core/modules/image/templates/image-scale-summary.html.twig index 32d75cc33..33d0ee133 100644 --- a/core/modules/image/templates/image-scale-summary.html.twig +++ b/core/modules/image/templates/image-scale-summary.html.twig @@ -17,15 +17,15 @@ */ #} {% if data.width and data.height -%} - {{ data.width|e }}×{{ data.height|e }} + {{ data.width }}×{{ data.height }} {%- else -%} {% if data.width %} {% trans %} - width {{ data.width|e }} + width {{ data.width }} {% endtrans %} {% elseif data.height %} {% trans %} - height {{ data.height|e }} + height {{ data.height }} {% endtrans %} {% endif %} {%- endif %} diff --git a/core/modules/image/tests/src/Kernel/Plugin/migrate/source/d6/ImageCachePresetTest.php b/core/modules/image/tests/src/Kernel/Plugin/migrate/source/d6/ImageCachePresetTest.php new file mode 100644 index 000000000..edd2ad335 --- /dev/null +++ b/core/modules/image/tests/src/Kernel/Plugin/migrate/source/d6/ImageCachePresetTest.php @@ -0,0 +1,69 @@ + '1', + 'presetname' => 'slackjaw_boys', + ], + ]; + $tests[0]['source_data']['imagecache_action'] = [ + [ + 'actionid' => '3', + 'presetid' => '1', + 'weight' => '0', + 'module' => 'imagecache', + 'action' => 'imagecache_scale_and_crop', + 'data' => 'a:2:{s:5:"width";s:4:"100%";s:6:"height";s:4:"100%";}', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'presetid' => '1', + 'presetname' => 'slackjaw_boys', + 'actions' => [ + [ + 'actionid' => '3', + 'presetid' => '1', + 'weight' => '0', + 'module' => 'imagecache', + 'action' => 'imagecache_scale_and_crop', + 'data' => [ + 'width' => '100%', + 'height' => '100%', + ], + ], + ], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/image/tests/src/Kernel/Plugin/migrate/source/d7/ImageStylesTest.php b/core/modules/image/tests/src/Kernel/Plugin/migrate/source/d7/ImageStylesTest.php new file mode 100644 index 000000000..bb91de8a9 --- /dev/null +++ b/core/modules/image/tests/src/Kernel/Plugin/migrate/source/d7/ImageStylesTest.php @@ -0,0 +1,66 @@ + 1, + 'name' => 'custom_image_style_1', + 'label' => 'Custom image style 1', + ], + ]; + $tests[0]['source_data']['image_effects'] = [ + [ + 'ieid' => 1, + 'isid' => 1, + 'weight' => 1, + 'name' => 'image_desaturate', + 'data' => serialize(array()), + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'isid' => 1, + 'name' => 'custom_image_style_1', + 'label' => 'Custom image style 1', + 'effects' => [ + [ + 'ieid' => 1, + 'isid' => 1, + 'weight' => 1, + 'name' => 'image_desaturate', + 'data' => [], + ], + ], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/image/tests/src/Unit/Plugin/migrate/source/d6/ImageCachePresetTest.php b/core/modules/image/tests/src/Unit/Plugin/migrate/source/d6/ImageCachePresetTest.php deleted file mode 100644 index 7d42e8a07..000000000 --- a/core/modules/image/tests/src/Unit/Plugin/migrate/source/d6/ImageCachePresetTest.php +++ /dev/null @@ -1,67 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_imagecache_presets', - ), - ); - - protected $expectedResults = array( - array( - 'presetid' => '1', - 'presetname' => 'slackjaw_boys', - 'actions' => array( - array( - 'actionid' => '3', - 'presetid' => '1', - 'weight' => '0', - 'module' => 'imagecache', - 'action' => 'imagecache_scale_and_crop', - 'data' => array( - 'width' => '100%', - 'height' => '100%', - ), - ), - ), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['imagecache_preset'] = array( - array( - 'presetid' => '1', - 'presetname' => 'slackjaw_boys', - ), - ); - $this->databaseContents['imagecache_action'] = array( - array( - 'actionid' => '3', - 'presetid' => '1', - 'weight' => '0', - 'module' => 'imagecache', - 'action' => 'imagecache_scale_and_crop', - 'data' => 'a:2:{s:5:"width";s:4:"100%";s:6:"height";s:4:"100%";}', - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/image/tests/src/Unit/Plugin/migrate/source/d7/MigrateImageStylesTest.php b/core/modules/image/tests/src/Unit/Plugin/migrate/source/d7/MigrateImageStylesTest.php deleted file mode 100644 index 3c3a8cc85..000000000 --- a/core/modules/image/tests/src/Unit/Plugin/migrate/source/d7/MigrateImageStylesTest.php +++ /dev/null @@ -1,56 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_image_styles', - ], - ]; - - protected $expectedResults = [ - [ - 'isid' => 1, - 'name' => 'custom_image_style_1', - 'label' => 'Custom image style 1', - 'effects' => [ - [ - 'ieid' => 1, - 'isid' => 1, - 'weight' => 1, - 'name' => 'image_desaturate', - 'data' => [], - ] - ] - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $row) { - foreach (array('isid', 'name', 'label') as $field) { - $this->databaseContents['image_styles'][$k][$field] = $row[$field]; - } - foreach ($row['effects'] as $id => $effect) { - $row['effects'][$id]['data'] = serialize($row['effects'][$id]['data']); - } - $this->databaseContents['image_effects'] = $row['effects']; - } - parent::setUp(); - } - -} diff --git a/core/modules/language/config/optional/tour.tour.language-add.yml b/core/modules/language/config/optional/tour.tour.language-add.yml index 65c5dda0a..9f428b46d 100644 --- a/core/modules/language/config/optional/tour.tour.language-add.yml +++ b/core/modules/language/config/optional/tour.tour.language-add.yml @@ -25,5 +25,5 @@ tips: id: language-add-continue plugin: text label: 'Continuing on' - body: '

Now that you have an overview of the "Add languages" feature, you can continue by:

' + body: '

Now that you have an overview of the "Add languages" feature, you can continue by:

' weight: 3 diff --git a/core/modules/language/config/optional/tour.tour.language-edit.yml b/core/modules/language/config/optional/tour.tour.language-edit.yml index 0d37ead7f..454fef1b9 100644 --- a/core/modules/language/config/optional/tour.tour.language-edit.yml +++ b/core/modules/language/config/optional/tour.tour.language-edit.yml @@ -41,5 +41,5 @@ tips: id: language-edit-continue plugin: text label: 'Continuing on' - body: '

Now that you have an overview of the "Edit language" feature, you can continue by:

' + body: '

Now that you have an overview of the "Edit language" feature, you can continue by:

' weight: 5 diff --git a/core/modules/language/config/optional/tour.tour.language.yml b/core/modules/language/config/optional/tour.tour.language.yml index 7ee56b41a..7ab79d303 100644 --- a/core/modules/language/config/optional/tour.tour.language.yml +++ b/core/modules/language/config/optional/tour.tour.language.yml @@ -49,5 +49,5 @@ tips: id: language-continue plugin: text label: 'Continuing on' - body: '

Now that you have an overview of the "Languages" page, you can continue by:

' + body: '

Now that you have an overview of the "Languages" page, you can continue by:

' weight: 6 diff --git a/core/modules/language/migration_templates/default_language.yml b/core/modules/language/migration_templates/default_language.yml index d7e52adf1..6d7604dee 100644 --- a/core/modules/language/migration_templates/default_language.yml +++ b/core/modules/language/migration_templates/default_language.yml @@ -10,9 +10,22 @@ source: process: default_langcode: - - plugin: callback - callable: get_object_vars + plugin: default_value source: language_default + default_value: + 'language': 'en' + # Encode and decode to turn the default_language variable, which is + # an stdClass, into an array so it can be passed to extract. + - + plugin: callback + callable: + - '\Drupal\Component\Serialization\Json' + - 'encode' + - + plugin: callback + callable: + - '\Drupal\Component\Serialization\Json' + - 'decode' - plugin: extract index: diff --git a/core/modules/language/src/Form/NegotiationUrlForm.php b/core/modules/language/src/Form/NegotiationUrlForm.php index 13fe368c3..d5ad8639e 100644 --- a/core/modules/language/src/Form/NegotiationUrlForm.php +++ b/core/modules/language/src/Form/NegotiationUrlForm.php @@ -23,7 +23,7 @@ class NegotiationUrlForm extends ConfigFormBase { protected $languageManager; /** - * Constructs a new LanguageDeleteForm object. + * Constructs a new NegotiationUrlForm object. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. diff --git a/core/modules/language/tests/src/Kernel/Migrate/MigrateDefaultLanguageTrait.php b/core/modules/language/tests/src/Kernel/Migrate/MigrateDefaultLanguageTrait.php index deb60206e..94bb02d33 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/MigrateDefaultLanguageTrait.php +++ b/core/modules/language/tests/src/Kernel/Migrate/MigrateDefaultLanguageTrait.php @@ -38,7 +38,7 @@ trait MigrateDefaultLanguageTrait { // default_langcode config should be set. $default_language = ConfigurableLanguage::load($langcode); $this->assertNotNull($default_language); - $this->assertIdentical($langcode, $this->config('system.site')->get('default_langcode')); + $this->assertSame($langcode, $this->config('system.site')->get('default_langcode')); } else { // Otherwise, the migration log should contain an error message. @@ -46,11 +46,31 @@ trait MigrateDefaultLanguageTrait { $count = 0; foreach ($messages as $message) { $count++; - $this->assertEqual($message->message, "The language '$langcode' does not exist on this site."); - $this->assertEqual($message->level, MigrationInterface::MESSAGE_ERROR); + $this->assertSame($message->message, "The language '$langcode' does not exist on this site."); + $this->assertSame((int) $message->level, MigrationInterface::MESSAGE_ERROR); } - $this->assertEqual($count, 1); + $this->assertSame($count, 1); } } + /** + * Helper method to test migrating the default language when no default language is set. + */ + protected function doTestMigrationWithUnsetVariable() { + // Delete the language_default variable. + $this->sourceDatabase->delete('variable') + ->condition('name', 'language_default' ) + ->execute(); + + $this->startCollectingMessages(); + $this->executeMigrations(['language', 'default_language']); + $messages = $this->migration->getIdMap()->getMessageIterator()->fetchAll(); + + // Make sure there's no migration exceptions. + $this->assertEmpty($messages); + + // Make sure the default langcode is 'en', since it was the default on D6 & D7. + $this->assertSame('en', $this->config('system.site')->get('default_langcode')); + } + } diff --git a/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php index 20beb96bf..645af29fe 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d6/MigrateDefaultLanguageTest.php @@ -33,4 +33,11 @@ class MigrateDefaultLanguageTest extends MigrateDrupal6TestBase { $this->doTestMigration('tv', FALSE); } + /** + * Tests language_default migration with unset variable. + */ + public function testMigrationWithUnsetVariable() { + $this->doTestMigrationWithUnsetVariable(); + } + } diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php index 0e584e53b..79769bc00 100644 --- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php +++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateDefaultLanguageTest.php @@ -33,4 +33,11 @@ class MigrateDefaultLanguageTest extends MigrateDrupal7TestBase { $this->doTestMigration('tv', FALSE); } + /** + * Tests language_default migration with unset variable. + */ + public function testMigrationWithUnsetVariable() { + $this->doTestMigrationWithUnsetVariable(); + } + } diff --git a/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php b/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php index 3d657baa5..5de568dab 100644 --- a/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php +++ b/core/modules/link/src/Plugin/migrate/cckfield/d7/LinkField.php @@ -3,6 +3,7 @@ namespace Drupal\link\Plugin\migrate\cckfield\d7; use Drupal\link\Plugin\migrate\cckfield\LinkField as D6LinkField; +use Drupal\migrate\Plugin\MigrationInterface; /** * @MigrateCckField( @@ -27,4 +28,21 @@ class LinkField extends D6LinkField { return ['link_field' => 'link_default']; } + /** + * {@inheritdoc} + */ + public function processFieldInstance(MigrationInterface $migration) { + $process = [ + 'plugin' => 'static_map', + 'source' => 'instance_settings/title', + 'bypass' => TRUE, + 'map' => [ + 'disabled' => DRUPAL_DISABLED, + 'optional' => DRUPAL_OPTIONAL, + 'required' => DRUPAL_REQUIRED, + ], + ]; + $migration->mergeProcessOfProperty('settings/title', $process); + } + } diff --git a/core/modules/locale/config/optional/tour.tour.locale.yml b/core/modules/locale/config/optional/tour.tour.locale.yml index 041a62352..b6b376cf6 100644 --- a/core/modules/locale/config/optional/tour.tour.locale.yml +++ b/core/modules/locale/config/optional/tour.tour.locale.yml @@ -13,7 +13,7 @@ tips: id: locale-overview plugin: text label: 'User interface translation' - body: 'This page allows you to translate the user interface or modify existing translations. If you have installed your site initially in English, you must first add another language on the Languages page, in order to use this page.' + body: 'This page allows you to translate the user interface or modify existing translations. If you have installed your site initially in English, you must first add another language on the Languages page, in order to use this page.' weight: 1 locale-language: id: locale-language @@ -67,5 +67,5 @@ tips: id: locale-continue plugin: text label: 'Continuing on' - body: 'The translations you have made here will be used on your site''s user interface. If you want to use them on another site or modify them on an external translation editor, you can export them to a .po file and import them later.' + body: 'The translations you have made here will be used on your site''s user interface. If you want to use them on another site or modify them on an external translation editor, you can export them to a .po file and import them later.' weight: 8 diff --git a/core/modules/menu_link_content/migration_templates/menu_links.yml b/core/modules/menu_link_content/migration_templates/d6_menu_links.yml similarity index 90% rename from core/modules/menu_link_content/migration_templates/menu_links.yml rename to core/modules/menu_link_content/migration_templates/d6_menu_links.yml index b6bcdaf74..c418bfebd 100644 --- a/core/modules/menu_link_content/migration_templates/menu_links.yml +++ b/core/modules/menu_link_content/migration_templates/d6_menu_links.yml @@ -1,8 +1,7 @@ -id: menu_links +id: d6_menu_links label: Menu links migration_tags: - Drupal 6 - - Drupal 7 source: plugin: menu_link process: @@ -13,8 +12,11 @@ process: - plugin: migration # The menu migration is in the system module. - migration: menu + migration: d6_menu source: menu_name + - + plugin: skip_on_empty + method: row - plugin: static_map map: @@ -51,4 +53,4 @@ destination: no_stub: true migration_dependencies: required: - - menu + - d6_menu diff --git a/core/modules/menu_link_content/migration_templates/d7_menu_links.yml b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml new file mode 100644 index 000000000..8581e2c07 --- /dev/null +++ b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml @@ -0,0 +1,52 @@ +id: d7_menu_links +label: Menu links +migration_tags: + - Drupal 7 +source: + plugin: menu_link + constants: + bundle: menu_link_content +process: + id: mlid + bundle: 'constants/bundle' + title: link_title + description: description + menu_name: + - + plugin: migration + migration: d7_menu + source: menu_name + - + plugin: skip_on_empty + method: row + 'link/uri': + plugin: d7_internal_uri + source: + - link_path + 'link/options': options + route: + plugin: route + source: + - link_path + - options + route_name: '@route/route_name' + route_parameters: '@route/route_parameters' + url: '@route/url' + options: '@route/options' + external: external + weight: weight + expanded: expanded + enabled: enabled + parent: + plugin: menu_link_parent + source: + - plid + - '@menu_name' + - parent_link_path + changed: updated +destination: + plugin: entity:menu_link_content + no_stub: true +migration_dependencies: + required: + - d7_menu diff --git a/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php b/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php new file mode 100644 index 000000000..9d373568f --- /dev/null +++ b/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php @@ -0,0 +1,43 @@ +') { + return 'internal:/'; + } + else { + return 'internal:/' . $path; + } + } + return $path; + } + +} diff --git a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d6/MigrateMenuLinkTest.php b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d6/MigrateMenuLinkTest.php index 58171e361..eea87dbab 100644 --- a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d6/MigrateMenuLinkTest.php +++ b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d6/MigrateMenuLinkTest.php @@ -23,7 +23,7 @@ class MigrateMenuLinkTest extends MigrateDrupal6TestBase { protected function setUp() { parent::setUp(); $this->installEntitySchema('menu_link_content'); - $this->executeMigrations(['menu', 'menu_links']); + $this->executeMigrations(['d6_menu', 'd6_menu_links']); } /** diff --git a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php index 484c5a49d..0c915d423 100644 --- a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php +++ b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\menu_link_content\Kernel\Migrate\d7; -use Drupal\Core\Database\Database; use Drupal\Core\Menu\MenuTreeParameters; use Drupal\menu_link_content\Entity\MenuLinkContent; use Drupal\menu_link_content\MenuLinkContentInterface; @@ -27,7 +26,7 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase { protected function setUp() { parent::setUp(); $this->installEntitySchema('menu_link_content'); - $this->executeMigration('menu'); + $this->executeMigrations(['d7_menu', 'd7_menu_links']); \Drupal::service('router.builder')->rebuild(); } @@ -52,21 +51,24 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase { * The expected URI of the link. * @param int $weight * The expected weight of the link. + * + * @return \Drupal\menu_link_content\MenuLinkContentInterface + * The menu link content. */ protected function assertEntity($id, $title, $menu, $description, $enabled, $expanded, array $attributes, $uri, $weight) { /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */ $menu_link = MenuLinkContent::load($id); $this->assertTrue($menu_link instanceof MenuLinkContentInterface); - $this->assertIdentical($title, $menu_link->getTitle()); - $this->assertIdentical($menu, $menu_link->getMenuName()); + $this->assertSame($title, $menu_link->getTitle()); + $this->assertSame($menu, $menu_link->getMenuName()); // The migration sets the description of the link to the value of the // 'title' attribute. Bit strange, but there you go. - $this->assertIdentical($description, $menu_link->getDescription()); - $this->assertIdentical($enabled, $menu_link->isEnabled()); - $this->assertIdentical($expanded, $menu_link->isExpanded()); - $this->assertIdentical($attributes, $menu_link->link->options); - $this->assertIdentical($uri, $menu_link->link->uri); - $this->assertIdentical($weight, $menu_link->getWeight()); + $this->assertSame($description, $menu_link->getDescription()); + $this->assertSame($enabled, $menu_link->isEnabled()); + $this->assertSame($expanded, $menu_link->isExpanded()); + $this->assertSame($attributes, $menu_link->link->options); + $this->assertSame($uri, $menu_link->link->uri); + $this->assertSame($weight, $menu_link->getWeight()); return $menu_link; } @@ -74,31 +76,36 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase { * Tests migration of menu links. */ public function testMenuLinks() { - $this->executeMigration('menu_links'); $this->assertEntity(469, 'Bing', static::MENU_NAME, 'Bing', TRUE, FALSE, ['attributes' => ['title' => 'Bing']], 'http://bing.com', 0); $this->assertEntity(467, 'Google', static::MENU_NAME, 'Google', TRUE, FALSE, ['attributes' => ['title' => 'Google']], 'http://google.com', 0); $this->assertEntity(468, 'Yahoo', static::MENU_NAME, 'Yahoo', TRUE, FALSE, ['attributes' => ['title' => 'Yahoo']], 'http://yahoo.com', 0); + // Tests migrating an external link with an undefined title attribute. + $this->assertEntity(470, 'Ask', static::MENU_NAME, NULL, TRUE, FALSE, [], 'http://ask.com', 0); + $this->assertEntity(245, 'Home', 'main', NULL, TRUE, FALSE, [], 'internal:/', 0); + $this->assertEntity(478, 'custom link test', 'admin', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'internal:/admin/content/book', 0); + $this->assertEntity(479, 'node link test', 'tools', 'node 3', TRUE, FALSE, ['attributes' => ['title' => 'node 3']], 'entity:node/3', 3); + $menu_link_tree_service = \Drupal::service('menu.link_tree'); $parameters = new MenuTreeParameters(); $tree = $menu_link_tree_service->load(static::MENU_NAME, $parameters); - $this->assertEqual(2, count($tree)); + $this->assertCount(2, $tree); $children = 0; $google_found = FALSE; foreach ($tree as $menu_link_tree_element) { $children += $menu_link_tree_element->hasChildren; if ($menu_link_tree_element->link->getUrlObject()->toString() == 'http://bing.com') { - $this->assertEqual(reset($menu_link_tree_element->subtree)->link->getUrlObject()->toString(), 'http://google.com'); + $this->assertEquals(reset($menu_link_tree_element->subtree)->link->getUrlObject()->toString(), 'http://google.com'); $google_found = TRUE; } } - $this->assertEqual(1, $children); + $this->assertEquals(1, $children); $this->assertTrue($google_found); // Now find the custom link under a system link. $parameters->root = 'system.admin_structure'; $tree = $menu_link_tree_service->load(static::MENU_NAME, $parameters); $found = FALSE; foreach ($tree as $menu_link_tree_element) { - $this->pass($menu_link_tree_element->link->getUrlObject()->toString()); + $this->assertTrue($menu_link_tree_element->link->getUrlObject()->toString()); if ($menu_link_tree_element->link->getTitle() == 'custom link test') { $found = TRUE; break; @@ -107,20 +114,4 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase { $this->assertTrue($found); } - /** - * Tests migrating a link with an undefined title attribute. - */ - public function testUndefinedLinkTitle() { - Database::getConnection('default', 'migrate') - ->update('menu_links') - ->fields(array( - 'options' => 'a:0:{}', - )) - ->condition('mlid', 467) - ->execute(); - - $this->executeMigration('menu_links'); - $this->assertEntity(467, 'Google', static::MENU_NAME, NULL, TRUE, FALSE, [], 'http://google.com', 0); - } - } diff --git a/core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/source/MenuLinkTest.php b/core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/source/MenuLinkTest.php new file mode 100644 index 000000000..db6c5472c --- /dev/null +++ b/core/modules/menu_link_content/tests/src/Kernel/Plugin/migrate/source/MenuLinkTest.php @@ -0,0 +1,225 @@ + 'menu-test-menu', + 'mlid' => 140, + 'plid' => 0, + 'link_path' => 'admin/config/system/cron', + 'router_path' => 'admin/config/system/cron', + 'link_title' => 'Cron', + 'options' => array(), + 'module' => 'system', + 'hidden' => 0, + 'external' => 0, + 'has_children' => 0, + 'expanded' => 0, + 'weight' => 0, + 'depth' => 0, + 'customized' => 1, + 'p1' => '0', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + 'description' => '', + ], + [ + // D6 customized menu link, provided by menu module. + 'menu_name' => 'menu-test-menu', + 'mlid' => 141, + 'plid' => 0, + 'link_path' => 'node/141', + 'router_path' => 'node/%', + 'link_title' => 'Node 141', + 'options' => array(), + 'module' => 'menu', + 'hidden' => 0, + 'external' => 0, + 'has_children' => 0, + 'expanded' => 0, + 'weight' => 0, + 'depth' => 0, + 'customized' => 1, + 'p1' => '0', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + 'description' => '', + ], + [ + // D6 non-customized menu link, provided by menu module. + 'menu_name' => 'menu-test-menu', + 'mlid' => 142, + 'plid' => 0, + 'link_path' => 'node/142', + 'router_path' => 'node/%', + 'link_title' => 'Node 142', + 'options' => array(), + 'module' => 'menu', + 'hidden' => 0, + 'external' => 0, + 'has_children' => 0, + 'expanded' => 0, + 'weight' => 0, + 'depth' => 0, + 'customized' => 0, + 'p1' => '0', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + 'description' => '', + ], + [ + 'menu_name' => 'menu-test-menu', + 'mlid' => 138, + 'plid' => 0, + 'link_path' => 'admin', + 'router_path' => 'admin', + 'link_title' => 'Test 1', + 'options' => array('attributes' => array('title' => 'Test menu link 1')), + 'module' => 'menu', + 'hidden' => 0, + 'external' => 0, + 'has_children' => 1, + 'expanded' => 0, + 'weight' => 15, + 'depth' => 1, + 'customized' => 1, + 'p1' => '138', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + 'description' => 'Test menu link 1', + ], + [ + 'menu_name' => 'menu-test-menu', + 'mlid' => 139, + 'plid' => 138, + 'link_path' => 'admin/modules', + 'router_path' => 'admin/modules', + 'link_title' => 'Test 2', + 'options' => array('attributes' => array('title' => 'Test menu link 2')), + 'module' => 'menu', + 'hidden' => 0, + 'external' => 0, + 'has_children' => 0, + 'expanded' => 0, + 'weight' => 12, + 'depth' => 2, + 'customized' => 1, + 'p1' => '138', + 'p2' => '139', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + 'description' => 'Test menu link 2', + ], + [ + 'menu_name' => 'menu-user', + 'mlid' => 143, + 'plid' => 0, + 'link_path' => 'admin/build/menu-customize/navigation', + 'router_path' => 'admin/build/menu-customize/%', + 'link_title' => 'Navigation', + 'options' => array(), + 'module' => 'menu', + 'hidden' => 0, + 'external' => 0, + 'has_children' => 0, + 'expanded' => 0, + 'weight' => 0, + 'depth' => 0, + 'customized' => 0, + 'p1' => '0', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + 'description' => '', + ], + ]; + + // Add long link title attributes to source data. + $title = $this->getRandomGenerator()->string('500'); + $tests[0]['source_data']['menu_links'][0]['options']['attributes']['title'] = $title; + + // Build the expected results. + $expected = $tests[0]['source_data']['menu_links']; + + // Add long link title attributes to expected results. + $expected[0]['description'] = Unicode::truncate($title, 255); + + // Don't expect D6 menu link to a custom menu, provided by menu module. + unset($expected[5]); + + array_walk($tests[0]['source_data']['menu_links'], function (&$row) { + $row['options'] = serialize($row['options']); + }); + + $tests[0]['expected_data'] = $expected; + + return $tests; + } + +} diff --git a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/d7/InternalUriTest.php b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/d7/InternalUriTest.php new file mode 100644 index 000000000..9292718ae --- /dev/null +++ b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/d7/InternalUriTest.php @@ -0,0 +1,73 @@ +processPlugin = new InternalUri([], 'd7_internal_uri', []); + } + + /** + * Tests InternalUri::transform(). + * + * @param array $value + * The value to pass to InternalUri::transform(). + * @param string $expected + * The expected return value of InternalUri::transform(). + * + * @dataProvider providerTestTransform + * + * @covers ::transform + */ + public function testTransform(array $value, $expected) { + $migrate_executable = $this->prophesize(MigrateExecutableInterface::class); + $row = $this->prophesize(Row::class); + + $actual = $this->processPlugin->transform($value, $migrate_executable->reveal(), $row->reveal(), 'link/uri'); + $this->assertEquals($expected, $actual); + } + + /** + * Provides test cases for InternalUriTest::testTransform(). + * + * @return array + * An array of test cases, each which the following values: + * - The value array to pass to InternalUri::transform(). + * - The expected path returned by InternalUri::transform(). + */ + public function providerTestTransform() { + $tests = []; + $tests['with_scheme'] = [['http://example.com'], 'http://example.com']; + $tests['leading_slash'] = [['/test'], 'internal:/test']; + $tests['without_scheme'] = [['test'], 'internal:/test']; + $tests['front'] = [[''], 'internal:/']; + $tests['node'] = [['node/27'], 'entity:node/27']; + return $tests; + } + +} diff --git a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/MenuLinkSourceTest.php b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/MenuLinkSourceTest.php deleted file mode 100644 index 3c087dbeb..000000000 --- a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/MenuLinkSourceTest.php +++ /dev/null @@ -1,217 +0,0 @@ - 'mlid', - 'source' => array( - 'plugin' => 'menu_link', - ), - ); - - protected $expectedResults = array( - array( - // Customized menu link, provided by system module. - 'menu_name' => 'menu-test-menu', - 'mlid' => 140, - 'plid' => 0, - 'link_path' => 'admin/config/system/cron', - 'router_path' => 'admin/config/system/cron', - 'link_title' => 'Cron', - 'options' => array(), - 'module' => 'system', - 'hidden' => 0, - 'external' => 0, - 'has_children' => 0, - 'expanded' => 0, - 'weight' => 0, - 'depth' => 0, - 'customized' => 1, - 'p1' => '0', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - ), - array( - // D6 customized menu link, provided by menu module. - 'menu_name' => 'menu-test-menu', - 'mlid' => 141, - 'plid' => 0, - 'link_path' => 'node/141', - 'router_path' => 'node/%', - 'link_title' => 'Node 141', - 'options' => array(), - 'module' => 'menu', - 'hidden' => 0, - 'external' => 0, - 'has_children' => 0, - 'expanded' => 0, - 'weight' => 0, - 'depth' => 0, - 'customized' => 1, - 'p1' => '0', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - 'description' => '', - ), - array( - // D6 non-customized menu link, provided by menu module. - 'menu_name' => 'menu-test-menu', - 'mlid' => 142, - 'plid' => 0, - 'link_path' => 'node/142', - 'router_path' => 'node/%', - 'link_title' => 'Node 142', - 'options' => array(), - 'module' => 'menu', - 'hidden' => 0, - 'external' => 0, - 'has_children' => 0, - 'expanded' => 0, - 'weight' => 0, - 'depth' => 0, - 'customized' => 0, - 'p1' => '0', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - 'description' => '', - ), - array( - 'menu_name' => 'menu-test-menu', - 'mlid' => 138, - 'plid' => 0, - 'link_path' => 'admin', - 'router_path' => 'admin', - 'link_title' => 'Test 1', - 'options' => array('attributes' => array('title' => 'Test menu link 1')), - 'module' => 'menu', - 'hidden' => 0, - 'external' => 0, - 'has_children' => 1, - 'expanded' => 0, - 'weight' => 15, - 'depth' => 1, - 'customized' => 1, - 'p1' => '138', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - 'description' => 'Test menu link 1', - ), - array( - 'menu_name' => 'menu-test-menu', - 'mlid' => 139, - 'plid' => 138, - 'link_path' => 'admin/modules', - 'router_path' => 'admin/modules', - 'link_title' => 'Test 2', - 'options' => array('attributes' => array('title' => 'Test menu link 2')), - 'module' => 'menu', - 'hidden' => 0, - 'external' => 0, - 'has_children' => 0, - 'expanded' => 0, - 'weight' => 12, - 'depth' => 2, - 'customized' => 1, - 'p1' => '138', - 'p2' => '139', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - 'description' => 'Test menu link 2', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['menu_links'] = $this->expectedResults; - - // Add long link title attributes. - $title = $this->getRandomGenerator()->string('500'); - $this->databaseContents['menu_links'][0]['options']['attributes']['title'] = $title; - $this->expectedResults[0]['description'] = Unicode::truncate($title, 255); - - // D6 menu link to a custom menu, provided by menu module. - $this->databaseContents['menu_links'][] = [ - 'menu_name' => 'menu-user', - 'mlid' => 143, - 'plid' => 0, - 'link_path' => 'admin/build/menu-customize/navigation', - 'router_path' => 'admin/build/menu-customize/%', - 'link_title' => 'Navigation', - 'options' => array(), - 'module' => 'menu', - 'hidden' => 0, - 'external' => 0, - 'has_children' => 0, - 'expanded' => 0, - 'weight' => 0, - 'depth' => 0, - 'customized' => 0, - 'p1' => '0', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - 'description' => '', - ]; - - array_walk($this->databaseContents['menu_links'], function (&$row) { - $row['options'] = serialize($row['options']); - }); - - parent::setUp(); - } - -} diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php index c51b278ad..0488d9665 100644 --- a/core/modules/migrate/src/MigrateExecutable.php +++ b/core/modules/migrate/src/MigrateExecutable.php @@ -66,13 +66,6 @@ class MigrateExecutable implements MigrateExecutableInterface { */ protected $counts = array(); - /** - * The object currently being constructed. - * - * @var \stdClass - */ - protected $destinationValues; - /** * The source. * @@ -80,13 +73,6 @@ class MigrateExecutable implements MigrateExecutableInterface { */ protected $source; - /** - * The current data row retrieved from the source. - * - * @var \stdClass - */ - protected $sourceValues; - /** * The event dispatcher. * @@ -94,6 +80,15 @@ class MigrateExecutable implements MigrateExecutableInterface { */ protected $eventDispatcher; + /** + * Migration message service. + * + * @todo https://www.drupal.org/node/2822663 Make this protected. + * + * @var \Drupal\migrate\MigrateMessageInterface + */ + public $message; + /** * Constructs a MigrateExecutable and verifies and sets the memory limit. * @@ -229,7 +224,12 @@ class MigrateExecutable implements MigrateExecutableInterface { $save = FALSE; } catch (MigrateSkipRowException $e) { - $id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED); + if ($e->getSaveToMap()) { + $id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED); + } + if ($message = trim($e->getMessage())) { + $this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL); + } $save = FALSE; } @@ -263,8 +263,6 @@ class MigrateExecutable implements MigrateExecutableInterface { } } - // Reset row properties. - unset($sourceValues, $destinationValues); $this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED; // Check for memory exhaustion. diff --git a/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php b/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php index 0f69308f9..b6e86a09f 100644 --- a/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php +++ b/core/modules/migrate/src/Plugin/MigrateDestinationInterface.php @@ -21,15 +21,59 @@ use Drupal\migrate\Row; interface MigrateDestinationInterface extends PluginInspectionInterface { /** - * Get the destination IDs. + * Gets the destination IDs. * * To support MigrateIdMap maps, derived destination classes should return - * schema field definition(s) corresponding to the primary key of the - * destination being implemented. These are used to construct the destination - * key fields of the map table for a migration using this destination. + * field definition(s) corresponding to the primary key of the destination + * being implemented. These are used to construct the destination key fields + * of the map table for a migration using this destination. * - * @return array - * An array of IDs. + * @return array[] + * An associative array of field definitions keyed by field ID. Values are + * associative arrays with a structure that contains the field type ('type' + * key). The other keys are the field storage settings as they are returned + * by FieldStorageDefinitionInterface::getSettings(). As an example, for a + * composite destination primary key that is defined by an integer and a + * string, the returned value might look like: + * @code + * return [ + * 'id' => [ + * 'type' => 'integer', + * 'unsigned' => FALSE, + * 'size' => 'big', + * ], + * 'version' => [ + * 'type' => 'string', + * 'max_length' => 64, + * 'is_ascii' => TRUE, + * ], + * ]; + * @endcode + * If 'type' points to a field plugin with multiple columns and needs to + * refer to a column different than 'value', the key of that column will be + * appended as a suffix to the plugin name, separated by dot ('.'). Example: + * @code + * return [ + * 'format' => [ + * 'type' => 'text.format', + * ], + * ]; + * @endcode + * Additional custom keys/values, that are not part of field storage + * definition, can be passed in definitions: + * @code + * return [ + * 'nid' => [ + * 'type' => 'integer', + * 'custom_setting' => 'some_value', + * ], + * ]; + * @endcode + * + * @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings() + * @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem + * @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem + * @see \Drupal\text\Plugin\Field\FieldType\TextItem */ public function getIds(); diff --git a/core/modules/migrate/src/Plugin/MigratePluginManager.php b/core/modules/migrate/src/Plugin/MigratePluginManager.php index 8fd1644e5..94c59859e 100644 --- a/core/modules/migrate/src/Plugin/MigratePluginManager.php +++ b/core/modules/migrate/src/Plugin/MigratePluginManager.php @@ -41,8 +41,7 @@ class MigratePluginManager extends DefaultPluginManager implements MigratePlugin * 'Drupal\Component\Annotation\PluginID'. */ public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation = 'Drupal\Component\Annotation\PluginID') { - $plugin_interface = isset($plugin_interface_map[$type]) ? $plugin_interface_map[$type] : NULL; - parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, $plugin_interface, $annotation); + parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, NULL, $annotation); $this->alterInfo('migrate_' . $type . '_info'); $this->setCacheBackend($cache_backend, 'migrate_plugins_' . $type); } diff --git a/core/modules/migrate/src/Plugin/MigrateSourceInterface.php b/core/modules/migrate/src/Plugin/MigrateSourceInterface.php index 32d2b983b..ef1785e6f 100644 --- a/core/modules/migrate/src/Plugin/MigrateSourceInterface.php +++ b/core/modules/migrate/src/Plugin/MigrateSourceInterface.php @@ -49,9 +49,54 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection * prepareRow() or hook_migrate_prepare_row() to rewrite NULL values to * appropriate empty values (such as '' or 0). * - * @return array - * Array keyed by source field name, with values being a schema array - * describing the field (such as ['type' => 'string]). + * @return array[] + * An associative array of field definitions keyed by field ID. Values are + * associative arrays with a structure that contains the field type ('type' + * key). The other keys are the field storage settings as they are returned + * by FieldStorageDefinitionInterface::getSettings(). As an example, for a + * composite source primary key that is defined by an integer and a + * string, the returned value might look like: + * @code + * return [ + * 'id' => [ + * 'type' => 'integer', + * 'unsigned' => FALSE, + * 'size' => 'big', + * ], + * 'version' => [ + * 'type' => 'string', + * 'max_length' => 64, + * 'is_ascii' => TRUE, + * ], + * ]; + * @endcode + * If 'type' points to a field plugin with multiple columns and needs to + * refer to a column different than 'value', the key of that column will be + * appended as a suffix to the plugin name, separated by dot ('.'). Example: + * @code + * return [ + * 'format' => [ + * 'type' => 'text.format', + * ], + * ]; + * @endcode + * Additional custom keys/values, that are not part of field storage + * definition, can be passed in definitions. The most common setting, passed + * along the ID definition, is 'alias' used by SqlBase source plugin: + * @code + * return [ + * 'nid' => [ + * 'type' => 'integer', + * 'alias' => 'n', + * ], + * ]; + * @endcode + * + * @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings() + * @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem + * @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem + * @see \Drupal\text\Plugin\Field\FieldType\TextItem + * @see \Drupal\migrate\Plugin\migrate\source\SqlBase */ public function getIds(); diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php index d3adce883..9da3de6f9 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php @@ -125,15 +125,13 @@ class EntityContentBase extends Entity { */ public function getIds() { $id_key = $this->getKey('id'); - $ids[$id_key]['type'] = 'integer'; + $ids[$id_key] = $this->getDefinitionFromEntity($id_key); if ($this->isTranslationDestination()) { - if ($key = $this->getKey('langcode')) { - $ids[$key]['type'] = 'string'; - } - else { + if (!$langcode_key = $this->getKey('langcode')) { throw new MigrateException('This entity type does not support translation.'); } + $ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key); } return $ids; @@ -263,4 +261,32 @@ class EntityContentBase extends Entity { } } + /** + * Gets the field definition from a specific entity base field. + * + * The method takes the field ID as an argument and returns the field storage + * definition to be used in getIds() by querying the destination entity base + * field definition. + * + * @param string $key + * The field ID key. + * + * @return array + * An associative array with a structure that contains the field type, keyed + * as 'type', together with field storage settings as they are returned by + * FieldStorageDefinitionInterface::getSettings(). + * + * @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings() + */ + protected function getDefinitionFromEntity($key) { + $entity_type_id = static::getEntityTypeId($this->getPluginId()); + /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */ + $definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id); + $field_definition = $definitions[$key]; + + return [ + 'type' => $field_definition->getType(), + ] + $field_definition->getSettings(); + } + } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php index 62469e730..381974291 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php @@ -73,8 +73,7 @@ class EntityRevision extends EntityContentBase { */ public function getIds() { if ($key = $this->getKey('revision')) { - $ids[$key]['type'] = 'integer'; - return $ids; + return [$key => $this->getDefinitionFromEntity($key)]; } throw new MigrateException('This entity type does not support revisions.'); } diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index cbf4a9f46..962e05b23 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -441,20 +441,40 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP * Creates schema from an ID definition. * * @param array $id_definition - * A field schema definition. Can be SQL schema or a type data - * based schema. In the latter case, the value of type needs to be - * $typed_data_type.$column. + * The definition of the field having the structure as the items returned by + * MigrateSourceInterface or MigrateDestinationInterface::getIds(). * * @return array - * The schema definition. + * The database schema definition. + * + * @see \Drupal\migrate\Plugin\MigrateSourceInterface::getIds() + * @see \Drupal\migrate\Plugin\MigrateDestinationInterface::getIds() */ protected function getFieldSchema(array $id_definition) { $type_parts = explode('.', $id_definition['type']); if (count($type_parts) == 1) { $type_parts[] = 'value'; } - $schema = BaseFieldDefinition::create($type_parts[0])->getColumns(); - return $schema[$type_parts[1]]; + unset($id_definition['type']); + + // Get the field storage definition. + $definition = BaseFieldDefinition::create($type_parts[0]); + + // Get a list of setting keys belonging strictly to the field definition. + $default_field_settings = $definition->getSettings(); + // Separate field definition settings from custom settings. Custom settings + // are settings passed in $id_definition that are not part of field storage + // definition settings. + $field_settings = array_intersect_key($id_definition, $default_field_settings); + $custom_settings = array_diff_key($id_definition, $default_field_settings); + + // Resolve schema from field storage definition settings. + $schema = $definition + ->setSettings($field_settings) + ->getColumns()[$type_parts[1]]; + + // Merge back custom settings. + return $schema + $custom_settings; } /** diff --git a/core/modules/migrate/src/Plugin/migrate/process/Download.php b/core/modules/migrate/src/Plugin/migrate/process/Download.php new file mode 100644 index 000000000..08c9b57b1 --- /dev/null +++ b/core/modules/migrate/src/Plugin/migrate/process/Download.php @@ -0,0 +1,117 @@ + FALSE, + 'guzzle_options' => [], + ]; + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->fileSystem = $file_system; + $this->httpClient = $http_client; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('file_system'), + $container->get('http_client') + ); + } + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + // If we're stubbing a file entity, return a uri of NULL so it will get + // stubbed by the general process. + if ($row->isStub()) { + return NULL; + } + list($source, $destination) = $value; + + // Modify the destination filename if necessary. + $replace = !empty($this->configuration['rename']) ? + FILE_EXISTS_RENAME : + FILE_EXISTS_REPLACE; + $final_destination = file_destination($destination, $replace); + + // Try opening the file first, to avoid calling file_prepare_directory() + // unnecessarily. We're suppressing fopen() errors because we want to try + // to prepare the directory before we give up and fail. + $destination_stream = @fopen($final_destination, 'w'); + if (!$destination_stream) { + // If fopen didn't work, make sure there's a writable directory in place. + $dir = $this->fileSystem->dirname($final_destination); + if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { + throw new MigrateException("Could not create or write to directory '$dir'"); + } + // Let's try that fopen again. + $destination_stream = @fopen($final_destination, 'w'); + if (!$destination_stream) { + throw new MigrateException("Could not write to file '$final_destination'"); + } + } + + // Stream the request body directly to the final destination stream. + $this->configuration['guzzle_options']['sink'] = $destination_stream; + + // Make the request. Guzzle throws an exception for anything other than 200. + $this->httpClient->get($source, $this->configuration['guzzle_options']); + + return $final_destination; + } + +} diff --git a/core/modules/migrate/src/Plugin/migrate/process/Extract.php b/core/modules/migrate/src/Plugin/migrate/process/Extract.php index 81671d879..1ac88ca5b 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/Extract.php +++ b/core/modules/migrate/src/Plugin/migrate/process/Extract.php @@ -14,7 +14,8 @@ use Drupal\migrate\Row; * @link https://www.drupal.org/node/2152731 Online handbook documentation for extract process plugin @endlink * * @MigrateProcessPlugin( - * id = "extract" + * id = "extract", + * handle_multiples = TRUE * ) */ class Extract extends ProcessPluginBase { diff --git a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php index 7c49715d6..c68fe344e 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php +++ b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php @@ -8,6 +8,7 @@ use Drupal\Core\StreamWrapper\LocalStream; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\MigrateExecutableInterface; +use Drupal\migrate\Plugin\MigrateProcessInterface; use Drupal\migrate\ProcessPluginBase; use Drupal\migrate\Row; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -35,6 +36,13 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf */ protected $fileSystem; + /** + * An instance of the download process plugin. + * + * @var \Drupal\migrate\Plugin\MigrateProcessInterface + */ + protected $downloadPlugin; + /** * Constructs a file_copy process plugin. * @@ -48,8 +56,10 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf * The stream wrapper manager service. * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system service. + * @param \Drupal\migrate\Plugin\MigrateProcessInterface $download_plugin + * An instance of the download plugin for handling remote URIs. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system, MigrateProcessInterface $download_plugin) { $configuration += array( 'move' => FALSE, 'rename' => FALSE, @@ -58,6 +68,7 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf parent::__construct($configuration, $plugin_id, $plugin_definition); $this->streamWrapperManager = $stream_wrappers; $this->fileSystem = $file_system; + $this->downloadPlugin = $download_plugin; } /** @@ -69,7 +80,8 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf $plugin_id, $plugin_definition, $container->get('stream_wrapper_manager'), - $container->get('file_system') + $container->get('file_system'), + $container->get('plugin.manager.migrate.process')->createInstance('download') ); } @@ -84,8 +96,14 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf } list($source, $destination) = $value; + // If the source path or URI represents a remote resource, delegate to the + // download plugin. + if (!$this->isLocalUri($source)) { + return $this->downloadPlugin->transform($value, $migrate_executable, $row, $destination_property); + } + // Ensure the source file exists, if it's a local URI or path. - if ($this->isLocalUri($source) && !file_exists($source)) { + if (!file_exists($source)) { throw new MigrateException("File '$source' does not exist"); } @@ -128,20 +146,14 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf * File destination on success, FALSE on failure. */ protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) { - if ($this->configuration['move']) { - return file_unmanaged_move($source, $destination, $replace); - } // Check if there is a destination available for copying. If there isn't, // it already exists at the destination and the replace flag tells us to not // replace it. In that case, return the original destination. if (!($final_destination = file_destination($destination, $replace))) { return $destination; } - // We can't use file_unmanaged_copy because it will break with remote Urls. - if (@copy($source, $final_destination)) { - return $final_destination; - } - return FALSE; + $function = 'file_unmanaged_' . ($this->configuration['move'] ? 'move' : 'copy'); + return $function($source, $destination, $replace); } /** @@ -187,8 +199,6 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf /** * Determines if the source and destination URIs represent identical paths. * - * If either URI is a remote stream, will return FALSE. - * * @param string $source * The source URI. * @param string $destination @@ -199,10 +209,7 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf * otherwise FALSE. */ protected function isLocationUnchanged($source, $destination) { - if ($this->isLocalUri($source) && $this->isLocalUri($destination)) { - return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination); - } - return FALSE; + return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination); } /** @@ -219,6 +226,13 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf */ protected function isLocalUri($uri) { $scheme = $this->fileSystem->uriScheme($uri); + + // The vfs scheme is vfsStream, which is used in testing. vfsStream is a + // simulated file system that exists only in memory, but should be treated + // as a local resource. + if ($scheme == 'vfs') { + $scheme = FALSE; + } return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream; } diff --git a/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php b/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php index 45e2be5e0..6eebdeac0 100644 --- a/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php +++ b/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php @@ -207,6 +207,9 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter catch (MigrateSkipRowException $e) { $skip = TRUE; $save_to_map = $e->getSaveToMap(); + if ($message = trim($e->getMessage())) { + $this->idMap->saveMessage($row->getSourceIdValues(), $message, MigrationInterface::MESSAGE_INFORMATIONAL); + } } // We're explicitly skipping this row - keep track in the map table. diff --git a/core/modules/migrate/src/Row.php b/core/modules/migrate/src/Row.php index 94cb8b2bd..ea70acdf0 100644 --- a/core/modules/migrate/src/Row.php +++ b/core/modules/migrate/src/Row.php @@ -91,7 +91,7 @@ class Row { * @throws \InvalidArgumentException * Thrown when a source ID property does not exist. */ - public function __construct(array $values, array $source_ids, $is_stub = FALSE) { + public function __construct(array $values = [], array $source_ids = [], $is_stub = FALSE) { $this->source = $values; $this->sourceIds = $source_ids; $this->isStub = $is_stub; diff --git a/core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml b/core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml new file mode 100644 index 000000000..8f2da388c --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_entity_test/migrate_entity_test.info.yml @@ -0,0 +1,6 @@ +name: 'Migrate entity test' +type: module +description: 'Support module for entity destination test.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/migrate/tests/modules/migrate_entity_test/src/Entity/StringIdEntityTest.php b/core/modules/migrate/tests/modules/migrate_entity_test/src/Entity/StringIdEntityTest.php new file mode 100644 index 000000000..060e355f8 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_entity_test/src/Entity/StringIdEntityTest.php @@ -0,0 +1,36 @@ + BaseFieldDefinition::create('integer') + ->setSetting('size', 'big') + ->setLabel('ID'), + 'version' => BaseFieldDefinition::create('string') + ->setLabel('Version'), + ]; + } + +} diff --git a/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.module b/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.module index 66f1bd937..1da25f5a6 100644 --- a/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.module +++ b/core/modules/migrate/tests/modules/migrate_prepare_row_test/migrate_prepare_row_test.module @@ -18,9 +18,11 @@ function migrate_prepare_row_test_migrate_prepare_row(Row $row, MigrateSourceInt // Test both options for save_to_map. $data = $row->getSourceProperty('data'); if ($data == 'skip_and_record') { + // Record mapping but don't record a message. throw new MigrateSkipRowException('', TRUE); } elseif ($data == 'skip_and_dont_record') { - throw new MigrateSkipRowException('', FALSE); + // Don't record mapping but record a message. + throw new MigrateSkipRowException('skip_and_dont_record message', FALSE); } } diff --git a/core/modules/migrate/tests/modules/migrate_prepare_row_test/src/Plugin/migrate/process/TestSkipRowProcess.php b/core/modules/migrate/tests/modules/migrate_prepare_row_test/src/Plugin/migrate/process/TestSkipRowProcess.php new file mode 100644 index 000000000..4822113b3 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_prepare_row_test/src/Plugin/migrate/process/TestSkipRowProcess.php @@ -0,0 +1,34 @@ +getSourceProperty('data'); + if ($data == 'skip_and_record (use plugin)') { + throw new MigrateSkipRowException('', TRUE); + } + elseif ($data == 'skip_and_dont_record (use plugin)') { + throw new MigrateSkipRowException('', FALSE); + } + return $value; + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php index 352b6606c..0ebc719ec 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php @@ -4,6 +4,8 @@ namespace Drupal\Tests\migrate\Kernel; use Drupal\KernelTests\KernelTestBase; use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\migrate\MigrateExecutable; +use Drupal\migrate\MigrateMessage; use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\migrate\Plugin\MigrationInterface; @@ -123,7 +125,7 @@ class MigrateEntityContentBaseTest extends KernelTestBase { // Import some rows. foreach ($destination_rows as $idx => $destination_row) { - $row = new Row([], []); + $row = new Row(); foreach ($destination_row as $key => $value) { $row->setDestinationProperty($key, $value); } @@ -156,4 +158,50 @@ class MigrateEntityContentBaseTest extends KernelTestBase { $this->assertTranslations(4, 'fr'); } + /** + * Tests creation of ID columns table with definitions taken from entity type. + */ + public function testEntityWithStringId() { + $this->enableModules(['migrate_entity_test']); + $this->installEntitySchema('migrate_string_id_entity_test'); + + $definition = [ + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => [ + ['id' => 123, 'version' => 'foo'], + // This integer needs an 'int' schema with 'big' size. If 'destid1' + // is not correctly taking the definition from the destination entity + // type, the import will fail with a SQL exception. + ['id' => 123456789012, 'version' => 'bar'], + ], + 'ids' => [ + 'id' => ['type' => 'integer', 'size' => 'big'], + 'version' => ['type' => 'string'], + ], + ], + 'process' => [ + 'id' => 'id', + 'version' => 'version', + ], + 'destination' => [ + 'plugin' => 'entity:migrate_string_id_entity_test', + ], + ]; + + $migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition); + $executable = new MigrateExecutable($migration, new MigrateMessage()); + $result = $executable->import(); + $this->assertEquals(MigrationInterface::RESULT_COMPLETED, $result); + + /** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map_plugin */ + $id_map_plugin = $migration->getIdMap(); + + // Check that the destination has been stored. + $map_row = $id_map_plugin->getRowBySource(['id' => 123, 'version' => 'foo']); + $this->assertEquals(123, $map_row['destid1']); + $map_row = $id_map_plugin->getRowBySource(['id' => 123456789012, 'version' => 'bar']); + $this->assertEquals(123456789012, $map_row['destid1']); + } + } diff --git a/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php b/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php index acad26d25..3b0091d92 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateSkipRowTest.php @@ -54,14 +54,49 @@ class MigrateSkipRowTest extends KernelTestBase { $result = $executable->import(); $this->assertEqual($result, MigrationInterface::RESULT_COMPLETED); + /** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map_plugin */ $id_map_plugin = $migration->getIdMap(); // The first row is recorded in the map as ignored. $map_row = $id_map_plugin->getRowBySource(['id' => 1]); $this->assertEqual(MigrateIdMapInterface::STATUS_IGNORED, $map_row['source_row_status']); + // Check that no message has been logged for the first exception. + $messages = $id_map_plugin->getMessageIterator(['id' => 1])->fetchAll(); + $this->assertEmpty($messages); + // The second row is not recorded in the map. $map_row = $id_map_plugin->getRowBySource(['id' => 2]); $this->assertFalse($map_row); + // Check that the correct message has been logged for the second exception. + $messages = $id_map_plugin->getMessageIterator(['id' => 2])->fetchAll(); + $this->assertCount(1, $messages); + $message = reset($messages); + $this->assertEquals('skip_and_dont_record message', $message->message); + $this->assertEquals(MigrationInterface::MESSAGE_INFORMATIONAL, $message->level); + // Insert a custom processor in the process flow. + $definition['process']['value'] = [ + 'source' => 'data', + 'plugin' => 'test_skip_row_process', + ]; + // Change data to avoid triggering again hook_migrate_prepare_row(). + $definition['source']['data_rows'] = [ + ['id' => '1', 'data' => 'skip_and_record (use plugin)'], + ['id' => '2', 'data' => 'skip_and_dont_record (use plugin)'], + ]; + $migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition); + + $executable = new MigrateExecutable($migration, new MigrateMessage()); + $result = $executable->import(); + $this->assertEquals($result, MigrationInterface::RESULT_COMPLETED); + + $id_map_plugin = $migration->getIdMap(); + + // The first row is recorded in the map as ignored. + $map_row = $id_map_plugin->getRowBySource(['id' => 1]); + $this->assertEquals(MigrateIdMapInterface::STATUS_IGNORED, $map_row['source_row_status']); + // The second row is not recorded in the map. + $map_row = $id_map_plugin->getRowBySource(['id' => 2]); + $this->assertFalse($map_row); } } diff --git a/core/modules/migrate/tests/src/Kernel/process/CopyFileTest.php b/core/modules/migrate/tests/src/Kernel/process/CopyFileTest.php index 53ada1d77..427d413bc 100644 --- a/core/modules/migrate/tests/src/Kernel/process/CopyFileTest.php +++ b/core/modules/migrate/tests/src/Kernel/process/CopyFileTest.php @@ -18,7 +18,7 @@ class CopyFileTest extends FileTestBase { /** * {@inheritdoc} */ - public static $modules = ['system']; + public static $modules = ['migrate', 'system']; /** * The file system service. @@ -172,7 +172,7 @@ class CopyFileTest extends FileTestBase { protected function doImport($source_path, $destination_path, $configuration = []) { $plugin = FileCopy::create($this->container, $configuration, 'file_copy', []); $executable = $this->prophesize(MigrateExecutableInterface::class)->reveal(); - $row = new Row([], []); + $row = new Row(); $result = $plugin->transform([$source_path, $destination_path], $executable, $row, 'foobaz'); diff --git a/core/modules/migrate/tests/src/Kernel/process/DownloadTest.php b/core/modules/migrate/tests/src/Kernel/process/DownloadTest.php new file mode 100644 index 000000000..69e3aa002 --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/process/DownloadTest.php @@ -0,0 +1,128 @@ +container->get('stream_wrapper_manager')->registerWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream', StreamWrapperInterface::LOCAL_NORMAL); + } + + /** + * Tests a download that overwrites an existing local file. + */ + public function testOverwritingDownload() { + // Create a pre-existing file at the destination, to test overwrite behavior. + $destination_uri = $this->createUri('existing_file.txt'); + + // Test destructive download. + $actual_destination = $this->doTransform($destination_uri); + $this->assertSame($destination_uri, $actual_destination, 'Import returned a destination that was not renamed'); + $this->assertFileNotExists('public://existing_file_0.txt', 'Import did not rename the file'); + } + + /** + * Tests a download that renames the downloaded file if there's a collision. + */ + public function testNonDestructiveDownload() { + // Create a pre-existing file at the destination, to test overwrite behavior. + $destination_uri = $this->createUri('another_existing_file.txt'); + + // Test non-destructive download. + $actual_destination = $this->doTransform($destination_uri, ['rename' => TRUE]); + $this->assertSame('public://another_existing_file_0.txt', $actual_destination, 'Import returned a renamed destination'); + $this->assertFileExists($actual_destination, 'Downloaded file was created'); + } + + /** + * Tests that an exception is thrown if the destination URI is not writable. + */ + public function testWriteProectedDestination() { + // Create a pre-existing file at the destination, to test overwrite behavior. + $destination_uri = $this->createUri('not-writable.txt'); + + // Make the destination non-writable. + $this->container + ->get('file_system') + ->chmod($destination_uri, 0444); + + // Pass or fail, we'll need to make the file writable again so the test + // can clean up after itself. + $fix_permissions = function () use ($destination_uri) { + $this->container + ->get('file_system') + ->chmod($destination_uri, 0755); + }; + + try { + $this->doTransform($destination_uri); + $fix_permissions(); + $this->fail('MigrateException was not thrown for non-writable destination URI.'); + } + catch (MigrateException $e) { + $this->assertTrue(TRUE, 'MigrateException was thrown for non-writable destination URI.'); + $fix_permissions(); + } + } + + /** + * Runs an input value through the download plugin. + * + * @param string $destination_uri + * The destination URI to download to. + * @param array $configuration + * (optional) Configuration for the download plugin. + * + * @return string + * The local URI of the downloaded file. + */ + protected function doTransform($destination_uri, $configuration = []) { + // The HTTP client will return a file with contents 'It worked!' + $body = fopen('data://text/plain;base64,SXQgd29ya2VkIQ==', 'r'); + + // Prepare a mock HTTP client. + $this->container->set('http_client', $this->getMock(Client::class)); + $this->container->get('http_client') + ->method('get') + ->willReturn(new Response(200, [], $body)); + + // Instantiate the plugin statically so it can pull dependencies out of + // the container. + $plugin = Download::create($this->container, $configuration, 'download', []); + + // Execute the transformation. + $executable = $this->getMock(MigrateExecutableInterface::class); + $row = new Row([], []); + + // Return the downloaded file's local URI. + $value = [ + 'http://drupal.org/favicon.ico', + $destination_uri, + ]; + return $plugin->transform($value, $executable, $row, 'foobaz'); + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/process/ExtractTest.php b/core/modules/migrate/tests/src/Kernel/process/ExtractTest.php new file mode 100644 index 000000000..8c747abad --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/process/ExtractTest.php @@ -0,0 +1,111 @@ + [ + 'plugin' => 'embedded_data', + 'data_rows' => [], + 'ids' => [ + 'id' => ['type' => 'string'], + ], + ], + 'process' => [ + 'first' => [ + 'plugin' => 'extract', + 'index' => [0], + 'source' => 'simple_array', + ], + 'second' => [ + 'plugin' => 'extract', + 'index' => [1], + 'source' => 'complex_array', + ], + ], + 'destination' => [ + 'plugin' => 'config', + 'config_name' => 'migrate_test.settings', + ], + ]; + } + + /** + * Tests multiple value handling. + * + * @dataProvider multipleValueProviderSource + * + * @param array $source_data + * @param array $expected_data + */ + public function testMultipleValueExplode(array $source_data, array $expected_data) { + $definition = $this->getDefinition(); + $definition['source']['data_rows'] = [$source_data]; + + $migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition); + + $executable = new MigrateExecutable($migration, new MigrateMessage()); + $result = $executable->import(); + + // Migration needs to succeed before further assertions are made. + $this->assertSame(MigrationInterface::RESULT_COMPLETED, $result); + + // Compare with expected data. + $this->assertEquals($expected_data, \Drupal::config('migrate_test.settings')->get()); + } + + /** + * Provides multiple source data for "extract" process plugin test. + */ + public function multipleValueProviderSource() { + $tests = [ + [ + 'source_data' => [ + 'id' => '1', + 'simple_array' => ['alpha', 'beta'], + 'complex_array' => [['alpha', 'beta'], ['psi', 'omega']], + ], + 'expected_data' => [ + 'first' => 'alpha', + 'second' => ['psi', 'omega'], + ], + ], + [ + 'source_data' => [ + 'id' => '2', + 'simple_array' => ['one'], + 'complex_array' => [0, 1], + ], + 'expected_data' => [ + 'first' => 'one', + 'second' => 1, + ], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php b/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php new file mode 100644 index 000000000..ee75e4545 --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php @@ -0,0 +1,180 @@ +fileSystem = $this->container->get('file_system'); + $this->container->get('stream_wrapper_manager')->registerWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream', StreamWrapperInterface::LOCAL_NORMAL); + } + + /** + * Test successful imports/copies. + */ + public function testSuccessfulCopies() { + $file = $this->createUri(NULL, NULL, 'temporary'); + $file_absolute = $this->fileSystem->realpath($file); + $data_sets = [ + // Test a local to local copy. + [ + $this->root . '/core/modules/simpletest/files/image-test.jpg', + 'public://file1.jpg' + ], + // Test a temporary file using an absolute path. + [ + $file_absolute, + 'temporary://test.jpg' + ], + // Test a temporary file using a relative path. + [ + $file_absolute, + 'temporary://core/modules/simpletest/files/test.jpg' + ], + ]; + foreach ($data_sets as $data) { + list($source_path, $destination_path) = $data; + $actual_destination = $this->doTransform($source_path, $destination_path); + $message = sprintf('File %s exists', $destination_path); + $this->assertFileExists($destination_path, $message); + // Make sure we didn't accidentally do a move. + $this->assertFileExists($source_path, $message); + $this->assertSame($actual_destination, $destination_path, 'The import returned the copied filename.'); + } + } + + /** + * Test successful moves. + */ + public function testSuccessfulMoves() { + $file_1 = $this->createUri(NULL, NULL, 'temporary'); + $file_1_absolute = $this->fileSystem->realpath($file_1); + $file_2 = $this->createUri(NULL, NULL, 'temporary'); + $file_2_absolute = $this->fileSystem->realpath($file_2); + $local_file = $this->createUri(NULL, NULL, 'public'); + $data_sets = [ + // Test a local to local copy. + [ + $local_file, + 'public://file1.jpg' + ], + // Test a temporary file using an absolute path. + [ + $file_1_absolute, + 'temporary://test.jpg' + ], + // Test a temporary file using a relative path. + [ + $file_2_absolute, + 'temporary://core/modules/simpletest/files/test.jpg' + ], + ]; + foreach ($data_sets as $data) { + list($source_path, $destination_path) = $data; + $actual_destination = $this->doTransform($source_path, $destination_path, ['move' => TRUE]); + $message = sprintf('File %s exists', $destination_path); + $this->assertFileExists($destination_path, $message); + $message = sprintf('File %s does not exist', $source_path); + $this->assertFileNotExists($source_path, $message); + $this->assertSame($actual_destination, $destination_path, 'The importer returned the moved filename.'); + } + } + + /** + * Test that non-existent files throw an exception. + * + * @expectedException \Drupal\migrate\MigrateException + * + * @expectedExceptionMessage File '/non/existent/file' does not exist + */ + public function testNonExistentSourceFile() { + $source = '/non/existent/file'; + $this->doTransform($source, 'public://wontmatter.jpg'); + } + + /** + * Test the 'rename' overwrite mode. + */ + public function testRenameFile() { + $source = $this->createUri(NULL, NULL, 'temporary'); + $destination = $this->createUri('foo.txt', NULL, 'public'); + $expected_destination = 'public://foo_0.txt'; + $actual_destination = $this->doTransform($source, $destination, ['rename' => TRUE]); + $this->assertFileExists($expected_destination, 'File was renamed on import'); + $this->assertSame($actual_destination, $expected_destination, 'The importer returned the renamed filename.'); + } + + /** + * Tests that remote URIs are delegated to the download plugin. + */ + public function testDownloadRemoteUri() { + $download_plugin = $this->getMock(MigrateProcessInterface::class); + $download_plugin->expects($this->once())->method('transform'); + + $plugin = new FileCopy( + [], + $this->randomMachineName(), + [], + $this->container->get('stream_wrapper_manager'), + $this->container->get('file_system'), + $download_plugin + ); + + $plugin->transform( + ['http://drupal.org/favicon.ico', '/destination/path'], + $this->getMock(MigrateExecutableInterface::class), + new Row([], []), + $this->randomMachineName() + ); + } + + /** + * Do an import using the destination. + * + * @param string $source_path + * Source path to copy from. + * @param string $destination_path + * The destination path to copy to. + * @param array $configuration + * Process plugin configuration settings. + * + * @return string + * The URI of the copied file. + */ + protected function doTransform($source_path, $destination_path, $configuration = []) { + $plugin = FileCopy::create($this->container, $configuration, 'file_copy', []); + $executable = $this->prophesize(MigrateExecutableInterface::class)->reveal(); + $row = new Row([], []); + + return $plugin->transform([$source_path, $destination_path], $executable, $row, 'foobaz'); + } + +} diff --git a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php index 84c97f2a2..60809a90d 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php @@ -398,7 +398,7 @@ class MigrateExecutableTest extends MigrateTestCase { ->method('getProcessPlugins') ->with(NULL) ->will($this->returnValue($plugins)); - $row = new Row(array(), array()); + $row = new Row(); $this->executable->processRow($row); foreach ($expected as $key => $value) { $this->assertSame($row->getDestinationProperty($key), $value); @@ -414,7 +414,7 @@ class MigrateExecutableTest extends MigrateTestCase { ->method('getProcessPlugins') ->with(NULL) ->will($this->returnValue(array('test' => array()))); - $row = new Row(array(), array()); + $row = new Row(); $this->executable->processRow($row); $this->assertSame($row->getDestination(), array()); } diff --git a/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php b/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php index db8313e70..4a462e985 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php @@ -286,7 +286,7 @@ class MigrateSourceTest extends MigrateTestCase { // Get a new migration with an id. $migration = $this->getMigration(); $source = new StubSourcePlugin([], '', [], $migration); - $row = new Row([], []); + $row = new Row(); $module_handler = $this->prophesize(ModuleHandlerInterface::class); $module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration]) @@ -328,7 +328,7 @@ class MigrateSourceTest extends MigrateTestCase { $migration = $this->getMigration(); $source = new StubSourcePlugin([], '', [], $migration); - $row = new Row([], []); + $row = new Row(); $module_handler = $this->prophesize(ModuleHandlerInterface::class); // Return a failure from a prepare row hook. @@ -357,7 +357,7 @@ class MigrateSourceTest extends MigrateTestCase { $migration = $this->getMigration(); $source = new StubSourcePlugin([], '', [], $migration); - $row = new Row([], []); + $row = new Row(); $module_handler = $this->prophesize(ModuleHandlerInterface::class); // Return a failure from a prepare row hook. @@ -386,7 +386,7 @@ class MigrateSourceTest extends MigrateTestCase { $migration = $this->getMigration(); $source = new StubSourcePlugin([], '', [], $migration); - $row = new Row([], []); + $row = new Row(); $module_handler = $this->prophesize(ModuleHandlerInterface::class); // Return a failure from a prepare row hook. diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php b/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php index d07bb3005..962a75dca 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlSourceTestCase.php @@ -147,7 +147,7 @@ abstract class MigrateSqlSourceTestCase extends MigrateTestCase { public function testSourceCount() { $count = $this->source->count(); $this->assertTrue(is_numeric($count)); - $this->assertEquals($count, $this->expectedCount); + $this->assertEquals($this->expectedCount, $count); } /** diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php index 33aec586a..e5d63c476 100644 --- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php +++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/EntityContentBaseTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityType; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; @@ -74,7 +75,7 @@ class EntityContentBaseTest extends UnitTestCase { ->willReturn(5); $destination->setEntity($entity->reveal()); // Ensure the id is saved entity id is returned from import. - $this->assertEquals([5], $destination->import(new Row([], []))); + $this->assertEquals([5], $destination->import(new Row())); // Assert that import set the rollback action. $this->assertEquals(MigrateIdMapInterface::ROLLBACK_DELETE, $destination->rollbackAction()); } @@ -95,7 +96,7 @@ class EntityContentBaseTest extends UnitTestCase { $this->entityManager->reveal(), $this->prophesize(FieldTypePluginManagerInterface::class)->reveal()); $destination->setEntity(FALSE); - $destination->import(new Row([], [])); + $destination->import(new Row()); } /** @@ -109,6 +110,8 @@ class EntityContentBaseTest extends UnitTestCase { $entity_type = $this->prophesize(ContentEntityType::class); $entity_type->getKey('langcode')->willReturn(''); $entity_type->getKey('id')->willReturn('id'); + $this->entityManager->getBaseFieldDefinitions('foo') + ->willReturn(['id' => BaseFieldDefinitionTest::create('integer')]); $this->storage->getEntityType()->willReturn($entity_type->reveal()); @@ -144,4 +147,27 @@ class EntityTestDestination extends EntityContentBase { return $this->entity; } + public static function getEntityTypeId($plugin_id) { + return 'foo'; + } + +} + +/** + * Stub class for BaseFieldDefinition. + */ +class BaseFieldDefinitionTest extends BaseFieldDefinition { + + public static function create($type) { + return new static([]); + } + + public function getSettings() { + return []; + } + + public function getType() { + return 'integer'; + } + } diff --git a/core/modules/migrate/tests/src/Unit/RowTest.php b/core/modules/migrate/tests/src/Unit/RowTest.php index 96c27eab1..ec04f826b 100644 --- a/core/modules/migrate/tests/src/Unit/RowTest.php +++ b/core/modules/migrate/tests/src/Unit/RowTest.php @@ -49,7 +49,7 @@ class RowTest extends UnitTestCase { * Tests object creation: empty. */ public function testRowWithoutData() { - $row = new Row(array(), array()); + $row = new Row(); $this->assertSame(array(), $row->getSource(), 'Empty row'); } diff --git a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php index f1e288e57..1886914b5 100644 --- a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php +++ b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php @@ -67,7 +67,7 @@ class EntityRevisionTest extends UnitTestCase { $this->storage->loadRevision(12) ->shouldBeCalled() ->willReturn($entity->reveal()); - $row = new Row([], []); + $row = new Row(); $this->assertEquals($entity->reveal(), $destination->getEntity($row, [12, 13])); } diff --git a/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityDisplayTest.php b/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityDisplayTest.php index 1be14bc56..4782dc0f4 100644 --- a/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityDisplayTest.php +++ b/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityDisplayTest.php @@ -29,7 +29,7 @@ class PerComponentEntityDisplayTest extends MigrateTestCase { 'field_name' => 'field_name_test', 'options' => array('test setting'), ); - $row = new Row(array(), array()); + $row = new Row(); foreach ($values as $key => $value) { $row->setDestinationProperty($key, $value); } diff --git a/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityFormDisplayTest.php b/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityFormDisplayTest.php index 18df3621a..50cdbf6dc 100644 --- a/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityFormDisplayTest.php +++ b/core/modules/migrate/tests/src/Unit/destination/PerComponentEntityFormDisplayTest.php @@ -29,7 +29,7 @@ class PerComponentEntityFormDisplayTest extends MigrateTestCase { 'field_name' => 'field_name_test', 'options' => array('test setting'), ); - $row = new Row(array(), array()); + $row = new Row(); foreach ($values as $key => $value) { $row->setDestinationProperty($key, $value); } diff --git a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php b/core/modules/migrate/tests/src/Unit/process/IteratorTest.php index 7c731a2e0..cdffac07a 100644 --- a/core/modules/migrate/tests/src/Unit/process/IteratorTest.php +++ b/core/modules/migrate/tests/src/Unit/process/IteratorTest.php @@ -67,7 +67,7 @@ class IteratorTest extends MigrateTestCase { ), ); // This is not used but the interface requires it, so create an empty row. - $row = new Row(array(), array()); + $row = new Row(); // After transformation, check to make sure that source_foo and source_id's // values ended up in the proper destinations, and that the value of the diff --git a/core/modules/migrate/tests/src/Unit/process/UrlEncodeTest.php b/core/modules/migrate/tests/src/Unit/process/UrlEncodeTest.php index 539858f61..78d89a16e 100644 --- a/core/modules/migrate/tests/src/Unit/process/UrlEncodeTest.php +++ b/core/modules/migrate/tests/src/Unit/process/UrlEncodeTest.php @@ -57,7 +57,7 @@ class UrlEncodeTest extends MigrateTestCase { */ protected function doTransform($value) { $executable = new MigrateExecutable($this->getMigration(), new MigrateMessage()); - $row = new Row([], []); + $row = new Row(); return (new UrlEncode([], 'urlencode', [])) ->transform($value, $executable, $row, 'foobaz'); diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php new file mode 100644 index 000000000..df3311f5f --- /dev/null +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php @@ -0,0 +1,59 @@ +select('config', 'c') + ->fields('c', array('collection', 'name', 'data')); + if (!empty($this->configuration['collections'])) { + $query->condition('collection', (array) $this->configuration['collections'], 'IN'); + } + if (!empty($this->configuration['names'])) { + $query->condition('name', (array) $this->configuration['names'], 'IN'); + } + return $query; + } + + /** + * {@inheritdoc} + */ + public function prepareRow(Row $row) { + $row->setSourceProperty('data', unserialize($row->getSourceProperty('data'))); + } + + /** + * {@inheritdoc} + */ + public function fields() { + return [ + 'collection' => $this->t('The config object collection.'), + 'name' => $this->t('The config object name.'), + 'data' => $this->t('Serialized configuration object data.'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + $ids['collection']['type'] = 'string'; + $ids['name']['type'] = 'string'; + return $ids; + } + +} diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal6.php b/core/modules/migrate_drupal/tests/fixtures/drupal6.php index 347b2f272..9debae008 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal6.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal6.php @@ -42136,13 +42136,13 @@ $connection->insert('permission') ->values(array( 'pid' => '1', 'rid' => '1', - 'perm' => 'migrate test anonymous permission', + 'perm' => 'access content, migrate test anonymous permission', 'tid' => '0', )) ->values(array( 'pid' => '2', 'rid' => '2', - 'perm' => 'migrate test authenticated permission', + 'perm' => 'access comments, access content, post comments, post comments without approval, migrate test authenticated permission', 'tid' => '0', )) ->values(array( @@ -42157,18 +42157,6 @@ $connection->insert('permission') 'perm' => 'migrate test role 2 test permission, use PHP for settings, administer contact forms, skip comment approval, edit own blog content, edit any blog content, delete own blog content, delete any blog content, create forum content, delete any forum content, delete own forum content, edit any forum content, edit own forum content, administer nodes', 'tid' => '0', )) -->values(array( - 'pid' => '5', - 'rid' => '1', - 'perm' => 'access content', - 'tid' => '0', -)) -->values(array( - 'pid' => '6', - 'rid' => '2', - 'perm' => 'access comments, access content, post comments, post comments without approval', - 'tid' => '0', -)) ->execute(); $connection->schema()->createTable('profile_fields', array( diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php index e8c006b2d..98e56382f 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php @@ -3726,7 +3726,7 @@ $connection->insert('field_config_instance') 'field_name' => 'field_link', 'entity_type' => 'node', 'bundle' => 'article', - 'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:2:"10";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:12:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"optional";s:11:"title_value";s:19:"Unused Static Title";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"81";}s:10:"attributes";a:6:{s:6:"target";s:6:"_blank";s:3:"rel";s:8:"nofollow";s:18:"configurable_class";i:0;s:5:"class";s:7:"classes";s:18:"configurable_title";i:1;s:5:"title";s:0:"";}s:10:"rel_remove";s:19:"rel_remove_external";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:6:"weight";s:1:"9";s:8:"settings";a:0:{}s:6:"module";s:4:"link";}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:2:"10";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:12:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"disabled";s:11:"title_value";s:19:"Unused Static Title";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"81";}s:10:"attributes";a:6:{s:6:"target";s:6:"_blank";s:3:"rel";s:8:"nofollow";s:18:"configurable_class";i:0;s:5:"class";s:7:"classes";s:18:"configurable_title";i:1;s:5:"title";s:0:"";}s:10:"rel_remove";s:19:"rel_remove_external";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:6:"weight";s:1:"9";s:8:"settings";a:0:{}s:6:"module";s:4:"link";}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', 'deleted' => '0', )) ->values(array( @@ -3747,6 +3747,15 @@ $connection->insert('field_config_instance') 'data' => 'a:7:{s:5:"label";s:7:"Integer";s:6:"widget";a:5:{s:6:"weight";s:1:"9";s:4:"type";s:6:"number";s:6:"module";s:6:"number";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:5:{s:3:"min";s:1:"1";s:3:"max";s:2:"25";s:6:"prefix";s:3:"abc";s:6:"suffix";s:3:"xyz";s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:14:"number_integer";s:6:"weight";s:1:"8";s:8:"settings";a:4:{s:18:"thousand_separator";s:1:" ";s:17:"decimal_separator";s:1:".";s:5:"scale";i:0;s:13:"prefix_suffix";b:1;}s:6:"module";s:6:"number";}}s:8:"required";i:1;s:11:"description";s:0:"";s:13:"default_value";N;}', 'deleted' => '0', )) +->values(array( + 'id' => '37', + 'field_id' => '15', + 'field_name' => 'field_link', + 'entity_type' => 'node', + 'bundle' => 'blog', + 'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:2:"10";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:12:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"required";s:11:"title_value";s:19:"Unused Static Title";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"81";}s:10:"attributes";a:6:{s:6:"target";s:6:"_blank";s:3:"rel";s:8:"nofollow";s:18:"configurable_class";i:0;s:5:"class";s:7:"classes";s:18:"configurable_title";i:1;s:5:"title";s:0:"";}s:10:"rel_remove";s:19:"rel_remove_external";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:6:"weight";s:1:"9";s:8:"settings";a:0:{}s:6:"module";s:4:"link";}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) ->execute(); $connection->schema()->createTable('field_data_body', array( @@ -19190,6 +19199,33 @@ $connection->insert('menu_links') 'p9' => '0', 'updated' => '0', )) +->values(array( + 'menu_name' => 'menu-test-menu', + 'mlid' => '470', + 'plid' => '469', + 'link_path' => 'http://ask.com', + 'router_path' => '', + 'link_title' => 'Ask', + 'options' => 'a:0:{}', + 'module' => 'menu', + 'hidden' => '0', + 'external' => '1', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '2', + 'customized' => '1', + 'p1' => '469', + 'p2' => '470', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) ->values(array( 'menu_name' => 'shortcut-set-2', 'mlid' => '472', @@ -19325,6 +19361,33 @@ $connection->insert('menu_links') 'p9' => '0', 'updated' => '0', )) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '479', + 'plid' => '0', + 'link_path' => 'node/3', + 'router_path' => 'node/3', + 'link_title' => 'node link test', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:6:"node 3";}}', + 'module' => 'menu', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '3', + 'depth' => '1', + 'customized' => '1', + 'p1' => '479', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) ->execute(); $connection->schema()->createTable('menu_router', array( @@ -41044,11 +41107,29 @@ $connection->insert('users') 'login' => '0', 'status' => '1', 'timezone' => 'America/Chicago', - 'language' => 'en', + 'language' => 'is', 'picture' => '0', 'init' => 'odo@local.host', 'data' => 'a:1:{s:7:"contact";i:1;}', )) +->values(array( + 'uid' => '3', + 'name' => 'Bob', + 'pass' => '$S$DGFZUE.FhrXbe4y52eC7p0ZVRGD/gOPtVctDlmC89qkujnBokAlJ', + 'mail' => 'bob@local.host', + 'theme' => '', + 'signature' => '', + 'signature_format' => 'filtered_html', + 'created' => '1440532218', + 'access' => '0', + 'login' => '0', + 'status' => '1', + 'timezone' => 'America/New_York', + 'language' => 'fr', + 'picture' => '0', + 'init' => 'bob@local.host', + 'data' => 'a:1:{s:7:"contact";i:1;}', +)) ->execute(); $connection->schema()->createTable('users_roles', array( @@ -41084,6 +41165,14 @@ $connection->insert('users_roles') 'uid' => '1', 'rid' => '3', )) +->values(array( + 'uid' => '2', + 'rid' => '3', +)) +->values(array( + 'uid' => '3', + 'rid' => '3', +)) ->execute(); $connection->schema()->createTable('variable', array( diff --git a/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d8/ConfigTest.php b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d8/ConfigTest.php new file mode 100644 index 000000000..20b80957d --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d8/ConfigTest.php @@ -0,0 +1,115 @@ + [ + [ + 'collection' => 'language.af', + 'name' => 'user.settings', + 'data' => 'a:1:{s:9:"anonymous";s:14:"af - Anonymous";}', + ], + [ + 'collection' => '', + 'name' => 'user.settings', + 'data' => 'a:1:{s:9:"anonymous";s:9:"Anonymous";}', + ], + [ + 'collection' => 'language.de', + 'name' => 'user.settings', + 'data' => 'a:1:{s:9:"anonymous";s:14:"de - Anonymous";}', + ], + [ + 'collection' => 'language.af', + 'name' => 'bar', + 'data' => 'b:0;', + ], + ], + ]; + + // The expected results. + $data[0]['expected_results'] = [ + [ + 'collection' => 'language.af', + 'name' => 'user.settings', + 'data' => [ + 'anonymous' => 'af - Anonymous', + ], + ], + [ + 'collection' => 'language.af', + 'name' => 'bar', + 'data' => FALSE, + ], + ]; + $data[0]['expected_count'] = NULL; + $data[0]['configuration'] = [ + 'names' => [ + 'user.settings', + 'bar', + ], + 'collections' => [ + 'language.af', + ] + ]; + + // Test with name and no collection in configuration. + $data[1]['source_data'] = $data[0]['source_data']; + $data[1]['expected_results'] = [ + [ + 'collection' => 'language.af', + 'name' => 'bar', + 'data' => FALSE, + ], + ]; + $data[1]['expected_count'] = NULL; + $data[1]['configuration'] = [ + 'names' => [ + 'bar', + ], + ]; + + // Test with collection and no name in configuration. + $data[2]['source_data'] = $data[0]['source_data']; + $data[2]['expected_results'] = [ + [ + 'collection' => 'language.de', + 'name' => 'user.settings', + 'data' => [ + 'anonymous' => 'de - Anonymous', + ], + ], + ]; + $data[2]['expected_count'] = NULL; + $data[2]['configuration'] = [ + 'collections' => [ + 'language.de', + ], + ]; + + return $data; + } + +} diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php index b53069e7a..2d60c4694 100644 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php @@ -290,7 +290,7 @@ class MigrateUpgradeForm extends ConfirmFormBase { 'source_module' => 'locale', 'destination_module' => 'locale', ], - 'menu_links' => [ + 'd6_menu_links' => [ 'source_module' => 'menu', 'destination_module' => 'menu_link_content', ], @@ -298,13 +298,17 @@ class MigrateUpgradeForm extends ConfirmFormBase { 'source_module' => 'menu', 'destination_module' => 'menu_ui', ], + 'd7_menu_links' => [ + 'source_module' => 'menu', + 'destination_module' => 'menu_link_content', + ], 'd6_node' => [ 'source_module' => 'node', 'destination_module' => 'node', ], 'd6_node_translation' => [ - 'source_module' => 'node', - 'destination_module' => 'node', + 'source_module' => 'translation', + 'destination_module' => 'content_translation', ], 'd6_node_revision' => [ 'source_module' => 'node', @@ -474,7 +478,11 @@ class MigrateUpgradeForm extends ConfirmFormBase { 'source_module' => 'system', 'destination_module' => 'system', ], - 'menu' => [ + 'd6_menu' => [ + 'source_module' => 'menu', + 'destination_module' => 'system', + ], + 'd7_menu' => [ 'source_module' => 'menu', 'destination_module' => 'system', ], @@ -690,7 +698,7 @@ class MigrateUpgradeForm extends ConfirmFormBase { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - $step = $form_state->getValue('step', 'overview'); + $step = $form_state->get('step') ?: 'overview'; switch ($step) { case 'overview': return $this->buildOverviewForm($form, $form_state); @@ -788,7 +796,7 @@ class MigrateUpgradeForm extends ConfirmFormBase { * The current state of the form. */ public function submitOverviewForm(array &$form, FormStateInterface $form_state) { - $form_state->setValue('step', 'credentials'); + $form_state->set('step', 'credentials'); $form_state->setRebuild(); } @@ -977,7 +985,7 @@ class MigrateUpgradeForm extends ConfirmFormBase { */ public function submitCredentialForm(array &$form, FormStateInterface $form_state) { // Indicate the next step is confirmation. - $form_state->setValue('step', 'confirm'); + $form_state->set('step', 'confirm'); $form_state->setRebuild(); } diff --git a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php index bac70e538..74d4237ca 100644 --- a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php @@ -43,7 +43,7 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase { 'configurable_language' => 4, 'contact_form' => 3, 'editor' => 2, - 'field_config' => 44, + 'field_config' => 45, 'field_storage_config' => 33, 'file' => 1, 'filter_format' => 7, @@ -56,14 +56,14 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase { 'search_page' => 2, 'shortcut' => 6, 'shortcut_set' => 2, - 'action' => 18, - 'menu' => 10, + 'action' => 16, + 'menu' => 6, 'taxonomy_term' => 18, 'taxonomy_vocabulary' => 3, 'tour' => 4, - 'user' => 3, - 'user_role' => 4, - 'menu_link_content' => 9, + 'user' => 4, + 'user_role' => 3, + 'menu_link_content' => 7, 'view' => 12, 'date_format' => 11, 'entity_form_display' => 16, diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeByNodeTypeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeByNodeTypeTest.php new file mode 100644 index 000000000..d5a8db09d --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeByNodeTypeTest.php @@ -0,0 +1,185 @@ + 1, + 'vid' => 1, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 1', + 'uid' => 1, + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 1, + 'translate' => 0, + ], + [ + 'nid' => 2, + 'vid' => 2, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 2', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 2, + 'translate' => 0, + ], + // Add another row with an article node and make sure it is not migrated. + [ + 'nid' => 5, + 'vid' => 5, + 'type' => 'article', + 'language' => 'en', + 'title' => 'node title 5', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + ], + ]; + + $tests[0]['source_data']['node_revisions'] = [ + [ + 'nid' => 1, + 'vid' => 1, + 'title' => 'node title 1', + 'uid' => 2, + 'timestamp' => 1279051598, + 'body' => 'body for node 1', + 'teaser' => 'teaser for node 1', + 'format' => 1, + 'log' => 'log message 1', + ], + [ + 'nid' => 2, + 'vid' => 2, + 'title' => 'node title 2', + 'uid' => 2, + 'timestamp' => 1279290908, + 'body' => 'body for node 2', + 'teaser' => 'teaser for node 2', + 'format' => 1, + 'log' => 'log message 2', + ], + // Add another row with an article node and make sure it is not migrated. + [ + 'nid' => 5, + 'vid' => 5, + 'title' => 'node title 5', + 'uid' => 2, + 'timestamp' => 1279290908, + 'body' => 'body for node 5', + 'teaser' => 'body for node 5', + 'format' => 1, + 'log' => 'log message 3', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + // Node fields. + 'nid' => 1, + 'vid' => 1, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 1', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'timestamp' => 1279051598, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 1, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 1', + 'teaser' => 'teaser for node 1', + 'format' => 1, + 'log' => 'log message 1', + ], + [ + // Node fields. + 'nid' => 2, + 'vid' => 2, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 2', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'timestamp' => 1279290908, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 2, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 2', + 'teaser' => 'teaser for node 2', + 'format' => 1, + 'log' => 'log message 2', + ], + ]; + + // Do an automatic count. + $tests[0]['expected_count'] = NULL; + + // Set up source plugin configuration. + $tests[0]['configuration'] = [ + 'node_type' => 'page', + ]; + + return $tests; + } + +} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php similarity index 50% rename from core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php rename to core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php index d548ff3d4..27e17cc41 100644 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php @@ -1,40 +1,31 @@ 'test', - // The fake configuration for the source. - 'source' => [ - 'plugin' => 'd6_node_revision', - 'node_type' => 'page', - ], - 'sourceIds' => [ - 'vid' => [ - 'alias' => 'v', - ], - ], - 'destinationIds' => [ - 'vid' => [ - // This is where the field schema would go. - ], - ], - ]; + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = []; - protected $databaseContents = [ - 'node' => [ + // The source data. + $tests[0]['source_data']['node'] = [ [ 'nid' => 1, 'type' => 'page', @@ -69,8 +60,8 @@ class NodeRevisionByNodeTypeTest extends MigrateSqlSourceTestCase { 'uid' => 1, 'title' => 'title for revision 2 (node 2)', ], - ], - 'node_revisions' => [ + ]; + $tests[0]['source_data']['node_revisions'] = [ [ 'nid' => 1, 'vid' => 1, @@ -115,58 +106,69 @@ class NodeRevisionByNodeTypeTest extends MigrateSqlSourceTestCase { 'format' => 1, 'timestamp' => 1279308993, ], - ], - ]; + ]; - // There are three revisions of nid 1; vid 4 is the current one. The - // NodeRevision plugin should capture every revision EXCEPT that one. - // nid 2 will be ignored because $this->migrationConfiguration specifies - // a particular node type. - protected $expectedResults = [ - [ - 'nid' => 1, - 'type' => 'page', - 'language' => 'en', - 'status' => 1, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 1, - 'translate' => 0, - 'vid' => 1, - 'node_uid' => 1, - 'revision_uid' => 1, - 'title' => 'title for revision 1 (node 1)', - 'body' => 'body for revision 1 (node 1)', - 'teaser' => 'teaser for revision 1 (node 1)', - 'log' => 'log for revision 1 (node 1)', - 'format' => 1, - ], - [ - 'nid' => 1, - 'type' => 'page', - 'language' => 'en', - 'status' => 1, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 1, - 'translate' => 0, - 'vid' => 3, - 'node_uid' => 1, - 'revision_uid' => 1, - 'title' => 'title for revision 3 (node 1)', - 'body' => 'body for revision 3 (node 1)', - 'teaser' => 'teaser for revision 3 (node 1)', - 'log' => 'log for revision 3 (node 1)', - 'format' => 1, - ], - ]; + // The expected results. + // There are three revisions of nid 1; vid 4 is the current one. The + // NodeRevision plugin should capture every revision EXCEPT that one. + // nid 2 will be ignored because source plugin configuration specifies + // a particular node type. + $tests[0]['expected_data'] = [ + [ + 'nid' => 1, + 'type' => 'page', + 'language' => 'en', + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 1, + 'translate' => 0, + 'vid' => 1, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 1 (node 1)', + 'body' => 'body for revision 1 (node 1)', + 'teaser' => 'teaser for revision 1 (node 1)', + 'log' => 'log for revision 1 (node 1)', + 'format' => 1, + ], + [ + 'nid' => 1, + 'type' => 'page', + 'language' => 'en', + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 1, + 'translate' => 0, + 'vid' => 3, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 3 (node 1)', + 'body' => 'body for revision 3 (node 1)', + 'teaser' => 'teaser for revision 3 (node 1)', + 'log' => 'log for revision 3 (node 1)', + 'format' => 1, + ], + ]; + + // Do an automatic count. + $tests[0]['expected_count'] = NULL; + + // Set up source plugin configuration. + $tests[0]['configuration'] = [ + 'node_type' => 'page', + ]; + + return $tests; + } } diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeRevisionTest.php similarity index 50% rename from core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionTest.php rename to core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeRevisionTest.php index 752ed9bfc..fbc28c5ce 100644 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionTest.php +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeRevisionTest.php @@ -1,39 +1,31 @@ 'test', - // The fake configuration for the source. - 'source' => [ - 'plugin' => 'd6_node_revision', - ], - 'sourceIds' => [ - 'vid' => [ - 'alias' => 'v', - ], - ], - 'destinationIds' => [ - 'vid' => [ - // This is where the field schema would go. - ], - ], - ]; + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = []; - protected $databaseContents = [ - 'node' => [ + // The source data. + $tests[0]['source_data']['node'] = [ [ 'nid' => 1, 'type' => 'page', @@ -68,8 +60,8 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'uid' => 1, 'title' => 'title for revision 2 (node 2)', ], - ], - 'node_revisions' => [ + ]; + $tests[0]['source_data']['node_revisions'] = [ [ 'nid' => 1, 'vid' => 1, @@ -114,61 +106,64 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'format' => 1, 'timestamp' => 1279308993, ], - ], - ]; + ]; - // There are three revisions of nid 1, but the NodeRevision source ignores - // the current revision. So only two revisions will be returned here. nid 2 - // is ignored because it only has one revision (the current one). - protected $expectedResults = [ - [ - // Node fields. - 'nid' => 1, - 'type' => 'page', - 'language' => 'en', - 'status' => 1, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 1, - 'translate' => 0, - // Node revision fields. - 'vid' => 1, - 'node_uid' => 1, - 'revision_uid' => 1, - 'title' => 'title for revision 1 (node 1)', - 'body' => 'body for revision 1 (node 1)', - 'teaser' => 'teaser for revision 1 (node 1)', - 'log' => 'log for revision 1 (node 1)', - 'format' => 1, - ], - [ - // Node fields. - 'nid' => 1, - 'type' => 'page', - 'language' => 'en', - 'status' => 1, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 1, - 'translate' => 0, - // Node revision fields. - 'vid' => 3, - 'node_uid' => 1, - 'revision_uid' => 1, - 'title' => 'title for revision 3 (node 1)', - 'body' => 'body for revision 3 (node 1)', - 'teaser' => 'teaser for revision 3 (node 1)', - 'log' => 'log for revision 3 (node 1)', - 'format' => 1, - ], - ]; + // The expected results. + // There are three revisions of nid 1, but the NodeRevision source ignores + // the current revision. So only two revisions will be returned here. nid 2 + // is ignored because it only has one revision (the current one). + $tests[0]['expected_data'] = [ + [ + // Node fields. + 'nid' => 1, + 'type' => 'page', + 'language' => 'en', + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 1, + 'translate' => 0, + // Node revision fields. + 'vid' => 1, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 1 (node 1)', + 'body' => 'body for revision 1 (node 1)', + 'teaser' => 'teaser for revision 1 (node 1)', + 'log' => 'log for revision 1 (node 1)', + 'format' => 1, + ], + [ + // Node fields. + 'nid' => 1, + 'type' => 'page', + 'language' => 'en', + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 1, + 'translate' => 0, + // Node revision fields. + 'vid' => 3, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 3 (node 1)', + 'body' => 'body for revision 3 (node 1)', + 'teaser' => 'teaser for revision 3 (node 1)', + 'log' => 'log for revision 3 (node 1)', + 'format' => 1, + ], + ]; + + return $tests; + } } diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php new file mode 100644 index 000000000..4bc1be0af --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTest.php @@ -0,0 +1,328 @@ + 'field_test_four', + 'type' => 'number_float', + 'global_settings' => 'a:0:{}', + 'required' => '0', + 'multiple' => '0', + 'db_storage' => '1', + 'module' => 'number', + 'db_columns' => 'a:1:{s:5:"value";a:3:{s:4:"type";s:5:"float";s:8:"not null";b:0;s:8:"sortable";b:1;}}', + 'active' => '1', + 'locked' => '0', + ], + ]; + $tests[0]['source_data']['content_node_field_instance'] = [ + [ + 'field_name' => 'field_test_four', + 'type_name' => 'story', + 'weight' => '3', + 'label' => 'Float Field', + 'widget_type' => 'number', + 'widget_settings' => 'a:0:{}', + 'display_settings' => 'a:0:{}', + 'description' => 'An example float field.', + 'widget_module' => 'number', + 'widget_active' => '1', + ], + ]; + $tests[0]['source_data']['content_type_story'] = [ + [ + 'nid' => 5, + 'vid' => 5, + 'uid' => 5, + 'field_test_four_value' => '3.14159', + ], + ]; + $tests[0]['source_data']['system'] = [ + [ + 'type' => 'module', + 'name' => 'content', + 'schema_version' => 6001, + 'status' => TRUE, + ], + ]; + $tests[0]['source_data']['node'] = [ + [ + 'nid' => 1, + 'vid' => 1, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 1', + 'uid' => 1, + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 0, + ], + [ + 'nid' => 2, + 'vid' => 2, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 2', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 0, + ], + [ + 'nid' => 5, + 'vid' => 5, + 'type' => 'story', + 'language' => 'en', + 'title' => 'node title 5', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 0, + ], + [ + 'nid' => 6, + 'vid' => 6, + 'type' => 'story', + 'language' => 'en', + 'title' => 'node title 6', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290909, + 'changed' => 1279308994, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 6, + ], + [ + 'nid' => 7, + 'vid' => 7, + 'type' => 'story', + 'language' => 'fr', + 'title' => 'node title 7', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290910, + 'changed' => 1279308995, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 6, + ], + ]; + $tests[0]['source_data']['node_revisions'] = [ + [ + 'nid' => 1, + 'vid' => 1, + 'uid' => 2, + 'title' => 'node title 1', + 'body' => 'body for node 1', + 'teaser' => 'teaser for node 1', + 'log' => '', + 'format' => 1, + 'timestamp' => 1279051598, + ], + [ + 'nid' => 2, + 'vid' => 2, + 'uid' => 2, + 'title' => 'node title 2', + 'body' => 'body for node 2', + 'teaser' => 'teaser for node 2', + 'log' => '', + 'format' => 1, + 'timestamp' => 1279308993, + ], + [ + 'nid' => 5, + 'vid' => 5, + 'uid' => 2, + 'title' => 'node title 5', + 'body' => 'body for node 5', + 'teaser' => 'body for node 5', + 'log' => '', + 'format' => 1, + 'timestamp' => 1279308993, + ], + [ + 'nid' => 6, + 'vid' => 6, + 'uid' => 2, + 'title' => 'node title 6', + 'body' => 'body for node 6', + 'teaser' => 'body for node 6', + 'log' => '', + 'format' => 1, + 'timestamp' => 1279308994, + ], + [ + 'nid' => 7, + 'vid' => 7, + 'uid' => 2, + 'title' => 'node title 7', + 'body' => 'body for node 7', + 'teaser' => 'body for node 7', + 'log' => '', + 'format' => 1, + 'timestamp' => 1279308995, + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + // Node fields. + 'nid' => 1, + 'vid' => 1, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 1', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 1, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 1', + 'teaser' => 'teaser for node 1', + 'log' => '', + 'timestamp' => 1279051598, + 'format' => 1, + ], + [ + // Node fields. + 'nid' => 2, + 'vid' => 2, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 2', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 2, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 2', + 'teaser' => 'teaser for node 2', + 'log' => '', + 'timestamp' => 1279308993, + 'format' => 1, + ], + [ + 'nid' => 5, + 'vid' => 5, + 'type' => 'story', + 'language' => 'en', + 'title' => 'node title 5', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 5, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 5', + 'teaser' => 'body for node 5', + 'log' => '', + 'timestamp' => 1279308993, + 'format' => 1, + 'field_test_four' => [ + [ + 'value' => '3.14159', + 'delta' => 0, + ], + ], + ], + [ + 'nid' => 6, + 'vid' => 6, + 'type' => 'story', + 'language' => 'en', + 'title' => 'node title 6', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279290909, + 'changed' => 1279308994, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 6', + 'teaser' => 'body for node 6', + 'log' => '', + 'timestamp' => 1279308994, + 'format' => 1, + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTranslationTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTranslationTest.php new file mode 100644 index 000000000..e7de62b07 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTranslationTest.php @@ -0,0 +1,65 @@ + 7, + 'vid' => 7, + 'type' => 'story', + 'language' => 'fr', + 'title' => 'node title 7', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279290910, + 'changed' => 1279308995, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 7', + 'teaser' => 'body for node 7', + 'log' => '', + 'timestamp' => 1279308995, + 'format' => 1, + ], + ]; + + // Do an automatic count. + $tests[0]['expected_count'] = NULL; + + // Set up source plugin configuration. + $tests[0]['configuration'] = [ + 'translations' => TRUE, + ]; + + return $tests; + } + +} diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTypeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTypeTest.php new file mode 100644 index 000000000..38af680a1 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/NodeTypeTest.php @@ -0,0 +1,67 @@ + 'page', + 'name' => 'Page', + 'module' => 'node', + 'description' => 'A page, similar in form to a story, is a simple method for creating and displaying information that rarely changes, such as an "About us" section of a website. By default, a page entry does not allow visitor comments and is not featured on the site\'s initial home page.', + 'help' => '', + 'title_label' => 'Title', + 'has_body' => 1, + 'body_label' => 'Body', + 'min_word_count' => 0, + 'custom' => 1, + 'modified' => 0, + 'locked' => 0, + 'orig_type' => 'page', + ], + [ + 'type' => 'story', + 'name' => 'Story', + 'module' => 'node', + 'description' => 'A story, similar in form to a page, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a story entry. By default, a story entry is automatically featured on the site\'s initial home page, and provides the ability to post comments.', + 'help' => '', + 'title_label' => 'Title', + 'has_body' => 1, + 'body_label' => 'Body', + 'min_word_count' => 0, + 'custom' => 1, + 'modified' => 0, + 'locked' => 0, + 'orig_type' => 'story', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $tests[0]['source_data']['node_type']; + + return $tests; + } + +} diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/ViewModeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/ViewModeTest.php new file mode 100644 index 000000000..18d941c8a --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d6/ViewModeTest.php @@ -0,0 +1,71 @@ + serialize([ + 'weight' => '31', + 'parent' => '', + 'label' => [ + 'format' => 'above', + ], + 'teaser' => [ + 'format' => 'default', + 'exclude' => 0, + ], + 'full' => [ + 'format' => 'default', + 'exclude' => 0, + ], + 4 => [ + 'format' => 'default', + 'exclude' => 0, + ], + ]), + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'entity_type' => 'node', + 'view_mode' => '4', + ], + [ + 'entity_type' => 'node', + 'view_mode' => 'teaser', + ], + [ + 'entity_type' => 'node', + 'view_mode' => 'full', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php new file mode 100644 index 000000000..6b19b9580 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php @@ -0,0 +1,232 @@ + 1, + 'vid' => 1, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 1', + 'uid' => 1, + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + ], + [ + // Node fields. + 'nid' => 2, + 'vid' => 2, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 2', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + ], + [ + // Node fields. + 'nid' => 5, + 'vid' => 5, + 'type' => 'article', + 'language' => 'en', + 'title' => 'node title 5', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + ], + + ]; + $tests[0]['source_data']['node_revision'] = [ + [ + // Node fields. + 'nid' => 1, + 'vid' => 1, + 'uid' => 2, + 'title' => 'node title 1', + 'log' => '', + 'timestamp' => 1279051598, + 'status' => 1, + 'comment' => 2, + 'promote' => 1, + 'sticky' => 0, + ], + [ + // Node fields. + 'nid' => 2, + 'vid' => 2, + 'uid' => 2, + 'title' => 'node title 2', + 'log' => '', + 'timestamp' => 1279308993, + 'status' => 1, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + ], + [ + // Node fields. + 'nid' => 5, + 'vid' => 5, + 'uid' => 2, + 'title' => 'node title 5', + 'log' => '', + 'timestamp' => 1279308993, + 'status' => 1, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + ], + ]; + $tests[0]['source_data']['field_config_instance'] = [ + [ + 'id' => '2', + 'field_id' => '2', + 'field_name' => 'body', + 'entity_type' => 'node', + 'bundle' => 'page', + 'data' => 'a:0:{}', + 'deleted' => '0', + ], + [ + 'id' => '2', + 'field_id' => '2', + 'field_name' => 'body', + 'entity_type' => 'node', + 'bundle' => 'article', + 'data' => 'a:0:{}', + 'deleted' => '0', + ], + ]; + $tests[0]['source_data']['field_revision_body'] = [ + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '1', + 'language' => 'en', + 'delta' => '0', + 'body_value' => 'Foobaz', + 'body_summary' => '', + 'body_format' => 'filtered_html', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + // Node fields. + 'nid' => 1, + 'vid' => 1, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 1', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + 'log' => '', + 'timestamp' => 1279051598, + 'body' => [ + [ + 'value' => 'Foobaz', + 'summary' => '', + 'format' => 'filtered_html', + ], + ], + ], + [ + // Node fields. + 'nid' => 2, + 'vid' => 2, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 2', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + 'log' => '', + 'timestamp' => 1279308993, + ], + [ + // Node fields. + 'nid' => 5, + 'vid' => 5, + 'type' => 'article', + 'language' => 'en', + 'title' => 'node title 5', + 'node_uid' => 1, + 'revision_uid' => 2, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + 'log' => '', + 'timestamp' => 1279308993, + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTypeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTypeTest.php new file mode 100644 index 000000000..1c738d1a8 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTypeTest.php @@ -0,0 +1,87 @@ + 'page', + 'name' => 'Page', + 'base' => 'node', + 'description' => 'A page, similar in form to a story, is a simple method for creating and displaying information that rarely changes, such as an "About us" section of a website. By default, a page entry does not allow visitor comments and is not featured on the site\'s initial home page.', + 'help' => '', + 'title_label' => 'Title', + 'custom' => 1, + 'modified' => 0, + 'locked' => 0, + 'disabled' => 0, + 'orig_type' => 'page', + ], + [ + 'type' => 'story', + 'name' => 'Story', + 'base' => 'node', + 'description' => 'A story, similar in form to a page, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a story entry. By default, a story entry is automatically featured on the site\'s initial home page, and provides the ability to post comments.', + 'help' => '', + 'title_label' => 'Title', + 'custom' => 1, + 'modified' => 0, + 'locked' => 0, + 'disabled' => 0, + 'orig_type' => 'story', + ], + ]; + $tests[0]['source_data']['variable'] = [ + [ + 'name' => 'node_options_page', + 'value' => 'a:1:{i:0;s:6:"status";}', + ], + [ + 'name' => 'node_options_story', + 'value' => 'a:1:{i:0;s:6:"status";}', + ], + ]; + $tests[0]['source_data']['field_config_instance'] = [ + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'field_name' => 'body', + 'data' => 'a:1:{s:5:"label";s:4:"Body";}', + ], + [ + 'entity_type' => 'node', + 'bundle' => 'story', + 'field_name' => 'body', + 'data' => 'a:1:{s:5:"label";s:4:"Body";}', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $tests[0]['source_data']['node_type']; + + return $tests; + } + +} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php deleted file mode 100644 index 6a308ffff..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php +++ /dev/null @@ -1,135 +0,0 @@ - 'test', - // The fake configuration for the source. - 'source' => array( - 'plugin' => 'd6_node', - 'node_type' => 'page', - ), - ); - - protected $expectedResults = array( - array( - // Node fields. - 'nid' => 1, - 'vid' => 1, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 1', - 'uid' => 1, - 'status' => 1, - 'timestamp' => 1279051598, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 1, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 1', - 'teaser' => 'teaser for node 1', - 'format' => 1, - 'log' => 'log message 1', - ), - array( - // Node fields. - 'nid' => 2, - 'vid' => 2, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 2', - 'uid' => 1, - 'status' => 1, - 'timestamp' => 1279290908, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 2, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 2', - 'teaser' => 'teaser for node 2', - 'format' => 1, - 'log' => 'log message 2', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $database_contents = $this->expectedResults; - array_walk($this->expectedResults, function (&$row) { - $row['node_uid'] = $row['uid']; - $row['revision_uid'] = $row['uid'] + 1; - unset($row['uid']); - }); - - $database_contents[] = array( - // Node fields. - 'nid' => 5, - 'vid' => 5, - 'type' => 'article', - 'language' => 'en', - 'title' => 'node title 5', - 'uid' => 1, - 'status' => 1, - 'timestamp' => 1279290908, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 0, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 5', - 'teaser' => 'body for node 5', - 'format' => 1, - 'log' => 'log message 3', - ); - - // Add another row with an article node and make sure it is not migrated. - - foreach ($database_contents as $k => $row) { - foreach (array('nid', 'vid', 'title', 'uid', 'body', 'teaser', 'format', 'timestamp', 'log') as $field) { - $this->databaseContents['node_revisions'][$k][$field] = $row[$field]; - switch ($field) { - case 'nid': case 'vid': - break; - case 'uid': - $this->databaseContents['node_revisions'][$k]['uid']++; - break; - default: - unset($row[$field]); - break; - } - } - $this->databaseContents['node'][$k] = $row; - } - - parent::setUp(); - } - -} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php deleted file mode 100644 index b6ce22e2e..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php +++ /dev/null @@ -1,122 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_node', - ), - ); - - protected $expectedResults = array( - array( - // Node fields. - 'nid' => 1, - 'vid' => 1, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 1', - 'uid' => 1, - 'status' => 1, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 1, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 1', - 'teaser' => 'teaser for node 1', - 'log' => '', - 'timestamp' => 1279051598, - 'format' => 1, - ), - array( - // Node fields. - 'nid' => 2, - 'vid' => 2, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 2', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 2, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 2', - 'teaser' => 'teaser for node 2', - 'log' => '', - 'timestamp' => 1279308993, - 'format' => 1, - ), - array( - 'nid' => 5, - 'vid' => 5, - 'type' => 'story', - 'language' => 'en', - 'title' => 'node title 5', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 5, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 5', - 'teaser' => 'body for node 5', - 'log' => '', - 'timestamp' => 1279308993, - 'format' => 1, - 'field_test_four' => array( - array( - 'value' => '3.14159', - 'delta' => 0, - ), - ), - ), - array( - 'nid' => 6, - 'vid' => 6, - 'type' => 'story', - 'language' => 'en', - 'title' => 'node title 6', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290909, - 'changed' => 1279308994, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 6, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 6', - 'teaser' => 'body for node 6', - 'log' => '', - 'timestamp' => 1279308994, - 'format' => 1, - ), - ); - -} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTestBase.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTestBase.php deleted file mode 100644 index 8850fc461..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTestBase.php +++ /dev/null @@ -1,179 +0,0 @@ -databaseContents['content_node_field'] = array( - array( - 'field_name' => 'field_test_four', - 'type' => 'number_float', - 'global_settings' => 'a:0:{}', - 'required' => '0', - 'multiple' => '0', - 'db_storage' => '1', - 'module' => 'number', - 'db_columns' => 'a:1:{s:5:"value";a:3:{s:4:"type";s:5:"float";s:8:"not null";b:0;s:8:"sortable";b:1;}}', - 'active' => '1', - 'locked' => '0', - ), - ); - $this->databaseContents['content_node_field_instance'] = array( - array( - 'field_name' => 'field_test_four', - 'type_name' => 'story', - 'weight' => '3', - 'label' => 'Float Field', - 'widget_type' => 'number', - 'widget_settings' => 'a:0:{}', - 'display_settings' => 'a:0:{}', - 'description' => 'An example float field.', - 'widget_module' => 'number', - 'widget_active' => '1', - ), - ); - $this->databaseContents['content_type_story'] = array( - array( - 'nid' => 5, - 'vid' => 5, - 'uid' => 5, - 'field_test_four_value' => '3.14159', - ), - ); - $this->databaseContents['system'] = array( - array( - 'type' => 'module', - 'name' => 'content', - 'schema_version' => 6001, - 'status' => TRUE, - ), - ); - $this->databaseContents['node'] = [ - [ - 'nid' => 1, - 'vid' => 1, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 1', - 'uid' => 1, - 'status' => 1, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'translate' => 0, - 'tnid' => 0, - ], - [ - 'nid' => 2, - 'vid' => 2, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 2', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'translate' => 0, - 'tnid' => 0, - ], - [ - 'nid' => 5, - 'vid' => 5, - 'type' => 'story', - 'language' => 'en', - 'title' => 'node title 5', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'translate' => 0, - 'tnid' => 0, - ], - [ - 'nid' => 6, - 'vid' => 6, - 'type' => 'story', - 'language' => 'en', - 'title' => 'node title 6', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290909, - 'changed' => 1279308994, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'translate' => 0, - 'tnid' => 6, - ], - [ - 'nid' => 7, - 'vid' => 7, - 'type' => 'story', - 'language' => 'fr', - 'title' => 'node title 7', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290910, - 'changed' => 1279308995, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'translate' => 0, - 'tnid' => 6, - ], - ]; - - foreach ($this->databaseContents['node'] as $k => $row) { - // Find the equivalent row from expected results. - $result_row = NULL; - foreach ($this->expectedResults as $result) { - if (in_array($result['nid'], [$row['nid'], $row['tnid']]) && $result['language'] == $row['language']) { - $result_row = $result; - break; - } - } - - // Populate node_revisions. - foreach (array('nid', 'vid', 'title', 'uid', 'body', 'teaser', 'format', 'timestamp', 'log') as $field) { - $value = isset($row[$field]) ? $row[$field] : $result_row[$field]; - $this->databaseContents['node_revisions'][$k][$field] = $value; - if ($field == 'uid') { - $this->databaseContents['node_revisions'][$k]['uid']++; - } - } - } - - array_walk($this->expectedResults, function (&$row) { - $row['node_uid'] = $row['uid']; - $row['revision_uid'] = $row['uid'] + 1; - unset($row['uid']); - }); - - parent::setUp(); - } - -} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTranslationTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTranslationTest.php deleted file mode 100644 index da0b167ed..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTranslationTest.php +++ /dev/null @@ -1,46 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_node', - 'translations' => TRUE, - ), - ); - - protected $expectedResults = array( - array( - 'nid' => 7, - 'vid' => 7, - 'type' => 'story', - 'language' => 'fr', - 'title' => 'node title 7', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290910, - 'changed' => 1279308995, - 'comment' => 0, - 'promote' => 1, - 'moderate' => 0, - 'sticky' => 0, - 'tnid' => 6, - 'translate' => 0, - // Node revision fields. - 'body' => 'body for node 7', - 'teaser' => 'body for node 7', - 'log' => '', - 'timestamp' => 1279308995, - 'format' => 1, - ), - ); - -} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTypeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTypeTest.php deleted file mode 100644 index 591bcd9bc..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTypeTest.php +++ /dev/null @@ -1,70 +0,0 @@ - 'test_nodetypes', - 'source' => array( - 'plugin' => 'd6_nodetype', - ), - ); - - // We need to set up the database contents; it's easier to do that below. - // These are sample result queries. - protected $expectedResults = array( - array( - 'type' => 'page', - 'name' => 'Page', - 'module' => 'node', - 'description' => 'A page, similar in form to a story, is a simple method for creating and displaying information that rarely changes, such as an "About us" section of a website. By default, a page entry does not allow visitor comments and is not featured on the site\'s initial home page.', - 'help' => '', - 'title_label' => 'Title', - 'has_body' => 1, - 'body_label' => 'Body', - 'min_word_count' => 0, - 'custom' => 1, - 'modified' => 0, - 'locked' => 0, - 'orig_type' => 'page', - ), - array( - 'type' => 'story', - 'name' => 'Story', - 'module' => 'node', - 'description' => 'A story, similar in form to a page, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a story entry. By default, a story entry is automatically featured on the site\'s initial home page, and provides the ability to post comments.', - 'help' => '', - 'title_label' => 'Title', - 'has_body' => 1, - 'body_label' => 'Body', - 'min_word_count' => 0, - 'custom' => 1, - 'modified' => 0, - 'locked' => 0, - 'orig_type' => 'story', - ), - ); - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->databaseContents['node_type'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/ViewModeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/ViewModeTest.php deleted file mode 100644 index 1df544390..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/ViewModeTest.php +++ /dev/null @@ -1,73 +0,0 @@ - 'view_mode_test', - 'source' => array( - 'plugin' => 'd6_field_instance_view_mode', - ), - ); - - protected $expectedResults = array( - array( - 'entity_type' => 'node', - 'view_mode' => '4', - ), - array( - 'entity_type' => 'node', - 'view_mode' => 'teaser', - ), - array( - 'entity_type' => 'node', - 'view_mode' => 'full', - ), - ); - - - /** - * {@inheritdoc} - */ - protected function setUp() { - - $this->databaseContents['content_node_field_instance'][] = array( - 'display_settings' => serialize(array( - 'weight' => '31', - 'parent' => '', - 'label' => array( - 'format' => 'above', - ), - 'teaser' => array( - 'format' => 'default', - 'exclude' => 0, - ), - 'full' => array( - 'format' => 'default', - 'exclude' => 0, - ), - 4 => array( - 'format' => 'default', - 'exclude' => 0, - ), - )), - ); - - parent::setUp(); - } - -} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTest.php deleted file mode 100644 index b0489bfad..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTest.php +++ /dev/null @@ -1,147 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd7_node', - ), - ); - - protected $expectedResults = array( - array( - // Node fields. - 'nid' => 1, - 'vid' => 1, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 1', - 'uid' => 1, - 'status' => 1, - 'created' => 1279051598, - 'changed' => 1279051598, - 'comment' => 2, - 'promote' => 1, - 'sticky' => 0, - 'tnid' => 0, - 'translate' => 0, - 'log' => '', - 'timestamp' => 1279051598, - ), - array( - // Node fields. - 'nid' => 2, - 'vid' => 2, - 'type' => 'page', - 'language' => 'en', - 'title' => 'node title 2', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'sticky' => 0, - 'tnid' => 0, - 'translate' => 0, - 'log' => '', - 'timestamp' => 1279308993, - ), - array( - // Node fields. - 'nid' => 5, - 'vid' => 5, - 'type' => 'article', - 'language' => 'en', - 'title' => 'node title 5', - 'uid' => 1, - 'status' => 1, - 'created' => 1279290908, - 'changed' => 1279308993, - 'comment' => 0, - 'promote' => 1, - 'sticky' => 0, - 'tnid' => 0, - 'translate' => 0, - 'log' => '', - 'timestamp' => 1279308993, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $row) { - foreach (array('nid', 'vid', 'title', 'uid', 'timestamp', 'log') as $field) { - $this->databaseContents['node_revision'][$k][$field] = $row[$field]; - switch ($field) { - case 'nid': case 'vid': - break; - case 'uid': - $this->databaseContents['node_revision'][$k]['uid']++; - break; - default: - unset($row[$field]); - break; - } - } - $this->databaseContents['node'][$k] = $row; - } - array_walk($this->expectedResults, function (&$row) { - $row['node_uid'] = $row['uid']; - $row['revision_uid'] = $row['uid'] + 1; - unset($row['uid']); - }); - - $this->databaseContents['field_config_instance'] = array( - array( - 'id' => '2', - 'field_id' => '2', - 'field_name' => 'body', - 'entity_type' => 'node', - 'bundle' => 'page', - 'data' => 'a:0:{}', - 'deleted' => '0', - ), - array( - 'id' => '2', - 'field_id' => '2', - 'field_name' => 'body', - 'entity_type' => 'node', - 'bundle' => 'article', - 'data' => 'a:0:{}', - 'deleted' => '0', - ), - ); - $this->databaseContents['field_revision_body'] = array( - array( - 'entity_type' => 'node', - 'bundle' => 'page', - 'deleted' => '0', - 'entity_id' => '1', - 'revision_id' => '1', - 'language' => 'en', - 'delta' => '0', - 'body_value' => 'Foobaz', - 'body_summary' => '', - 'body_format' => 'filtered_html', - ), - ); - - parent::setUp(); - } - -} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTypeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTypeTest.php deleted file mode 100644 index 19e1491af..000000000 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d7/NodeTypeTest.php +++ /dev/null @@ -1,84 +0,0 @@ - 'test_nodetypes', - 'source' => array( - 'plugin' => 'd7_node_type', - ), - ); - - protected $expectedResults = array( - array( - 'type' => 'page', - 'name' => 'Page', - 'base' => 'node', - 'description' => 'A page, similar in form to a story, is a simple method for creating and displaying information that rarely changes, such as an "About us" section of a website. By default, a page entry does not allow visitor comments and is not featured on the site\'s initial home page.', - 'help' => '', - 'title_label' => 'Title', - 'custom' => 1, - 'modified' => 0, - 'locked' => 0, - 'disabled' => 0, - 'orig_type' => 'page', - ), - array( - 'type' => 'story', - 'name' => 'Story', - 'base' => 'node', - 'description' => 'A story, similar in form to a page, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a story entry. By default, a story entry is automatically featured on the site\'s initial home page, and provides the ability to post comments.', - 'help' => '', - 'title_label' => 'Title', - 'custom' => 1, - 'modified' => 0, - 'locked' => 0, - 'disabled' => 0, - 'orig_type' => 'story', - ), - ); - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->databaseContents['node_type'] = $this->expectedResults; - $this->databaseContents['variable'] = array( - array( - 'name' => 'node_options_page', - 'value' => 'a:1:{i:0;s:6:"status";}', - ), - array( - 'name' => 'node_options_story', - 'value' => 'a:1:{i:0;s:6:"status";}', - ), - ); - $this->databaseContents['field_config_instance'] = array( - array( - 'entity_type' => 'node', - 'bundle' => 'page', - 'field_name' => 'body', - 'data' => 'a:1:{s:5:"label";s:4:"Body";}', - ), - array( - 'entity_type' => 'node', - 'bundle' => 'story', - 'field_name' => 'body', - 'data' => 'a:1:{s:5:"label";s:4:"Body";}', - ) - ); - parent::setUp(); - } - -} diff --git a/core/modules/options/options.api.php b/core/modules/options/options.api.php index a4ac605d4..28ac77114 100644 --- a/core/modules/options/options.api.php +++ b/core/modules/options/options.api.php @@ -20,7 +20,7 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; * properties. * @param array $context * An associative array containing: - * - field_definition: The field definition + * - fieldDefinition: The field definition * (\Drupal\Core\Field\FieldDefinitionInterface). * - entity: The entity object the field is attached to * (\Drupal\Core\Entity\EntityInterface). @@ -30,7 +30,7 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; */ function hook_options_list_alter(array &$options, array $context) { // Check if this is the field we want to change. - if ($context['field']->id() == 'field_option') { + if ($context['fieldDefinition']->id() == 'field_option') { // Change the label of the empty option. $options['_none'] = t('== Empty =='); } diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php index aaea32f9b..4527ca361 100644 --- a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php +++ b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php @@ -168,7 +168,7 @@ abstract class ListItemBase extends FieldItemBase implements OptionsProviderInte * @return array|null * The array of extracted key/value pairs, or NULL if the string is invalid. * - * @see \Drupal\options\Plugin\Field\FieldType\ListTextItem::allowedValuesString() + * @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::allowedValuesString() */ protected static function extractAllowedValues($string, $has_data) { $values = array(); @@ -277,7 +277,7 @@ abstract class ListItemBase extends FieldItemBase implements OptionsProviderInte * Allowed values were the array key is the 'value' value, the value is * the 'label' value. * - * @see Drupal\options\Plugin\Field\FieldType\ListItemBase::structureAllowedValues() + * @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::structureAllowedValues() */ protected static function simplifyAllowedValues(array $structured_values) { $values = array(); @@ -302,7 +302,7 @@ abstract class ListItemBase extends FieldItemBase implements OptionsProviderInte * Array of items with a 'value' and 'label' key each for the allowed * values. * - * @see Drupal\options\Plugin\Field\FieldType\ListItemBase::simplifyAllowedValues() + * @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::simplifyAllowedValues() */ protected static function structureAllowedValues(array $values) { $structured_values = array(); diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js index a4f94aa90..d079664cd 100644 --- a/core/modules/outside_in/js/outside_in.js +++ b/core/modules/outside_in/js/outside_in.js @@ -7,7 +7,7 @@ 'use strict'; - var blockConfigureSelector = '[data-dialog-renderer="offcanvas"]'; + var blockConfigureSelector = '[data-outside-in-edit]'; var toggleEditSelector = '[data-drupal-outsidein="toggle"]'; var itemsToToggleSelector = '#main-canvas, #toolbar-bar, [data-drupal-outsidein="editable"] a, [data-drupal-outsidein="editable"] button'; var contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button'; @@ -186,7 +186,11 @@ // @todo Move logic for data-dialog-renderer attribute into ajax.js // https://www.drupal.org/node/2784443 instance.options.url = instance.options.url.replace(search, replace); - instance.options.data.dialogOptions = {outsideInActiveEditableId: $(instance.element).parents('.outside-in-editable').attr('id')}; + // Check to make sure existing dialogOptions aren't overridden. + if (!('dialogOptions' in instance.options.data)) { + instance.options.data.dialogOptions = {}; + } + instance.options.data.dialogOptions.outsideInActiveEditableId = $(instance.element).parents('.outside-in-editable').attr('id'); instance.progress = {type: 'fullscreen'}; }); } diff --git a/core/modules/outside_in/outside_in.module b/core/modules/outside_in/outside_in.module index 298b994c9..a7fab7bd2 100644 --- a/core/modules/outside_in/outside_in.module +++ b/core/modules/outside_in/outside_in.module @@ -38,6 +38,7 @@ function outside_in_contextual_links_view_alter(&$element, $items) { 'class' => ['use-ajax'], 'data-dialog-type' => 'dialog', 'data-dialog-renderer' => 'offcanvas', + 'data-outside-in-edit' => TRUE, ]; $element['#attached']['library'][] = 'outside_in/drupal.off_canvas'; diff --git a/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php b/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php index f6663deff..d78dc3539 100644 --- a/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php +++ b/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php @@ -14,8 +14,10 @@ class OpenOffCanvasDialogCommand extends OpenDialogCommand { /** * Constructs an OpenOffCanvasDialogCommand object. * - * Drupal provides a built-in offcanvas tray for this purpose, so no selector - * needs to be provided. + * The off-canvas dialog differs from the normal modal provided by + * OpenDialogCommand in that a off-canvas has built in positioning and + * behaviours. Drupal provides a built-in off-canvas tray for this purpose, + * so the selector is hard-coded in the call to the parent constructor. * * @param string $title * The title of the dialog. @@ -46,15 +48,10 @@ class OpenOffCanvasDialogCommand extends OpenDialogCommand { * {@inheritdoc} */ public function render() { - return [ - 'command' => 'openDialog', - 'selector' => '#drupal-offcanvas', - 'settings' => $this->settings, - 'data' => $this->getRenderedContent(), - 'dialogOptions' => $this->dialogOptions, - 'effect' => 'fade', - 'speed' => 1000, - ]; + $build = parent::render(); + $build['effect'] = 'fade'; + $build['speed'] = 1000; + return $build; } } diff --git a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php b/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php index 1e5b0d195..f3510427b 100644 --- a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php +++ b/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php @@ -39,7 +39,7 @@ class OffCanvasDialogTest extends AjaxTestBase { 'data' => $dialog_contents, 'dialogOptions' => [ - 'title' => 'AJAX Dialog contents', + 'title' => 'AJAX Dialog & contents', 'modal' => FALSE, 'autoResize' => FALSE, 'resizable' => 'w', diff --git a/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig b/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig index 1d1c50fc7..588918334 100644 --- a/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig +++ b/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig @@ -12,12 +12,10 @@ */ #} {% if children %} - {% spaceless %} -
-
- {{ children }} -
+
+
+ {{ children }}
-
- {% endspaceless %} +
+
{% endif %} diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php b/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php index e711fc3f5..9ee8e1768 100644 --- a/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php +++ b/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php @@ -2,6 +2,7 @@ namespace Drupal\offcanvas_test\Controller; +use Drupal\Component\Serialization\Json; use Drupal\Core\Url; /** @@ -66,6 +67,9 @@ class TestController { 'class' => ['use-ajax'], 'data-dialog-type' => 'dialog', 'data-dialog-renderer' => 'offcanvas', + 'data-dialog-options' => Json::encode([ + 'width' => 555, + ]), ], '#attached' => [ 'library' => [ diff --git a/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php b/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php index a2a9514af..8e04fd8b5 100644 --- a/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php +++ b/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php @@ -52,10 +52,13 @@ class OffCanvasTest extends OutsideInJavascriptTestBase { $tray_text = $offcanvas_tray->findById('drupal-offcanvas')->getText(); $this->assertEquals("Thing $link_index says hello", $tray_text); - // Check no title behavior. if ($link_index == '2') { + // Check no title behavior. $web_assert->elementExists('css', '.ui-dialog-empty-title'); $this->assertEquals('', $header_text); + + $style = $page->find('css', '.ui-dialog-offcanvas')->getAttribute('style'); + self::assertTrue(strstr($style, 'width: 555px;') !== FALSE, 'Dialog width respected.'); } else { // Check that header is correct. diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php index 22b8b51a1..1ec9282eb 100644 --- a/core/modules/page_cache/src/Tests/PageCacheTest.php +++ b/core/modules/page_cache/src/Tests/PageCacheTest.php @@ -510,4 +510,31 @@ class PageCacheTest extends WebTestBase { $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); } + /** + * Tests that HEAD requests are treated the same as GET requests. + */ + public function testHead() { + // GET, then HEAD. + $url_a = $this->buildUrl('system-test/set-header', ['query' => ['name' => 'Foo', 'value' => 'bar']]); + $response_body = $this->curlExec([CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $url_a, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_NOBODY => FALSE]); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); + $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); + $this->assertEqual('The following header was set: Foo: bar', $response_body); + $response_body = $this->curlExec([CURLOPT_HTTPGET => FALSE, CURLOPT_URL => $url_a, CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_NOBODY => FALSE]); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); + $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); + $this->assertEqual('', $response_body); + + // HEAD, then GET. + $url_b = $this->buildUrl('system-test/set-header', ['query' => ['name' => 'Foo', 'value' => 'baz']]); + $response_body = $this->curlExec([CURLOPT_HTTPGET => FALSE, CURLOPT_URL => $url_b, CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_NOBODY => FALSE]); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); + $this->assertEqual($this->drupalGetHeader('Foo'), 'baz', 'Custom header was sent.'); + $this->assertEqual('', $response_body); + $response_body = $this->curlExec([CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $url_b, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_NOBODY => FALSE]); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); + $this->assertEqual($this->drupalGetHeader('Foo'), 'baz', 'Custom header was sent.'); + $this->assertEqual('The following header was set: Foo: baz', $response_body); + } + } diff --git a/core/modules/path/tests/src/Kernel/Plugin/migrate/source/d6/UrlAliasTest.php b/core/modules/path/tests/src/Kernel/Plugin/migrate/source/d6/UrlAliasTest.php new file mode 100644 index 000000000..1057dffbe --- /dev/null +++ b/core/modules/path/tests/src/Kernel/Plugin/migrate/source/d6/UrlAliasTest.php @@ -0,0 +1,48 @@ + 1, + 'src' => 'node/1', + 'dst' => 'test-article', + 'language' => 'en', + ], + [ + 'pid' => 2, + 'src' => 'node/2', + 'dst' => 'another-alias', + 'language' => 'en', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $tests[0]['source_data']['url_alias']; + + return $tests; + } + +} diff --git a/core/modules/path/tests/src/Kernel/Plugin/migrate/source/d7/UrlAliasTest.php b/core/modules/path/tests/src/Kernel/Plugin/migrate/source/d7/UrlAliasTest.php new file mode 100644 index 000000000..2fdfd361d --- /dev/null +++ b/core/modules/path/tests/src/Kernel/Plugin/migrate/source/d7/UrlAliasTest.php @@ -0,0 +1,48 @@ + 1, + 'source' => 'node/1', + 'alias' => 'test-article', + 'language' => 'en', + ], + [ + 'pid' => 2, + 'source' => 'node/2', + 'alias' => 'another-alias', + 'language' => 'en', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $tests[0]['source_data']['url_alias']; + + return $tests; + } + +} diff --git a/core/modules/path/tests/src/Unit/Migrate/UrlAliasTestBase.php b/core/modules/path/tests/src/Unit/Migrate/UrlAliasTestBase.php deleted file mode 100644 index c929192cb..000000000 --- a/core/modules/path/tests/src/Unit/Migrate/UrlAliasTestBase.php +++ /dev/null @@ -1,20 +0,0 @@ -databaseContents['url_alias'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/path/tests/src/Unit/Migrate/d6/UrlAliasTest.php b/core/modules/path/tests/src/Unit/Migrate/d6/UrlAliasTest.php deleted file mode 100644 index fa00d60c9..000000000 --- a/core/modules/path/tests/src/Unit/Migrate/d6/UrlAliasTest.php +++ /dev/null @@ -1,38 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_url_alias', - ), - ); - - protected $expectedResults = array( - array( - 'pid' => 1, - 'src' => 'node/1', - 'dst' => 'test-article', - 'language' => 'en', - ), - array( - 'pid' => 2, - 'src' => 'node/2', - 'dst' => 'another-alias', - 'language' => 'en', - ), - ); - -} diff --git a/core/modules/path/tests/src/Unit/Migrate/d7/UrlAliasTest.php b/core/modules/path/tests/src/Unit/Migrate/d7/UrlAliasTest.php deleted file mode 100644 index d2fce564d..000000000 --- a/core/modules/path/tests/src/Unit/Migrate/d7/UrlAliasTest.php +++ /dev/null @@ -1,38 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd7_url_alias', - ), - ); - - protected $expectedResults = array( - array( - 'pid' => 1, - 'source' => 'node/1', - 'alias' => 'test-article', - 'language' => 'en', - ), - array( - 'pid' => 2, - 'source' => 'node/2', - 'alias' => 'another-alias', - 'language' => 'en', - ), - ); - -} diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index 89ac37f49..15ec5375a 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -182,7 +182,7 @@ function template_preprocess_responsive_image(&$variables) { if (isset($variables['sources']) && count($variables['sources']) === 1 && !isset($variables['sources'][0]['media'])) { // There is only one source tag with an empty media attribute. This means - // we can output an image tag with the srcset attribute in stead of a + // we can output an image tag with the srcset attribute instead of a // picture tag. $variables['output_image_tag'] = TRUE; foreach ($variables['sources'][0] as $attribute => $value) { diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 7e4b90717..91ce482ec 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -268,7 +268,7 @@ class EntityResource extends ResourceBase implements DependentPluginInterface { $entity->delete(); $this->logger->notice('Deleted entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id())); - // Delete responses have an empty body. + // DELETE responses have an empty body. return new ModifiedResourceResponse(NULL, 204); } catch (EntityStorageException $e) { diff --git a/core/modules/rest/src/Tests/CreateTest.php b/core/modules/rest/src/Tests/CreateTest.php index 8c7db07ba..2454d2a0c 100644 --- a/core/modules/rest/src/Tests/CreateTest.php +++ b/core/modules/rest/src/Tests/CreateTest.php @@ -366,10 +366,16 @@ class CreateTest extends RESTTestBase { // Note: this will fail with PHP 5.6 when always_populate_raw_post_data is // set to something other than -1. See https://www.drupal.org/node/2456025. // Try first without the CSRF token, which should fail. - $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType, TRUE); - $this->assertResponse(403, 'X-CSRF-Token request header is missing'); + $url = Url::fromUri('internal:/entity/' . $entity_type)->setOption('query', ['_format' => $this->defaultFormat]); + $this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType, FALSE); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is missing'); + // Then try with an invalid CSRF token. + $this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType, 'invalid-csrf-token'); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is invalid'); // Then try with the CSRF token. - $response = $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType); + $response = $this->httpRequest($url, 'POST', $serialized, $this->defaultMimeType); $this->assertResponse(201); // Make sure that the response includes an entity in the body and check the diff --git a/core/modules/rest/src/Tests/DeleteTest.php b/core/modules/rest/src/Tests/DeleteTest.php index a1443422a..88db0fd9d 100644 --- a/core/modules/rest/src/Tests/DeleteTest.php +++ b/core/modules/rest/src/Tests/DeleteTest.php @@ -38,10 +38,17 @@ class DeleteTest extends RESTTestBase { $entity = $this->entityCreate($entity_type); $entity->save(); // Try first to delete over REST API without the CSRF token. - $this->httpRequest($entity->urlInfo(), 'DELETE', NULL, NULL, TRUE); - $this->assertResponse(403, 'X-CSRF-Token request header is missing'); + $url = $entity->toUrl()->setRouteParameter('_format', $this->defaultFormat); + $this->httpRequest($url, 'DELETE', NULL, 'application/hal+json', FALSE); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is missing'); + // Then try with an invalid CSRF token. + $this->httpRequest($url, 'DELETE', NULL, 'application/hal+json', 'invalid-csrf-token'); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is invalid'); // Delete it over the REST API. - $response = $this->httpRequest($entity->urlInfo(), 'DELETE'); + $response = $this->httpRequest($url, 'DELETE'); + $this->assertResponse(204); // Clear the static cache with entity_load(), otherwise we won't see the // update. $storage = $this->container->get('entity_type.manager') diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index 2a752274b..7cb0f1bde 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -85,19 +85,22 @@ abstract class RESTTestBase extends WebTestBase { * The body for POST and PUT. * @param string $mime_type * The MIME type of the transmitted content. - * @param bool $forget_xcsrf_token - * If TRUE, the CSRF token won't be included in request. + * @param bool $csrf_token + * If NULL, a CSRF token will be retrieved and used. If FALSE, omit the + * X-CSRF-Token request header (to simulate developer error). Otherwise, the + * passed in value will be used as the value for the X-CSRF-Token request + * header (to simulate developer error, by sending an invalid CSRF token). * * @return string * The content returned from the request. */ - protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $forget_xcsrf_token = FALSE) { + protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $csrf_token = NULL) { if (!isset($mime_type)) { $mime_type = $this->defaultMimeType; } if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) { // GET the CSRF token first for writing requests. - $token = $this->drupalGet('session/token'); + $requested_token = $this->drupalGet('session/token'); } $url = $this->buildUrl($url); @@ -132,9 +135,9 @@ abstract class RESTTestBase extends WebTestBase { CURLOPT_POSTFIELDS => $body, CURLOPT_URL => $url, CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array( + CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( 'Content-Type: ' . $mime_type, - 'X-CSRF-Token: ' . $token, + 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), ) : array( 'Content-Type: ' . $mime_type, ), @@ -148,9 +151,9 @@ abstract class RESTTestBase extends WebTestBase { CURLOPT_POSTFIELDS => $body, CURLOPT_URL => $url, CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array( + CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( 'Content-Type: ' . $mime_type, - 'X-CSRF-Token: ' . $token, + 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), ) : array( 'Content-Type: ' . $mime_type, ), @@ -164,9 +167,9 @@ abstract class RESTTestBase extends WebTestBase { CURLOPT_POSTFIELDS => $body, CURLOPT_URL => $url, CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array( + CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( 'Content-Type: ' . $mime_type, - 'X-CSRF-Token: ' . $token, + 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), ) : array( 'Content-Type: ' . $mime_type, ), @@ -179,7 +182,9 @@ abstract class RESTTestBase extends WebTestBase { CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_URL => $url, CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => !$forget_xcsrf_token ? array('X-CSRF-Token: ' . $token) : array(), + CURLOPT_HTTPHEADER => $csrf_token !== FALSE ? array( + 'X-CSRF-Token: ' . ($csrf_token === NULL ? $requested_token : $csrf_token), + ) : array(), ); break; } @@ -197,6 +202,8 @@ abstract class RESTTestBase extends WebTestBase { $this->verbose($method . ' request to: ' . $url . '
Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) . + (isset($curl_options[CURLOPT_HTTPHEADER]) ? '
Request headers: ' . nl2br(print_r($curl_options[CURLOPT_HTTPHEADER], TRUE)) : '' ) . + (isset($curl_options[CURLOPT_POSTFIELDS]) ? '
Request body: ' . nl2br(print_r($curl_options[CURLOPT_POSTFIELDS], TRUE)) : '' ) . '
Response headers: ' . nl2br(print_r($headers, TRUE)) . '
Response body: ' . $this->responseBody); diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php index cdef82c34..d3784ed66 100644 --- a/core/modules/rest/src/Tests/UpdateTest.php +++ b/core/modules/rest/src/Tests/UpdateTest.php @@ -381,9 +381,13 @@ class UpdateTest extends RESTTestBase { $serialized = $serializer->serialize($normalized, $format, $context); // Try first without CSRF token which should fail. - $this->httpRequest($url, 'PATCH', $serialized, $mime_type, TRUE); - $this->assertResponse(403, 'X-CSRF-Token request header is missing'); - + $this->httpRequest($url, 'PATCH', $serialized, $mime_type, FALSE); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is missing'); + // Then try with an invalid CSRF token. + $this->httpRequest($url, 'PATCH', $serialized, $mime_type, 'invalid-csrf-token'); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is invalid'); // Then try with CSRF token. $this->httpRequest($url, 'PATCH', $serialized, $mime_type); $this->assertResponse(200); diff --git a/core/modules/search/migration_templates/d6_search_settings.yml b/core/modules/search/migration_templates/d6_search_settings.yml index 8efb9fdb1..28f860060 100644 --- a/core/modules/search/migration_templates/d6_search_settings.yml +++ b/core/modules/search/migration_templates/d6_search_settings.yml @@ -10,8 +10,6 @@ source: - minimum_word_size - overlap_cjk - search_cron_limit - - search_tag_weights - - search_and_or_limit process: 'index/minimum_word_size': minimum_word_size 'index/overlap_cjk': overlap_cjk diff --git a/core/modules/serialization/src/Normalizer/ListNormalizer.php b/core/modules/serialization/src/Normalizer/ListNormalizer.php index 94aca1134..1729cfce8 100644 --- a/core/modules/serialization/src/Normalizer/ListNormalizer.php +++ b/core/modules/serialization/src/Normalizer/ListNormalizer.php @@ -26,7 +26,7 @@ class ListNormalizer extends NormalizerBase { public function normalize($object, $format = NULL, array $context = array()) { $attributes = array(); foreach ($object as $fieldItem) { - $attributes[] = $this->serializer->normalize($fieldItem, $format); + $attributes[] = $this->serializer->normalize($fieldItem, $format, $context); } return $attributes; } diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php index 170fd3ecd..bbc7c1103 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php @@ -7,6 +7,7 @@ use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\Tests\UnitTestCase; use Drupal\serialization\Normalizer\ListNormalizer; use Drupal\Core\TypedData\Plugin\DataType\ItemList; +use Symfony\Component\Serializer\Serializer; /** * @coversDefaultClass \Drupal\serialization\Normalizer\ListNormalizer @@ -35,13 +36,20 @@ class ListNormalizerTest extends UnitTestCase { */ protected $expectedListValues = array('test', 'test', 'test'); + /** + * The mocked typed data. + * + * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\TypedData\TypedDataInterface + */ + protected $typedData; + protected function setUp() { // Mock the TypedDataManager to return a TypedDataInterface mock. - $typed_data = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); + $this->typedData = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); $typed_data_manager = $this->getMock(TypedDataManagerInterface::class); $typed_data_manager->expects($this->any()) ->method('getPropertyInstance') - ->will($this->returnValue($typed_data)); + ->will($this->returnValue($this->typedData)); // Set up a mock container as ItemList() will call for the 'typed_data_manager' // service. @@ -73,16 +81,14 @@ class ListNormalizerTest extends UnitTestCase { * Tests the normalize() method. */ public function testNormalize() { - $serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer') - ->setMethods(array('normalize')) - ->getMock(); - $serializer->expects($this->exactly(3)) - ->method('normalize') - ->will($this->returnValue('test')); + $serializer = $this->prophesize(Serializer::class); + $serializer->normalize($this->typedData, 'json', ['mu' => 'nu']) + ->shouldBeCalledTimes(3) + ->willReturn('test'); - $this->normalizer->setSerializer($serializer); + $this->normalizer->setSerializer($serializer->reveal()); - $normalized = $this->normalizer->normalize($this->list); + $normalized = $this->normalizer->normalize($this->list, 'json', ['mu' => 'nu']); $this->assertEquals($this->expectedListValues, $normalized); } diff --git a/core/modules/shortcut/migration_templates/d7_shortcut.yml b/core/modules/shortcut/migration_templates/d7_shortcut.yml index ba986eb85..da3d89494 100644 --- a/core/modules/shortcut/migration_templates/d7_shortcut.yml +++ b/core/modules/shortcut/migration_templates/d7_shortcut.yml @@ -23,4 +23,4 @@ destination: migration_dependencies: required: - d7_shortcut_set - - menu_links + - d7_menu_links diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 066afcc95..b7294ad65 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -311,7 +311,8 @@ function shortcut_preprocess_page_title(&$variables) { // Replicate template_preprocess_html()'s processing to get the title in // string form, so we can set the default name for the shortcut. - $name = render($variables['title']); + // Strip HTML tags from the title. + $name = trim(strip_tags(render($variables['title']))); $query = array( 'link' => $link, 'name' => $name, diff --git a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php index c6efe4134..679f89bbe 100644 --- a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php +++ b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php @@ -2,7 +2,9 @@ namespace Drupal\shortcut\Tests; +use Drupal\block_content\Entity\BlockContentType; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; use Drupal\shortcut\Entity\Shortcut; use Drupal\shortcut\Entity\ShortcutSet; @@ -161,18 +163,12 @@ class ShortcutLinksTest extends ShortcutTestBase { // Test the "Add to shortcuts" link for a page generated by views. $this->clickLink('Add to Default shortcuts'); $this->assertText('Added a shortcut for People.'); - // Due to the structure of the markup in the link ::assertLink() doesn't - // works here. - $link = $this->xpath('//a[normalize-space()=:label]', array(':label' => 'Remove from Default shortcuts')); - $this->assertTrue(!empty($link), 'Link Remove from Default shortcuts found.'); + $this->assertShortcutQuickLink('Remove from Default shortcuts'); // Test the "Remove from shortcuts" link for a page generated by views. $this->clickLink('Remove from Default shortcuts'); $this->assertText('The shortcut People has been deleted.'); - // Due to the structure of the markup in the link ::assertLink() doesn't - // works here. - $link = $this->xpath('//a[normalize-space()=:label]', array(':label' => 'Add to Default shortcuts')); - $this->assertTrue(!empty($link), 'Link Add to Default shortcuts found.'); + $this->assertShortcutQuickLink('Add to Default shortcuts'); // Test two pages which use same route name but different route parameters. $this->drupalGet('node/add/page'); @@ -186,6 +182,37 @@ class ShortcutLinksTest extends ShortcutTestBase { // Add Shortcut for Article. $this->clickLink('Add to Default shortcuts'); $this->assertText('Added a shortcut for Create Article.'); + + $this->config('system.theme')->set('default', 'seven')->save(); + $this->drupalGet('node/' . $this->node->id()); + $title = $this->node->getTitle(); + + // Test the "Add to shortcuts" link for node view route. + $this->clickLink('Add to Default shortcuts'); + $this->assertText(new FormattableMarkup('Added a shortcut for @title.', ['@title' => $title])); + $this->assertShortcutQuickLink('Remove from Default shortcuts'); + + // Test the "Remove from shortcuts" link for node view route. + $this->clickLink('Remove from Default shortcuts'); + $this->assertText(new FormattableMarkup('The shortcut @title has been deleted.', ['@title' => $title])); + $this->assertShortcutQuickLink('Add to Default shortcuts'); + + \Drupal::service('module_installer')->install(['block_content']); + BlockContentType::create(array( + 'id' => 'basic', + 'label' => 'Basic block', + 'revision' => FALSE, + ))->save(); + // Test page with HTML tags in title. + $this->drupalGet('admin/structure/block/block-content/manage/basic'); + $page_title = new FormattableMarkup('Edit %label custom block type', ['%label' => 'Basic block']); + $this->assertRaw($page_title); + // Add shortcut to this page. + $this->clickLink('Add to Default shortcuts'); + $this->assertRaw(new FormattableMarkup('Added a shortcut for %title.', [ + '%title' => trim(strip_tags($page_title)), + ])); + } /** @@ -404,4 +431,32 @@ class ShortcutLinksTest extends ShortcutTestBase { $this->assertNoBlockAppears($block); } + /** + * Passes if a shortcut quick link with the specified label is found. + * + * An optional link index may be passed. + * + * @param string $label + * Text between the anchor tags. + * @param int $index + * Link position counting from zero. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param string $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertShortcutQuickLink($label, $index = 0, $message = '', $group = 'Other') { + $links = $this->xpath('//a[normalize-space()=:label]', array(':label' => $label)); + $message = ($message ? $message : SafeMarkup::format('Shortcut quick link with label %label found.', array('%label' => $label))); + return $this->assert(isset($links[$index]), $message, $group); + } + } diff --git a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php index 316c53f24..865828641 100644 --- a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php +++ b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php @@ -34,8 +34,8 @@ class MigrateShortcutSetTest extends MigrateDrupal7TestBase { $this->installEntitySchema('menu_link_content'); \Drupal::service('router.builder')->rebuild(); $this->executeMigration('d7_shortcut_set'); - $this->executeMigration('menu'); - $this->executeMigration('menu_links'); + $this->executeMigration('d7_menu'); + $this->executeMigration('d7_menu_links'); $this->executeMigration('d7_shortcut'); } diff --git a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php index 6a9ebe346..1b15dcdef 100644 --- a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php +++ b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php @@ -36,8 +36,8 @@ class MigrateShortcutSetUsersTest extends MigrateDrupal7TestBase { $this->executeMigration('d7_user_role'); $this->executeMigration('d7_user'); $this->executeMigration('d7_shortcut_set'); - $this->executeMigration('menu'); - $this->executeMigration('menu_links'); + $this->executeMigration('d7_menu'); + $this->executeMigration('d7_menu_links'); $this->executeMigration('d7_shortcut'); $this->executeMigration('d7_shortcut_set_users'); } diff --git a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php index 791deb219..18f55170a 100644 --- a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php +++ b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php @@ -34,8 +34,8 @@ class MigrateShortcutTest extends MigrateDrupal7TestBase { $this->installEntitySchema('menu_link_content'); \Drupal::service('router.builder')->rebuild(); $this->executeMigration('d7_shortcut_set'); - $this->executeMigration('menu'); - $this->executeMigration('menu_links'); + $this->executeMigration('d7_menu'); + $this->executeMigration('d7_menu_links'); $this->executeMigration('d7_shortcut'); } diff --git a/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetTest.php b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetTest.php new file mode 100644 index 000000000..e1982de64 --- /dev/null +++ b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetTest.php @@ -0,0 +1,41 @@ + 'shortcut-set-2', + 'title' => 'Alternative shortcut set', + ], + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['shortcut_set']; + + return $tests; + } + +} diff --git a/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetUsersTest.php b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetUsersTest.php new file mode 100644 index 000000000..035aec31c --- /dev/null +++ b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetUsersTest.php @@ -0,0 +1,41 @@ + '2', + 'set_name' => 'shortcut-set-2', + ], + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['shortcut_set_users']; + + return $tests; + } + +} diff --git a/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutTest.php b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutTest.php new file mode 100644 index 000000000..9f8fd3ae6 --- /dev/null +++ b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutTest.php @@ -0,0 +1,72 @@ + 'shortcut-set-2', + 'mlid' => '473', + 'plid' => '0', + 'link_path' => 'admin/people', + 'router_path' => 'admin/people', + 'link_title' => 'People', + 'options' => 'a:0:{}', + 'module' => 'menu', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '-50', + 'depth' => '1', + 'customized' => '0', + 'p1' => '473', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + ] + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'mlid' => '473', + 'menu_name' => 'shortcut-set-2', + 'link_path' => 'admin/people', + 'link_title' => 'People', + 'weight' => '-50', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetTest.php b/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetTest.php deleted file mode 100644 index f2f221f09..000000000 --- a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetTest.php +++ /dev/null @@ -1,38 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_shortcut_set', - ], - ]; - - protected $expectedResults = [ - [ - 'set_name' => 'shortcut-set-2', - 'title' => 'Alternative shortcut set', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['shortcut_set'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetUsersTest.php b/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetUsersTest.php deleted file mode 100644 index 5681f6dfc..000000000 --- a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetUsersTest.php +++ /dev/null @@ -1,38 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_shortcut_set_users', - ], - ]; - - protected $expectedResults = [ - [ - 'uid' => '2', - 'set_name' => 'shortcut-set-2', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['shortcut_set_users'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutTest.php b/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutTest.php deleted file mode 100644 index 9fde228bc..000000000 --- a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutTest.php +++ /dev/null @@ -1,68 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_shortcut', - ], - ]; - - protected $expectedResults = [ - [ - 'mlid' => '473', - 'menu_name' => 'shortcut-set-2', - 'link_path' => 'admin/people', - 'link_title' => 'People', - 'weight' => '-50', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['menu_links'][] = [ - 'menu_name' => 'shortcut-set-2', - 'mlid' => '473', - 'plid' => '0', - 'link_path' => 'admin/people', - 'router_path' => 'admin/people', - 'link_title' => 'People', - 'options' => 'a:0:{}', - 'module' => 'menu', - 'hidden' => '0', - 'external' => '0', - 'has_children' => '0', - 'expanded' => '0', - 'weight' => '-50', - 'depth' => '1', - 'customized' => '0', - 'p1' => '473', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - ]; - - parent::setUp(); - } - -} diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 0752e4251..f72ca5c6a 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -13,6 +13,7 @@ use Drupal\simpletest\TestBase; use Drupal\Core\Test\TestDatabase; use Drupal\simpletest\TestDiscovery; use Symfony\Component\Process\PhpExecutableFinder; +use Drupal\Core\Test\TestStatus; /** * Implements hook_help(). @@ -183,16 +184,15 @@ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames $phpunit_file = simpletest_phpunit_xml_filepath($test_id); simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file, $status, $output); - $rows = simpletest_phpunit_xml_to_rows($test_id, $phpunit_file); - // A $status of 0 = passed test, 1 = failed test, > 1 indicates segfault - // timeout, or other type of failure. - if ($status > 1) { - // Something broke during the execution of phpunit. - // Return an error record of all failed classes. + $rows = []; + if ($status == TestStatus::PASS) { + $rows = simpletest_phpunit_xml_to_rows($test_id, $phpunit_file); + } + else { $rows[] = [ 'test_id' => $test_id, 'test_class' => implode(",", $unescaped_test_classnames), - 'status' => 'fail', + 'status' => TestStatus::label($status), 'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output), 'message_group' => 'Other', 'function' => implode(",", $unescaped_test_classnames), @@ -200,20 +200,6 @@ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames 'file' => $phpunit_file, ]; } - - if ($status === 1) { - $rows[] = [ - 'test_id' => $test_id, - 'test_class' => implode(",", $unescaped_test_classnames), - 'status' => 'fail', - 'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output), - 'message_group' => 'Other', - 'function' => implode(",", $unescaped_test_classnames), - 'line' => '0', - 'file' => $phpunit_file, - ]; - } - return $rows; } diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index fe25f9510..f4a23e7c1 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -1532,7 +1532,11 @@ abstract class TestBase { * need to get deleted too. */ public static function filePreDeleteCallback($path) { - chmod($path, 0700); + // When the webserver runs with the same system user as the test runner, we + // can make read-only files writable again. If not, chmod will fail while + // the file deletion still works if file permissions have been configured + // correctly. Thus, we ignore any problems while running chmod. + @chmod($path, 0700); } /** diff --git a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php index c4528a53a..3b2061ec4 100644 --- a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php +++ b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php @@ -4,6 +4,7 @@ namespace Drupal\simpletest\Tests; use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; +use Drupal\Tests\simpletest\Functional\ThroughUITest; /** * Tests the Simpletest UI internal browser. @@ -131,7 +132,7 @@ class SimpleTestBrowserTest extends WebTestBase { // A PHPUnit unit test. 'Drupal\Tests\action\Unit\Menu\ActionLocalTasksTest', // A PHPUnit functional test. - 'Drupal\FunctionalTests\BrowserTestBaseTest', + ThroughUITest::class, ); foreach ($tests as $test) { diff --git a/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php b/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php index 6fb61d523..a4ffc7ae4 100644 --- a/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php +++ b/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php @@ -2,22 +2,27 @@ namespace Drupal\Tests\simpletest\Unit; -use Drupal\Tests\UnitTestCase; - /** * This test crashes PHP. * * To avoid accidentally running, it is not in a normal PSR-4 directory, the * file name does not adhere to PSR-4 and an environment variable also needs to * be set for the crash to happen. + * + * @see \Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTest::testSimpletestPhpUnitRunCommand() */ -class SimpletestPhpunitRunCommandTestWillDie extends UnitTestCase { +class SimpletestPhpunitRunCommandTestWillDie extends \PHPUnit_Framework_TestCase { + /** + * Performs the status specified by SimpletestPhpunitRunCommandTestWillDie. + */ public function testWillDie() { - if (getenv('SimpletestPhpunitRunCommandTestWillDie') === 'fail') { - exit(2); + $status = (int) getenv('SimpletestPhpunitRunCommandTestWillDie'); + if ($status == 0) { + $this->assertTrue(TRUE, 'Assertion to ensure test pass'); + return; } - $this->assertTrue(TRUE, 'Assertion to ensure test pass'); + exit($status); } } diff --git a/core/modules/simpletest/tests/src/Functional/ThroughUITest.php b/core/modules/simpletest/tests/src/Functional/ThroughUITest.php new file mode 100644 index 000000000..48737433f --- /dev/null +++ b/core/modules/simpletest/tests/src/Functional/ThroughUITest.php @@ -0,0 +1,23 @@ +pass('Success!'); + } + +} diff --git a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php index 2afea1c7f..a96db963c 100644 --- a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php +++ b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php @@ -3,7 +3,7 @@ namespace Drupal\Tests\simpletest\Unit; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Tests\UnitTestCase; +use Drupal\Core\File\FileSystemInterface; /** * Tests simpletest_run_phpunit_tests() handles PHPunit fatals correctly. @@ -11,27 +11,92 @@ use Drupal\Tests\UnitTestCase; * @group simpletest * * @runTestsInSeparateProcesses - * @preserveGlobalState disabled */ -class SimpletestPhpunitRunCommandTest extends UnitTestCase { +class SimpletestPhpunitRunCommandTest extends \PHPUnit_Framework_TestCase { - function testSimpletestPhpUnitRunCommand() { - include_once __DIR__ . '/../../fixtures/simpletest_phpunit_run_command_test.php'; - $app_root = __DIR__ . '/../../../../../..'; - include_once "$app_root/core/modules/simpletest/simpletest.module"; + /** + * Path to the app root. + * + * @var string + */ + protected static $root; + + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + // Figure out our app root. + self::$root = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + // Include the files we need for tests. The stub test we will run is + // SimpletestPhpunitRunCommandTestWillDie which is located in + // simpletest_phpunit_run_command_test.php. + include_once self::$root . '/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php'; + // Since we're testing simpletest_run_phpunit_tests(), we need to include + // simpletest.module. + include_once self::$root . '/core/modules/simpletest/simpletest.module'; + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Organize our mock container. $container = new ContainerBuilder(); - $container->set('app.root', $app_root); - $file_system = $this->prophesize('Drupal\Core\File\FileSystemInterface'); + $container->set('app.root', self::$root); + $file_system = $this->prophesize(FileSystemInterface::class); + // The simpletest directory wrapper will always point to /tmp. $file_system->realpath('public://simpletest')->willReturn(sys_get_temp_dir()); $container->set('file_system', $file_system->reveal()); \Drupal::setContainer($container); - $test_id = basename(tempnam(sys_get_temp_dir(), 'xxx')); - foreach (['pass', 'fail'] as $status) { - putenv('SimpletestPhpunitRunCommandTestWillDie=' . $status); - $ret = simpletest_run_phpunit_tests($test_id, ['Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTestWillDie']); - $this->assertSame($ret[0]['status'], $status); + } + + /** + * Data provider for testSimpletestPhpUnitRunCommand(). + * + * @return array + * Arrays of status codes and the label they're expected to have. + */ + public function provideStatusCodes() { + $data = [ + [0, 'pass'], + [1, 'fail'], + [2, 'exception'], + ]; + // All status codes 3 and above should be labeled 'error'. + // @todo: The valid values here would be 3 to 127. But since the test + // touches the file system a lot, we only have 3, 4, and 127 for speed. + foreach ([3, 4, 127] as $status) { + $data[] = [$status, 'error']; } + return $data; + } + + /** + * Test the round trip for PHPUnit execution status codes. + * + * @covers ::simpletest_run_phpunit_tests + * + * @dataProvider provideStatusCodes + */ + public function testSimpletestPhpUnitRunCommand($status, $label) { + $test_id = basename(tempnam(sys_get_temp_dir(), 'xxx')); + putenv('SimpletestPhpunitRunCommandTestWillDie=' . $status); + $ret = simpletest_run_phpunit_tests($test_id, [SimpletestPhpunitRunCommandTestWillDie::class]); + $this->assertSame($ret[0]['status'], $label); + putenv('SimpletestPhpunitRunCommandTestWillDie'); unlink(simpletest_phpunit_xml_filepath($test_id)); } + /** + * {@inheritdoc} + */ + protected function tearDown() { + // We unset the $base_url global, since the test code sets it as a + // side-effect. + unset($GLOBALS['base_url']); + parent::tearDown(); + } + } diff --git a/core/modules/statistics/migration_templates/d6_statistics_settings.yml b/core/modules/statistics/migration_templates/statistics_settings.yml similarity index 92% rename from core/modules/statistics/migration_templates/d6_statistics_settings.yml rename to core/modules/statistics/migration_templates/statistics_settings.yml index 348ad38e4..62c2c0612 100644 --- a/core/modules/statistics/migration_templates/d6_statistics_settings.yml +++ b/core/modules/statistics/migration_templates/statistics_settings.yml @@ -1,7 +1,8 @@ -id: d6_statistics_settings +id: statistics_settings label: Statistics configuration migration_tags: - Drupal 6 + - Drupal 7 source: plugin: variable variables: diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php index aa9760a52..401a5e82b 100644 --- a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php +++ b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php @@ -24,7 +24,7 @@ class MigrateStatisticsConfigsTest extends MigrateDrupal6TestBase { */ protected function setUp() { parent::setUp(); - $this->executeMigration('d6_statistics_settings'); + $this->executeMigration('statistics_settings'); } /** diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php new file mode 100644 index 000000000..e5d3310ad --- /dev/null +++ b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php @@ -0,0 +1,41 @@ +executeMigration('statistics_settings'); + } + + /** + * Tests migration of statistics variables to statistics.settings.yml. + */ + public function testStatisticsSettings() { + $config = $this->config('statistics.settings'); + $this->assertIdentical(TRUE, $config->get('access_log.enabled')); + $this->assertIdentical(3600, $config->get('access_log.max_lifetime')); + $this->assertIdentical(1, $config->get('count_content_views')); + $this->assertConfigSchema(\Drupal::service('config.typed'), 'statistics.settings', $config->get()); + } + +} diff --git a/core/modules/system/migration_templates/menu.yml b/core/modules/system/migration_templates/d6_menu.yml similarity index 91% rename from core/modules/system/migration_templates/menu.yml rename to core/modules/system/migration_templates/d6_menu.yml index 974a2d68a..25ad0af9b 100644 --- a/core/modules/system/migration_templates/menu.yml +++ b/core/modules/system/migration_templates/d6_menu.yml @@ -1,9 +1,8 @@ # The menu_settings migration is in the menu_ui module. -id: menu +id: d6_menu label: Menus migration_tags: - Drupal 6 - - Drupal 7 source: plugin: menu process: diff --git a/core/modules/system/migration_templates/d7_menu.yml b/core/modules/system/migration_templates/d7_menu.yml new file mode 100644 index 000000000..da47b3656 --- /dev/null +++ b/core/modules/system/migration_templates/d7_menu.yml @@ -0,0 +1,20 @@ +id: d7_menu +label: Menus +migration_tags: + - Drupal 7 +source: + plugin: menu +process: + id: + plugin: static_map + bypass: true + source: menu_name + map: + main-menu: main + management: admin + navigation: tools + user-menu: account + label: title + description: description +destination: + plugin: entity:menu diff --git a/core/modules/system/src/Tests/Ajax/DialogTest.php b/core/modules/system/src/Tests/Ajax/DialogTest.php index 80b465067..254f01d33 100644 --- a/core/modules/system/src/Tests/Ajax/DialogTest.php +++ b/core/modules/system/src/Tests/Ajax/DialogTest.php @@ -39,7 +39,7 @@ class DialogTest extends AjaxTestBase { 'data' => $dialog_contents, 'dialogOptions' => array( 'modal' => TRUE, - 'title' => 'AJAX Dialog contents', + 'title' => 'AJAX Dialog & contents', ), ); $form_expected_response = array( @@ -67,7 +67,7 @@ class DialogTest extends AjaxTestBase { 'data' => $dialog_contents, 'dialogOptions' => array( 'modal' => FALSE, - 'title' => 'AJAX Dialog contents', + 'title' => 'AJAX Dialog & contents', ), ); $no_target_expected_response = array( @@ -77,7 +77,7 @@ class DialogTest extends AjaxTestBase { 'data' => $dialog_contents, 'dialogOptions' => array( 'modal' => FALSE, - 'title' => 'AJAX Dialog contents', + 'title' => 'AJAX Dialog & contents', ), ); $close_expected_response = array( @@ -97,6 +97,9 @@ class DialogTest extends AjaxTestBase { // Emulate going to the JS version of the page and check the JSON response. $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal'))); $this->assertEqual($modal_expected_response, $ajax_result[3], 'Modal dialog JSON response matches.'); + // Test the HTML escaping of & character. + $this->assertEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog & contents'); + $this->assertNotEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog & contents'); // Check that requesting a "normal" dialog without JS goes to a page. $this->drupalGet('ajax-test/dialog-contents'); @@ -152,6 +155,8 @@ class DialogTest extends AjaxTestBase { // Check that the response matches the expected value. $this->assertEqual($modal_expected_response, $ajax_result[4], 'POST request modal dialog JSON response matches.'); + // Test the HTML escaping of & character. + $this->assertNotEqual($ajax_result[4]['dialogOptions']['title'], 'AJAX Dialog & contents'); // Abbreviated test for "normal" dialogs, testing only the difference. $ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(), 'button2'); diff --git a/core/modules/system/src/Tests/Common/FormatDateTest.php b/core/modules/system/src/Tests/Common/FormatDateTest.php index 36589ef46..cddf0a3fa 100644 --- a/core/modules/system/src/Tests/Common/FormatDateTest.php +++ b/core/modules/system/src/Tests/Common/FormatDateTest.php @@ -77,46 +77,4 @@ class FormatDateTest extends WebTestBase { $this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'fallback'), 'Test format_date() defaulting to `fallback` when $type not found.'); } - /** - * Tests the format_date() function. - */ - function testFormatDate() { - $timestamp = strtotime('2007-03-26T00:00:00+00:00'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.'); - $this->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.'); - $this->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.'); - $this->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.'); - - // Change the default language and timezone. - $this->config('system.site')->set('default_langcode', static::LANGCODE)->save(); - date_default_timezone_set('America/Los_Angeles'); - - // Reset the language manager so new negotiations attempts will fall back on - // on the new language. - $this->container->get('language_manager')->reset(); - - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test custom date format.'); - $this->assertIdentical(format_date($timestamp, 'long'), 'domingo, 25. marzo 2007 - 17:00', 'Test long date format.'); - $this->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', 'Test medium date format.'); - $this->assertIdentical(format_date($timestamp, 'short'), '2007 Mar 25 - 5:00pm', 'Test short date format.'); - $this->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', 'Test default date format.'); - // Test HTML time element formats. - $this->assertIdentical(format_date($timestamp, 'html_datetime'), '2007-03-25T17:00:00-0700', 'Test html_datetime date format.'); - $this->assertIdentical(format_date($timestamp, 'html_date'), '2007-03-25', 'Test html_date date format.'); - $this->assertIdentical(format_date($timestamp, 'html_time'), '17:00:00', 'Test html_time date format.'); - $this->assertIdentical(format_date($timestamp, 'html_yearless_date'), '03-25', 'Test html_yearless_date date format.'); - $this->assertIdentical(format_date($timestamp, 'html_week'), '2007-W12', 'Test html_week date format.'); - $this->assertIdentical(format_date($timestamp, 'html_month'), '2007-03', 'Test html_month date format.'); - $this->assertIdentical(format_date($timestamp, 'html_year'), '2007', 'Test html_year date format.'); - - // HTML is not escaped by the date formatter, it must be escaped later. - $formatter = \Drupal::service('date.formatter'); - $this->assertIdentical($formatter->format($timestamp, 'custom', '\<\s\c\r\i\p\t\>\a\l\e\r\t\(\'Y\'\)\;\<\/\s\c\r\i\p\t\>'), "", 'Script tags not removed from dates.'); - $this->assertIdentical($formatter->format($timestamp, 'custom', '\<\e\m\>Y\<\/\e\m\>'), '2007', 'Em tags are not removed from dates.'); - } - } diff --git a/core/modules/system/src/Tests/Render/AjaxPageStateTest.php b/core/modules/system/src/Tests/Render/AjaxPageStateTest.php index 08cd2768e..19e623775 100644 --- a/core/modules/system/src/Tests/Render/AjaxPageStateTest.php +++ b/core/modules/system/src/Tests/Render/AjaxPageStateTest.php @@ -11,6 +11,13 @@ use Drupal\simpletest\WebTestBase; */ class AjaxPageStateTest extends WebTestBase { + /** + * Modules to install. + * + * @var array + */ + public static $modules = ['node', 'views']; + /** * User account with all available permissions * @@ -77,22 +84,16 @@ class AjaxPageStateTest extends WebTestBase { } /** - * Test if multiple libaries can be excluded. + * Test if multiple libraries can be excluded. * - * ajax_page_state[libraries] should be able to support multiple libraries + * The ajax_page_state[libraries] should be able to support multiple libraries * comma separated. */ public function testMultipleLibrariesAreNotLoaded() { $this->drupalGet('node', - array( - "query" => - array( - 'ajax_page_state' => array( - 'libraries' => 'core/html5shiv,core/drupalSettings' - ) - ) - ) + ['query' => ['ajax_page_state' => ['libraries' => 'core/html5shiv,core/drupalSettings']]] ); + $this->assertResponse(200); $this->assertNoRaw( '/core/assets/vendor/html5shiv/html5shiv.min.js', 'The html5shiv library from core should be excluded from loading.' @@ -102,6 +103,17 @@ class AjaxPageStateTest extends WebTestBase { '/core/misc/drupalSettingsLoader.js', 'The drupalSettings library from core should be excluded from loading.' ); + + $this->drupalGet('node'); + $this->assertRaw( + '/core/assets/vendor/html5shiv/html5shiv.min.js', + 'The html5shiv library from core should be included in loading.' + ); + + $this->assertRaw( + '/core/misc/drupalSettingsLoader.js', + 'The drupalSettings library from core should be included in loading.' + ); } } diff --git a/core/modules/system/src/Tests/Theme/EngineTwigTest.php b/core/modules/system/src/Tests/Theme/EngineTwigTest.php index ae08fc835..51d092d44 100644 --- a/core/modules/system/src/Tests/Theme/EngineTwigTest.php +++ b/core/modules/system/src/Tests/Theme/EngineTwigTest.php @@ -2,6 +2,7 @@ namespace Drupal\system\Tests\Theme; +use Drupal\Core\Render\Markup; use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; @@ -75,12 +76,16 @@ class EngineTwigTest extends WebTestBase { /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */ $link_generator = $this->container->get('link_generator'); + + $generated_url = Url::fromRoute('user.register', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl(); $expected = [ 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE, 'attributes' => ['foo' => 'bar']])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['foo' => 'bar', 'id' => 'kitten']])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['id' => 'kitten']])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['class' => ['llama', 'kitten', 'panda']]])), + 'link via the linkgenerator: ' . $link_generator->generate(Markup::create('register'), new Url('user.register', [], ['absolute' => TRUE])), + 'link via the linkgenerator: register', ]; // Verify that link() has the ability to bubble cacheability metadata: diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 3ef24a5a1..cbdafbc9f 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -276,7 +276,7 @@ function system_requirements($phase) { if (empty($drivers)) { $database_ok = FALSE; $pdo_message = t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that Drupal supports.', array( - ':drupal-databases' => 'https://www.drupal.org/node/270#database', + ':drupal-databases' => 'https://www.drupal.org/requirements/database', )); } // Make sure the native PDO extension is available, not the older PEAR diff --git a/core/modules/system/system.libraries.yml b/core/modules/system/system.libraries.yml index dd9d2ec43..206ae4fc8 100644 --- a/core/modules/system/system.libraries.yml +++ b/core/modules/system/system.libraries.yml @@ -59,6 +59,7 @@ drupal.system.modules: - core/drupal - core/drupal.debounce - core/jquery.once + - core/drupal.announce diff: version: VERSION diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 2f627bef5..01c8c5e1e 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -623,7 +623,7 @@ function system_page_attachments(array &$page) { // Handle setting the "active" class on links by: // - loading the active-link library if the current user is authenticated; // - applying a response filter if the current user is anonymous. - // @see l() + // @see \Drupal\Core\Link // @see \Drupal\Core\Utility\LinkGenerator::generate() // @see template_preprocess_links() // @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter @@ -655,15 +655,6 @@ function system_js_settings_build(&$settings, AttachedAssetsInterface $assets) { // @see \Drupal\Core\Theme\AjaxBasePageNegotiator $theme_key = \Drupal::theme()->getActiveTheme()->getName(); $settings['ajaxPageState']['theme'] = $theme_key; - - // Provide the page with information about the individual asset libraries - // used, information not otherwise available when aggregation is enabled. - $minimal_libraries = $library_dependency_resolver->getMinimalRepresentativeSubset(array_merge( - $assets->getLibraries(), - $assets->getAlreadyLoadedLibraries() - )); - sort($minimal_libraries); - $settings['ajaxPageState']['libraries'] = implode(',', $minimal_libraries); } } @@ -715,7 +706,7 @@ function system_js_settings_alter(&$settings, AttachedAssetsInterface $assets) { $settings['pluralDelimiter'] = LOCALE_PLURAL_DELIMITER; } // Add the theme token to ajaxPageState, ensuring the database is available - // before doing so. + // before doing so. Also add the loaded libraries to ajaxPageState. /** @var \Drupal\Core\Asset\LibraryDependencyResolver $library_dependency_resolver */ $library_dependency_resolver = \Drupal::service('library.dependency_resolver'); if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()))) { @@ -729,6 +720,14 @@ function system_js_settings_alter(&$settings, AttachedAssetsInterface $assets) { ->get($active_theme_key); } } + // Provide the page with information about the individual asset libraries + // used, information not otherwise available when aggregation is enabled. + $minimal_libraries = $library_dependency_resolver->getMinimalRepresentativeSubset(array_merge( + $assets->getLibraries(), + $assets->getAlreadyLoadedLibraries() + )); + sort($minimal_libraries); + $settings['ajaxPageState']['libraries'] = implode(',', $minimal_libraries); } } diff --git a/core/modules/system/templates/html.html.twig b/core/modules/system/templates/html.html.twig index 195d82e77..107c56b0a 100644 --- a/core/modules/system/templates/html.html.twig +++ b/core/modules/system/templates/html.html.twig @@ -8,7 +8,7 @@ * - root_path: The root path of the current page (e.g., node, admin, user). * - node_type: The content type for the current node, if the page is a node. * - head_title: List of text elements that make up the head_title variable. - * May contain or more of the following: + * May contain one or more of the following: * - title: The title of the page. * - name: The name of the site. * - slogan: The slogan of the site. diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php index b618f0af7..63cc0abf1 100644 --- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php +++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php @@ -23,7 +23,7 @@ class AjaxTestController { public static function dialogContents() { // This is a regular render array; the keys do not have special meaning. $content = array( - '#title' => 'AJAX Dialog contents', + '#title' => 'AJAX Dialog & contents', 'content' => array( '#markup' => 'Example message', ), diff --git a/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php index 304e2bbaf..a97ed9525 100644 --- a/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php +++ b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php @@ -93,7 +93,7 @@ class AjaxTestDialogForm extends FormBase { protected function dialog($is_modal = FALSE) { $content = AjaxTestController::dialogContents(); $response = new AjaxResponse(); - $title = $this->t('AJAX Dialog contents'); + $title = $this->t('AJAX Dialog & contents'); // Attach the library necessary for using the Open(Modal)DialogCommand and // set the attachments for this Ajax response. diff --git a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php new file mode 100644 index 000000000..ef00053fe --- /dev/null +++ b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php @@ -0,0 +1,34 @@ +t('Condition that requires a non-existent context.'); + } + +} diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 453bdf114..b0c8cbf4d 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -674,14 +674,25 @@ function _entity_test_record_hooks($hook, $data) { * Implements hook_entity_prepare_view(). */ function entity_test_entity_prepare_view($entity_type, array $entities, array $displays) { - // Add a dummy field item attribute on field_test_text if it exists. if ($entity_type == 'entity_test') { foreach ($entities as $entity) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + + // Add a dummy field item attribute on field_test_text if it exists. if ($entity->hasField('field_test_text') && $displays[$entity->bundle()]->getComponent('field_test_text')) { foreach ($entity->get('field_test_text') as $item) { $item->_attributes += array('data-field-item-attr' => 'foobar'); } } + + // Add a dummy field item attribute on daterange fields if they exist. + $fields = $entity->getFieldDefinitions(); + foreach ($fields as $field) { + if ($field->getType() === 'daterange') { + $item = $entity->get($field->getName()); + $item->_attributes += array('data-field-item-attr' => 'foobar'); + } + } } } } diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml index 790b9ce21..2250a0bb2 100644 --- a/core/modules/system/tests/modules/form_test/form_test.routing.yml +++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml @@ -358,6 +358,14 @@ form_test.label: requirements: _access: 'TRUE' +form_test.machine_name: + path: '/form-test/machine-name' + defaults: + _form: '\Drupal\form_test\Form\FormTestMachineNameForm' + _title: 'Machine name fields' + requirements: + _access: 'TRUE' + form_test.state_persistence: path: '/form-test/state-persist' defaults: diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameForm.php new file mode 100644 index 000000000..542edc8a7 --- /dev/null +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameForm.php @@ -0,0 +1,63 @@ + 'textfield', + '#title' => 'Machine name 1 label', + ]; + $form['machine_name_1'] = [ + '#type' => 'machine_name', + '#title' => 'Machine name 1', + '#description' => 'A machine name.', + '#machine_name' => [ + 'source' => ['machine_name_1_label'] + ], + ]; + $form['machine_name_2_label'] = [ + '#type' => 'textfield', + '#title' => 'Machine name 2 label', + ]; + $form['machine_name_2'] = [ + '#type' => 'machine_name', + '#title' => 'Machine name 2', + '#description' => 'Another machine name.', + '#machine_name' => [ + 'source' => ['machine_name_2_label'] + ], + ]; + $form['submit'] = [ + '#type' => 'submit', + '#value' => 'Submit', + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $form_state->setResponse(new JsonResponse($form_state->getValues())); + } + +} diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig index 8925705be..a836aec93 100644 --- a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig +++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig @@ -3,3 +3,7 @@
link via the linkgenerator: {{ link('register', test_url_attribute, {'id': 'kitten'}) }}
link via the linkgenerator: {{ link('register', 'route:user.register', {'id': 'kitten'}) }}
link via the linkgenerator: {{ link('register', 'route:user.register', attributes) }}
+{% set title %}register{% endset %} +
link via the linkgenerator: {{ link(title, test_url) }}
+{% set title %}register{% endset %} +
link via the linkgenerator: {{ link(title, test_url) }}
diff --git a/core/modules/system/tests/src/Kernel/Form/FormElementLabelTest.php b/core/modules/system/tests/src/Kernel/Form/FormElementLabelTest.php new file mode 100644 index 000000000..f93d461bc --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Form/FormElementLabelTest.php @@ -0,0 +1,36 @@ + 'label', + '#attributes' => ['class' => ['kitten']], + '#title' => 'Kittens', + '#title_display' => 'above', + ]; + $css_selector_converter = new CssSelectorConverter(); + $this->render($render_array); + $elements = $this->xpath($css_selector_converter->toXPath('.kitten')); + $this->assertCount(1, $elements); + } + +} diff --git a/core/modules/system/tests/src/Kernel/Installer/InstallerMissingDependenciesTest.php b/core/modules/system/tests/src/Kernel/Installer/InstallerMissingDependenciesTest.php new file mode 100644 index 000000000..052c0afb8 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Installer/InstallerMissingDependenciesTest.php @@ -0,0 +1,39 @@ + ['profile' => 'testing_missing_dependencies'], + 'profile_info' => install_profile_info('testing_missing_dependencies'), + ]); + + $message = $info['required_modules']['description']->render(); + $this->assertContains('Missing_module1', $message); + $this->assertContains('Missing_module2', $message); + } + +} diff --git a/core/modules/system/tests/src/Kernel/Migrate/MigrateMenuTest.php b/core/modules/system/tests/src/Kernel/Migrate/d6/MigrateMenuTest.php similarity index 75% rename from core/modules/system/tests/src/Kernel/Migrate/MigrateMenuTest.php rename to core/modules/system/tests/src/Kernel/Migrate/d6/MigrateMenuTest.php index c66ca02c7..e6ac30c88 100644 --- a/core/modules/system/tests/src/Kernel/Migrate/MigrateMenuTest.php +++ b/core/modules/system/tests/src/Kernel/Migrate/d6/MigrateMenuTest.php @@ -1,6 +1,6 @@ executeMigration('menu'); + $this->executeMigration('d6_menu'); } /** @@ -26,12 +26,12 @@ class MigrateMenuTest extends MigrateDrupal6TestBase { */ public function testMenu() { $navigation_menu = Menu::load('navigation'); - $this->assertIdentical('navigation', $navigation_menu->id()); - $this->assertIdentical('Navigation', $navigation_menu->label()); + $this->assertSame('navigation', $navigation_menu->id()); + $this->assertSame('Navigation', $navigation_menu->label()); $expected = <<assertIdentical($expected, $navigation_menu->getDescription()); + $this->assertSame($expected, $navigation_menu->getDescription()); // Test that we can re-import using the ConfigEntityBase destination. Database::getConnection('default', 'migrate') @@ -40,14 +40,14 @@ EOT; ->condition('menu_name', 'navigation') ->execute(); - $migration = $this->getMigration('menu'); + $migration = $this->getMigration('d6_menu'); \Drupal::database() ->truncate($migration->getIdMap()->mapTableName()) ->execute(); $this->executeMigration($migration); $navigation_menu = Menu::load('navigation'); - $this->assertIdentical('Home Navigation', $navigation_menu->label()); + $this->assertSame('Home Navigation', $navigation_menu->label()); } } diff --git a/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateMenuTest.php b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateMenuTest.php new file mode 100644 index 000000000..987084539 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Migrate/d7/MigrateMenuTest.php @@ -0,0 +1,68 @@ +executeMigration('d7_menu'); + } + + /** + * Asserts various aspects of a menu. + * + * @param $id + * The menu ID. + * @param $label + * The menu label. + * @param $description + * The menu description. + */ + protected function assertEntity($id, $label, $description) { + $navigation_menu = Menu::load($id); + $this->assertSame($id, $navigation_menu->id()); + $this->assertSame($label, $navigation_menu->label()); + $this->assertSame($description, $navigation_menu->getDescription()); + } + + /** + * Tests the Drupal 7 menu to Drupal 8 migration. + */ + public function testMenu() { + $this->assertEntity('main', 'Main menu', 'The Main menu is used on many sites to show the major sections of the site, often in a top navigation bar.'); + $this->assertEntity('admin', 'Management', 'The Management menu contains links for administrative tasks.'); + $this->assertEntity('menu-test-menu', 'Test Menu', 'Test menu description.'); + $this->assertEntity('tools', 'Navigation', 'The Navigation menu contains links intended for site visitors. Links are added to the Navigation menu automatically by some modules.'); + $this->assertEntity('account', 'User menu', 'The User menu contains links related to the user\'s account, as well as the \'Log out\' link.'); + + // Test that we can re-import using the ConfigEntityBase destination. + Database::getConnection('default', 'migrate') + ->update('menu_custom') + ->fields(array('title' => 'Home Navigation')) + ->condition('menu_name', 'navigation') + ->execute(); + + $migration = $this->getMigration('d7_menu'); + \Drupal::database() + ->truncate($migration->getIdMap()->mapTableName()) + ->execute(); + $this->executeMigration($migration); + + $navigation_menu = Menu::load('tools'); + $this->assertSame('Home Navigation', $navigation_menu->label()); + } + +} diff --git a/core/modules/system/tests/src/Kernel/Plugin/migrate/source/MenuTest.php b/core/modules/system/tests/src/Kernel/Plugin/migrate/source/MenuTest.php new file mode 100644 index 000000000..0feb9a2b0 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Plugin/migrate/source/MenuTest.php @@ -0,0 +1,47 @@ + 'menu-name-1', + 'title' => 'menu custom value 1', + 'description' => 'menu custom description value 1', + ], + [ + 'menu_name' => 'menu-name-2', + 'title' => 'menu custom value 2', + 'description' => 'menu custom description value 2', + ], + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['menu_custom']; + + return $tests; + } + +} diff --git a/core/modules/system/tests/src/Unit/Plugin/migrate/source/MenuTest.php b/core/modules/system/tests/src/Unit/Plugin/migrate/source/MenuTest.php deleted file mode 100644 index ca38892d3..000000000 --- a/core/modules/system/tests/src/Unit/Plugin/migrate/source/MenuTest.php +++ /dev/null @@ -1,44 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'menu', - ), - ); - - protected $expectedResults = array( - array( - 'menu_name' => 'menu-name-1', - 'title' => 'menu custom value 1', - 'description' => 'menu custom description value 1', - ), - array( - 'menu_name' => 'menu-name-2', - 'title' => 'menu custom value 2', - 'description' => 'menu custom description value 2', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['menu_custom'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/taxonomy/src/Entity/Vocabulary.php b/core/modules/taxonomy/src/Entity/Vocabulary.php index fa6cc18c9..a2d7eef8a 100644 --- a/core/modules/taxonomy/src/Entity/Vocabulary.php +++ b/core/modules/taxonomy/src/Entity/Vocabulary.php @@ -73,13 +73,13 @@ class Vocabulary extends ConfigEntityBundleBase implements VocabularyInterface { * The type of hierarchy allowed within the vocabulary. * * Possible values: - * - TAXONOMY_HIERARCHY_DISABLED: No parents. - * - TAXONOMY_HIERARCHY_SINGLE: Single parent. - * - TAXONOMY_HIERARCHY_MULTIPLE: Multiple parents. + * - VocabularyInterface::HIERARCHY_DISABLED: No parents. + * - VocabularyInterface::HIERARCHY_SINGLE: Single parent. + * - VocabularyInterface::HIERARCHY_MULTIPL: Multiple parents. * * @var int */ - protected $hierarchy = TAXONOMY_HIERARCHY_DISABLED; + protected $hierarchy = VocabularyInterface::HIERARCHY_DISABLED; /** * The weight of this vocabulary in relation to other vocabularies. diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php index cdc661956..4c8cee3fb 100644 --- a/core/modules/taxonomy/src/Form/OverviewTerms.php +++ b/core/modules/taxonomy/src/Form/OverviewTerms.php @@ -223,7 +223,7 @@ class OverviewTerms extends FormBase { '#title' => $term->getName(), '#url' => $term->urlInfo(), ); - if ($taxonomy_vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) { + if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) { $parent_fields = TRUE; $form['terms'][$key]['term']['tid'] = array( '#type' => 'hidden', @@ -340,7 +340,7 @@ class OverviewTerms extends FormBase { 'group' => 'term-weight', ); - if ($taxonomy_vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) { + if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) { $form['actions'] = array('#type' => 'actions', '#tree' => FALSE); $form['actions']['submit'] = array( '#type' => 'submit', @@ -382,7 +382,7 @@ class OverviewTerms extends FormBase { $vocabulary = $form_state->get(['taxonomy', 'vocabulary']); // Update the current hierarchy type as we go. - $hierarchy = TAXONOMY_HIERARCHY_DISABLED; + $hierarchy = VocabularyInterface::HIERARCHY_DISABLED; $changed_terms = array(); $tree = $this->storageController->loadTree($vocabulary->id(), 0, NULL, TRUE); @@ -400,7 +400,7 @@ class OverviewTerms extends FormBase { $changed_terms[$term->id()] = $term; } $weight++; - $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy; + $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy; $term = $tree[$weight]; } @@ -427,7 +427,7 @@ class OverviewTerms extends FormBase { $term->parent->target_id = $values['term']['parent']; $changed_terms[$term->id()] = $term; } - $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy; + $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy; $weight++; } } @@ -440,7 +440,7 @@ class OverviewTerms extends FormBase { $term->setWeight($weight); $changed_terms[$term->id()] = $term; } - $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy; + $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy; } // Save all updated terms. diff --git a/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php b/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php index 3b1c2368d..7dd4aa2d6 100644 --- a/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php +++ b/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php @@ -49,6 +49,7 @@ class TermSelection extends DefaultSelection { */ public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { if ($match || $limit) { + $this->configuration['handler_settings']['sort'] = ['field' => 'name', 'direction' => 'asc']; return parent::getReferenceableEntities($match, $match_operator, $limit); } diff --git a/core/modules/taxonomy/src/TermForm.php b/core/modules/taxonomy/src/TermForm.php index cf05932e5..1f5635132 100644 --- a/core/modules/taxonomy/src/TermForm.php +++ b/core/modules/taxonomy/src/TermForm.php @@ -26,7 +26,7 @@ class TermForm extends ContentEntityForm { $form['relations'] = array( '#type' => 'details', '#title' => $this->t('Relations'), - '#open' => $vocabulary->getHierarchy() == TAXONOMY_HIERARCHY_MULTIPLE, + '#open' => $vocabulary->getHierarchy() == VocabularyInterface::HIERARCHY_MULTIPLE, '#weight' => 10, ); @@ -152,8 +152,8 @@ class TermForm extends ContentEntityForm { } // If we've increased the number of parents and this is a single or flat // hierarchy, update the vocabulary immediately. - elseif ($current_parent_count > $previous_parent_count && $vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE) { - $vocabulary->setHierarchy($current_parent_count == 1 ? TAXONOMY_HIERARCHY_SINGLE : TAXONOMY_HIERARCHY_MULTIPLE); + elseif ($current_parent_count > $previous_parent_count && $vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE) { + $vocabulary->setHierarchy($current_parent_count == 1 ? VocabularyInterface::HIERARCHY_SINGLE : VocabularyInterface::HIERARCHY_MULTIPLE); $vocabulary->save(); } diff --git a/core/modules/taxonomy/src/Tests/TermAutocompleteTest.php b/core/modules/taxonomy/src/Tests/TermAutocompleteTest.php new file mode 100644 index 000000000..7ff519df9 --- /dev/null +++ b/core/modules/taxonomy/src/Tests/TermAutocompleteTest.php @@ -0,0 +1,204 @@ +vocabulary = $this->createVocabulary(); + + // Create 11 terms, which have some sub-string in common, in a + // non-alphabetical order, so that we will have more than 10 matches later + // when we test the correct number of results is returned, and we can test + // the order of the results. The location of the sub-string to match varies + // also, since it should not be necessary to start with the sub-string to + // match it. Save term IDs to reuse later. + $termNames = [ + 'aaa 20 bbb', + 'aaa 70 bbb', + 'aaa 10 bbb', + 'aaa 12 bbb', + 'aaa 40 bbb', + 'aaa 11 bbb', + 'aaa 30 bbb', + 'aaa 50 bbb', + 'aaa 80', + 'aaa 90', + 'bbb 60 aaa', + ]; + foreach ($termNames as $termName) { + $term = $this->createTerm($this->vocabulary, ['name' => $termName]); + $this->termIds[$termName] = $term->id(); + } + + // Create a taxonomy_term_reference field on the article Content Type that + // uses a taxonomy_autocomplete widget. + $this->fieldName = Unicode::strtolower($this->randomMachineName()); + FieldStorageConfig::create([ + 'field_name' => $this->fieldName, + 'entity_type' => 'node', + 'type' => 'entity_reference', + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'settings' => [ + 'target_type' => 'taxonomy_term', + ], + ])->save(); + FieldConfig::create([ + 'field_name' => $this->fieldName, + 'bundle' => 'article', + 'entity_type' => 'node', + 'settings' => [ + 'handler' => 'default', + 'handler_settings' => [ + // Restrict selection of terms to a single vocabulary. + 'target_bundles' => [ + $this->vocabulary->id() => $this->vocabulary->id(), + ], + ], + ], + ])->save(); + EntityFormDisplay::load('node.article.default') + ->setComponent($this->fieldName, [ + 'type' => 'entity_reference_autocomplete', + ]) + ->save(); + EntityViewDisplay::load('node.article.default') + ->setComponent($this->fieldName, [ + 'type' => 'entity_reference_label', + ]) + ->save(); + + // Create a user and then login. + $this->adminUser = $this->drupalCreateUser(['create article content']); + $this->drupalLogin($this->adminUser); + + // Retrieve the autocomplete url. + $this->drupalGet('node/add/article'); + $result = $this->xpath('//input[@name="' . $this->fieldName . '[0][target_id]"]'); + $this->autocompleteUrl = $this->getAbsoluteUrl($result[0]['data-autocomplete-path']); + } + + /** + * Tests that the autocomplete method returns the good number of results. + * + * @see \Drupal\taxonomy\Controller\TermAutocompleteController::autocomplete() + */ + public function testAutocompleteCountResults() { + // Test that no matching term found. + $data = $this->drupalGetJSON( + $this->autocompleteUrl, + ['query' => ['q' => 'zzz']] + ); + $this->assertTrue(empty($data), 'Autocomplete returned no results'); + + // Test that only one matching term found, when only one matches. + $data = $this->drupalGetJSON( + $this->autocompleteUrl, + ['query' => ['q' => 'aaa 10']] + ); + $this->assertEqual(1, count($data), 'Autocomplete returned 1 result'); + + // Test the correct number of matches when multiple are partial matches. + $data = $this->drupalGetJSON( + $this->autocompleteUrl, + ['query' => ['q' => 'aaa 1']] + ); + $this->assertEqual(3, count($data), 'Autocomplete returned 3 results'); + + // Tests that only 10 results are returned, even if there are more than 10 + // matches. + $data = $this->drupalGetJSON( + $this->autocompleteUrl, + ['query' => ['q' => 'aaa']] + ); + $this->assertEqual(10, count($data), 'Autocomplete returned only 10 results (for over 10 matches)'); + } + + /** + * Tests that the autocomplete method returns properly ordered results. + * + * @see \Drupal\taxonomy\Controller\TermAutocompleteController::autocomplete() + */ + public function testAutocompleteOrderedResults() { + $expectedResults = [ + 'aaa 10 bbb', + 'aaa 11 bbb', + 'aaa 12 bbb', + 'aaa 20 bbb', + 'aaa 30 bbb', + 'aaa 40 bbb', + 'aaa 50 bbb', + 'aaa 70 bbb', + 'bbb 60 aaa', + ]; + // Build $expected to match the autocomplete results. + $expected = []; + foreach ($expectedResults as $termName) { + $expected[] = [ + 'value' => $termName . ' (' . $this->termIds[$termName] . ')', + 'label' => $termName + ]; + } + + $data = $this->drupalGetJSON( + $this->autocompleteUrl, + ['query' => ['q' => 'bbb']] + ); + + $this->assertIdentical($expected, $data); + } + +} diff --git a/core/modules/taxonomy/src/VocabularyInterface.php b/core/modules/taxonomy/src/VocabularyInterface.php index ff52bec82..cb54e2018 100644 --- a/core/modules/taxonomy/src/VocabularyInterface.php +++ b/core/modules/taxonomy/src/VocabularyInterface.php @@ -9,6 +9,21 @@ use Drupal\Core\Config\Entity\ConfigEntityInterface; */ interface VocabularyInterface extends ConfigEntityInterface { + /** + * Denotes that no term in the vocabulary has a parent. + */ + const HIERARCHY_DISABLED = 0; + + /** + * Denotes that one or more terms in the vocabulary has a single parent. + */ + const HIERARCHY_SINGLE = 1; + + /** + * Denotes that one or more terms in the vocabulary have multiple parents. + */ + const HIERARCHY_MULTIPLE = 2; + /** * Returns the vocabulary hierarchy. * @@ -23,9 +38,9 @@ interface VocabularyInterface extends ConfigEntityInterface { * @param int $hierarchy * The hierarchy type of vocabulary. * Possible values: - * - TAXONOMY_HIERARCHY_DISABLED: No parents. - * - TAXONOMY_HIERARCHY_SINGLE: Single parent. - * - TAXONOMY_HIERARCHY_MULTIPLE: Multiple parents. + * - VocabularyInterface::HIERARCHY_DISABLED: No parents. + * - VocabularyInterface::HIERARCHY_SINGLE: Single parent. + * - VocabularyInterface::HIERARCHY_MULTIPLE: Multiple parents. * * @return $this */ diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 32df678b0..7a1ca6521 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -19,16 +19,25 @@ use Drupal\taxonomy\VocabularyInterface; /** * Denotes that no term in the vocabulary has a parent. + * + * @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use + * \Drupal\taxonomy\VocabularyInterface::HIERARCHY_DISABLED instead. */ const TAXONOMY_HIERARCHY_DISABLED = 0; /** * Denotes that one or more terms in the vocabulary has a single parent. + * + * @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use + * \Drupal\taxonomy\VocabularyInterface::HIERARCHY_SINGLE instead. */ const TAXONOMY_HIERARCHY_SINGLE = 1; /** * Denotes that one or more terms in the vocabulary have multiple parents. + * + * @deprecated in Drupal 8.2.x and will be removed before 9.0.0. Use + * \Drupal\taxonomy\VocabularyInterface::HIERARCHY_MULTIPLE instead. */ const TAXONOMY_HIERARCHY_MULTIPLE = 2; @@ -67,11 +76,11 @@ function taxonomy_help($route_name, RouteMatchInterface $route_match) { case 'entity.taxonomy_vocabulary.overview_form': $vocabulary = $route_match->getParameter('taxonomy_vocabulary'); switch ($vocabulary->getHierarchy()) { - case TAXONOMY_HIERARCHY_DISABLED: + case VocabularyInterface::HIERARCHY_DISABLED: return '

' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label())) . '

'; - case TAXONOMY_HIERARCHY_SINGLE: + case VocabularyInterface::HIERARCHY_SINGLE: return '

' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label())) . '

'; - case TAXONOMY_HIERARCHY_MULTIPLE: + case VocabularyInterface::HIERARCHY_MULTIPLE: return '

' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()))) . '

'; } } @@ -134,10 +143,11 @@ function taxonomy_theme() { * Checks the current parents of all terms in a vocabulary and updates the * vocabulary's hierarchy setting to the lowest possible level. If no term * has parent terms then the vocabulary will be given a hierarchy of - * TAXONOMY_HIERARCHY_DISABLED. If any term has a single parent then the - * vocabulary will be given a hierarchy of TAXONOMY_HIERARCHY_SINGLE. If any - * term has multiple parents then the vocabulary will be given a hierarchy of - * TAXONOMY_HIERARCHY_MULTIPLE. + * VocabularyInterface::HIERARCHY_DISABLED. If any term has a single parent then + * the vocabulary will be given a hierarchy of + * VocabularyInterface::HIERARCHY_SINGLE. If any term has multiple parents then + * the vocabulary will be given a hierarchy of + * VocabularyInterface::HIERARCHY_MULTIPLE. * * @param \Drupal\taxonomy\VocabularyInterface $vocabulary * A taxonomy vocabulary entity. @@ -149,7 +159,7 @@ function taxonomy_theme() { */ function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $changed_term) { $tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vocabulary->id()); - $hierarchy = TAXONOMY_HIERARCHY_DISABLED; + $hierarchy = VocabularyInterface::HIERARCHY_DISABLED; foreach ($tree as $term) { // Update the changed term with the new parent value before comparison. if ($term->tid == $changed_term['tid']) { @@ -158,11 +168,11 @@ function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $c } // Check this term's parent count. if (count($term->parents) > 1) { - $hierarchy = TAXONOMY_HIERARCHY_MULTIPLE; + $hierarchy = VocabularyInterface::HIERARCHY_MULTIPLE; break; } elseif (count($term->parents) == 1 && !isset($term->parents[0])) { - $hierarchy = TAXONOMY_HIERARCHY_SINGLE; + $hierarchy = VocabularyInterface::HIERARCHY_SINGLE; } } if ($hierarchy != $vocabulary->getHierarchy()) { diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php new file mode 100644 index 000000000..39941b4c6 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php @@ -0,0 +1,19 @@ +vocabulary = $vocabulary; + } + +} diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php new file mode 100644 index 000000000..829edfbe0 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php @@ -0,0 +1,15 @@ +setVocabulary($taxonomy_vocabulary); + return $response; + } + +} diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml new file mode 100644 index 000000000..be28f20a2 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml @@ -0,0 +1,7 @@ +name: 'Vocabulary serialization test' +type: module +package: Testing +version: VERSION +core: 8.x +dependencies: + - taxonomy diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml new file mode 100644 index 000000000..82f2b2222 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml @@ -0,0 +1,6 @@ +vocabulary_serialization_test: + path: '/vocabulary_serialization_test/{taxonomy_vocabulary}' + defaults: + _controller: 'Drupal\vocabulary_serialization_test\VocabularySerializationTestController::vocabularyResponse' + requirements: + _access: 'TRUE' diff --git a/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php b/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php new file mode 100644 index 000000000..b9aba8a79 --- /dev/null +++ b/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php @@ -0,0 +1,47 @@ + 'test'])->save(); + } + + public function testSerialization() { + $this->drupalGet('/vocabulary_serialization_test/test'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSame('this is the output', $this->getSession()->getPage()->getContent()); + $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); + + $this->drupalGet('/vocabulary_serialization_test/test'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSame('this is the output', $this->getSession()->getPage()->getContent()); + $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php index f94d79197..3f29fbf77 100644 --- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php @@ -54,9 +54,9 @@ class MigrateTaxonomyVocabularyTest extends MigrateDrupal7TestBase { * Tests the Drupal 7 taxonomy vocabularies to Drupal 8 migration. */ public function testTaxonomyVocabulary() { - $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', TAXONOMY_HIERARCHY_DISABLED, 0); - $this->assertEntity('forums', 'Forums', 'Forum navigation vocabulary', TAXONOMY_HIERARCHY_SINGLE, -10); - $this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', TAXONOMY_HIERARCHY_SINGLE, 0); + $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', VocabularyInterface::HIERARCHY_DISABLED, 0); + $this->assertEntity('forums', 'Forums', 'Forum navigation vocabulary', VocabularyInterface::HIERARCHY_SINGLE, -10); + $this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', VocabularyInterface::HIERARCHY_SINGLE, 0); } } diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php new file mode 100644 index 000000000..4ac2766d7 --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php @@ -0,0 +1,56 @@ + 1, + 'vid' => 5, + 'name' => 'name value 1', + 'description' => 'description value 1', + 'weight' => 0, + 'parent' => [0], + ], + [ + 'tid' => 4, + 'vid' => 5, + 'name' => 'name value 4', + 'description' => 'description value 4', + 'weight' => 1, + 'parent' => [1], + ], + ]; + + // We know there are two rows with vid == 5. + $tests[0]['expected_count'] = 2; + + // Set up source plugin configuration. + $tests[0]['configuration'] = [ + 'vocabulary' => [5], + ]; + + return $tests; + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php new file mode 100644 index 000000000..bdffcf81c --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php @@ -0,0 +1,157 @@ + 1, + 'vid' => 5, + 'name' => 'name value 1', + 'description' => 'description value 1', + 'weight' => 0, + ], + [ + 'tid' => 2, + 'vid' => 6, + 'name' => 'name value 2', + 'description' => 'description value 2', + 'weight' => 0, + ], + [ + 'tid' => 3, + 'vid' => 6, + 'name' => 'name value 3', + 'description' => 'description value 3', + 'weight' => 0, + ], + [ + 'tid' => 4, + 'vid' => 5, + 'name' => 'name value 4', + 'description' => 'description value 4', + 'weight' => 1, + ], + [ + 'tid' => 5, + 'vid' => 6, + 'name' => 'name value 5', + 'description' => 'description value 5', + 'weight' => 1, + ], + [ + 'tid' => 6, + 'vid' => 6, + 'name' => 'name value 6', + 'description' => 'description value 6', + 'weight' => 0, + ], + ]; + $tests[0]['source_data']['term_hierarchy'] = [ + [ + 'tid' => 1, + 'parent' => 0, + ], + [ + 'tid' => 2, + 'parent' => 0, + ], + [ + 'tid' => 3, + 'parent' => 0, + ], + [ + 'tid' => 4, + 'parent' => 1, + ], + [ + 'tid' => 5, + 'parent' => 2, + ], + [ + 'tid' => 6, + 'parent' => 3, + ], + [ + 'tid' => 6, + 'parent' => 2, + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'tid' => 1, + 'vid' => 5, + 'name' => 'name value 1', + 'description' => 'description value 1', + 'weight' => 0, + 'parent' => [0], + ], + [ + 'tid' => 2, + 'vid' => 6, + 'name' => 'name value 2', + 'description' => 'description value 2', + 'weight' => 0, + 'parent' => [0], + ], + [ + 'tid' => 3, + 'vid' => 6, + 'name' => 'name value 3', + 'description' => 'description value 3', + 'weight' => 0, + 'parent' => [0], + ], + [ + 'tid' => 4, + 'vid' => 5, + 'name' => 'name value 4', + 'description' => 'description value 4', + 'weight' => 1, + 'parent' => [1], + ], + [ + 'tid' => 5, + 'vid' => 6, + 'name' => 'name value 5', + 'description' => 'description value 5', + 'weight' => 1, + 'parent' => [2], + ], + [ + 'tid' => 6, + 'vid' => 6, + 'name' => 'name value 6', + 'description' => 'description value 6', + 'weight' => 0, + 'parent' => [3, 2], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/TermNodeTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermNodeTest.php similarity index 54% rename from core/modules/taxonomy/tests/src/Unit/Migrate/d6/TermNodeTest.php rename to core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermNodeTest.php index 6ef0bd79f..3084acffa 100644 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/TermNodeTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermNodeTest.php @@ -1,59 +1,48 @@ 'test', - 'source' => array( - 'plugin' => 'd6_term_node', - 'vid' => 3, - ), - ); - - protected $expectedResults = array( - array( - 'nid' => 1, - 'vid' => 1, - 'type' => 'story', - 'tid' => array(1, 4, 5), - ), - ); +class TermNodeTest extends MigrateSqlSourceTestBase { /** * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['term_node'] = array( - array( + public static $modules = ['taxonomy', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = []; + + // The source data. + $tests[0]['source_data']['term_node'] = [ + [ 'nid' => '1', 'vid' => '1', 'tid' => '1', - ), - array( + ], + [ 'nid' => '1', 'vid' => '1', 'tid' => '4', - ), - array( + ], + [ 'nid' => '1', 'vid' => '1', 'tid' => '5', - ), - ); - $this->databaseContents['node'] = array( - array( + ], + ]; + $tests[0]['source_data']['node'] = [ + [ 'nid' => '1', 'vid' => '1', 'type' => 'story', @@ -69,32 +58,51 @@ class TermNodeTest extends MigrateSqlSourceTestCase { 'sticky' => '0', 'tnid' => '0', 'translate' => '0', - ), - ); - $this->databaseContents['term_data'] = array( - array( + ], + ]; + $tests[0]['source_data']['term_data'] = [ + [ 'tid' => '1', 'vid' => '3', 'name' => 'term 1 of vocabulary 3', 'description' => 'description of term 1 of vocabulary 3', 'weight' => '0', - ), - array( + ], + [ 'tid' => '4', 'vid' => '3', 'name' => 'term 4 of vocabulary 3', 'description' => 'description of term 4 of vocabulary 3', 'weight' => '6', - ), - array( + ], + [ 'tid' => '5', 'vid' => '3', 'name' => 'term 5 of vocabulary 3', 'description' => 'description of term 5 of vocabulary 3', 'weight' => '7', - ), - ); - parent::setUp(); + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'nid' => 1, + 'vid' => 1, + 'type' => 'story', + 'tid' => [1, 4, 5], + ], + ]; + + // Set default value for expected count. + $tests[0]['expected_count'] = NULL; + + // Set plugin configuration. + $tests[0]['configuration'] = [ + 'vid' => 3, + ]; + + return $tests; } } diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/VocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/VocabularyTest.php new file mode 100644 index 000000000..a98b2985e --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/VocabularyTest.php @@ -0,0 +1,108 @@ + 1, + 'name' => 'Tags', + 'description' => 'Tags description.', + 'help' => 1, + 'relations' => 0, + 'hierarchy' => 0, + 'multiple' => 0, + 'required' => 0, + 'tags' => 1, + 'module' => 'taxonomy', + 'weight' => 0, + ], + [ + 'vid' => 2, + 'name' => 'Categories', + 'description' => 'Categories description.', + 'help' => 1, + 'relations' => 1, + 'hierarchy' => 1, + 'multiple' => 0, + 'required' => 1, + 'tags' => 0, + 'module' => 'taxonomy', + 'weight' => 0, + ], + ]; + $tests[0]['source_data']['vocabulary_node_types'] = [ + [ + 'vid' => 1, + 'type' => 'page', + ], + [ + 'vid' => 1, + 'type' => 'article', + ], + [ + 'vid' => 2, + 'type' => 'article', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'vid' => 1, + 'name' => 'Tags', + 'description' => 'Tags description.', + 'help' => 1, + 'relations' => 0, + 'hierarchy' => 0, + 'multiple' => 0, + 'required' => 0, + 'tags' => 1, + 'module' => 'taxonomy', + 'weight' => 0, + 'node_types' => ['page', 'article'], + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + ], + [ + 'vid' => 2, + 'name' => 'Categories', + 'description' => 'Categories description.', + 'help' => 1, + 'relations' => 1, + 'hierarchy' => 1, + 'multiple' => 0, + 'required' => 1, + 'tags' => 0, + 'module' => 'taxonomy', + 'weight' => 0, + 'node_types' => ['article'], + 'cardinality' => 1, + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/VocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/VocabularyTest.php new file mode 100644 index 000000000..4d1975ceb --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/VocabularyTest.php @@ -0,0 +1,54 @@ + 1, + 'name' => 'Tags', + 'description' => 'Tags description.', + 'hierarchy' => 0, + 'module' => 'taxonomy', + 'weight' => 0, + 'machine_name' => 'tags', + ], + [ + 'vid' => 2, + 'name' => 'Categories', + 'description' => 'Categories description.', + 'hierarchy' => 1, + 'module' => 'taxonomy', + 'weight' => 0, + 'machine_name' => 'categories', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $tests[0]['source_data']['taxonomy_vocabulary']; + + return $tests; + } + +} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/TermSourceWithVocabularyFilterTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/TermSourceWithVocabularyFilterTest.php deleted file mode 100644 index 67c46cb5f..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/TermSourceWithVocabularyFilterTest.php +++ /dev/null @@ -1,25 +0,0 @@ -migrationConfiguration['source']['vocabulary'] = array(5); - parent::setUp(); - $this->expectedResults = array_values(array_filter($this->expectedResults, function($result) { - return $result['vid'] == 5; - })); - // We know there are two rows with vid == 5. - $this->expectedCount = 2; - } - -} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/TermTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/TermTest.php deleted file mode 100644 index ad2706bb7..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/TermTest.php +++ /dev/null @@ -1,12 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_taxonomy_term', - ), - ); - - protected $expectedResults = array( - array( - 'tid' => 1, - 'vid' => 5, - 'name' => 'name value 1', - 'description' => 'description value 1', - 'weight' => 0, - 'parent' => array(0), - ), - array( - 'tid' => 2, - 'vid' => 6, - 'name' => 'name value 2', - 'description' => 'description value 2', - 'weight' => 0, - 'parent' => array(0), - ), - array( - 'tid' => 3, - 'vid' => 6, - 'name' => 'name value 3', - 'description' => 'description value 3', - 'weight' => 0, - 'parent' => array(0), - ), - array( - 'tid' => 4, - 'vid' => 5, - 'name' => 'name value 4', - 'description' => 'description value 4', - 'weight' => 1, - 'parent' => array(1), - ), - array( - 'tid' => 5, - 'vid' => 6, - 'name' => 'name value 5', - 'description' => 'description value 5', - 'weight' => 1, - 'parent' => array(2), - ), - array( - 'tid' => 6, - 'vid' => 6, - 'name' => 'name value 6', - 'description' => 'description value 6', - 'weight' => 0, - 'parent' => array(3, 2), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $row) { - foreach ($row['parent'] as $parent) { - $this->databaseContents['term_hierarchy'][] = array( - 'tid' => $row['tid'], - 'parent' => $parent, - ); - } - unset($row['parent']); - $this->databaseContents['term_data'][$k] = $row; - } - parent::setUp(); - } - -} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php deleted file mode 100644 index 9227b80cc..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php +++ /dev/null @@ -1,74 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd6_vocabulary', - ], - ]; - - protected $expectedResults = [ - [ - 'vid' => 1, - 'name' => 'Tags', - 'description' => 'Tags description.', - 'help' => 1, - 'relations' => 0, - 'hierarchy' => 0, - 'multiple' => 0, - 'required' => 0, - 'tags' => 1, - 'module' => 'taxonomy', - 'weight' => 0, - 'node_types' => ['page', 'article'], - 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, - ], - [ - 'vid' => 2, - 'name' => 'Categories', - 'description' => 'Categories description.', - 'help' => 1, - 'relations' => 1, - 'hierarchy' => 1, - 'multiple' => 0, - 'required' => 1, - 'tags' => 0, - 'module' => 'taxonomy', - 'weight' => 0, - 'node_types' => ['article'], - 'cardinality' => 1, - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as &$row) { - foreach ($row['node_types'] as $type) { - $this->databaseContents['vocabulary_node_types'][] = [ - 'type' => $type, - 'vid' => $row['vid'], - ]; - } - unset($row['node_types']); - } - $this->databaseContents['vocabulary'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/d7/VocabularyTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/d7/VocabularyTest.php deleted file mode 100644 index 88cd14467..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/d7/VocabularyTest.php +++ /dev/null @@ -1,52 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_vocabulary', - ], - ]; - - protected $expectedResults = [ - [ - 'vid' => 1, - 'name' => 'Tags', - 'description' => 'Tags description.', - 'hierarchy' => 0, - 'module' => 'taxonomy', - 'weight' => 0, - 'machine_name' => 'tags', - ], - [ - 'vid' => 2, - 'name' => 'Categories', - 'description' => 'Categories description.', - 'hierarchy' => 1, - 'module' => 'taxonomy', - 'weight' => 0, - 'machine_name' => 'categories', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['taxonomy_vocabulary'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerNodeTest.php b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerNodeTest.php new file mode 100644 index 000000000..0318b8e7b --- /dev/null +++ b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerNodeTest.php @@ -0,0 +1,42 @@ + '2', + 'published' => '1', + 'changed' => '1421727536', + ] + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_results'] = $tests[0]['database']['tracker_node']; + + return $tests; + } + +} diff --git a/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerUserTest.php b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerUserTest.php new file mode 100644 index 000000000..ffcee93b5 --- /dev/null +++ b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerUserTest.php @@ -0,0 +1,43 @@ + '1', + 'uid' => '2', + 'published' => '1', + 'changed' => '1421727536', + ] + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_results'] = $tests[0]['database']['tracker_user']; + + return $tests; + } + +} diff --git a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerNodeTest.php b/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerNodeTest.php deleted file mode 100644 index 49f42400a..000000000 --- a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerNodeTest.php +++ /dev/null @@ -1,39 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_tracker_node', - ], - ]; - - protected $expectedResults = [ - [ - 'nid' => '2', - 'published' => '1', - 'changed' => '1421727536', - ] - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['tracker_node'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerUserTest.php b/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerUserTest.php deleted file mode 100644 index 847869e2f..000000000 --- a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerUserTest.php +++ /dev/null @@ -1,40 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_tracker_user', - ], - ]; - - protected $expectedResults = [ - [ - 'nid' => '1', - 'uid' => '2', - 'published' => '1', - 'changed' => '1421727536', - ] - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['tracker_user'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/user/migration_templates/d6_user.yml b/core/modules/user/migration_templates/d6_user.yml index bf6ec2ade..fa6326e93 100644 --- a/core/modules/user/migration_templates/d6_user.yml +++ b/core/modules/user/migration_templates/d6_user.yml @@ -16,7 +16,18 @@ process: timezone: plugin: user_update_7002 source: timezone - preferred_langcode: language + langcode: + plugin: user_langcode + source: language + fallback_to_site_default: false + preferred_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true + preferred_admin_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true init: init roles: plugin: migration @@ -34,6 +45,8 @@ migration_dependencies: required: - d6_user_role optional: + - language + - default_language - d6_user_picture_file - user_picture_entity_display - user_picture_entity_form_display diff --git a/core/modules/user/migration_templates/d6_user_role.yml b/core/modules/user/migration_templates/d6_user_role.yml index a97d5194f..1d224f489 100644 --- a/core/modules/user/migration_templates/d6_user_role.yml +++ b/core/modules/user/migration_templates/d6_user_role.yml @@ -9,11 +9,6 @@ process: - plugin: machine_name source: name - - - plugin: dedupe_entity - entity_type: user_role - field: id - length: 32 - plugin: user_update_8002 label: name @@ -26,15 +21,15 @@ process: 'use PHP for block visibility': 'use PHP for settings' 'administer site-wide contact form': 'administer contact forms' 'post comments without approval': 'skip comment approval' - 'edit own blog entries' : 'edit own blog content' - 'edit any blog entry' : 'edit any blog content' - 'delete own blog entries' : 'delete own blog content' - 'delete any blog entry' : 'delete any blog content' - 'create forum topics' : 'create forum content' - 'delete any forum topic' : 'delete any forum content' - 'delete own forum topics' : 'delete own forum content' - 'edit any forum topic' : 'edit any forum content' - 'edit own forum topics' : 'edit own forum content' + 'edit own blog entries': 'edit own blog content' + 'edit any blog entry': 'edit any blog content' + 'delete own blog entries': 'delete own blog content' + 'delete any blog entry': 'delete any blog content' + 'create forum topics': 'create forum content' + 'delete any forum topic': 'delete any forum content' + 'delete own forum topics': 'delete own forum content' + 'edit any forum topic': 'edit any forum content' + 'edit own forum topics': 'edit own forum content' - plugin: system_update_7000 - plugin: node_update_7008 - plugin: flatten diff --git a/core/modules/user/migration_templates/d7_user.yml b/core/modules/user/migration_templates/d7_user.yml index d68fddff4..b72e8c728 100644 --- a/core/modules/user/migration_templates/d7_user.yml +++ b/core/modules/user/migration_templates/d7_user.yml @@ -15,9 +15,18 @@ process: login: login status: status timezone: timezone - langcode: language - preferred_langcode: language - preferred_admin_langcode: language + langcode: + plugin: user_langcode + source: language + fallback_to_site_default: false + preferred_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true + preferred_admin_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true init: init roles: plugin: migration @@ -38,6 +47,8 @@ migration_dependencies: - d7_user_role optional: - d7_file + - language + - default_language - user_picture_field_instance - user_picture_entity_display - user_picture_entity_form_display diff --git a/core/modules/user/migration_templates/d7_user_role.yml b/core/modules/user/migration_templates/d7_user_role.yml index 68c0d1168..5f887574e 100644 --- a/core/modules/user/migration_templates/d7_user_role.yml +++ b/core/modules/user/migration_templates/d7_user_role.yml @@ -9,11 +9,6 @@ process: - plugin: machine_name source: name - - - plugin: dedupe_entity - entity_type: user_role - field: id - length: 32 - plugin: user_update_8002 label: name @@ -26,15 +21,15 @@ process: 'use PHP for block visibility': 'use PHP for settings' 'administer site-wide contact form': 'administer contact forms' 'post comments without approval': 'skip comment approval' - 'edit own blog entries' : 'edit own blog content' - 'edit any blog entry' : 'edit any blog content' - 'delete own blog entries' : 'delete own blog content' - 'delete any blog entry' : 'delete any blog content' - 'create forum topics' : 'create forum content' - 'delete any forum topic' : 'delete any forum content' - 'delete own forum topics' : 'delete own forum content' - 'edit any forum topic' : 'edit any forum content' - 'edit own forum topics' : 'edit own forum content' + 'edit own blog entries': 'edit own blog content' + 'edit any blog entry': 'edit any blog content' + 'delete own blog entries': 'delete own blog content' + 'delete any blog entry': 'delete any blog content' + 'create forum topics': 'create forum content' + 'delete any forum topic': 'delete any forum content' + 'delete own forum topics': 'delete own forum content' + 'edit any forum topic': 'edit any forum content' + 'edit own forum topics': 'edit own forum content' - plugin: flatten weight: weight destination: diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 030b9a933..6a3d6b161 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -416,6 +416,9 @@ class User extends ContentEntityBase implements UserInterface { static::$anonymousUser = new $class([ 'uid' => [LanguageInterface::LANGCODE_DEFAULT => 0], 'name' => [LanguageInterface::LANGCODE_DEFAULT => ''], + // Explicitly set the langcode to ensure that field definitions do not + // need to be fetched to figure out a default. + 'langcode' => [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED] ], $entity_type->id()); } return clone static::$anonymousUser; diff --git a/core/modules/user/src/Plugin/migrate/process/UserLangcode.php b/core/modules/user/src/Plugin/migrate/process/UserLangcode.php new file mode 100644 index 000000000..80f20783b --- /dev/null +++ b/core/modules/user/src/Plugin/migrate/process/UserLangcode.php @@ -0,0 +1,86 @@ +languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + if (!isset($this->configuration['fallback_to_site_default'])) { + $this->configuration['fallback_to_site_default'] = TRUE; + } + + // If the user's language is empty, it means the locale module was not + // installed, so the user's langcode should be English and the user's + // preferred_langcode and preferred_admin_langcode should fallback to the + // default language. + if (empty($value)) { + if ($this->configuration['fallback_to_site_default']) { + return $this->languageManager->getDefaultLanguage()->getId(); + } + else { + return 'en'; + } + } + // If the user's language does not exists, use the default language. + elseif ($this->languageManager->getLanguage($value) === NULL) { + return $this->languageManager->getDefaultLanguage()->getId(); + } + + // If the langcode is a valid one, just return it. + return $value; + } + +} diff --git a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php index 0970b8737..0ccbbeb51 100644 --- a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php +++ b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php @@ -21,7 +21,7 @@ class ProfileFieldValues extends DrupalSqlBase { public function query() { $query = $this->select('profile_values', 'pv') ->distinct() - ->fields('pv', array('fid', 'uid')); + ->fields('pv', array('uid')); return $query; } diff --git a/core/modules/user/src/Plugin/migrate/source/d6/Role.php b/core/modules/user/src/Plugin/migrate/source/d6/Role.php index f7bac99c2..2357c5c86 100644 --- a/core/modules/user/src/Plugin/migrate/source/d6/Role.php +++ b/core/modules/user/src/Plugin/migrate/source/d6/Role.php @@ -69,7 +69,15 @@ class Role extends DrupalSqlBase { ->condition('rid', $rid) ->execute() ->fetchField(); - $row->setSourceProperty('permissions', explode(', ', $permissions)); + + // If a role has no permissions then set to an empty array. The role will + // be migrated and given the default D8 permissions. + if ($permissions) { + $row->setSourceProperty('permissions', explode(', ', $permissions)); + } + else { + $row->setSourceProperty('permissions', []); + } if (isset($this->filterPermissions[$rid])) { $row->setSourceProperty("filter_permissions:$rid", $this->filterPermissions[$rid]); } diff --git a/core/modules/user/src/Plugin/migrate/source/d6/User.php b/core/modules/user/src/Plugin/migrate/source/d6/User.php index 1260ca75e..56f68bf8c 100644 --- a/core/modules/user/src/Plugin/migrate/source/d6/User.php +++ b/core/modules/user/src/Plugin/migrate/source/d6/User.php @@ -100,6 +100,7 @@ class User extends DrupalSqlBase { 'name' => $this->t('Username'), 'pass' => $this->t('Password'), 'mail' => $this->t('Email address'), + 'theme' => $this->t('Theme'), 'signature' => $this->t('Signature'), 'signature_format' => $this->t('Signature format'), 'created' => $this->t('Registered timestamp'), diff --git a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php index 978c0bf45..e734517a5 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php @@ -12,6 +12,11 @@ use Drupal\user\Entity\User; */ class MigrateUserProfileValuesTest extends MigrateDrupal6TestBase { + /** + * {@inheritdoc} + */ + public static $modules = ['language']; + /** * {@inheritdoc} */ @@ -19,6 +24,7 @@ class MigrateUserProfileValuesTest extends MigrateDrupal6TestBase { parent::setUp(); $this->executeMigrations([ + 'language', 'user_profile_field', 'user_profile_field_instance', 'user_profile_entity_display', diff --git a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php index d3f49ccfc..a52b6a771 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php @@ -3,7 +3,9 @@ namespace Drupal\Tests\user\Kernel\Migrate\d6; use Drupal\user\Entity\Role; +use Drupal\user\RoleInterface; use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase; +use Drupal\migrate\Plugin\MigrateIdMapInterface; /** * Upgrade user roles to user.role.*.yml. @@ -21,29 +23,69 @@ class MigrateUserRoleTest extends MigrateDrupal6TestBase { } /** - * Tests user role migration. + * Helper function to perform assertions on a user role. + * + * @param string $id + * The role ID. + * @param string[] $permissions + * An array of user permissions. + * @param int $lookupId + * The original numeric ID of the role in the source database. + * @param \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map + * The map table plugin. */ - public function testUserRole() { - /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ - $id_map = $this->getMigration('d6_user_role')->getIdMap(); - $rid = 'anonymous'; - $anonymous = Role::load($rid); - $this->assertSame($rid, $anonymous->id()); - $this->assertSame(array('migrate test anonymous permission', 'use text format filtered_html'), $anonymous->getPermissions()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(1))); - $rid = 'authenticated'; - $authenticated = Role::load($rid); - $this->assertSame($rid, $authenticated->id()); - $this->assertSame(array('migrate test authenticated permission', 'use text format filtered_html'), $authenticated->getPermissions()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(2))); - $rid = 'migrate_test_role_1'; - $migrate_test_role_1 = Role::load($rid); - $this->assertSame($rid, $migrate_test_role_1->id()); - $this->assertSame(array('migrate test role 1 test permission', 'use text format full_html', 'use text format php_code'), $migrate_test_role_1->getPermissions()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(3))); - $rid = 'migrate_test_role_2'; - $migrate_test_role_2 = Role::load($rid); - $this->assertSame(array( + protected function assertRole($id, array $permissions, $lookupId, MigrateIdMapInterface $id_map) { + /** @var \Drupal\user\RoleInterface $role */ + $role = Role::load($id); + $this->assertInstanceOf(RoleInterface::class, $role); + $this->assertSame($permissions, $role->getPermissions()); + $this->assertSame([[$id]], $id_map->lookupDestinationIds(['rid' => $lookupId])); + } + + /** + * Helper function to test the migration of the user roles. The user roles + * will be re-imported and the tests here will be repeated. + * + * @param \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map + * The map table plugin. + */ + protected function assertRoles(MigrateIdMapInterface $id_map) { + + // The permissions for each role are found in the two tables in the Drupal 6 + // source database. One is the permission table and the other is the + // filter_format table. + $permissions = [ + // From permission table. + 'access content', + 'migrate test anonymous permission', + // From filter_format tables. + 'use text format filtered_html' + ]; + $this->assertRole('anonymous', $permissions, 1, $id_map); + + $permissions = [ + // From permission table. + 'access comments', + 'access content', + 'post comments', + 'skip comment approval', + 'migrate test authenticated permission', + // From filter_format. + 'use text format filtered_html', + ]; + $this->assertRole('authenticated', $permissions, 2, $id_map); + + $permissions = [ + // From permission table. + 'migrate test role 1 test permission', + // From filter format. + 'use text format full_html', + 'use text format php_code' + ]; + $this->assertRole('migrate_test_role_1', $permissions, 3, $id_map); + + $permissions = [ + // From permission table. 'migrate test role 2 test permission', 'use PHP for settings', 'administer contact forms', @@ -59,14 +101,66 @@ class MigrateUserRoleTest extends MigrateDrupal6TestBase { 'edit own forum content', 'administer nodes', 'access content overview', + // From filter format. 'use text format php_code', - ), $migrate_test_role_2->getPermissions()); - $this->assertSame($rid, $migrate_test_role_2->id()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(4))); - $rid = 'migrate_test_role_3_that_is_long'; - $migrate_test_role_3 = Role::load($rid); - $this->assertSame($rid, $migrate_test_role_3->id()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(5))); + ]; + $this->assertRole('migrate_test_role_2', $permissions, 4, $id_map); + + // The only permission for this role is a filter format. + $permissions = ['use text format php_code']; + $this->assertRole('migrate_test_role_3_that_is_longer_than_thirty_two_characters', $permissions, 5, $id_map); + } + + /** + * Tests user role migration. + */ + public function testUserRole() { + $id_map = $this->getMigration('d6_user_role')->getIdMap(); + $this->assertRoles($id_map); + + // Test there are no duplicated roles. + $roles = [ + 'anonymous1', + 'authenticated1', + 'administrator1', + 'migrate_test_role_11', + 'migrate_test_role_21', + 'migrate_test_role_3_that_is_longer_than_thirty_two_characters1' + ]; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Remove the map row for the migrate_test_role_1 role and rerun the + // migration. This will re-import the migrate_test_role_1 role migration + // again. + $this->sourceDatabase->insert('role') + ->fields([ + 'rid' => 6, + 'name' => 'migrate test role 4', + ]) + ->execute(); + $this->sourceDatabase->insert('permission') + ->fields([ + 'pid' => 7, + 'rid' => 6, + 'perm' => 'access content', + 'tid' => 0, + ]) + ->execute(); + + $id_map->delete(['rid' => 3]); + + $this->executeMigration('d6_user_role'); + + // Test there are no duplicated roles. + $roles[] = 'migrate_test_role_41'; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Test that the existing roles have not changed. + $this->assertRoles($id_map); + + // Test the migration of the new role, migrate_test_role_4. + $permissions = ['access content']; + $this->assertRole('migrate_test_role_4', $permissions, 6, $id_map); } } diff --git a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php index 931783406..33952d177 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php @@ -19,6 +19,11 @@ class MigrateUserTest extends MigrateDrupal6TestBase { use FileMigrationTestTrait; + /** + * {@inheritdoc} + */ + public static $modules = ['language']; + /** * {@inheritdoc} */ @@ -60,6 +65,7 @@ class MigrateUserTest extends MigrateDrupal6TestBase { file_put_contents($file->getFileUri(), file_get_contents('core/modules/simpletest/files/image-2.jpg')); $file->save(); + $this->executeMigration('language'); $this->migrateUsers(); } @@ -91,28 +97,47 @@ class MigrateUserTest extends MigrateDrupal6TestBase { /** @var \Drupal\user\UserInterface $user */ $user = User::load($source->uid); - $this->assertIdentical($source->uid, $user->id()); - $this->assertIdentical($source->name, $user->label()); - $this->assertIdentical($source->mail, $user->getEmail()); - $this->assertIdentical($source->created, $user->getCreatedTime()); - $this->assertIdentical($source->access, $user->getLastAccessedTime()); - $this->assertIdentical($source->login, $user->getLastLoginTime()); + $this->assertSame($source->uid, $user->id()); + $this->assertSame($source->name, $user->label()); + $this->assertSame($source->mail, $user->getEmail()); + $this->assertSame($source->created, $user->getCreatedTime()); + $this->assertSame($source->access, $user->getLastAccessedTime()); + $this->assertSame($source->login, $user->getLastLoginTime()); $is_blocked = $source->status == 0; - $this->assertIdentical($is_blocked, $user->isBlocked()); + $this->assertSame($is_blocked, $user->isBlocked()); + $expected_timezone_name = $source->timezone_name ?: $this->config('system.date')->get('timezone.default'); + $this->assertSame($expected_timezone_name, $user->getTimeZone()); + $this->assertSame($source->init, $user->getInitialEmail()); + $this->assertSame($roles, $user->getRoles()); + + // Ensure the user's langcode, preferred_langcode and + // preferred_admin_langcode are valid. // $user->getPreferredLangcode() might fallback to default language if the // user preferred language is not configured on the site. We just want to // test if the value was imported correctly. - $this->assertIdentical($source->language, $user->preferred_langcode->value); - $expected_timezone_name = $source->timezone_name ?: $this->config('system.date')->get('timezone.default'); - $this->assertIdentical($expected_timezone_name, $user->getTimeZone()); - $this->assertIdentical($source->init, $user->getInitialEmail()); - $this->assertIdentical($roles, $user->getRoles()); + $language_manager = $this->container->get('language_manager'); + $default_langcode = $language_manager->getDefaultLanguage()->getId(); + if (empty($source->language)) { + $this->assertSame('en', $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + elseif ($language_manager->getLanguage($source->language) === NULL) { + $this->assertSame($default_langcode, $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + else { + $this->assertSame($source->language, $user->langcode->value); + $this->assertSame($source->language, $user->preferred_langcode->value); + $this->assertSame($source->language, $user->preferred_admin_langcode->value); + } // We have one empty picture in the data so don't try load that. if (!empty($source->picture)) { // Test the user picture. $file = File::load($user->user_picture->target_id); - $this->assertIdentical(basename($source->picture), $file->getFilename()); + $this->assertSame(basename($source->picture), $file->getFilename()); } else { // Ensure the user does not have a picture. diff --git a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php index de0d0b96a..edd5e3c03 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php @@ -56,6 +56,50 @@ class MigrateUserRoleTest extends MigrateDrupal7TestBase { $this->assertEntity('anonymous', 'anonymous user', 1); $this->assertEntity('authenticated', 'authenticated user', 2); $this->assertEntity('administrator', 'administrator', 3); + // Test there are no duplicated roles. + $roles = [ + 'anonymous1', + 'authenticated1', + 'administrator1', + ]; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Remove the map row for the administrator role and rerun the migration. + // This will re-import the administrator role again. + $id_map = $this->getMigration('d7_user_role')->getIdMap(); + $id_map->delete(['rid' => 3]); + + $this->sourceDatabase->insert('role') + ->fields([ + 'rid' => 4, + 'name' => 'test role', + 'weight' => 10, + ]) + ->execute(); + $this->sourceDatabase->insert('role_permission') + ->fields([ + 'rid' => 4, + 'permission' => 'access content', + 'module' => 'node', + ]) + ->execute(); + $this->executeMigration('d7_user_role'); + + // Test there are no duplicated roles. + $roles = [ + 'anonymous1', + 'authenticated1', + 'administrator1', + ]; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Test that the existing roles have not changed. + $this->assertEntity('administrator', 'administrator', 3); + $this->assertEntity('anonymous', 'anonymous user', 1); + $this->assertEntity('authenticated', 'authenticated user', 2); + + // Test the migration of the new role, test role. + $this->assertEntity('test_role', 'test role', 4); } } diff --git a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php index 33bc5fe8b..c21856e5b 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\user\Kernel\Migrate\d7; use Drupal\comment\Entity\CommentType; +use Drupal\Core\Database\Database; use Drupal\node\Entity\NodeType; use Drupal\taxonomy\Entity\Vocabulary; use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; @@ -25,6 +26,7 @@ class MigrateUserTest extends MigrateDrupal7TestBase { 'datetime', 'file', 'image', + 'language', 'link', 'node', 'system', @@ -49,6 +51,7 @@ class MigrateUserTest extends MigrateDrupal7TestBase { $this->createType('test_content_type'); Vocabulary::create(['vid' => 'test_vocabulary'])->save(); $this->executeMigrations([ + 'language', 'user_picture_field', 'user_picture_field_instance', 'd7_user_role', @@ -88,6 +91,8 @@ class MigrateUserTest extends MigrateDrupal7TestBase { * The user's email address. * @param string $password * The password for this user. + * @param int $created + * The user's creation time. * @param int $access * The last access time. * @param int $login @@ -96,37 +101,59 @@ class MigrateUserTest extends MigrateDrupal7TestBase { * Whether or not the account is blocked. * @param string $langcode * The user account's language code. + * @param string $timezone + * The user account's timezone name. * @param string $init * The user's initial email address. * @param string[] $roles * Role IDs the user account is expected to have. - * @param bool $has_picture - * Whether the user is expected to have a picture attached. * @param int $field_integer * The value of the integer field. + * @param bool $has_picture + * Whether the user is expected to have a picture attached. */ - protected function assertEntity($id, $label, $mail, $password, $access, $login, $blocked, $langcode, $init, array $roles = [RoleInterface::AUTHENTICATED_ID], $has_picture = FALSE, $field_integer = NULL) { + protected function assertEntity($id, $label, $mail, $password, $created, $access, $login, $blocked, $langcode, $timezone, $init, $roles, $field_integer, $has_picture = FALSE) { /** @var \Drupal\user\UserInterface $user */ $user = User::load($id); $this->assertTrue($user instanceof UserInterface); - $this->assertIdentical($label, $user->label()); - $this->assertIdentical($mail, $user->getEmail()); - $this->assertIdentical($access, $user->getLastAccessedTime()); - $this->assertIdentical($login, $user->getLastLoginTime()); - $this->assertIdentical($blocked, $user->isBlocked()); + $this->assertSame($label, $user->label()); + $this->assertSame($mail, $user->getEmail()); + $this->assertSame($password, $user->getPassword()); + $this->assertSame($created, $user->getCreatedTime()); + $this->assertSame($access, $user->getLastAccessedTime()); + $this->assertSame($login, $user->getLastLoginTime()); + $this->assertNotSame($blocked, $user->isBlocked()); + + // Ensure the user's langcode, preferred_langcode and + // preferred_admin_langcode are valid. // $user->getPreferredLangcode() might fallback to default language if the // user preferred language is not configured on the site. We just want to // test if the value was imported correctly. - $this->assertIdentical($langcode, $user->langcode->value); - $this->assertIdentical($langcode, $user->preferred_langcode->value); - $this->assertIdentical($langcode, $user->preferred_admin_langcode->value); - $this->assertIdentical($init, $user->getInitialEmail()); - $this->assertIdentical($roles, $user->getRoles()); - $this->assertIdentical($has_picture, !$user->user_picture->isEmpty()); - $this->assertIdentical($password, $user->getPassword()); + $language_manager = $this->container->get('language_manager'); + $default_langcode = $language_manager->getDefaultLanguage()->getId(); + if ($langcode == '') { + $this->assertSame('en', $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + elseif ($language_manager->getLanguage($langcode) === NULL) { + $this->assertSame($default_langcode, $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + else { + $this->assertSame($langcode, $user->langcode->value); + $this->assertSame($langcode, $user->preferred_langcode->value); + $this->assertSame($langcode, $user->preferred_admin_langcode->value); + } + + $this->assertSame($timezone, $user->getTimeZone()); + $this->assertSame($init, $user->getInitialEmail()); + $this->assertSame($roles, $user->getRoles()); + $this->assertSame($has_picture, !$user->user_picture->isEmpty()); if (!is_null($field_integer)) { $this->assertTrue($user->hasField('field_integer')); - $this->assertEquals($field_integer, $user->field_integer->value); + $this->assertEquals($field_integer[0], $user->field_integer->value); } } @@ -134,22 +161,65 @@ class MigrateUserTest extends MigrateDrupal7TestBase { * Tests the Drupal 7 user to Drupal 8 migration. */ public function testUser() { - $password = '$S$DGFZUE.FhrXbe4y52eC7p0ZVRGD/gOPtVctDlmC89qkujnBokAlJ'; - $this->assertEntity(2, 'Odo', 'odo@local.host', $password, '0', '0', FALSE, 'en', 'odo@local.host', [RoleInterface::AUTHENTICATED_ID], FALSE, 99); + $users = Database::getConnection('default', 'migrate') + ->select('users', 'u') + ->fields('u') + ->condition('uid', 1, '>') + ->execute() + ->fetchAll(); - // Ensure that the user can authenticate. - $this->assertEquals(2, \Drupal::service('user.auth')->authenticate('Odo', 'a password')); - // After authenticating the password will be rehashed because the password - // stretching iteration count has changed from 15 in Drupal 7 to 16 in - // Drupal 8. - $user = User::load(2); - $rehash = $user->getPassword(); - $this->assertNotEquals($password, $rehash); + foreach ($users as $source) { + $rids = Database::getConnection('default', 'migrate') + ->select('users_roles', 'ur') + ->fields('ur', array('rid')) + ->condition('ur.uid', $source->uid) + ->execute() + ->fetchCol(); + $roles = array(RoleInterface::AUTHENTICATED_ID); + $id_map = $this->getMigration('d7_user_role')->getIdMap(); + foreach ($rids as $rid) { + $role = $id_map->lookupDestinationId(array($rid)); + $roles[] = reset($role); + } - // Authenticate again and there should be no re-hash. - $this->assertEquals(2, \Drupal::service('user.auth')->authenticate('Odo', 'a password')); - $user = User::load(2); - $this->assertEquals($rehash, $user->getPassword()); + $field_integer = Database::getConnection('default', 'migrate') + ->select('field_data_field_integer', 'fi') + ->fields('fi', array('field_integer_value')) + ->condition('fi.entity_id', $source->uid) + ->execute() + ->fetchCol(); + $field_integer = !empty($field_integer) ? $field_integer : NULL; + + $this->assertEntity( + $source->uid, + $source->name, + $source->mail, + $source->pass, + $source->created, + $source->access, + $source->login, + $source->status, + $source->language, + $source->timezone, + $source->init, + $roles, + $field_integer + ); + + // Ensure that the user can authenticate. + $this->assertEquals($source->uid, $this->container->get('user.auth')->authenticate($source->name, 'a password')); + // After authenticating the password will be rehashed because the password + // stretching iteration count has changed from 15 in Drupal 7 to 16 in + // Drupal 8. + $user = User::load($source->uid); + $rehash = $user->getPassword(); + $this->assertNotEquals($source->pass, $rehash); + + // Authenticate again and there should be no re-hash. + $this->assertEquals($source->uid, $this->container->get('user.auth')->authenticate($source->name, 'a password')); + $user = User::load($source->uid); + $this->assertEquals($rehash, $user->getPassword()); + } } } diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/ProfileFieldTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/ProfileFieldTest.php new file mode 100644 index 000000000..c32118571 --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/ProfileFieldTest.php @@ -0,0 +1,129 @@ + [], + 'expected_data' => [], + ], + ]; + + $profile_fields = [ + [ + 'fid' => 1, + 'title' => 'First name', + 'name' => 'profile_first_name', + 'explanation' => 'First name user', + 'category' => 'profile', + 'page' => '', + 'type' => 'textfield', + 'weight' => 0, + 'required' => 1, + 'register' => 0, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => '', + ], + [ + 'fid' => 2, + 'title' => 'Last name', + 'name' => 'profile_last_name', + 'explanation' => 'Last name user', + 'category' => 'profile', + 'page' => '', + 'type' => 'textfield', + 'weight' => 0, + 'required' => 0, + 'register' => 0, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => '', + ], + [ + 'fid' => 3, + 'title' => 'Policy', + 'name' => 'profile_policy', + 'explanation' => 'A checkbox that say if you accept policy of website', + 'category' => 'profile', + 'page' => '', + 'type' => 'checkbox', + 'weight' => 0, + 'required' => 1, + 'register' => 1, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => '', + ], + [ + 'fid' => 4, + 'title' => 'Color', + 'name' => 'profile_color', + 'explanation' => 'A selection that allows user to select a color', + 'category' => 'profile', + 'page' => '', + 'type' => 'selection', + 'weight' => 0, + 'required' => 0, + 'register' => 0, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => "red\nblue\ngreen", + ], + ]; + + $tests[0]['source_data']['profile_fields'] = $profile_fields; + + // Profile values are merged with pre-set options of a "selection" field. + $tests[0]['source_data']['profile_values'] = [ + [ + 'fid' => 4, + 'uid' => 1, + 'value' => 'yellow', + ] + ]; + + // Expected options are: + // for "checkbox" fields - array with NULL options + // for "selection" fields - options in both keys and values + $expected_field_options = [ + '', + '', + [NULL, NULL], + [ + 'red' => 'red', + 'blue' => 'blue', + 'green' => 'green', + 'yellow' => 'yellow', + ] + ]; + + $tests[0]['expected_data'] = $profile_fields; + + foreach ($tests[0]['expected_data'] as $delta => $row) { + $tests[0]['expected_data'][$delta]['options'] = $expected_field_options[$delta]; + } + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/UserPictureInstanceTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/UserPictureInstanceTest.php new file mode 100644 index 000000000..d9e77edde --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/UserPictureInstanceTest.php @@ -0,0 +1,55 @@ + 'file_directory', + 'value' => serialize(NULL), + ], + [ + 'name' => 'user_picture_file_size', + 'value' => serialize(128), + ], + [ + 'name' => 'user_picture_dimensions', + 'value' => serialize('128x128'), + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'id' => '', + 'file_directory' => 'pictures', + 'max_filesize' => '128KB', + 'max_resolution' => '128x128', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/ProfileFieldValuesTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/ProfileFieldValuesTest.php similarity index 55% rename from core/modules/user/tests/src/Unit/Migrate/d6/ProfileFieldValuesTest.php rename to core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/ProfileFieldValuesTest.php index ce20e669c..26c2ed55f 100644 --- a/core/modules/user/tests/src/Unit/Migrate/d6/ProfileFieldValuesTest.php +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/ProfileFieldValuesTest.php @@ -1,57 +1,44 @@ 'test', - 'source' => array( - 'plugin' => 'd6_profile_field_values', - ), - ); - - protected $expectedResults = array( - array( - 'fid' => '8', - 'profile_color' => array('red'), - 'uid' => '2', - ), - array( - 'fid' => '9', - 'profile_biography' => array('Lorem ipsum dolor sit amet...'), - 'uid' => '2', - ), - ); +class ProfileFieldValuesTest extends MigrateSqlSourceTestBase { /** * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['profile_values'] = array( - array( + public static $modules = ['user', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = []; + + // The source data. + $tests[0]['source_data']['profile_values'] = [ + [ 'fid' => '8', 'uid' => '2', 'value' => 'red', - ), - array( + ], + [ 'fid' => '9', 'uid' => '2', 'value' => 'Lorem ipsum dolor sit amet...', - ), - ); - $this->databaseContents['profile_fields'] = array( - array( + ], + ]; + + $tests[0]['source_data']['profile_fields'] = [ + [ 'fid' => '8', 'title' => 'Favorite color', 'name' => 'profile_color', @@ -65,8 +52,8 @@ class ProfileFieldValuesTest extends MigrateSqlSourceTestCase { 'visibility' => '2', 'autocomplete' => '1', 'options' => '', - ), - array( + ], + [ 'fid' => '9', 'title' => 'Biography', 'name' => 'profile_biography', @@ -80,9 +67,19 @@ class ProfileFieldValuesTest extends MigrateSqlSourceTestCase { 'visibility' => '2', 'autocomplete' => '0', 'options' => '', - ), - ); - parent::setUp(); + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'profile_color' => ['red'], + 'profile_biography' => ['Lorem ipsum dolor sit amet...'], + 'uid' => '2', + ], + ]; + + return $tests; } } diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/RoleTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/RoleTest.php new file mode 100644 index 000000000..1a841d32d --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/RoleTest.php @@ -0,0 +1,87 @@ + [], + 'expected_data' => [], + ], + ]; + + $roles = [ + [ + 'rid' => 1, + 'name' => 'anonymous user', + 'permissions' => [ + 'access content', + ], + ], + [ + 'rid' => 2, + 'name' => 'authenticated user', + 'permissions' => [ + 'access comments', + 'access content', + 'post comments', + 'post comments without approval', + ], + ], + [ + 'rid' => 3, + 'name' => 'administrator', + 'permissions' => [ + 'access comments', + 'administer comments', + 'post comments', + 'post comments without approval', + 'access content', + 'administer content types', + 'administer nodes', + ], + ], + ]; + + // The source data. + foreach ($roles as $role) { + $tests[0]['source_data']['permission'][] = [ + 'rid' => $role['rid'], + 'perm' => implode(', ', $role['permissions']), + ]; + unset($role['permissions']); + $tests[0]['source_data']['role'][] = $role; + } + + $tests[0]['source_data']['filter_formats'] = [ + [ + 'format' => 1, + 'roles' => '', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $roles; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureFileTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureFileTest.php new file mode 100644 index 000000000..2724d35f5 --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureFileTest.php @@ -0,0 +1,49 @@ + '2', + 'picture' => 'core/modules/simpletest/files/image-test.jpg', + ], + [ + 'uid' => '15', + 'picture' => '', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'uid' => '2', + 'picture' => 'core/modules/simpletest/files/image-test.jpg', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureTest.php new file mode 100644 index 000000000..aea6afa7c --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureTest.php @@ -0,0 +1,46 @@ + 1, + 'access' => 1382835435, + 'picture' => 'sites/default/files/pictures/picture-1.jpg', + ], + [ + 'uid' => 2, + 'access' => 1382835436, + 'picture' => 'sites/default/files/pictures/picture-2.jpg', + ], + ]; + + // User picture data model is identical in source input and output. + $tests[0]['expected_data'] = $tests[0]['source_data']['users']; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserTest.php new file mode 100644 index 000000000..1dc34a7cd --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserTest.php @@ -0,0 +1,83 @@ + 2, + 'name' => 'admin', + 'pass' => '1234', + 'mail' => 'admin@example.com', + 'theme' => '', + 'signature' => '', + 'signature_format' => 0, + 'created' => 1279402616, + 'access' => 1322981278, + 'login' => 1322699994, + 'status' => 0, + 'timezone' => 'America/Lima', + 'language' => 'en', + // @todo Add the file when needed. + 'picture' => 'sites/default/files/pictures/picture-1.jpg', + 'init' => 'admin@example.com', + 'data' => NULL, + ], + [ + 'uid' => 4, + 'name' => 'alice', + // @todo d6 hash? + 'pass' => '1234', + 'mail' => 'alice@example.com', + 'theme' => '', + 'signature' => '', + 'signature_format' => 0, + 'created' => 1322981368, + 'access' => 1322982419, + 'login' => 132298140, + 'status' => 0, + 'timezone' => 'America/Lima', + 'language' => 'en', + 'picture' => '', + 'init' => 'alice@example.com', + 'data' => NULL, + ], + ]; + + // getDatabase() will not create empty tables, so we need to insert data + // even if it's irrelevant to the test. + $tests[0]['source_data']['users_roles'] = [ + [ + 'uid' => 99, + 'rid' => 99, + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $tests[0]['source_data']['users']; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d7/UserTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d7/UserTest.php new file mode 100644 index 000000000..8e14d63c9 --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d7/UserTest.php @@ -0,0 +1,120 @@ + '33', + 'field_id' => '11', + 'field_name' => 'field_file', + 'entity_type' => 'user', + 'bundle' => 'user', + 'data' => 'a:0:{}', + 'deleted' => '0', + ], + ]; + $tests[0]['source_data']['field_data_field_file'] = [ + [ + 'entity_type' => 'user', + 'bundle' => 'user', + 'deleted' => 0, + 'entity_id' => 2, + 'revision_id' => NULL, + 'language' => 'und', + 'delta' => 0, + 'field_file_fid' => 99, + 'field_file_display' => 1, + 'field_file_description' => 'None', + ], + ]; + $tests[0]['source_data']['role'] = [ + [ + 'rid' => 2, + 'name' => 'authenticated user', + 'weight' => 0, + ], + ]; + $tests[0]['source_data']['users'] = [ + [ + 'uid' => '2', + 'name' => 'Odo', + 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', + 'mail' => 'odo@local.host', + 'theme' => '', + 'signature' => '', + 'signature_format' => 'filtered_html', + 'created' => '1432750741', + 'access' => '0', + 'login' => '0', + 'status' => '1', + 'timezone' => 'America/Chicago', + 'language' => '', + 'picture' => '0', + 'init' => 'odo@local.host', + 'data' => 'a:1:{s:7:"contact";i:1;}', + ], + ]; + $tests[0]['source_data']['users_roles'] = [ + [ + 'uid' => 2, + 'rid' => 2, + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'uid' => '2', + 'name' => 'Odo', + 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', + 'mail' => 'odo@local.host', + 'signature' => '', + 'signature_format' => 'filtered_html', + 'created' => '1432750741', + 'access' => '0', + 'login' => '0', + 'status' => '1', + 'timezone' => 'America/Chicago', + 'language' => '', + 'picture' => '0', + 'init' => 'odo@local.host', + 'roles' => [2], + 'data' => [ + 'contact' => 1, + ], + 'field_file' => [ + [ + 'fid' => 99, + 'display' => 1, + 'description' => 'None', + ], + ], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Unit/Migrate/ProfileFieldTest.php b/core/modules/user/tests/src/Unit/Migrate/ProfileFieldTest.php deleted file mode 100644 index a639bf418..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/ProfileFieldTest.php +++ /dev/null @@ -1,82 +0,0 @@ - 'test_profile_fields', - 'source' => [ - 'plugin' => 'd6_profile_field', - ], - ]; - - protected $expectedResults = [ - [ - 'fid' => 1, - 'title' => 'First name', - 'name' => 'profile_first_name', - 'explanation' => 'First name user', - 'category' => 'profile', - 'page' => '', - 'type' => 'textfield', - 'weight' => 0, - 'required' => 1, - 'register' => 0, - 'visibility' => 2, - 'autocomplete' => 0, - 'options' => [], - ], - [ - 'fid' => 2, - 'title' => 'Last name', - 'name' => 'profile_last_name', - 'explanation' => 'Last name user', - 'category' => 'profile', - 'page' => '', - 'type' => 'textfield', - 'weight' => 0, - 'required' => 0, - 'register' => 0, - 'visibility' => 2, - 'autocomplete' => 0, - 'options' => [], - ], - [ - 'fid' => 3, - 'title' => 'Policy', - 'name' => 'profile_policy', - 'explanation' => 'A checkbox that say if you accept policy of website', - 'category' => 'profile', - 'page' => '', - 'type' => 'checkbox', - 'weight' => 0, - 'required' => 1, - 'register' => 1, - 'visibility' => 2, - 'autocomplete' => 0, - 'options' => [], - ], - ]; - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->databaseContents['profile_fields'] = $this->expectedResults; - foreach ($this->databaseContents['profile_fields'] as &$row) { - $row['options'] = serialize([]); - } - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/UserPictureInstanceTest.php b/core/modules/user/tests/src/Unit/Migrate/UserPictureInstanceTest.php deleted file mode 100644 index a650704d4..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/UserPictureInstanceTest.php +++ /dev/null @@ -1,54 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'user_picture_instance', - ], - ]; - - protected $expectedResults = array( - array( - 'id' => '', - 'file_directory' => 'pictures', - 'max_filesize' => '128KB', - 'max_resolution' => '128x128', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['variable'] = array( - array( - 'name' => 'file_directory', - 'value' => serialize(NULL), - ), - array( - 'name' => 'user_picture_file_size', - 'value' => serialize(128), - ), - array( - 'name' => 'user_picture_dimensions', - 'value' => serialize('128x128'), - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/RoleTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/RoleTest.php deleted file mode 100644 index 9d21b56c1..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/RoleTest.php +++ /dev/null @@ -1,75 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_user_role', - ), - ); - - protected $expectedResults = array( - array( - 'rid' => 1, - 'name' => 'anonymous user', - 'permissions' => array( - 'access content', - ), - ), - array( - 'rid' => 2, - 'name' => 'authenticated user', - 'permissions' => array( - 'access comments', - 'access content', - 'post comments', - 'post comments without approval', - ), - ), - array( - 'rid' => 3, - 'name' => 'administrator', - 'permissions' => array( - 'access comments', - 'administer comments', - 'post comments', - 'post comments without approval', - 'access content', - 'administer content types', - 'administer nodes', - ), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $row) { - $this->databaseContents['permission'][] = array( - 'perm' => implode(', ', $row['permissions']), - 'rid' => $row['rid'], - ); - unset($row['permissions']); - $this->databaseContents['role'][] = $row; - } - $this->databaseContents['filter_formats'][] = array( - 'format' => 1, - 'roles' => '', - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureFileTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureFileTest.php deleted file mode 100644 index 6c045eaf3..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureFileTest.php +++ /dev/null @@ -1,48 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_user_picture_file', - ), - ); - - protected $expectedResults = array( - array( - 'uid' => '2', - 'picture' => 'core/modules/simpletest/files/image-test.jpg', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['users'] = array( - array( - 'uid' => '2', - 'picture' => 'core/modules/simpletest/files/image-test.jpg', - ), - array( - 'uid' => '15', - 'picture' => '', - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureTest.php deleted file mode 100644 index 1962b1732..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureTest.php +++ /dev/null @@ -1,44 +0,0 @@ - 'test_user_picture', - 'source' => array( - 'plugin' => 'd6_user_picture', - ), - ); - - protected $expectedResults = array( - array( - 'uid' => 1, - 'access' => 1382835435, - 'picture' => 'sites/default/files/pictures/picture-1.jpg', - ), - array( - 'uid' => 2, - 'access' => 1382835436, - 'picture' => 'sites/default/files/pictures/picture-2.jpg', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['users'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/UserTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/UserTest.php deleted file mode 100644 index d087451d0..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/UserTest.php +++ /dev/null @@ -1,83 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_user', - ), - ); - - protected $expectedResults = array( - array( - 'uid' => 2, - 'name' => 'admin', - // @todo d6 hash? - 'pass' => '1234', - 'mail' => 'admin@example.com', - 'theme' => '', - 'signature' => '', - 'signature_format' => 0, - 'created' => 1279402616, - 'access' => 1322981278, - 'login' => 1322699994, - 'status' => 0, - 'timezone' => 'America/Lima', - 'language' => 'en', - // @todo Add the file when needed. - 'picture' => 'sites/default/files/pictures/picture-1.jpg', - 'init' => 'admin@example.com', - 'data' => NULL, - ), - array( - 'uid' => 4, - 'name' => 'alice', - // @todo d6 hash? - 'pass' => '1234', - 'mail' => 'alice@example.com', - 'theme' => '', - 'signature' => '', - 'signature_format' => 0, - 'created' => 1322981368, - 'access' => 1322982419, - 'login' => 132298140, - 'status' => 0, - 'timezone' => 'America/Lima', - 'language' => 'en', - 'picture' => '', - 'init' => 'alice@example.com', - 'data' => NULL, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $row) { - $this->databaseContents['users'][$k] = $row; - } - // getDatabase() will not create empty tables, so we need to insert data - // even if it's irrelevant to the test. - $this->databaseContents['users_roles'] = array( - array( - 'uid' => 99, - 'rid' => 99, - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php b/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php deleted file mode 100644 index f2c862bc6..000000000 --- a/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php +++ /dev/null @@ -1,112 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_user', - ], - ]; - - protected $expectedResults = [ - [ - 'uid' => '2', - 'name' => 'Odo', - 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', - 'mail' => 'odo@local.host', - 'signature' => '', - 'signature_format' => 'filtered_html', - 'created' => '1432750741', - 'access' => '0', - 'login' => '0', - 'status' => '1', - 'timezone' => 'America/Chicago', - 'language' => '', - 'picture' => '0', - 'init' => 'odo@local.host', - 'roles' => [2], - 'data' => [ - 'contact' => 1, - ], - 'field_file' => [ - [ - 'fid' => 99, - 'display' => 1, - 'description' => 'None', - ], - ], - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['users'][] = [ - 'uid' => '2', - 'name' => 'Odo', - 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', - 'mail' => 'odo@local.host', - 'theme' => '', - 'signature' => '', - 'signature_format' => 'filtered_html', - 'created' => '1432750741', - 'access' => '0', - 'login' => '0', - 'status' => '1', - 'timezone' => 'America/Chicago', - 'language' => '', - 'picture' => '0', - 'init' => 'odo@local.host', - 'data' => 'a:1:{s:7:"contact";i:1;}', - ]; - $this->databaseContents['users_roles'][] = [ - 'uid' => 2, - 'rid' => 2, - ]; - $this->databaseContents['role'][] = [ - 'rid' => 2, - 'name' => 'authenticated user', - 'weight' => 0, - ]; - $this->databaseContents['field_config_instance'] = [ - [ - 'id' => '33', - 'field_id' => '11', - 'field_name' => 'field_file', - 'entity_type' => 'user', - 'bundle' => 'user', - 'data' => 'a:0:{}', - 'deleted' => '0', - ], - ]; - $this->databaseContents['field_data_field_file'] = [ - [ - 'entity_type' => 'user', - 'bundle' => 'user', - 'deleted' => 0, - 'entity_id' => 2, - 'revision_id' => NULL, - 'language' => 'und', - 'delta' => 0, - 'field_file_fid' => 99, - 'field_file_display' => 1, - 'field_file_description' => 'None', - ], - ]; - parent::setUp(); - } - -} diff --git a/core/modules/views/js/base.js b/core/modules/views/js/base.js index 7c7622db4..cca3be4dd 100644 --- a/core/modules/views/js/base.js +++ b/core/modules/views/js/base.js @@ -53,9 +53,11 @@ Drupal.Views.parseViewArgs = function (href, viewPath) { var returnObj = {}; var path = Drupal.Views.getPath(href); + // Get viewPath url without baseUrl portion. + var viewHref = Drupal.url(viewPath).substring(drupalSettings.path.baseUrl.length); // Ensure we have a correct path. - if (viewPath && path.substring(0, viewPath.length + 1) === viewPath + '/') { - returnObj.view_args = decodeURIComponent(path.substring(viewPath.length + 1, path.length)); + if (viewHref && path.substring(0, viewHref.length + 1) === viewHref + '/') { + returnObj.view_args = decodeURIComponent(path.substring(viewHref.length + 1, path.length)); returnObj.view_path = path; } return returnObj; diff --git a/core/modules/views/src/ManyToOneHelper.php b/core/modules/views/src/ManyToOneHelper.php index 09accee5a..a3c4834ac 100644 --- a/core/modules/views/src/ManyToOneHelper.php +++ b/core/modules/views/src/ManyToOneHelper.php @@ -298,7 +298,7 @@ class ManyToOneHelper { } $placeholders = array( $placeholder => $value, - ) + $this->placeholders; + ); $this->handler->query->addWhereExpression($options['group'], "$field $operator", $placeholders); } else { diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index cde5ecb35..103dd9dee 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -1271,7 +1271,20 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf // @todo Views should expect and store a leading /. See // https://www.drupal.org/node/2423913. - $more_link = ' ' . $this->linkGenerator()->generate($more_link_text, CoreUrl::fromUserInput('/' . $more_link_path, array('attributes' => array('class' => array('views-more-link'))))); + $options = array( + 'attributes' => array( + 'class' => array( + 'views-more-link', + ), + ), + ); + if (UrlHelper::isExternal($more_link_path)) { + $more_link_url = CoreUrl::fromUri($more_link_path, $options); + } + else { + $more_link_url = CoreUrl::fromUserInput('/' . $more_link_path, $options); + } + $more_link = ' ' . $this->linkGenerator()->generate($more_link_text, $more_link_url); } } diff --git a/core/modules/views/src/Plugin/views/filter/Combine.php b/core/modules/views/src/Plugin/views/filter/Combine.php index e13df51ec..1b4ab9c1c 100644 --- a/core/modules/views/src/Plugin/views/filter/Combine.php +++ b/core/modules/views/src/Plugin/views/filter/Combine.php @@ -3,6 +3,7 @@ namespace Drupal\views\Plugin\views\filter; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Database\Database; /** * Filter handler which allows to search on multiple fields. @@ -138,6 +139,40 @@ class Combine extends StringFilter { $this->query->addWhereExpression($this->options['group'], "$expression LIKE $placeholder", array($placeholder => '%' . db_like($this->value) . '%')); } + /** + * Filters by one or more words. + * + * By default opContainsWord uses add_where, that doesn't support complex + * expressions. + * + * @param string $expression + */ + protected function opContainsWord($expression) { + $placeholder = $this->placeholder(); + + // Don't filter on empty strings. + if (empty($this->value)) { + return; + } + + // Match all words separated by spaces or sentences encapsulated by double + // quotes. + preg_match_all(static::WORDS_PATTERN, ' ' . $this->value, $matches, PREG_SET_ORDER); + + // Switch between the 'word' and 'allwords' operator. + $type = $this->operator == 'word' ? 'OR' : 'AND'; + $group = $this->query->setWhereGroup($type); + $operator = Database::getConnection()->mapConditionOperator('LIKE'); + $operator = isset($operator['operator']) ? $operator['operator'] : 'LIKE'; + + foreach ($matches as $match_key => $match) { + $temp_placeholder = $placeholder . '_' . $match_key; + // Clean up the user input and remove the sentence delimiters. + $word = trim($match[2], ',?!();:-"'); + $this->query->addWhereExpression($group, "$expression $operator $temp_placeholder", array($temp_placeholder => '%' . Database::getConnection()->escapeLike($word) . '%')); + } + } + protected function opStartsWith($expression) { $placeholder = $this->placeholder(); $this->query->addWhereExpression($this->options['group'], "$expression LIKE $placeholder", array($placeholder => db_like($this->value) . '%')); diff --git a/core/modules/views/src/Plugin/views/filter/StringFilter.php b/core/modules/views/src/Plugin/views/filter/StringFilter.php index 10c407017..618b58458 100644 --- a/core/modules/views/src/Plugin/views/filter/StringFilter.php +++ b/core/modules/views/src/Plugin/views/filter/StringFilter.php @@ -14,6 +14,11 @@ use Drupal\Core\Form\FormStateInterface; */ class StringFilter extends FilterPluginBase { + /** + * All words separated by spaces or sentences encapsulated by double quotes. + */ + const WORDS_PATTERN = '/ (-?)("[^"]+"|[^" ]+)/i'; + // exposed filter options protected $alwaysMultiple = TRUE; @@ -267,7 +272,7 @@ class StringFilter extends FilterPluginBase { return; } - preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->value, $matches, PREG_SET_ORDER); + preg_match_all(static::WORDS_PATTERN, ' ' . $this->value, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $phrase = FALSE; // Strip off phrase quotes diff --git a/core/modules/views/src/Plugin/views/pager/Mini.php b/core/modules/views/src/Plugin/views/pager/Mini.php index 5f1750ea3..c6520f4fb 100644 --- a/core/modules/views/src/Plugin/views/pager/Mini.php +++ b/core/modules/views/src/Plugin/views/pager/Mini.php @@ -47,14 +47,17 @@ class Mini extends SqlBase { public function query() { parent::query(); - // Don't query for the next page if we have a pager that has a limited - // amount of pages. - if ($this->getItemsPerPage() > 0 && (empty($this->options['total_pages']) || ($this->getCurrentPage() < $this->options['total_pages']))) { - // Increase the items in the query in order to be able to find out whether - // there is another page. - $limit = $this->view->query->getLimit(); - $limit += 1; - $this->view->query->setLimit($limit); + // Only modify the query if we don't want to do a total row count + if (!$this->view->get_total_rows) { + // Don't query for the next page if we have a pager that has a limited + // amount of pages. + if ($this->getItemsPerPage() > 0 && (empty($this->options['total_pages']) || ($this->getCurrentPage() < $this->options['total_pages']))) { + // Increase the items in the query in order to be able to find out + // whether there is another page. + $limit = $this->view->query->getLimit(); + $limit += 1; + $this->view->query->setLimit($limit); + } } } @@ -69,19 +72,16 @@ class Mini extends SqlBase { * {@inheritdoc} */ public function postExecute(&$result) { - // In query() one more item might have been retrieved than necessary. If so, - // the next link needs to be displayed and the item removed. - if ($this->getItemsPerPage() > 0 && count($result) > $this->getItemsPerPage()) { - array_pop($result); - // Make sure the pager shows the next link by setting the total items to - // the biggest possible number but prevent failing calculations like - // ceil(PHP_INT_MAX) we take PHP_INT_MAX / 2. - $total = PHP_INT_MAX / 2; + // Only modify the result if we didn't do a total row count + if (!$this->view->get_total_rows) { + $this->total_items = $this->getCurrentPage() * $this->getItemsPerPage() + count($result); + // query() checks if we need a next link by setting limit 1 record past + // this page If we got the extra record we need to remove it before we + // render the result. + if ($this->getItemsPerPage() > 0 && count($result) > $this->getItemsPerPage()) { + array_pop($result); + } } - else { - $total = $this->getCurrentPage() * $this->getItemsPerPage() + count($result); - } - $this->total_items = $total; } /** diff --git a/core/modules/views/src/Tests/Plugin/MiniPagerTest.php b/core/modules/views/src/Tests/Plugin/MiniPagerTest.php index f39a9d65f..1a73b6980 100644 --- a/core/modules/views/src/Tests/Plugin/MiniPagerTest.php +++ b/core/modules/views/src/Tests/Plugin/MiniPagerTest.php @@ -70,6 +70,18 @@ class MiniPagerTest extends PluginTestBase { $this->assertText($this->nodes[18]->label()); $this->assertText($this->nodes[19]->label()); + // Test @total value in result summary + $view = Views::getView('test_mini_pager'); + $view->setDisplay('page_4'); + $this->executeView($view); + $this->assertIdentical($view->get_total_rows, TRUE, 'The query was set to calculate the total number of rows.'); + $this->assertEqual(count($this->nodes), $view->total_rows, 'The total row count is equal to the number of nodes.'); + + $this->drupalGet('test_mini_pager_total', array('query' => array('page' => 1))); + $this->assertText('of ' . count($this->nodes), 'The first page shows the total row count.'); + $this->drupalGet('test_mini_pager_total', array('query' => array('page' => 6))); + $this->assertText('of ' . count($this->nodes), 'The last page shows the total row count.'); + // Test a mini pager with just one item per page. $this->drupalGet('test_mini_pager_one'); $this->assertText('››'); diff --git a/core/modules/views/src/Tests/TaxonomyGlossaryTest.php b/core/modules/views/src/Tests/TaxonomyGlossaryTest.php new file mode 100644 index 000000000..ccda8877d --- /dev/null +++ b/core/modules/views/src/Tests/TaxonomyGlossaryTest.php @@ -0,0 +1,58 @@ +enableViewsTestModule(); + + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = $this->createVocabulary(); + for ($i = 0; $i < 10; $i++) { + $this->taxonomyTerms[] = $this->createTerm($vocabulary); + } + } + + /** + * Tests a taxonomy glossary view. + */ + public function testTaxonomyGlossaryView() { + // Go the taxonomy glossary page for the first term. + $this->drupalGet('test_taxonomy_glossary/' . substr($this->taxonomyTerms[0]->getName(), 0, 1)); + $this->assertText($this->taxonomyTerms[0]->getName()); + } + +} diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml index bf44f36e6..e2e143eaf 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml @@ -5,56 +5,450 @@ dependencies: - node - user id: test_glossary -label: test_glossary -module: views -description: '' +label: Test glossary +module: node +description: 'All content, by letter.' tag: default base_table: node_field_data base_field: nid core: '8' display: default: + id: default + display_title: Master + display_plugin: default + position: 0 display_options: - access: - type: perm - arguments: - title: - default_argument_type: fixed - field: title - glossary: true - id: title - limit: 1 - summary: - format: default_summary - number_of_records: 0 - summary_options: - items_per_page: 25 - table: node_field_data - plugin_id: string - entity_type: node - entity_field: title - cache: - type: tag - exposed_form: - type: basic - fields: - title: - field: title - id: title - label: '' - table: node_field_data - plugin_id: field - entity_type: node - entity_field: title - pager: - type: full query: type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + use_ajax: true + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 36 + offset: 0 + id: 0 + total_pages: 0 + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + fields: + title: + id: title + table: node_field_data + field: title + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: Title + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: title + name: + id: name + table: users_field_data + field: name + label: Author + relationship: uid + plugin_id: field + type: user_name + group_type: group + admin_label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: user + entity_field: name + changed: + id: changed + table: node_field_data + field: changed + label: 'Last update' + type: timestamp + settings: + date_format: long + custom_date_format: '' + timezone: '' + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: changed + arguments: + title: + id: title + table: node_field_data + field: title + default_action: default + exception: + title_enable: true + default_argument_type: fixed + default_argument_options: + argument: a + summary: + format: default_summary + specify_validation: true + glossary: true + limit: 1 + case: upper + path_case: lower + transform_dash: false + plugin_id: string + relationship: none + group_type: group + admin_label: '' + title_enable: false + title: '' + default_argument_skip_url: false + summary_options: { } + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + entity_type: node + entity_field: title + relationships: + uid: + id: uid + table: node_field_data + field: uid + plugin_id: standard + relationship: none + group_type: group + admin_label: author + required: false style: - type: default + type: table + options: + columns: + title: title + name: name + changed: changed + default: title + info: + title: + sortable: true + separator: '' + name: + sortable: true + separator: '' + changed: + sortable: true + separator: '' + override: true + sticky: false + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + order: asc + summary: '' + empty_table: false row: type: fields - display_plugin: default - display_title: Master - id: default - position: 0 + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + header: { } + footer: { } + empty: { } + sorts: { } + filters: + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + attachment_1: + id: attachment_1 + display_title: Attachment + display_plugin: attachment + position: 2 + display_options: + query: + type: views_query + options: { } + pager: + type: none + options: + offset: 0 + items_per_page: 0 + defaults: + arguments: false + arguments: + title: + id: title + table: node_field_data + field: title + default_action: summary + exception: + title_enable: true + default_argument_type: fixed + default_argument_options: + argument: a + summary: + format: unformatted_summary + summary_options: + items_per_page: 25 + inline: true + separator: ' | ' + specify_validation: true + glossary: true + limit: 1 + case: upper + path_case: lower + transform_dash: false + plugin_id: string + relationship: none + group_type: group + admin_label: '' + title_enable: false + title: '' + default_argument_skip_url: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + entity_type: node + entity_field: title + displays: + default: default + page_1: page_1 + inherit_arguments: false + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 1 + display_options: + query: + type: views_query + options: { } + path: test-glossary + menu: + type: normal + title: Glossary + weight: 0 + menu_name: main + parent: '' + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_invalid_tokens.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_invalid_tokens.yml new file mode 100644 index 000000000..b57593a80 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_invalid_tokens.yml @@ -0,0 +1,53 @@ +langcode: en +status: true +dependencies: { } +id: test_invalid_tokens +label: 'Test invalid tokens' +module: views +description: 'Test view to invalid token replacement tests.' +tag: '' +base_table: views_test_data +base_field: id +core: 8.x +display: + default: + display_options: + title: 'Test token default' + defaults: + fields: false + pager: false + sorts: false + fields: + age: + field: age + id: age + relationship: none + table: views_test_data + plugin_id: numeric + id: + field: id + id: id + relationship: none + table: views_test_data + plugin_id: numeric + name: + field: name + id: name + relationship: none + table: views_test_data + plugin_id: string + pager: + type: full + options: + items_per_page: 10 + display_plugin: default + display_title: Master + id: default + position: 0 + block_1: + display_plugin: block + id: block_1 + display_title: Block + position: 1 + display_options: + display_extenders: { } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml index e65ea4a4e..289f83db9 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml @@ -118,3 +118,26 @@ display: type: mini options: items_per_page: 0 + page_4: + display_plugin: page + id: page_4 + display_title: 'Page 4' + position: 4 + display_options: + display_extenders: { } + path: test_mini_pager_total + empty: { } + defaults: + empty: false + header: false + header: + result: + id: result + table: views + field: result + relationship: none + group_type: group + admin_label: '' + empty: false + content: 'Displaying @start - @end of @total' + plugin_id: result diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_taxonomy_glossary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_taxonomy_glossary.yml new file mode 100644 index 000000000..8e4182dc1 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_taxonomy_glossary.yml @@ -0,0 +1,191 @@ +langcode: en +status: true +dependencies: + module: + - taxonomy +id: test_taxonomy_glossary +label: test_taxonomy_glossary +module: views +description: '' +tag: '' +base_table: taxonomy_term_field_data +base_field: tid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + row: + type: fields + fields: + name: + id: name + table: taxonomy_term_field_data + field: name + entity_type: taxonomy_term + entity_field: name + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + type: string + settings: + link_to_entity: true + plugin_id: term_name + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + convert_spaces: false + filters: { } + sorts: { } + title: test_taxonomy_glossary + header: { } + footer: { } + empty: { } + relationships: { } + arguments: + name: + id: name + table: taxonomy_term_field_data + field: name + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + glossary: true + limit: 1 + case: none + path_case: none + transform_dash: false + break_phrase: false + add_table: false + require_value: false + entity_type: taxonomy_term + entity_field: name + plugin_id: string + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test_taxonomy_glossary + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + tags: { } diff --git a/core/modules/views/tests/src/FunctionalJavascript/GlossaryViewTest.php b/core/modules/views/tests/src/FunctionalJavascript/GlossaryViewTest.php new file mode 100644 index 000000000..42da7b659 --- /dev/null +++ b/core/modules/views/tests/src/FunctionalJavascript/GlossaryViewTest.php @@ -0,0 +1,129 @@ +createContentType(['type' => 'page']); + + $titles = [ + 'Page One', + 'Page Two', + 'Another page', + ]; + foreach ($titles as $title) { + $this->createNode([ + 'title' => $title, + 'language' => 'en', + ]); + $this->createNode([ + 'title' => $title, + 'language' => 'nl', + ]); + } + + // Create a user privileged enough to use exposed filters and view content. + $user = $this->drupalCreateUser([ + 'administer site configuration', + 'access content', + 'access content overview', + ]); + $this->drupalLogin($user); + } + + /** + * Tests the AJAX callbacks for the glossary view. + */ + public function testGlossaryDefault() { + // Visit the default Glossary page. + $url = Url::fromRoute('view.test_glossary.page_1'); + $this->drupalGet($url); + + $session = $this->getSession(); + $web_assert = $this->assertSession(); + + $page = $session->getPage(); + $rows = $page->findAll('css', '.view-test-glossary tr'); + // We expect 2 rows plus the header row. + $this->assertCount(3, $rows); + // Click on the P link, this should show 4 rows plus the header row. + $page->clickLink('P'); + $web_assert->assertWaitOnAjaxRequest(); + $rows = $page->findAll('css', '.view-test-glossary tr'); + $this->assertCount(5, $rows); + } + + /** + * Test that the glossary also works on a language prefixed URL. + */ + public function testGlossaryLanguagePrefix() { + $this->language = ConfigurableLanguage::createFromLangcode('nl')->save(); + + $config = $this->config('language.negotiation'); + $config->set('url.prefixes', ['en' => 'en', 'nl' => 'nl']) + ->save(); + + \Drupal::service('kernel')->rebuildContainer(); + + $url = Url::fromRoute('view.test_glossary.page_1'); + $this->drupalGet($url); + + $session = $this->getSession(); + $web_assert = $this->assertSession(); + + $page = $session->getPage(); + + $rows = $page->findAll('css', '.view-test-glossary tr'); + // We expect 2 rows plus the header row. + $this->assertCount(3, $rows); + // Click on the P link, this should show 4 rows plus the header row. + $page->clickLink('P'); + $web_assert->assertWaitOnAjaxRequest(); + + $rows = $page->findAll('css', '.view-test-glossary tr'); + $this->assertCount(5, $rows); + } + +} diff --git a/core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/ContextualFilterTest.php b/core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/ContextualFilterTest.php new file mode 100644 index 000000000..8cca9c360 --- /dev/null +++ b/core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/ContextualFilterTest.php @@ -0,0 +1,75 @@ +getEditable('views.settings')->set('ui.show.advanced_column', TRUE)->save(); + + $account = $this->drupalCreateUser(['administer views']); + $this->drupalLogin($account); + } + + /** + * Test adding a contextual filter handler through the UI. + */ + public function testAddContextualFilterUI() { + $web_assert = $this->assertSession(); + + $this->drupalGet('/admin/structure/views/view/test_field_body'); + $web_assert->assertWaitOnAjaxRequest(); + + $page = $this->getSession()->getPage(); + + $page->clickLink('views-add-argument'); + $web_assert->assertWaitOnAjaxRequest(); + + $page->checkField('name[node_field_data.nid]'); + $add_button = $page->find('css', '.ui-dialog-buttonset .button--primary'); + $add_button->click(); + $web_assert->assertWaitOnAjaxRequest(); + + $page->fillField('options[default_action]', 'default'); + $page->selectFieldOption('options[default_argument_type]', 'node'); + $add_button = $page->find('css', '.ui-dialog-buttonset .button--primary'); + $add_button->click(); + $web_assert->assertWaitOnAjaxRequest(); + $page->pressButton('edit-actions-submit'); + $web_assert->assertWaitOnAjaxRequest(); + $page->clickLink('Content: ID'); + // Check that the dialog opens. + $web_assert->assertWaitOnAjaxRequest(); + $page->pressButton('Close'); + } + +} diff --git a/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php b/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php index 775015bbe..042426383 100644 --- a/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php @@ -95,6 +95,101 @@ class FilterCombineTest extends ViewsKernelTestBase { $this->assertIdenticalResultset($view, $resultset, $this->columnMap); } + /** + * Tests the Combine field filter with the 'word' operator. + */ + public function testFilterCombineWord() { + $view = Views::getView('test_view'); + $view->setDisplay(); + + $fields = $view->displayHandlers->get('default')->getOption('fields'); + $view->displayHandlers->get('default')->overrideOption('fields', $fields + array( + 'job' => array( + 'id' => 'job', + 'table' => 'views_test_data', + 'field' => 'job', + 'relationship' => 'none', + ), + )); + + // Change the filtering. + $view->displayHandlers->get('default')->overrideOption('filters', array( + 'age' => array( + 'id' => 'combine', + 'table' => 'views', + 'field' => 'combine', + 'relationship' => 'none', + 'operator' => 'word', + 'fields' => array( + 'name', + 'job', + ), + 'value' => 'singer ringo', + ), + )); + + $this->executeView($view); + $resultset = array( + array( + 'name' => 'John', + 'job' => 'Singer', + ), + array( + 'name' => 'George', + 'job' => 'Singer', + ), + array( + 'name' => 'Ringo', + 'job' => 'Drummer', + ), + ); + $this->assertIdenticalResultset($view, $resultset, $this->columnMap); + } + + /** + * Tests the Combine field filter with the 'allwords' operator. + */ + public function testFilterCombineAllWords() { + $view = Views::getView('test_view'); + $view->setDisplay(); + + $fields = $view->displayHandlers->get('default')->getOption('fields'); + $view->displayHandlers->get('default')->overrideOption('fields', $fields + array( + 'job' => array( + 'id' => 'job', + 'table' => 'views_test_data', + 'field' => 'job', + 'relationship' => 'none', + ), + )); + + // Set the filtering to allwords and simulate searching for a phrase. + $view->displayHandlers->get('default')->overrideOption('filters', array( + 'age' => array( + 'id' => 'combine', + 'table' => 'views', + 'field' => 'combine', + 'relationship' => 'none', + 'operator' => 'allwords', + 'fields' => array( + 'name', + 'job', + 'age', + ), + 'value' => '25 "john singer"', + ), + )); + + $this->executeView($view); + $resultset = array( + array( + 'name' => 'John', + 'job' => 'Singer', + ), + ); + $this->assertIdenticalResultset($view, $resultset, $this->columnMap); + } + /** * Tests if the filter can handle removed fields. * diff --git a/core/modules/views/tests/src/Kernel/TokenReplaceTest.php b/core/modules/views/tests/src/Kernel/TokenReplaceTest.php index 9377ff409..5cf40ad58 100644 --- a/core/modules/views/tests/src/Kernel/TokenReplaceTest.php +++ b/core/modules/views/tests/src/Kernel/TokenReplaceTest.php @@ -19,7 +19,7 @@ class TokenReplaceTest extends ViewsKernelTestBase { * * @var array */ - public static $testViews = array('test_tokens'); + public static $testViews = array('test_tokens', 'test_invalid_tokens'); protected function setUp($import_test_views = TRUE) { parent::setUp(); @@ -90,4 +90,23 @@ class TokenReplaceTest extends ViewsKernelTestBase { } } + /** + * Tests path token replacements generated from a view without a path. + */ + function testTokenReplacementNoPath() { + $token_handler = \Drupal::token(); + $view = Views::getView('test_invalid_tokens'); + $view->setDisplay('block_1'); + $this->executeView($view); + + $expected = array( + '[view:url]' => '', + ); + + foreach ($expected as $token => $expected_output) { + $output = $token_handler->replace($token, array('view' => $view)); + $this->assertIdentical($output, $expected_output, format_string('Token %token replaced correctly.', array('%token' => $token))); + } + } + } diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php index 5135c4f00..0b33a5891 100644 --- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php @@ -227,11 +227,17 @@ class FieldPluginBaseTest extends UnitTestCase { } /** - * Test rendering as a link without a path. + * Test rendering with a more link. * + * @param string $path + * An internal or external path. + * @param string $url + * The final url used by the more link. + * + * @dataProvider providerTestRenderTrimmedWithMoreLinkAndPath * @covers ::renderText */ - public function testRenderTrimmedWithMoreLink() { + public function testRenderTrimmedWithMoreLinkAndPath($path, $url) { $alter = [ 'trim' => TRUE, 'max_length' => 7, @@ -239,6 +245,7 @@ class FieldPluginBaseTest extends UnitTestCase { // Don't invoke translation. 'ellipsis' => FALSE, 'more_link_text' => 'more link', + 'more_link_path' => $path, ]; $this->display->expects($this->any()) @@ -253,11 +260,38 @@ class FieldPluginBaseTest extends UnitTestCase { $field->field_alias = 'key'; $row = new ResultRow(['key' => 'a long value']); - $expected_result = 'a long more link'; + $expected_result = 'a long more link'; $result = $field->advancedRender($row); $this->assertEquals($expected_result, $result); } + /** + * Data provider for ::testRenderTrimmedWithMoreLinkAndPath(). + * + * @return array + * Test data. + */ + public function providerTestRenderTrimmedWithMoreLinkAndPath() { + $data = []; + // Simple path with default options. + $data[] = ['test-path', '/test-path']; + // Add a fragment. + $data[] = ['test-path#test', '/test-path#test']; + // Query specified as part of the path. + $data[] = ['test-path?foo=bar', '/test-path?foo=bar']; + // Empty path. + $data[] = ['', '/%3Cfront%3E']; + // Front page path. + $data[] = ['', '/%3Cfront%3E']; + + // External URL. + $data[] = ['https://www.drupal.org', 'https://www.drupal.org']; + $data[] = ['http://www.drupal.org', 'http://www.drupal.org']; + $data[] = ['www.drupal.org', '/www.drupal.org']; + + return $data; + } + /** * Tests the "No results text" rendering. * diff --git a/core/modules/views/views.tokens.inc b/core/modules/views/views.tokens.inc index 59faec39b..a0929552d 100644 --- a/core/modules/views/views.tokens.inc +++ b/core/modules/views/views.tokens.inc @@ -101,9 +101,15 @@ function views_tokens($type, $tokens, array $data, array $options, BubbleableMet break; case 'url': - if ($url = $view->getUrl()) { - $replacements[$original] = $url->setOptions($url_options) - ->toString(); + try { + if ($url = $view->getUrl()) { + $replacements[$original] = $url->setOptions($url_options) + ->toString(); + } + } + catch (\InvalidArgumentException $e) { + // The view has no URL so we leave the value empty. + $replacements[$original] = ''; } break; case 'base-table': diff --git a/core/modules/views_ui/src/Tests/DisplayCRUDTest.php b/core/modules/views_ui/src/Tests/DisplayCRUDTest.php index 20f57d9ef..1e060dcb7 100644 --- a/core/modules/views_ui/src/Tests/DisplayCRUDTest.php +++ b/core/modules/views_ui/src/Tests/DisplayCRUDTest.php @@ -81,6 +81,16 @@ class DisplayCRUDTest extends UITestBase { $this->drupalPostForm(NULL, array(), t('Save')); $this->assertNoLinkByHref($path_prefix . '/page_1', 'Make sure there is no display tab for the deleted display.'); + + // Test deleting a display that has a modified machine name. + $view = $this->randomView(); + $machine_name = 'new_machine_name'; + $path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit'; + $this->drupalPostForm("admin/structure/views/nojs/display/{$view['id']}/page_1/display_id", array('display_id' => $machine_name), 'Apply'); + $this->drupalPostForm(NULL, array(), 'Delete Page'); + $this->drupalPostForm(NULL, array(), t('Save')); + $this->assertResponse(200); + $this->assertNoLinkByHref($path_prefix . '/new_machine_name', 'Make sure there is no display tab for the deleted display.'); } /** diff --git a/core/modules/views_ui/src/Tests/FieldUITest.php b/core/modules/views_ui/src/Tests/FieldUITest.php index 5b79da9b4..8c486d8b3 100644 --- a/core/modules/views_ui/src/Tests/FieldUITest.php +++ b/core/modules/views_ui/src/Tests/FieldUITest.php @@ -2,6 +2,7 @@ namespace Drupal\views_ui\Tests; +use Drupal\Component\Serialization\Json; use Drupal\views\Views; /** @@ -55,6 +56,23 @@ class FieldUITest extends UITestBase { $result = $this->xpath('//details[@id="edit-options-more"]'); $this->assertEqual(empty($result), TRUE, "Container 'more' is empty and should not be displayed."); + + // Ensure that dialog titles are not escaped. + $edit_groupby_url = 'admin/structure/views/nojs/handler/test_view/default/field/name'; + $this->assertNoLinkByHref($edit_groupby_url, 0, 'No aggregation link found.'); + + // Enable aggregation on the view. + $edit = array( + 'group_by' => TRUE, + ); + $this->drupalPostForm('/admin/structure/views/nojs/display/test_view/default/group_by', $edit, t('Apply')); + + $this->assertLinkByHref($edit_groupby_url, 0, 'Aggregation link found.'); + + $edit_handler_url = '/admin/structure/views/ajax/handler-group/test_view/default/field/name'; + $this->drupalGet($edit_handler_url); + $data = Json::decode($this->getRawContent()); + $this->assertEqual($data[3]['dialogOptions']['title'], 'Configure aggregation settings for field Views test: Name'); } /** diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php index 5f8c095ad..530a47f9b 100644 --- a/core/modules/views_ui/src/ViewEditForm.php +++ b/core/modules/views_ui/src/ViewEditForm.php @@ -266,7 +266,7 @@ class ViewEditForm extends ViewFormBase { // Rename display ids if needed. foreach ($executable->displayHandlers as $id => $display) { - if (!empty($display->display['new_id'])) { + if (!empty($display->display['new_id']) && empty($display->display['deleted'])) { $new_id = $display->display['new_id']; $display->display['id'] = $new_id; unset($display->display['new_id']); diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist index 9658f2ce0..ef1ae3b6f 100644 --- a/core/phpunit.xml.dist +++ b/core/phpunit.xml.dist @@ -15,8 +15,8 @@ result printer that links to the html output results for functional tests. Unfortunately, this breaks the output of PHPStorm's PHPUnit runner. However, if using the command line you can add - - -printerClass="\Drupal\Tests\Listeners\HtmlOutputPrinter" to use it (note - there should be no spaces between the hyphens). + - -printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" to use it (note there + should be no spaces between the hyphens). --> diff --git a/core/profiles/testing_missing_dependencies/testing_missing_dependencies.info.yml b/core/profiles/testing_missing_dependencies/testing_missing_dependencies.info.yml new file mode 100644 index 000000000..442e93421 --- /dev/null +++ b/core/profiles/testing_missing_dependencies/testing_missing_dependencies.info.yml @@ -0,0 +1,10 @@ +name: 'Testing missing dependencies' +type: profile +description: 'Minimal profile for running a test when dependencies are listed but missing.' +version: VERSION +core: 8.x +hidden: true +dependencies: + - missing_module1 + - missing_module2 +keep_english: true diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php new file mode 100644 index 000000000..e05940537 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php @@ -0,0 +1,85 @@ +install(['stable', 'seven']); + $theme_config = \Drupal::configFactory()->getEditable('system.theme'); + $theme_config->set('admin', 'seven'); + $theme_config->set('default', 'stable'); + $theme_config->save(); + + $account = $this->drupalCreateUser(['view the administration theme']); + $this->drupalLogin($account); + + // First visit the site directly via the URL. This should render it in the + // admin theme. + $this->drupalGet('admin/ajax-test/theme'); + $assert = $this->assertSession(); + $assert->pageTextContains('Current theme: seven'); + + // Now click the modal, which should also use the admin theme. + $this->drupalGet('ajax-test/dialog'); + $assert->pageTextNotContains('Current theme: stable'); + $this->clickLink('Link 8 (ajax)'); + $assert->assertWaitOnAjaxRequest(); + + $assert->pageTextContains('Current theme: stable'); + $assert->pageTextNotContains('Current theme: seven'); + } + + /** + * Test that AJAX loaded libraries are not retained between requests. + * + * @see https://www.drupal.org/node/2647916 + */ + public function testDrupalSettingsCachingRegression() { + $this->drupalGet('ajax-test/dialog'); + $assert = $this->assertSession(); + $session = $this->getSession(); + + // Insert a fake library into the already loaded library settings. + $fake_library = 'fakeLibrary/fakeLibrary'; + $session->evaluateScript("drupalSettings.ajaxPageState.libraries = drupalSettings.ajaxPageState.libraries + ',$fake_library';"); + + $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); + // Test that the fake library is set. + $this->assertContains($fake_library, $libraries); + + // Click on the AJAX link. + $this->clickLink('Link 8 (ajax)'); + $assert->assertWaitOnAjaxRequest(); + + // Test that the fake library is still set after the AJAX call. + $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); + $this->assertContains($fake_library, $libraries); + + // Reload the page, this should reset the loaded libraries and remove the + // fake library. + $this->drupalGet('ajax-test/dialog'); + $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); + $this->assertNotContains($fake_library, $libraries); + + // Click on the AJAX link again, and the libraries should still not contain + // the fake library. + $this->clickLink('Link 8 (ajax)'); + $assert->assertWaitOnAjaxRequest(); + $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); + $this->assertNotContains($fake_library, $libraries); + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxThemeTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxThemeTest.php deleted file mode 100644 index 929a943ad..000000000 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxThemeTest.php +++ /dev/null @@ -1,45 +0,0 @@ -install(['stable', 'seven']); - $theme_config = \Drupal::configFactory()->getEditable('system.theme'); - $theme_config->set('admin', 'seven'); - $theme_config->set('default', 'stable'); - $theme_config->save(); - - $account = $this->drupalCreateUser(['view the administration theme']); - $this->drupalLogin($account); - - // First visit the site directly via the URL. This should render it in the - // admin theme. - $this->drupalGet('admin/ajax-test/theme'); - $assert = $this->assertSession(); - $assert->pageTextContains('Current theme: seven'); - - // Now click the modal, which should also use the admin theme. - $this->drupalGet('ajax-test/dialog'); - $assert->pageTextNotContains('Current theme: stable'); - $this->clickLink('Link 8 (ajax)'); - $assert->assertWaitOnAjaxRequest(); - - $assert->pageTextContains('Current theme: stable'); - $assert->pageTextNotContains('Current theme: seven'); - } - -} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php new file mode 100644 index 000000000..23c2f0bb9 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php @@ -0,0 +1,116 @@ +drupalCreateUser(array( + 'access content', + )); + $this->drupalLogin($account); + } + + /** + * Tests that machine name field functions. + * + * Makes sure that the machine name field automatically provides a valid + * machine name and that the manual editing mode functions. + */ + public function testMachineName() { + // Visit the machine name test page which contains two machine name fields. + $this->drupalGet('form-test/machine-name'); + + // Test values for conversion. + $test_values = [ + [ + 'input' => 'Test value !0-9@', + 'message' => 'A title that should be transliterated must be equal to the php generated machine name', + 'expected' => 'test_value_0_9_', + ], + [ + 'input' => 'Test value', + 'message' => 'A title that should not be transliterated must be equal to the php generated machine name', + 'expected' => 'test_value', + ], + ]; + + // Get page and session. + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + // Get elements from the page. + $title_1 = $page->findField('machine_name_1_label'); + $machine_name_1_field = $page->findField('machine_name_1'); + $machine_name_2_field = $page->findField('machine_name_2'); + $machine_name_1_wrapper = $machine_name_1_field->getParent(); + $machine_name_2_wrapper = $machine_name_2_field->getParent(); + $machine_name_1_value = $page->find('css', '#edit-machine-name-1-label-machine-name-suffix .machine-name-value'); + $machine_name_2_value = $page->find('css', '#edit-machine-name-2-label-machine-name-suffix .machine-name-value'); + $button_1 = $page->find('css', '#edit-machine-name-1-label-machine-name-suffix button.link'); + + // Assert both fields are initialized correctly. + $this->assertNotEmpty($machine_name_1_value, 'Machine name field 1 must be initialized'); + $this->assertNotEmpty($machine_name_2_value, 'Machine name field 2 must be initialized'); + + // Field must be present for the rest of the test to work. + if (empty($machine_name_1_value)) { + $this->fail('Cannot finish test, missing machine name field'); + } + + // Test each value for conversion to a machine name. + foreach ($test_values as $test_info) { + // Set the value for the field, triggering the machine name update. + $title_1->setValue($test_info['input']); + + // Wait the set timeout for fetching the machine name. + $this->getSession()->wait(1000, 'jQuery("#edit-machine-name-1-label-machine-name-suffix .machine-name-value").html() == "' . $test_info['expected'] . '"'); + + // Validate the generated machine name. + $this->assertEquals($test_info['expected'], $machine_name_1_value->getHtml(), $test_info['message']); + + // Validate the second machine name field is empty. + $this->assertEmpty($machine_name_2_value->getHtml(), 'The second machine name field should still be empty'); + } + + // Validate the machine name field is hidden. Elements are visually hidden + // using positioning, isVisible() will therefore not work. + $this->assertEquals(TRUE, $machine_name_1_wrapper->hasClass('visually-hidden'), 'The ID field must not be visible'); + $this->assertEquals(TRUE, $machine_name_2_wrapper->hasClass('visually-hidden'), 'The ID field must not be visible'); + + // Test switching back to the manual editing mode by clicking the edit link. + $button_1->click(); + + // Validate the visibility of the machine name field. + $this->assertEquals(FALSE, $machine_name_1_wrapper->hasClass('visually-hidden'), 'The ID field must now be visible'); + + // Validate the visibility of the second machine name field. + $this->assertEquals(TRUE, $machine_name_2_wrapper->hasClass('visually-hidden'), 'The ID field must not be visible'); + + // Validate if the element contains the correct value. + $this->assertEquals($test_values[1]['expected'], $machine_name_1_field->getValue(), 'The ID field value must be equal to the php generated machine name'); + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php new file mode 100644 index 000000000..a116c6fde --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceAutocompleteWidgetTest.php @@ -0,0 +1,102 @@ +createContentType(['type' => 'page']); + $this->createNode(['title' => 'Test page']); + $this->createNode(['title' => 'Page test']); + + $user = $this->drupalCreateUser([ + 'access content', + 'create page content', + ]); + $this->drupalLogin($user); + } + + /** + * Tests that the default autocomplete widget return the correct results. + */ + public function testEntityReferenceAutocompleteWidget() { + // Create an entity reference field and use the default 'CONTAINS' match + // operator. + $field_name = 'field_test'; + $this->createEntityReferenceField('node', 'page', $field_name, $field_name, 'node', 'default', ['target_bundles' => ['page']]); + entity_get_form_display('node', 'page', 'default') + ->setComponent($field_name, [ + 'type' => 'entity_reference_autocomplete', + 'settings' => array( + 'match_operator' => 'CONTAINS', + ), + ]) + ->save(); + + // Visit the node add page. + $this->drupalGet('node/add/page'); + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + $autocomplete_field = $page->findField($field_name . '[0][target_id]'); + $autocomplete_field->setValue('Test'); + $this->getSession()->getDriver()->keyDown($autocomplete_field->getXpath(), ' '); + $assert_session->waitOnAutocomplete(); + + $results = $page->findAll('css', '.ui-autocomplete li'); + + $this->assertCount(2, $results); + $assert_session->pageTextContains('Test page'); + $assert_session->pageTextContains('Page test'); + + // Now switch the autocomplete widget to the 'STARTS_WITH' match operator. + entity_get_form_display('node', 'page', 'default') + ->setComponent($field_name, [ + 'type' => 'entity_reference_autocomplete', + 'settings' => array( + 'match_operator' => 'STARTS_WITH', + ), + ]) + ->save(); + + $this->drupalGet('node/add/page'); + $page = $this->getSession()->getPage(); + + $autocomplete_field = $page->findField($field_name . '[0][target_id]'); + $autocomplete_field->setValue('Test'); + $this->getSession()->getDriver()->keyDown($autocomplete_field->getXpath(), ' '); + $assert_session->waitOnAutocomplete(); + + $results = $page->findAll('css', '.ui-autocomplete li'); + + $this->assertCount(1, $results); + $assert_session->pageTextContains('Test page'); + $assert_session->pageTextNotContains('Page test'); + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php b/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php index 8d3ff479f..46f6017ed 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php @@ -28,4 +28,15 @@ class JSWebAssert extends WebAssert { } } + /** + * Waits for the jQuery autocomplete delay duration. + * + * @see https://api.jqueryui.com/autocomplete/#option-delay + */ + public function waitOnAutocomplete() { + // Drupal is using the default delay value of 300 milliseconds. + $this->session->wait(300); + $this->assertWaitOnAjaxRequest(); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php b/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php index 9b5db1a6c..e4f4d6704 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php @@ -113,6 +113,19 @@ class FetchTest extends DatabaseTestBase { $this->assertIdentical(count($records), 1, 'There is only one record.'); } + /** + * Confirms that we can fetch all records into an array explicitly. + */ + public function testQueryFetchAllColumn() { + $query = db_select('test'); + $query->addField('test', 'name'); + $query->orderBy('name'); + $query_result = $query->execute()->fetchAll(\PDO::FETCH_COLUMN); + + $expected_result = ['George', 'John', 'Paul', 'Ringo']; + $this->assertEqual($query_result, $expected_result, 'Returned the correct result.'); + } + /** * Confirms that we can fetch an entire column of a result set at once. */ diff --git a/core/tests/Drupal/KernelTests/Core/Datetime/FormatDateTest.php b/core/tests/Drupal/KernelTests/Core/Datetime/FormatDateTest.php new file mode 100644 index 000000000..5fbb4dff0 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Datetime/FormatDateTest.php @@ -0,0 +1,92 @@ +installConfig(['system']); + + $this->setSetting('locale_custom_strings_' . self::LANGCODE, [ + '' => ['Sunday' => 'domingo'], + 'Long month name' => ['March' => 'marzo'], + ]); + + $formats = $this->container->get('entity.manager') + ->getStorage('date_format') + ->loadMultiple(['long', 'medium', 'short']); + $formats['long']->setPattern('l, j. F Y - G:i')->save(); + $formats['medium']->setPattern('j. F Y - G:i')->save(); + $formats['short']->setPattern('Y M j - g:ia')->save(); + + ConfigurableLanguage::createFromLangcode(static::LANGCODE)->save(); + } + + /** + * Tests the format_date() function. + */ + public function testFormatDate() { + /** @var \Drupal\Core\Datetime\DateFormatterInterface $formatter */ + $formatter = $this->container->get('date.formatter'); + + $timestamp = strtotime('2007-03-26T00:00:00+00:00'); + $this->assertSame('Sunday, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Test all parameters.'); + $this->assertSame('domingo, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test translated format.'); + $this->assertSame('l, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test an escaped format string.'); + $this->assertSame('\\domingo, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test format containing backslash character.'); + $this->assertSame('\\l, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'Test format containing backslash followed by escaped format string.'); + $this->assertSame('Monday, 26-Mar-07 01:00:00 BST', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Test a different time zone.'); + + // Change the default language and timezone. + $this->config('system.site')->set('default_langcode', static::LANGCODE)->save(); + date_default_timezone_set('America/Los_Angeles'); + + // Reset the language manager so new negotiations attempts will fall back on + // on the new language. + $this->container->get('language_manager')->reset(); + + $this->assertSame('Sunday, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Test a different language.'); + $this->assertSame('Monday, 26-Mar-07 01:00:00 BST', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Test a different time zone.'); + $this->assertSame('domingo, 25-Mar-07 17:00:00 PDT', $formatter->format($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'Test custom date format.'); + $this->assertSame('domingo, 25. marzo 2007 - 17:00', $formatter->format($timestamp, 'long'), 'Test long date format.'); + $this->assertSame('25. marzo 2007 - 17:00', $formatter->format($timestamp, 'medium'), 'Test medium date format.'); + $this->assertSame('2007 Mar 25 - 5:00pm', $formatter->format($timestamp, 'short'), 'Test short date format.'); + $this->assertSame('25. marzo 2007 - 17:00', $formatter->format($timestamp), 'Test default date format.'); + // Test HTML time element formats. + $this->assertSame('2007-03-25T17:00:00-0700', $formatter->format($timestamp, 'html_datetime'), 'Test html_datetime date format.'); + $this->assertSame('2007-03-25', $formatter->format($timestamp, 'html_date'), 'Test html_date date format.'); + $this->assertSame('17:00:00', $formatter->format($timestamp, 'html_time'), 'Test html_time date format.'); + $this->assertSame('03-25', $formatter->format($timestamp, 'html_yearless_date'), 'Test html_yearless_date date format.'); + $this->assertSame('2007-W12', $formatter->format($timestamp, 'html_week'), 'Test html_week date format.'); + $this->assertSame('2007-03', $formatter->format($timestamp, 'html_month'), 'Test html_month date format.'); + $this->assertSame('2007', $formatter->format($timestamp, 'html_year'), 'Test html_year date format.'); + + // HTML is not escaped by the date formatter, it must be escaped later. + $this->assertSame("", $formatter->format($timestamp, 'custom', '\<\s\c\r\i\p\t\>\a\l\e\r\t\(\'Y\'\)\;\<\/\s\c\r\i\p\t\>'), 'Script tags not removed from dates.'); + $this->assertSame('2007', $formatter->format($timestamp, 'custom', '\<\e\m\>Y\<\/\e\m\>'), 'Em tags are not removed from dates.'); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/File/DirectoryTest.php b/core/tests/Drupal/KernelTests/Core/File/DirectoryTest.php index 774bd7c0e..d7e7c382f 100644 --- a/core/tests/Drupal/KernelTests/Core/File/DirectoryTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/DirectoryTest.php @@ -161,4 +161,17 @@ class DirectoryTest extends FileTestBase { $this->assertEqual($config->get('path.temporary'), $tmp_directory); } + /** + * Tests directory creation. + */ + public function testDirectoryCreation() { + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = $this->container->get('file_system'); + + // mkdir() recursion should work with or without a trailing slash. + $dir = $this->siteDirectory . '/files'; + $this->assertTrue($file_system->mkdir($dir . '/foo/bar', 0775, TRUE)); + $this->assertTrue($file_system->mkdir($dir . '/foo/baz/', 0775, TRUE)); + } + } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index bc3ef4252..8d0cd39a0 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -528,10 +528,11 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { * The file path. */ public static function filePreDeleteCallback($path) { - $success = @chmod($path, 0700); - if (!$success) { - trigger_error("Can not make $path writable whilst cleaning up test directory. The webserver and phpunit are probably not being run by the same user."); - } + // When the webserver runs with the same system user as phpunit, we can + // make read-only files writable again. If not, chmod will fail while the + // file deletion still works if file permissions have been configured + // correctly. Thus, we ignore any problems while running chmod. + @chmod($path, 0700); } /** diff --git a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php index 43d17fab1..121aebc95 100644 --- a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php @@ -86,7 +86,7 @@ class LibraryDiscoveryParserTest extends UnitTestCase { $path = substr($path, strlen($this->root) + 1); $this->libraryDiscoveryParser->setPaths('module', 'example_module', $path); - $libraries = $this->libraryDiscoveryParser->buildByExtension('example_module', 'example'); + $libraries = $this->libraryDiscoveryParser->buildByExtension('example_module'); $library = $libraries['example']; $this->assertCount(0, $library['js']); diff --git a/core/tests/Drupal/Tests/Core/Cache/Context/SessionCacheContextTest.php b/core/tests/Drupal/Tests/Core/Cache/Context/SessionCacheContextTest.php new file mode 100644 index 000000000..b621b2dac --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Cache/Context/SessionCacheContextTest.php @@ -0,0 +1,85 @@ +requestStack = new RequestStack(); + $this->requestStack->push($request); + + $this->session = $this->getMock('\Symfony\Component\HttpFoundation\Session\SessionInterface'); + $request->setSession($this->session); + + $this->cacheContext = new SessionCacheContext($this->requestStack); + } + + /** + * @covers ::getContext + */ + public function testSameContextForSameSession() { + $session_id = 'aSebeZ52bbM6SvADurQP89SFnEpxY6j8'; + $this->session->expects($this->exactly(2)) + ->method('getId') + ->will($this->returnValue($session_id)); + + $context1 = $this->cacheContext->getContext(); + $context2 = $this->cacheContext->getContext(); + $this->assertSame($context1, $context2); + $this->assertSame(FALSE, strpos($context1, $session_id), 'Session ID not contained in cache context'); + } + + /** + * @covers ::getContext + */ + public function testDifferentContextForDifferentSession() { + $session1_id = 'pjH_8aSoofyCDQiuVYXJcbfyr-CPtkUY'; + $this->session->expects($this->at(0)) + ->method('getId') + ->will($this->returnValue($session1_id)); + + $session2_id = 'aSebeZ52bbM6SvADurQP89SFnEpxY6j8'; + $this->session->expects($this->at(1)) + ->method('getId') + ->will($this->returnValue($session2_id)); + + $context1 = $this->cacheContext->getContext(); + $context2 = $this->cacheContext->getContext(); + $this->assertNotEquals($context1, $context2); + + $this->assertSame(FALSE, strpos($context1, $session1_id), 'Session ID not contained in cache context'); + $this->assertSame(FALSE, strpos($context2, $session2_id), 'Session ID not contained in cache context'); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php index bacab58af..6e164708b 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php @@ -416,6 +416,27 @@ class EntityResolverManagerTest extends UnitTestCase { $this->assertEquals($expect, $parameters); } + /** + * Tests setRouteOptions() with an _entity_form route for an add form. + * + * @covers ::setRouteOptions + * @covers ::getControllerClass + * @covers ::getEntityTypes + * @covers ::setParametersFromReflection + * @covers ::setParametersFromEntityInformation + */ + public function testSetRouteOptionsWithEntityAddFormRoute() { + $this->setupEntityTypes(); + $route = new Route('/example/add', array( + '_entity_form' => 'entity_test.add', + )); + + $defaults = $route->getDefaults(); + $this->entityResolverManager->setRouteOptions($route); + $this->assertEquals($defaults, $route->getDefaults()); + $this->assertFalse($route->hasOption('parameters')); + } + /** * Creates the entity manager mock returning entity type objects. */ diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/PathRootsSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/PathRootsSubscriberTest.php index 9e77accfe..96a922829 100644 --- a/core/tests/Drupal/Tests/Core/EventSubscriber/PathRootsSubscriberTest.php +++ b/core/tests/Drupal/Tests/Core/EventSubscriber/PathRootsSubscriberTest.php @@ -43,6 +43,11 @@ class PathRootsSubscriberTest extends UnitTestCase { * @covers ::onRouteFinished */ public function testSubscribing() { + + // Ensure that onRouteFinished can be called without throwing notices + // when no path roots got set. + $this->pathRootsSubscriber->onRouteFinished(); + $route_collection = new RouteCollection(); $route_collection->add('test_route1', new Route('/test/bar')); $route_collection->add('test_route2', new Route('/test/baz')); diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index 1530292d9..dfbf843a0 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -568,6 +568,29 @@ class FormBuilderTest extends FormTestBase { $this->formBuilder->buildForm($form_arg, $form_state); } + /** + * @covers ::buildForm + */ + public function testGetPostAjaxRequest() { + $request = new Request([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE], ['form_id' => 'different_form_id']); + $request->setMethod('POST'); + $this->requestStack->push($request); + + $form_state = (new FormState()) + ->setUserInput([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]) + ->setMethod('get') + ->setAlwaysProcess() + ->disableRedirect() + ->set('ajax', TRUE); + + $form_id = '\Drupal\Tests\Core\Form\TestForm'; + $expected_form = (new TestForm())->buildForm([], $form_state); + + $form = $this->formBuilder->buildForm($form_id, $form_state); + $this->assertFormElement($expected_form, $form, 'test'); + $this->assertSame('test-form', $form['#id']); + } + /** * @covers ::buildForm * diff --git a/core/tests/Drupal/Tests/Core/Routing/ContentTypeHeaderMatcherTest.php b/core/tests/Drupal/Tests/Core/Routing/ContentTypeHeaderMatcherTest.php index ec9783886..e2c73bbe9 100644 --- a/core/tests/Drupal/Tests/Core/Routing/ContentTypeHeaderMatcherTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/ContentTypeHeaderMatcherTest.php @@ -4,12 +4,16 @@ namespace Drupal\Tests\Core\Routing; use Drupal\Core\Routing\ContentTypeHeaderMatcher; use Drupal\Tests\UnitTestCase; +use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; /** * Confirm that the content types partial matcher is functioning properly. * * @group Routing + * + * @coversDefaultClass \Drupal\Core\Routing\ContentTypeHeaderMatcher */ class ContentTypeHeaderMatcherTest extends UnitTestCase { @@ -88,8 +92,7 @@ class ContentTypeHeaderMatcherTest extends UnitTestCase { /** * Confirms that the matcher throws an exception for no-route. * - * @expectedException \Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException - * @expectedExceptionMessage No route found that matches "Content-Type: application/hal+json" + * @covers ::filter */ public function testNoRouteFound() { $matcher = new ContentTypeHeaderMatcher(); @@ -97,8 +100,24 @@ class ContentTypeHeaderMatcherTest extends UnitTestCase { $routes = $this->fixtures->contentRouteCollection(); $request = Request::create('path/two', 'POST'); $request->headers->set('Content-type', 'application/hal+json'); + $this->setExpectedException(UnsupportedMediaTypeHttpException::class, 'No route found that matches "Content-Type: application/hal+json"'); + $matcher->filter($routes, $request); + } + + /** + * Confirms that the matcher throws an exception for missing request header. + * + * @covers ::filter + */ + public function testContentTypeRequestHeaderMissing() { + $matcher = new ContentTypeHeaderMatcher(); + + $routes = $this->fixtures->contentRouteCollection(); + $request = Request::create('path/two', 'POST'); + // Delete all request headers that Request::create() sets by default. + $request->headers = new ParameterBag(); + $this->setExpectedException(UnsupportedMediaTypeHttpException::class, 'No "Content-Type" request header specified'); $matcher->filter($routes, $request); - $this->fail('No exception was thrown.'); } } diff --git a/core/themes/classy/templates/layout/html.html.twig b/core/themes/classy/templates/layout/html.html.twig index 8330ccc77..4fe57a01c 100644 --- a/core/themes/classy/templates/layout/html.html.twig +++ b/core/themes/classy/templates/layout/html.html.twig @@ -8,7 +8,7 @@ * - root_path: The root path of the current page (e.g., node, admin, user). * - node_type: The content type for the current node, if the page is a node. * - head_title: List of text elements that make up the head_title variable. - * May contain or more of the following: + * May contain one or more of the following: * - title: The title of the page. * - name: The name of the site. * - slogan: The slogan of the site. diff --git a/core/themes/stable/templates/admin/image-crop-summary.html.twig b/core/themes/stable/templates/admin/image-crop-summary.html.twig index 6f3b0fcb3..e401f2423 100644 --- a/core/themes/stable/templates/admin/image-crop-summary.html.twig +++ b/core/themes/stable/templates/admin/image-crop-summary.html.twig @@ -16,15 +16,15 @@ */ #} {% if data.width and data.height -%} - {{ data.width|e }}×{{ data.height|e }} + {{ data.width }}×{{ data.height }} {%- else -%} {% if data.width %} {% trans %} - width {{ data.width|e }} + width {{ data.width }} {% endtrans %} {% elseif data.height %} {% trans %} - height {{ data.height|e }} + height {{ data.height }} {% endtrans %} {% endif %} {%- endif %} diff --git a/core/themes/stable/templates/admin/image-resize-summary.html.twig b/core/themes/stable/templates/admin/image-resize-summary.html.twig index fde09d23a..dc0ba198f 100644 --- a/core/themes/stable/templates/admin/image-resize-summary.html.twig +++ b/core/themes/stable/templates/admin/image-resize-summary.html.twig @@ -14,15 +14,15 @@ */ #} {% if data.width and data.height -%} - {{ data.width|e }}×{{ data.height|e }} + {{ data.width }}×{{ data.height }} {%- else -%} {% if data.width %} {% trans %} - width {{ data.width|e }} + width {{ data.width }} {% endtrans %} {% elseif data.height %} {% trans %} - height {{ data.height|e }} + height {{ data.height }} {% endtrans %} {% endif %} {%- endif %} diff --git a/core/themes/stable/templates/admin/image-rotate-summary.html.twig b/core/themes/stable/templates/admin/image-rotate-summary.html.twig index 542f2df80..c3f446e52 100644 --- a/core/themes/stable/templates/admin/image-rotate-summary.html.twig +++ b/core/themes/stable/templates/admin/image-rotate-summary.html.twig @@ -17,8 +17,9 @@ */ #} {% if data.random %} + {% set degrees = data.degrees|abs %} {% trans %} - random between -{{ data.degrees|abs }}° and {{ data.degrees|abs }}° + random between -{{ degrees }}° and {{ degrees }}° {% endtrans %} {% else %} {{ data.degrees }}° diff --git a/core/themes/stable/templates/admin/image-scale-summary.html.twig b/core/themes/stable/templates/admin/image-scale-summary.html.twig index bf54445a0..5b7c6864c 100644 --- a/core/themes/stable/templates/admin/image-scale-summary.html.twig +++ b/core/themes/stable/templates/admin/image-scale-summary.html.twig @@ -15,15 +15,15 @@ */ #} {% if data.width and data.height -%} - {{ data.width|e }}×{{ data.height|e }} + {{ data.width }}×{{ data.height }} {%- else -%} {% if data.width %} {% trans %} - width {{ data.width|e }} + width {{ data.width }} {% endtrans %} {% elseif data.height %} {% trans %} - height {{ data.height|e }} + height {{ data.height }} {% endtrans %} {% endif %} {%- endif %} diff --git a/core/themes/stable/templates/layout/html.html.twig b/core/themes/stable/templates/layout/html.html.twig index 5e4f25e86..32079e6fa 100644 --- a/core/themes/stable/templates/layout/html.html.twig +++ b/core/themes/stable/templates/layout/html.html.twig @@ -8,7 +8,7 @@ * - root_path: The root path of the current page (e.g., node, admin, user). * - node_type: The content type for the current node, if the page is a node. * - head_title: List of text elements that make up the head_title variable. - * May contain or more of the following: + * May contain one or more of the following: * - title: The title of the page. * - name: The name of the site. * - slogan: The slogan of the site.