From 57a9bcd72192d079f6e7b87d940031ebeea40f80 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Thu, 4 Jan 2018 11:55:52 +0100 Subject: [PATCH] entrees block is displaying well and interact with map on mouse hover, map nodes are well drawn centered, map performance improvements, added domain contrib module and configured domain for mobile display --- .../modules/contrib/admin/domain/.travis.yml | 99 ++ .../modules/contrib/admin/domain/CHANGELOG.md | 126 ++ .../all/modules/contrib/admin/domain/LICENSE | 339 ++++ .../modules/contrib/admin/domain/LICENSE.txt | 339 ++++ .../modules/contrib/admin/domain/README.md | 244 +++ .../contrib/admin/domain/composer.json | 17 + .../domain/config/install/domain.settings.yml | 4 + ...eld.field.user.user.field_domain_admin.yml | 24 + .../field.storage.user.field_domain_admin.yml | 19 + .../system.action.domain_default_action.yml | 10 + .../system.action.domain_delete_action.yml | 10 + .../system.action.domain_disable_action.yml | 10 + .../system.action.domain_enable_action.yml | 10 + .../domain/config/schema/domain.schema.yml | 85 + .../admin/domain/domain/domain.api.php | 138 ++ .../admin/domain/domain/domain.drush.inc | 641 ++++++++ .../admin/domain/domain/domain.info.yml | 15 + .../admin/domain/domain/domain.install | 38 + .../admin/domain/domain/domain.libraries.yml | 7 + .../domain/domain/domain.links.action.yml | 5 + .../admin/domain/domain/domain.links.menu.yml | 18 + .../admin/domain/domain/domain.links.task.yml | 8 + .../contrib/admin/domain/domain/domain.module | 162 ++ .../domain/domain/domain.permissions.yml | 32 + .../admin/domain/domain/domain.routing.yml | 55 + .../admin/domain/domain/domain.services.yml | 41 + .../contrib/admin/domain/domain/js/domain.js | 42 + .../domain/migration_templates/d7_domain.yml | 17 + .../domain/src/Access/DomainAccessCheck.php | 93 ++ .../domain/src/Access/DomainListCheck.php | 28 + .../domain/src/Access/DomainRouteCheck.php | 82 + .../ContextProvider/CurrentDomainContext.php | 65 + .../src/Controller/DomainController.php | 76 + .../src/Controller/DomainControllerBase.php | 59 + .../domain/src/DomainAccessControlHandler.php | 119 ++ .../admin/domain/domain/src/DomainCreator.php | 81 + .../domain/src/DomainCreatorInterface.php | 47 + .../domain/src/DomainElementManager.php | 204 +++ .../src/DomainElementManagerInterface.php | 105 ++ .../admin/domain/domain/src/DomainForm.php | 218 +++ .../domain/domain/src/DomainInterface.php | 241 +++ .../domain/domain/src/DomainListBuilder.php | 347 ++++ .../admin/domain/domain/src/DomainLoader.php | 162 ++ .../domain/src/DomainLoaderInterface.php | 118 ++ .../domain/domain/src/DomainNegotiator.php | 223 +++ .../domain/src/DomainNegotiatorInterface.php | 91 ++ .../domain/src/DomainRedirectResponse.php | 180 ++ .../admin/domain/domain/src/DomainStorage.php | 205 +++ .../domain/src/DomainStorageInterface.php | 124 ++ .../admin/domain/domain/src/DomainToken.php | 187 +++ .../domain/domain/src/DomainValidator.php | 154 ++ .../domain/src/DomainValidatorInterface.php | 45 + .../admin/domain/domain/src/Entity/Domain.php | 565 +++++++ .../src/EventSubscriber/DomainSubscriber.php | 140 ++ .../domain/src/Form/DomainDeleteForm.php | 45 + .../domain/src/Form/DomainSettingsForm.php | 92 ++ .../src/Plugin/Action/DefaultDomain.php | 47 + .../domain/src/Plugin/Action/DeleteDomain.php | 47 + .../src/Plugin/Action/DisableDomain.php | 47 + .../domain/src/Plugin/Action/EnableDomain.php | 47 + .../src/Plugin/Block/DomainBlockBase.php | 20 + .../src/Plugin/Block/DomainNavBlock.php | 213 +++ .../src/Plugin/Block/DomainServerBlock.php | 126 ++ .../src/Plugin/Block/DomainSwitcherBlock.php | 52 + .../src/Plugin/Block/DomainTokenBlock.php | 83 + .../domain/src/Plugin/Condition/Domain.php | 145 ++ .../DomainAdminSelection.php | 36 + .../DomainSelection.php | 114 ++ .../Plugin/migrate/source/d7/DomainRecord.php | 56 + .../domain/src/Plugin/views/access/Domain.php | 189 +++ .../domain/src/Tests/DomainTestBase.php | 102 ++ .../templates/domain-nav-block.html.twig | 32 + .../contrib/admin/domain/domain/tests/200.png | Bin 0 -> 593 bytes .../views.view.domain_views_access.yml | 172 ++ .../modules/domain_test/domain_test.info.yml | 16 + .../modules/domain_test/domain_test.module | 60 + .../Condition/DomainConditionTest.php | 88 + .../src/Functional/DomainActionsTest.php | 103 ++ .../src/Functional/DomainAdminElementTest.php | 142 ++ .../tests/src/Functional/DomainCSSTest.php | 77 + .../Functional/DomainCheckResponseTest.php | 59 + .../tests/src/Functional/DomainCreateTest.php | 54 + .../Functional/DomainEntityReferenceTest.php | 160 ++ .../tests/src/Functional/DomainFormsTest.php | 79 + .../src/Functional/DomainGetResponseTest.php | 44 + .../src/Functional/DomainInactiveTest.php | 98 ++ .../src/Functional/DomainListBuilderTest.php | 213 +++ .../src/Functional/DomainListWeightTest.php | 110 ++ .../src/Functional/DomainNavBlockTest.php | 97 ++ .../src/Functional/DomainNegotiatorTest.php | 46 + .../src/Functional/DomainReferencesTest.php | 216 +++ .../tests/src/Functional/DomainTestBase.php | 218 +++ .../tests/src/Functional/DomainTokenTest.php | 76 + .../src/Functional/DomainValidatorTest.php | 91 ++ .../src/Functional/DomainViewsAccessTest.php | 84 + .../tests/src/Kernel/DomainHookTest.php | 160 ++ .../src/Kernel/DomainVariableSchemeTest.php | 72 + .../tests/src/Traits/DomainTestTrait.php | 158 ++ .../config/install/domain_access.settings.yml | 1 + ...field.storage.node.field_domain_access.yml | 22 + ...orage.node.field_domain_all_affiliates.yml | 21 + ...field.storage.user.field_domain_access.yml | 22 + ...orage.user.field_domain_all_affiliates.yml | 21 + ...system.action.domain_access_all_action.yml | 10 + ...m.action.domain_access_edit_all_action.yml | 10 + ....action.domain_access_edit_none_action.yml | 10 + ...ystem.action.domain_access_none_action.yml | 10 + .../config/schema/domain_access.schema.yml | 47 + .../domain_access/domain_access.info.yml | 15 + .../domain_access/domain_access.install | 36 + .../domain_access.links.menu.yml | 6 + .../domain_access.links.task.yml | 4 + .../domain/domain_access/domain_access.module | 735 +++++++++ .../domain_access.permissions.yml | 2 + .../domain_access/domain_access.routing.yml | 7 + .../domain_access/domain_access.services.yml | 9 + .../src/Access/DomainAccessViewsAccess.php | 101 ++ .../domain_access/src/DomainAccessManager.php | 140 ++ .../src/DomainAccessManagerInterface.php | 85 + .../src/DomainAccessPermissions.php | 86 + .../src/Form/DomainAccessSettingsForm.php | 53 + .../Plugin/Action/DomainAccessActionBase.php | 100 ++ .../src/Plugin/Action/DomainAccessAdd.php | 31 + .../Plugin/Action/DomainAccessAddEditor.php | 31 + .../src/Plugin/Action/DomainAccessAll.php | 24 + .../src/Plugin/Action/DomainAccessEditAll.php | 24 + .../Plugin/Action/DomainAccessEditNone.php | 24 + .../src/Plugin/Action/DomainAccessNone.php | 24 + .../src/Plugin/Action/DomainAccessRemove.php | 31 + .../Action/DomainAccessRemoveEditor.php | 31 + .../views/access/DomainAccessContent.php | 169 ++ .../views/access/DomainAccessEditor.php | 26 + .../views/argument/DomainAccessArgument.php | 24 + .../Plugin/views/field/DomainAccessField.php | 50 + .../filter/DomainAccessCurrentAllFilter.php | 73 + .../views/filter/DomainAccessFilter.php | 26 + ...rage.taxonomy_term.field_domain_access.yml | 19 + ...onomy_term.field_domain_all_affiliates.yml | 18 + .../domain_access_test.info.yml | 18 + .../DomainAccessAllAffiliatesTest.php | 103 ++ .../DomainAccessDefaultValueTest.php | 99 ++ .../Functional/DomainAccessElementTest.php | 130 ++ .../DomainAccessEntityFieldTest.php | 71 + .../DomainAccessEntityReferenceTest.php | 98 ++ .../src/Functional/DomainAccessFieldTest.php | 190 +++ .../src/Functional/DomainAccessGrantsTest.php | 88 + .../DomainAccessLanguageSaveTest.php | 82 + .../DomainAccessPermissionsTest.php | 295 ++++ .../Functional/DomainAccessRecordsTest.php | 80 + .../src/Functional/DomainAccessSaveTest.php | 76 + .../admin/domain/domain_alias/README.md | 182 +++ .../config/install/domain_alias.settings.yml | 6 + .../config/schema/domain_alias.schema.yml | 34 + .../domain/domain_alias/domain_alias.info.yml | 14 + .../domain/domain_alias/domain_alias.install | 32 + .../domain_alias.links.action.yml | 5 + .../domain_alias/domain_alias.links.task.yml | 4 + .../domain/domain_alias/domain_alias.module | 168 ++ .../domain_alias/domain_alias.permissions.yml | 14 + .../domain_alias/domain_alias.routing.yml | 34 + .../domain_alias/domain_alias.services.yml | 8 + .../src/Controller/DomainAliasController.php | 48 + .../src/DomainAliasAccessControlHandler.php | 45 + .../domain_alias/src/DomainAliasForm.php | 235 +++ .../domain_alias/src/DomainAliasInterface.php | 44 + .../src/DomainAliasListBuilder.php | 103 ++ .../domain_alias/src/DomainAliasLoader.php | 258 +++ .../src/DomainAliasLoaderInterface.php | 104 ++ .../domain_alias/src/DomainAliasStorage.php | 250 +++ .../src/DomainAliasStorageInterface.php | 76 + .../domain_alias/src/DomainAliasValidator.php | 121 ++ .../src/DomainAliasValidatorInterface.php | 21 + .../domain_alias/src/Entity/DomainAlias.php | 138 ++ .../src/Form/DomainAliasDeleteForm.php | 22 + .../src/Tests/DomainAliasTestBase.php | 33 + .../src/Functional/DomainAliasActionsTest.php | 147 ++ .../Functional/DomainAliasEnvironmentTest.php | 95 ++ .../Functional/DomainAliasListBuilderTest.php | 103 ++ .../DomainAliasListHostnameTest.php | 85 + .../Functional/DomainAliasNegotiatorTest.php | 111 ++ .../src/Functional/DomainAliasSortTest.php | 86 + .../src/Functional/DomainAliasTestBase.php | 29 + .../Functional/DomainAliasValidatorTest.php | 77 + .../Functional/DomainAliasWildcardTest.php | 87 + .../Kernel/DomainAliasDomainDeleteTest.php | 83 + .../tests/src/Traits/DomainAliasTestTrait.php | 67 + .../domain/domain_alpha/domain_alpha.info.yml | 14 + .../domain/domain_alpha/domain_alpha.install | 92 ++ .../admin/domain/domain_config/README.md | 111 ++ .../domain_config/domain_config.info.yml | 14 + .../domain/domain_config/domain_config.module | 6 + .../domain_config/domain_config.services.yml | 7 + .../src/DomainConfigOverrider.php | 192 +++ .../src/DomainConfigServiceProvider.php | 38 + .../src/Routing/DomainRouteProvider.php | 51 + .../src/Tests/DomainConfigTestBase.php | 71 + ...domain_config_middleware_test.settings.yml | 1 + .../domain_config_middleware_test.schema.yml | 8 + .../domain_config_middleware_test.info.yml | 17 + ...domain_config_middleware_test.services.yml | 8 + .../src/Middleware.php | 50 + ...in.config.four_example_com.system.site.yml | 3 + ....config.one_example_com.en.system.site.yml | 10 + ...onfig.three_example_com.en.system.site.yml | 9 + ....config.two_example_com.en.system.site.yml | 10 + ....config.two_example_com.es.system.site.yml | 10 + .../domain_config_test.info.yml | 17 + .../domain_config_test.module | 25 + .../Functional/DomainConfigAlterHookTest.php | 63 + .../Functional/DomainConfigHomepageTest.php | 71 + .../Functional/DomainConfigOverriderTest.php | 111 ++ .../src/Functional/DomainConfigTestBase.php | 66 + .../optional/views.view.domain_content.yml | 1301 +++++++++++++++ .../views.view.domain_content_editors.yml | 1454 +++++++++++++++++ .../domain_content/domain_content.info.yml | 18 + .../domain_content/domain_content.install | 17 + .../domain_content.links.menu.yml | 12 + .../domain_content/domain_content.module | 71 + .../domain_content.permissions.yml | 4 + .../domain_content/domain_content.routing.yml | 14 + .../Controller/DomainContentController.php | 125 ++ .../views/access/DomainContentAccess.php | 30 + .../views/access/DomainEditorAccess.php | 30 + .../Functional/DomainContentActionsTest.php | 79 + .../src/Functional/DomainContentCountTest.php | 49 + .../DomainContentPermissionsTest.php | 173 ++ .../src/Functional/DomainContentTestBase.php | 109 ++ .../admin/domain/domain_source/README.md | 58 + .../config/install/domain_source.settings.yml | 1 + ...field.storage.node.field_domain_source.yml | 22 + .../config/schema/domain_source.schema.yml | 10 + .../domain_source/domain_source.api.php | 64 + .../domain_source/domain_source.info.yml | 15 + .../domain_source/domain_source.install | 30 + .../domain_source/domain_source.libraries.yml | 7 + .../domain_source.links.menu.yml | 6 + .../domain_source.links.task.yml | 4 + .../domain/domain_source/domain_source.module | 265 +++ .../domain_source/domain_source.routing.yml | 7 + .../domain_source/domain_source.services.yml | 9 + .../domain/domain_source/js/domain_source.js | 59 + .../src/DomainSourceElementManager.php | 27 + .../src/DomainSourceServiceProvider.php | 20 + ...DomainSourceRedirectResponseSubscriber.php | 69 + .../src/Form/DomainSourceSettingsForm.php | 60 + .../HttpKernel/DomainSourcePathProcessor.php | 262 +++ .../src/Plugin/views/field/DomainSource.php | 50 + .../src/Plugin/views/filter/DomainSource.php | 41 + .../Functional/DomainSourceElementTest.php | 94 ++ .../DomainSourceEntityReferenceTest.php | 167 ++ .../Functional/DomainSourceExcludeTest.php | 90 + .../Functional/DomainSourceLanguageTest.php | 117 ++ .../DomainSourceTrustedHostTest.php | 76 + .../src/Functional/DomainSourceUrlTest.php | 62 + .../assets/dist/scripts/corpus.min.js | 178 +- .../edlp_corpus/assets/scripts/corpus.js | 178 +- .../src/Plugin/Block/BlockEntrees.php | 63 +- .../edlptheme/assets/dist/styles/app.min.css | 211 ++- .../custom/edlptheme/assets/img/search.svg | 35 + .../custom/edlptheme/assets/img/user.svg | 39 + .../custom/edlptheme/assets/styles/app.scss | 185 ++- .../edlptheme/assets/styles/base/_colors.scss | 44 +- .../themes/custom/edlptheme/edlptheme.theme | 26 + .../block/block--userlogin.html.twig | 41 + .../templates/form/form-element.html.twig | 96 ++ .../templates/form/input--textfield.html.twig | 16 + sites/all/themes/custom/edlptheme/theme.theme | 6 - sites/default/default.services.yml | 155 -- sites/default/default.settings.php | 716 -------- sites/development.services.yml | 6 + 270 files changed, 24298 insertions(+), 1127 deletions(-) create mode 100644 sites/all/modules/contrib/admin/domain/.travis.yml create mode 100644 sites/all/modules/contrib/admin/domain/CHANGELOG.md create mode 100644 sites/all/modules/contrib/admin/domain/LICENSE create mode 100644 sites/all/modules/contrib/admin/domain/LICENSE.txt create mode 100644 sites/all/modules/contrib/admin/domain/README.md create mode 100644 sites/all/modules/contrib/admin/domain/composer.json create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/install/domain.settings.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/install/field.field.user.user.field_domain_admin.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/install/field.storage.user.field_domain_admin.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_default_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_delete_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_disable_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_enable_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/config/schema/domain.schema.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.api.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.drush.inc create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.install create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.libraries.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.links.action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.links.menu.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.links.task.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.module create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.permissions.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.routing.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/domain.services.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/js/domain.js create mode 100644 sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Access/DomainListCheck.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Access/DomainRouteCheck.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/ContextProvider/CurrentDomainContext.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainController.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainControllerBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainAccessControlHandler.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainCreator.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainCreatorInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainElementManager.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainElementManagerInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainForm.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainListBuilder.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainLoader.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainLoaderInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiatorInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainRedirectResponse.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainStorage.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainStorageInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainToken.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/DomainValidatorInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Entity/Domain.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/EventSubscriber/DomainSubscriber.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Form/DomainDeleteForm.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Form/DomainSettingsForm.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DefaultDomain.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DeleteDomain.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DisableDomain.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/EnableDomain.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainBlockBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainNavBlock.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainAdminSelection.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainSelection.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Plugin/views/access/Domain.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/src/Tests/DomainTestBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/templates/domain-nav-block.html.twig create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/200.png create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/config/optional/views.view.domain_views_access.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.module create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/Condition/DomainConditionTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainActionsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainAdminElementTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCSSTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCheckResponseTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCreateTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainEntityReferenceTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainFormsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainGetResponseTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainInactiveTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListBuilderTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListWeightTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNavBlockTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNegotiatorTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainReferencesTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTestBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTokenTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainValidatorTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainViewsAccessTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainHookTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainVariableSchemeTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain/tests/src/Traits/DomainTestTrait.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/domain_access.settings.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_access.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_all_affiliates.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_access.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_all_affiliates.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_all_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_all_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_none_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_none_action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.schema.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/domain_access.install create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.menu.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.task.yml create mode 100755 sites/all/modules/contrib/admin/domain/domain_access/domain_access.module create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/domain_access.permissions.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/domain_access.routing.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/domain_access.services.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Access/DomainAccessViewsAccess.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManager.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManagerInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessPermissions.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Form/DomainAccessSettingsForm.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessActionBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAdd.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAddEditor.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAll.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditAll.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditNone.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessNone.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemove.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemoveEditor.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessContent.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessEditor.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/argument/DomainAccessArgument.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/field/DomainAccessField.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessCurrentAllFilter.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessFilter.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_access.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_all_affiliates.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessAllAffiliatesTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessDefaultValueTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessElementTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityFieldTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityReferenceTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessGrantsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessLanguageSaveTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessPermissionsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessRecordsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessSaveTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/README.md create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/config/install/domain_alias.settings.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/config/schema/domain_alias.schema.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.install create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.action.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.task.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.module create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.permissions.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.routing.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.services.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/Controller/DomainAliasController.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasAccessControlHandler.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasForm.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasListBuilder.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoader.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoaderInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorage.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorageInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidator.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidatorInterface.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/Entity/DomainAlias.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/Form/DomainAliasDeleteForm.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/src/Tests/DomainAliasTestBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasActionsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasEnvironmentTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListBuilderTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListHostnameTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasNegotiatorTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasSortTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasTestBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasValidatorTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasWildcardTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Kernel/DomainAliasDomainDeleteTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Traits/DomainAliasTestTrait.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.install create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/README.md create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/domain_config.module create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/domain_config.services.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigOverrider.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigServiceProvider.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/src/Routing/DomainRouteProvider.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/src/Tests/DomainConfigTestBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/install/domain_config_middleware_test.settings.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/schema/domain_config_middleware_test.schema.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.services.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/src/Middleware.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.four_example_com.system.site.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.one_example_com.en.system.site.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.three_example_com.en.system.site.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.en.system.site.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.es.system.site.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.module create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigAlterHookTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHomepageTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigOverriderTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigTestBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/domain_content.install create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/domain_content.links.menu.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/domain_content.module create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/domain_content.permissions.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/domain_content.routing.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainContentAccess.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainEditorAccess.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentActionsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentCountTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentPermissionsTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentTestBase.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/README.md create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/config/install/domain_source.settings.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/config/install/field.storage.node.field_domain_source.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.schema.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.api.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.install create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.libraries.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.menu.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.task.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.module create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.routing.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/domain_source.services.yml create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/js/domain_source.js create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceElementManager.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceServiceProvider.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/src/Form/DomainSourceSettingsForm.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/field/DomainSource.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/filter/DomainSource.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceEntityReferenceTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceExcludeTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceLanguageTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceTrustedHostTest.php create mode 100644 sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceUrlTest.php create mode 100644 sites/all/themes/custom/edlptheme/assets/img/search.svg create mode 100644 sites/all/themes/custom/edlptheme/assets/img/user.svg create mode 100644 sites/all/themes/custom/edlptheme/edlptheme.theme create mode 100644 sites/all/themes/custom/edlptheme/templates/block/block--userlogin.html.twig create mode 100644 sites/all/themes/custom/edlptheme/templates/form/form-element.html.twig create mode 100644 sites/all/themes/custom/edlptheme/templates/form/input--textfield.html.twig delete mode 100644 sites/all/themes/custom/edlptheme/theme.theme delete mode 100644 sites/default/default.services.yml delete mode 100644 sites/default/default.settings.php diff --git a/sites/all/modules/contrib/admin/domain/.travis.yml b/sites/all/modules/contrib/admin/domain/.travis.yml new file mode 100644 index 000000000..b77fae3a0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/.travis.yml @@ -0,0 +1,99 @@ +language: php +sudo: false + +matrix: + fast_finish: true + include: + - env: DRUPAL=8.4.x + php: 7.0 + - env: DRUPAL=8.4.x + php: 5.6 + - env: DRUPAL=8.4.x + php: 5.5 + - env: DRUPAL=8.5.x + php: 7.1 + - env: DRUPAL=8.5.x + php: 7.0 + - env: DRUPAL=8.5.x + php: 5.6 + - env: DRUPAL=8.5.x + php: 5.5 + +addons: + hosts: + - example.com + - one.example.com + - two.example.com + - three.example.com + - four.example.com + - five.example.com + +cache: + directories: + - $HOME/.composer/cache + +env: + global: + # add composer's global bin directory to the path + # see: https://github.com/drush-ops/drush#install---composer + - PATH="$PATH:$HOME/.composer/vendor/bin" + +mysql: + database: domain + username: root + encoding: utf8 + +notifications: + email: false + +before_install: + # Remove Xdebug. Not an issue for PHP 7. + - phpenv config-rm xdebug.ini || true + + - composer self-update + + # Install Drush. + - composer global require --no-interaction drush/drush:8.* + +install: + # Optimize MySQL timeout and max packet size. + - echo "mysql.connect_timeout=3000" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo "default_socket_timeout=3000" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - mysql -e 'create database domain' + - mysql -e "SET GLOBAL wait_timeout = 36000;" + - mysql -e "SET GLOBAL max_allowed_packet = 134209536;" + +before_script: + + # Remember the current rules test directory for later use in the Drupal installation. + - TESTDIR=$(pwd) + # Navigate out of module directory to prevent blown stack by recursive module lookup. + - cd .. + + # Download Drupal 8 core. + - travis_retry drush dl drupal-$DRUPAL --drupal-project-rename=drupal + - cd drupal + + # Make the module appear in the correct place + - ln -s $TESTDIR modules/domain + + # Install drupal default profile + - /usr/bin/env PHP_OPTIONS="-d sendmail_path=$(which true)" drush --yes --verbose site-install minimal --db-url=mysql://root:@127.0.0.1/domain + - drush --yes en simpletest domain domain_access domain_alias domain_config domain_source + - drush cr + + # Start a web server on port 8080 in the background. + - nohup php -S 0.0.0.0:8080 > /dev/null 2>&1 & + + # Wait until the web server is responding. + - until curl -s example.com:8080; do true; done > /dev/null + +script: + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_access + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_alias + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain_alias + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_config + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_content + - php core/scripts/run-tests.sh --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_source diff --git a/sites/all/modules/contrib/admin/domain/CHANGELOG.md b/sites/all/modules/contrib/admin/domain/CHANGELOG.md new file mode 100644 index 000000000..687fc5135 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/CHANGELOG.md @@ -0,0 +1,126 @@ +Changelog +===== + +31-JUL-2016 8.x-1.0-alpha1 +22-AUG-2016 8.x-1.0-alpha2 +30-AUG-2016 8.x-1.0-alpha3 +01-SEP-2016 8.x-1.0-alpha4 +25-OCT-2016 8.x-1.0-alpha5 +20-NOV-2016 8.x-1.0-alpha6 +06-DEC-2016 Adds the domain_alpha module to handle critical pre-release updates. +13-DEC-2016 8.x-1.0-alpha7 +12-MAR-2017 8.x-1.0-alpha8 +23-APR-2017 8.x-1.0-alpha9 +01-DEC-2017 8.x-1.0-alpha10 +19-DEC-2017 8.x-1.0-alpha11 + +Status +==== + +The 8.x-1.x version is a ground up rewrite of a module originally written for +Drupal 5. + +The following feature sets are considered critical for each release stage. Items +marked with [x] are considered complete. + +# Alpha +- [x] Domain entity for storage and configuration +- [x] Domain negotiation for active requests +- [x] API for modifying active domain requests +- [x] API for modifying paths per domain +- [x] Mechanism for access restriction inactive domains +- [x] Form for adding / editing / deleting domain records +- [x] Validation of domain record patterns +- [x] Domain server information block +- [x] Domain switcher block +- [x] Domain context for block visibility +- [x] Entity reference support, with an API for altering entity lists +- [x] Alias registration and negotiation +- [x] Alias validation +- [x] Configuration overrides per domain +- [x] Language-aware configuration overrides +- [x] Enable node access control via domain references (Domain Access) +- [x] Allow nodes to be assigned to domains +- [x] Allow users to be assigned as domain editors +- [x] Set permissions for content editing based on domain +- [x] Restrict publication options based on user domains +- [x] Support the all affiliates concept of publication +- [x] Support the all affiliates concept for editors +- [x] Test and document cross-domain logins +- [x] Make language optional for domain overrides +- [x] Use domain source for path rewrites +- [x] Finish the path alter behavior +- [x] Catalog @TODO items as issues +- [x] Drupal cache system makes it difficult to serve different homepage for each domain +- [x] Provide common views support for attached fields +- [x] Recreate the Domain Content module with bulk operations +- [x] Add help text to domain overview screen +- [x] Views argument handler to show proper title. +- [x] Ensure unique numeric ids for use with node access +- [x] Invalidate cache on Domain save +- [x] Invalidate render cache on Alias save +- [x] DomainConfigOverrider returns empty $overrides +- [x] Allow non-ascii domains and aliases +- [x] The domain_validate hook needs tests +- [x] Allow partial config loading + +# Beta +- [x] Actions for domain operations +- [x] Drush support for domain operations +- [x] Replace / inject the storage manager in DomainAliasLoader. +- [x] Replace / inject the storage manager in domainStorage. +- [x] Write tests for Domain Content. +- [x] Views access handler for domain content. +- [x] Restrict Domain Source options using JS +- [x] Handle site name overrides -- perhaps as a new field? +- [x] Restore the `domain_grant_all` permission? +- [x] Domain token support +- [x] Module configurations + - [x] Allow configuration of access-exempt paths for inactive domains + - [x] www prefix handling + - [x] Add domain-specific CSS classes + - [x] Path matching for URL rewrites? + - [x] Allow non-ascii characters in domains +- [x] Recreate the Domain Nav module +- [x] Allow selective access to domain record editing +- [x] Allow access to actions based on assigned domain permissions +- [x] Tests for all module hooks +- [x] Proper tests for domain record validation +- [x] Check test logic in testDomainAliasNegotiator() +- [x] Test that sort logic in DomainAliasLoader matches what is documented +- [x] Error handling in DomainAliasForm +- [x] Error checking in DomainAliasController +- [x] Deprecated methods in DomainAliasController +- [x] Error reporting in `domain_alias_domain_request_alter()` +- [x] Ensure completeness of DomainAccessPermissionsTest +- [x] Check module setup behavior in tests +- [x] Make all affiliates default value configurable +- [x] Review drupalUserIsLoggedIn() hack +- [x] Review DomainNegotiatorTest for completeness +- [x] Review core note in DomainEntityReferenceTest +- [x] Expand DomainActionsTest +- [x] DomainViewBuilder review +- [x] Dependency Injection in DomainValidator +- [x] Inject the module handler service in DomainListBuilder::getOperations() +- [x] `drush_domain_generate_domains()` has odd counting logic +- [x] Separate permissions for Domain Alias +- [x] Check loader logic in the DomainSource PathProcessor +- [x] Check loader logic in Domain Access `node_access` +- [x] Check id logic in Domain Alias list controller +- [x] Check domain responses on configuration forms +- [ ] Remove deprecated `entity_get_form_display` +- [ ] Implement theme functions or twig templates where proper +- [ ] Advanced drush integration / complete labelled tasks +- [ ] Test cron handling +- [ ] Caching strategies in DomainNegotiator +- [ ] Caching strategies in DomainConfigOverrides +- [ ] Cache in the DomainAccessManager +- [o] Recreate the Domain Theme module -- see https://www.drupal.org/project/domain_theme_switch + +# Final +- [ ] Security review +- [ ] Provide an upgrade path from 6.x +- [ ] Provide an upgrade path from 7.x-3.x +- [x] Remove calls to deprecated methods / classes +- [ ] Remove unnecessary use statements +- [ ] Support Tour module diff --git a/sites/all/modules/contrib/admin/domain/LICENSE b/sites/all/modules/contrib/admin/domain/LICENSE new file mode 100644 index 000000000..6b5dc9742 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/LICENSE @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Domain module for Drupal port to Drupal 8. + Copyright (C) 2013 Ken Rickard + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/admin/domain/LICENSE.txt b/sites/all/modules/contrib/admin/domain/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/admin/domain/README.md b/sites/all/modules/contrib/admin/domain/README.md new file mode 100644 index 000000000..67f3c8d85 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/README.md @@ -0,0 +1,244 @@ +Domain +====== + +The Domain module suite lets you share users, content, and configuration across a group of domains from a single installation and database. + +Current Status +------ + +Domain module for Drupal port to Drupal 8, under active development. + +Active branch is 8-x.1-x. Begin any forks from there. + +The underlying API is stable, and it's currently usable for access control. +The configuration supports manual editing. Themes should work. Views and Bulk +Operations are not yet supported. + +For a complete feature status list, see [CHANGELOG.md](https://github.com/agentrickard/domain/blob/8.x-1.x/CHANGELOG.md) + +Included modules +------- + +* *Domain* + The core module. Domain provides means for registering multiple domains within a + single Drupal installation. It allows users to be assigned as domain administrators, + provides a Block and Views display context, and creates a default entity reference + field for use by other modules. + +* *Domain Access* + Provides node access controls based on domains. (This module contains much of the + Drupal 7 functionality). It allows users to be assigned as editors of content per-domain, + sets content visibility rules, and provides Views integration for content. + +* *Domain Alias* + Allows multiple hostnames to be pointed to a single registered domain. These aliases + can include wildcards (such as *.example.com) and may be configured to redirect to + their canonical domain. Domain Alias also allows developers to register aliases per + `environment`, so that different hosts are used consistently across development + environments. See the README file for Domain Alias for more information. + +* *Domain Alpha* + Provides limited alpha-to-alpha updates. Recommended. + +* *Domain Config* + Provides a means for changing configuration settings on a per-domain basis. See the + README for Domain Config for more information. + +* *Domain Content* + Provides content overview pages on a per-domain basis, so that editors may review + content assigned to specific domains. This module is a series of Views. + +* *Domain Source* + Allows content to be assigned a canonical domain when writing URLs. Domain Source will + ensure that content that appears on multiple domains always links to one URL. See + the module's README for more information. + + +Alpha release updates +------ + +A limited set of updates are provided for major architecture changes during the +alpha release. + +You can run these updates by enabling the `domain_alpha` module and running +Drupal's database updates. The updates and affected versions are: + +* Update 8001: Affects versions of Alpha6 or lower. + +Implementation Notes +====== + +Cross-domain logins +------ + +To use cross-domain logins, you must now set the *cookie_domain* value in +*sites/default/services.yml*. + +To do so, clone `default.services.yml` to `services.yml` and change the +`cookie_domain` value to match the root hostname of your sites. Note that +cross-domain login requires the sharing of a top-level domain, so a setting like +`.example.com` will work for all `example.com` subdomains. + +Example: + +``` +cookie_domain: '.example.com' +``` + +See https://www.drupal.org/node/2391871. + +Cross-Site HTTP requests (CORS) +------ +As of Drupal 8.2, it's possible to allow a particular site to enable CORS for responses +served by Drupal. + +In the case of Domain, allowing CORS may remove AJAX errors caused when using some forms, +particularly entity references, when the AJAX request goes to another domain. + +This feature is not enabled by default because there are security consequences. See +https://www.drupal.org/node/2715637 for more information and instructions. + +To enable CORS for all domains, copy `default.services.yml` to `services.yml` and enable +the following lines: + +``` + # Configure Cross-Site HTTP requests (CORS). + # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + # for more information about the topic in general. + # Note: By default the configuration is disabled. + cors.config: + enabled: false + # Specify allowed headers, like 'x-allowed-header'. + allowedHeaders: [] + # Specify allowed request methods, specify ['*'] to allow all possible ones. + allowedMethods: [] + # Configure requests allowed from specific origins. + allowedOrigins: ['*'] + # Sets the Access-Control-Expose-Headers header. + exposedHeaders: false + # Sets the Access-Control-Max-Age header. + maxAge: false + # Sets the Access-Control-Allow-Credentials header. + supportsCredentials: false +``` + +The key here is setting `enabled` to `false`. + +Trusted host settings +------ + +If using the trusted host security setting in Drupal 8, be sure to add each domain +and alias to the pattern list. For example: + +``` +$settings['trusted_host_patterns'] = array( + '^.+\.example\.org$', + '^myexample\.com$', + '^myexample\.dev$', + '^localhost$', +); +``` + +We *strongly encourage* the use of trusted host settings. When Domain issues a redirect, +it will check the domain hostname against these settings. Any redirect that does not +match the trusted host settings will be denied and throw an error. + +See https://www.drupal.org/node/1992030 for more information. + +Configuring domain records +------- +To create a domain record, you must provide the following information: + +* A unique *hostname*, which may include a port. (Therefore, example.com and +example.com:8080 are considered different.) The hostname may only contain +alphanumeric characters, dashes, dots, and one colon. If you wish to use +international domain names, toggle the `Allow non-ASCII characters in domains + and aliases.` setting. +* A *machine_name* that must be unique. This value will be autogenerated and +cannot be edited once created. +* A *name* to be used in lists of domains. +* A URL scheme, used for writing links to the domain. The scheme may be `http`, +`https`, or `variable`. If `variable` is used, the scheme will be inherited from +the server or request settings. This option is good if your test environments +do not have secure certificates but your production environment does. +* A *status* indicating `active` or `inactive`. Inactive domains may only be +viewed by users with permission to `view inactive domains` all other users will +be redirected to the default domain (see below). +* The *weight* to be used when sorting domains. These values autoincrement as +new domains are created. +* Whether the domain is the *default* or not. Only one domain can be set as + `default`. The default domain is used for redirects in cases where other + domains are either restricted (inactive) or fail to load. This value can be + reassigned after domains are created. + +Domain records are *configuration entities*, which means they are not stored in +the database nor accessible to Views by default. They are, however, exportable +as part of your configuration. + +Domains and caching +------ + +If some variable changes are not picked up when the page renders, you may need +add domain-sensitivity to the site's cache. + +To do so, clone `default.services.yml` to `services.yml` (if you have not +already done so) and change the `required_cache_contexts` value to: + +```YAML + required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions', 'url.site'] +``` + +The addition of `url.site` should provide the domain context that the cache +layer requires. + +For developers, see also the information in the Domain Alias README. + +Contributing +==== + +If you'd like to contribute, please do. Github forks and pull requests are preferable. +If you prefer a patch-based workflow, you can attach patches to GitHub issues or Drupal.org +issues. If you open a Drupal.org issue, please link to it from the appropriate GitHub +issue. + +The GitHub issues are grouped under three milestones: + +1. Alpha -- items required for a test release. When this is complete, we will roll an +alpha1 release on Drupal.org. +2. Beta -- items considered critical features for a release. When complete, we will roll +a beta release on Drupal.org. +3. Final -- items required for a stable, secure release on Drupal.org. + +We would like to tackle issues in that order, but feel free to work on what motivates you. + +Testing +==== + +@zerolab built a Travis definition file for automated testing! That means all pull requests will automatically run tests! + +[![Build Status](https://travis-ci.org/agentrickard/domain.svg?branch=8.x-1.x)](https://travis-ci.org/agentrickard/domain) + +The module does have solid test coverage, and complete coverage is required for release. +Right now, we mostly use SimpleTest, because it is most familiar, and much of our +testing is about browser and http behavior. + +If you file a pull request or patch, please (at a minimum) run the existing tests to check +for failures. Writing additional tests will greatly speed completion, as I won't commit +code without test coverage. + +New tests should be written as Functional, Kernel, or Unit tests. Conversion patches that +move Simpletests to Functional tests are welcome. + +Because Domain requires varying http host requests to test, we can't normally use the Drupal.org +testing infrastructure. (This may change, but we are not counting on it.) + +To setup a proper environment locally, you need multiple or wilcard domains configured to +point to your drupal instance. I use variants of `example.com` for local tests. See +`DomainTestBase` for documentation. Domain testing should work with root hosts other than +`example.com`, though we also expect to find the subdomains `one.*, two.*, three.*, four.*, five.*` +in most test cases. See `DomainTestBase::domainCreateTestDomains()` for the logic. + +When running tests, you normally need to be on the default domain. + +If anyone is capable of building a vagrant box to simplify testing, that would be ideal. + diff --git a/sites/all/modules/contrib/admin/domain/composer.json b/sites/all/modules/contrib/admin/domain/composer.json new file mode 100644 index 000000000..c5d5ff1d6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/composer.json @@ -0,0 +1,17 @@ +{ + "name": "drupal/domain", + "description": "Creates domain records within a Drupal installation.", + "type": "drupal-module", + "license": "GPL-2.0+", + "authors": [ + { + "name": "Ken Rickard", + "email": "agentrickard@gmail.com" + } + ], + "support": { + "issues": "http://drupal.org/project/issues/domain", + "irc": "http://irc.freenode.org/drupal-contribute", + "source": "http://cgit.drupalcode.org/domain" + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/domain.settings.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/domain.settings.yml new file mode 100644 index 000000000..946b224db --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/domain.settings.yml @@ -0,0 +1,4 @@ +allow_non_ascii: false +www_prefix: false +login_paths: '/user/login\r\n/user/password' +css_classes: '' diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/field.field.user.user.field_domain_admin.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/field.field.user.user.field_domain_admin.yml new file mode 100644 index 000000000..e70c002b4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/field.field.user.user.field_domain_admin.yml @@ -0,0 +1,24 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_domain_admin + module: + - user +id: user.user.field_domain_admin +field_name: field_domain_admin +entity_type: user +bundle: user +label: 'Domain administrator' +description: 'Select the domains this user may administer.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'domain:domain' + target_bundles: null + sort: + field: weight + direction: ASC +field_type: entity_reference diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/field.storage.user.field_domain_admin.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/field.storage.user.field_domain_admin.yml new file mode 100644 index 000000000..86ec5b253 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/field.storage.user.field_domain_admin.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - domain + - user +id: user.field_domain_admin +field_name: field_domain_admin +entity_type: user +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_default_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_default_action.yml new file mode 100644 index 000000000..c7bf8b9c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_default_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_default_action +label: 'Set default domain record' +type: domain +plugin: domain_default_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_delete_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_delete_action.yml new file mode 100644 index 000000000..8f1e94d57 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_delete_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_delete_action +label: 'Delete domain record' +type: domain +plugin: domain_delete_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_disable_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_disable_action.yml new file mode 100644 index 000000000..b9817b947 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_disable_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_disable_action +label: 'Disable domain record' +type: domain +plugin: domain_disable_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_enable_action.yml b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_enable_action.yml new file mode 100644 index 000000000..c23ede79b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/install/system.action.domain_enable_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - domain +id: domain_enable_action +label: 'Enable domain record' +type: domain +plugin: domain_enable_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain/config/schema/domain.schema.yml b/sites/all/modules/contrib/admin/domain/domain/config/schema/domain.schema.yml new file mode 100644 index 000000000..b068fdf2e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/config/schema/domain.schema.yml @@ -0,0 +1,85 @@ +# Schema for the configuration files of the Domain module. +domain.settings: + type: config_object + label: 'Domain settings' + mapping: + allow_non_ascii: + type: boolean + label: 'Allow non-ASCII characters' + www_prefix: + type: boolean + label: 'Ignore www prefix when negotiating domains' + login_paths: + type: text + label: 'Paths that should be accessible for inactive domains' + css_classes: + type: text + label: 'Custom css classes to apply to the body tag of each domain' + +domain.record.*: + type: config_entity + label: 'Domain record' + mapping: + id: + type: string + label: 'ID' + uuid: + type: string + label: 'ID' + domain_id: + type: integer + label: 'Domain ID' + hostname: + type: string + label: 'Hostname' + name: + type: label + label: 'Name' + scheme: + type: string + label: 'Scheme' + status: + type: boolean + label: 'Status' + weight: + type: integer + label: 'Weight' + is_default: + type: boolean + label: 'Default domain' + +action.configuration.domain_default_action: + type: action_configuration_default + label: 'Set default domain record' +action.configuration.domain_delete_action: + type: action_configuration_default + label: 'Delete domain record' +action.configuration.domain_disable_action: + type: action_configuration_default + label: 'Disable domain record' +action.configuration.domain_enable_action: + type: action_configuration_default + label: 'Enable domain record' + +block.settings.domain_nav_block: + type: block_settings + label: 'Domain navigation block' + mapping: + link_options: + type: string + label: 'Link paths' + link_theme: + type: string + label: 'Link theme' + link_label: + type: string + label: 'Link text' + +condition.plugin.domain: + type: condition.plugin + mapping: + domains: + type: sequence + sequence: + type: string + diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.api.php b/sites/all/modules/contrib/admin/domain/domain/domain.api.php new file mode 100644 index 000000000..0b98801e6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.api.php @@ -0,0 +1,138 @@ +addProperty('myvar', 'mydomainvar'); + } +} + +/** + * Allows modules to modify the inbound domain request. + * + * When using this hook, first check $domain->getMatchType(), which returns a + * numeric constant indicating the type of match derived by the caller or by + * earlier returns of this hook (such as domain_alias_request_alter()). + * Use this value to determine if the request needs to be overridden. Valid + * types are DomainNegotiator::DOMAIN_MATCH_NONE, + * DomainNegotiator::DOMAIN_MATCH_EXACT, DomainNegotiator::DOMAIN_MATCH_ALIAS. + * + * To issue a redirect, as in the case of Domain Alias, set a redirect + * property to a valid response code (301 or 302). + * + * @param \Drupal\domain\DomainInterface $domain + * A domain object defined by Drupal\domain\DomainInterface. + */ +function hook_domain_request_alter(\Drupal\domain\DomainInterface &$domain) { + // Add a special case to the example domain. + if ($domain->getMatchType() == \Drupal\domain\DomainNegotiator::DOMAIN_MATCH_EXACT && $domain->id() == 'example_com') { + // Do something here. + $domain->addProperty('foo', 'Bar'); + } +} + +/** + * Adds administrative operations for the domain overview form. + * + * These operations are only available to users who can administer the domain. + * That access check happens prior to this hook being called. If your use-case + * requires additional permission checking, you should provide it before + * returning any values. + * + * @param \Drupal\domain\DomainInterface $domain + * A domain record object. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account performing the operation. + * + * @return array + * An array of operations which uses a unique string key and requires the + * elements 'title' and 'url'; the 'query' value is optional, and used + * for link-actions with tokens + */ +function hook_domain_operations(\Drupal\domain\DomainInterface $domain, \Drupal\Core\Session\AccountInterface $account) { + $operations = []; + // Check permissions. + if ($account->hasPermission('view domain aliases') || $account->hasPermission('administer domain aliases')) { + // Add aliases to the list. + $id = $domain->id(); + $operations['domain_alias'] = array( + 'title' => t('Aliases'), + 'url' => Url::fromRoute('domain_alias.admin', array('domain' => $id)), + 'weight' => 60, + ); + } + return $operations; +} + +/** + * Alter the validation step of a domain record. + * + * This hook allows modules to change or extend how domain validation + * happens. Most useful for international domains or other special cases + * where a site wants to restrict domain creation is some manner. + * + * NOTE: This does not apply to Domain Alias records. + * + * @param array &$error_list + * The list of current validation errors. Modify this value by reference. + * If you return an empty array or NULL, the domain is considered valid. + * @param string $hostname + * The HTTP_HOST string value being validated, such as one.example.com. + * Note that this is checked for uniqueness separately. This value is not + * modifiable. + * + * No return value. Modify $error_list by reference. Return an empty array + * or NULL to validate this domain. + * + * @see domain_valid_domain() + */ +function hook_domain_validate_alter(&$error_list, $hostname) { + // Only allow TLDs to be .org for our site. + if (substr($hostname, -4) != '.org') { + $error_list[] = t('Only .org domains may be registered.'); + } +} + +/** + * Alter the list of domains that may be referenced. + * + * Note that this hook does not fire for users with the 'administer domains' + * permission. + * + * @param \Drupal\Core\Entity\Query\QueryInterface $query + * An entity query prepared by DomainSelection::buildEntityQuery(). + * @param \Drupal\Core\Session\AccountInterface $account + * The account of the user viewing the reference list. + * @param array $context + * A keyed array passing two items: + * - entity_type The type of entity (e.g. node, user) that requested the list. + * - bundle The entity subtype (e.g. 'article' or 'page'). + * - field_type The access field group used for this selection. Groups are + * 'editor' for assigning editorial permissions (as in Domain Access) + * 'admin' for assigning administrative permissions for a specific domain. + * Most contributed modules will use 'editor'. + * + * No return value. Modify the $query object via methods. + */ +function hook_domain_references_alter($query, $account, $context) { + // Remove the default domain from non-admins when editing nodes. + if ($context['entity_type'] == 'node' && $context['field_type'] == 'editor' && !$account->hasPermission('edit assigned domains')) { + $default = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultId(); + $query->condition('id', $default, '<>'); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.drush.inc b/sites/all/modules/contrib/admin/domain/domain/domain.drush.inc new file mode 100644 index 000000000..48f620dff --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.drush.inc @@ -0,0 +1,641 @@ + 'List active domains for the site.', + 'examples' => array( + 'drush domain-list', + 'drush domains', + ), + 'aliases' => array('domains'), + ); + $items['domain-add'] = array( + 'description' => 'Add a new domain to the site.', + 'examples' => array( + 'drush domain-add example.com \'My Test Site\'', + 'drush domain-add example.com \'My Test Site\' --inactive=1 --https==1', + 'drush domain-add example.com \'My Test Site\' --weight=10', + 'drush domain-add example.com \'My Test Site\' --validate=1', + ), + 'arguments' => array( + 'hostname' => 'The domain hostname to register (e.g. example.com).', + 'name' => 'The name of the site (e.g. Domain Two).', + ), + 'options' => array( + 'inactive' => 'Set the domain to inactive status if set.', + 'https' => 'Use https protocol for this domain if set.', + 'weight' => 'Set the order (weight) of the domain.', + 'is_default' => 'Set this domain as the default domain.', + 'validate' => 'Force a check of the URL response before allowing registration.', + ), + ); + $items['domain-delete'] = array( + 'description' => 'Delete a domain from the site.', + 'examples' => array( + 'drush domain-delete example.com', + 'drush domain-delete 1', + ), + 'arguments' => array( + 'domain' => 'The numeric id or hostname of the domain to delete.', + ), + ); + $items['domain-test'] = array( + 'description' => 'Tests domains for proper response. If run from a subfolder, you must specify the --uri.', + 'examples' => array( + 'drush domain-test', + 'drush domain-test example.com', + 'drush domain-test 1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to test. If no value is passed, all domains are tested.', + ), + 'options' => array( + 'base_path' => 'The subdirectory name if Drupal is installed in a folder other than server root.', + ), + ); + $items['domain-default'] = array( + 'description' => 'Sets the default domain. If run from a subfolder, you must specify the --uri.', + 'examples' => array( + 'drush domain-default example.com', + 'drush domain-default 1', + 'drush domain-default 1 --validate=1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to make default.', + ), + 'options' => array( + 'validate' => 'Force a check of the URL response before allowing registration.', + ), + ); + $items['domain-disable'] = array( + 'description' => 'Sets a domain status to off.', + 'examples' => array( + 'drush domain-disable example.com', + 'drush domain-disable 1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to disable.', + ), + ); + $items['domain-enable'] = array( + 'description' => 'Sets a domain status to on.', + 'examples' => array( + 'drush domain-disable example.com', + 'drush domain-disable 1', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to enable.', + ), + ); + $items['domain-name'] = array( + 'description' => 'Changes a domain label.', + 'examples' => array( + 'drush domain-name example.com Foo', + 'drush domain-name 1 Foo', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to relabel.', + 'name' => 'The name to use for the domain.', + ), + ); + $items['domain-machine-name'] = array( + 'description' => 'Changes a domain name.', + 'examples' => array( + 'drush domain-machine-name example.com foo', + 'drush domain-machine-name 1 foo', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to rename.', + 'name' => 'The machine-readable name to use for the domain.', + ), + ); + $items['domain-scheme'] = array( + 'description' => 'Changes a domain scheme.', + 'examples' => array( + 'drush domain-scheme example.com https', + 'drush domain-scheme 1 https', + ), + 'arguments' => array( + 'domain_id' => 'The numeric id or hostname of the domain to change.', + 'scheme' => 'The URL schema (http or https) to use for the domain.', + ), + ); + $items['generate-domains'] = array( + 'description' => 'Generate domains for testing.', + 'arguments' => array( + 'primary' => 'The primary domain to use. This will be created and used for *.example.com hostnames.', + ), + 'options' => array( + 'count' => 'The count of extra domains to generate. Default is 15.', + 'empty' => 'Pass empty=1 to truncate the {domain} table before creating records.' + ), + 'examples' => array( + 'drush domain-generate example.com', + 'drush domain-generate example.com --count=25', + 'drush domain-generate example.com --count=25 --empty=1', + 'drush gend', + 'drush gend --count=25', + 'drush gend --count=25 --empty=1', + ), + 'aliases' => array('gend'), + ); + return $items; +} + +/** + * Implements hook_drush_help(). + */ +function domain_drush_help($section) { + $items = domain_drush_command(); + $name = str_replace('domain:', '', $section); + if (isset($items[$name])) { + return dt($items[$name]['description']); + } +} + +/** + * Shows the domain list. + */ +function drush_domain_list() { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted(NULL, TRUE); + + if (empty($domains)) { + drush_print(dt('No domains have been created. Use drush domain-add to create one.')); + return; + } + + $header = array( + 'weight' => dt('Weight'), + 'name' => dt('Name'), + 'hostname' => dt('Hostname'), + 'scheme' => dt('Scheme'), + 'status' => dt('Status'), + 'is_default' => dt('Default'), + 'domain_id' => dt('Domain Id'), + 'id' => dt('Machine name'), + ); + $rows = array(array_values($header)); + foreach ($domains as $domain) { + $row = array(); + foreach ($header as $key => $name) { + $row[] = Html::escape($domain->get($key)); + } + $rows[] = $row; + } + + drush_print_table($rows, TRUE); +} + +/** + * Generates a list of domains for testing. + * + * In my environment, I name hostnames one.* two.* up to ten. I also use + * foo.* bar.* and baz.*. We also want a non-hostname here and use + * myexample.com. + * + * The script may also add test1, test2, test3 up to any number to test a + * large number of domains. This test is mostly for UI testing. + * + * @param $primary + * The root domain to use for domain creation. + * + * @return + * A list of the domains created. + */ +function drush_domain_generate_domains($primary = 'example.com') { + // Check the number of domains to create. + $count = drush_get_option('count'); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + if (empty($count)) { + $count = 15; + } + // Ensure we don't duplicate any domains. + $existing = array(); + if (!empty($domains)) { + foreach ($domains as $domain) { + $existing[] = $domain->getHostname(); + } + } + // Set up one.* and so on. + $names = array( + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'foo', + 'bar', + 'baz', + ); + // Set the creation array. + $new = array($primary); + foreach ($names as $name) { + $new[] = $name . '.' . $primary; + } + // Include a non hostname. + $new[] = 'my' . $primary; + // Filter against existing so we can count correctly. + $prepared = array(); + foreach ($new as $key => $value) { + if (!in_array($value, $existing)) { + $prepared[] = $value; + } + } + // Add any test domains that have numeric prefixes. We don't expect these URLs to work, + // and mainly use these for testing the user interface. + $needed = $count - count($prepared); + for ($i = 1; $i <= $needed; $i++) { + $prepared[] = 'test' . $i . '.' . $primary; + } + + // Get the initial item weight for sorting. + $start_weight = count($domains); + $prepared = array_slice($prepared, 0, $count); + + // Create the domains. + foreach ($prepared as $key => $item) { + $hostname = strtolower($item); + $values = array( + 'name' => ($item != $primary) ? ucwords(str_replace(".$primary", '', $item)) : \Drupal::config('system.site')->get('name'), + 'hostname' => $hostname, + 'scheme' => 'http', + 'status' => 1, + 'weight' => ($item != $primary) ? $key + $start_weight + 1 : -1, + 'is_default' => 0, + 'id' => \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($hostname), + ); + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->create($values); + domain_drush_create($domain); + } + + // If nothing created, say so. + if (empty($new)) { + drush_print(dt('No new domains were created.')); + } +} + +/** + * Validates the domain generation script. + * + * @param $primary + * The root domain to use for domain creation. + */ +function drush_domain_generate_domains_validate($primary = 'example.com') { + if ($empty = drush_get_option('empty')) { + db_query("TRUNCATE TABLE {domain}"); + } + return; + // TODO: Update this validation. + $error = domain_valid_domain($primary); + if (!empty($error)) { + return drush_set_error('domain', $error); + } +} + +/** + * Adds a new domain. + * + * @param string $hostname + * The domain name to register. + * @param string $name + * The name to use for this domain. + */ +function drush_domain_add($hostname, $name) { + $records_count = \Drupal::entityTypeManager()->getStorage('domain')->getQuery()->count()->execute(); + $start_weight = $records_count + 1; + $hostname = strtolower($hostname); + /** @var \Drupal\domain\DomainStorageInterface $domain_storage */ + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $values = array( + 'hostname' => $hostname, + 'name' => $name, + 'status' => (!drush_get_option('invalid')) ? 1 : 0, + 'scheme' => (!drush_get_option('https')) ? 'http' : 'https', + 'weight' => ($weight = drush_get_option('weight')) ? $weight : $start_weight + 1, + 'is_default' => ($is_default = drush_get_option('is_default')) ? $is_default : 0, + 'id' => $domain_storage->createMachineName($hostname), + 'validate_url' => (drush_get_option('validate')) ? 1 : 0, + ); + $domain = $domain_storage->create($values); + domain_drush_create($domain); +} + +/** + * Validates the domain add command arguments. + * + * @param string $hostname + * The domain name to register. + * @param string $name + * The name to use for this domain. + * + * @return bool + * TRUE when validation passed, FALSE otherwise. + */ +function drush_domain_add_validate($hostname, $name) { + $errors = domain_drush_validate_domain($hostname); + if (!empty($errors)) { + return drush_set_error('domain', $errors); + } + elseif (\Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($hostname)) { + return drush_set_error('domain', dt('The hostname is already registered.')); + } + return TRUE; +} + +/** + * Creates a domain record. + * + * @param Drupal\domain\DomainInterface $domain + * A domain entity. + */ +function domain_drush_create(DomainInterface $domain) { + if ($error = domain_drush_check_response($domain)) { + drush_set_error('hostname', $error); + } + else { + $domain->save(); + if ($domain->getDomainId()) { + drush_print(dt('Created @name at @domain.', array('@name' => $domain->label(), '@domain' => $domain->getHostname()))); + } + else { + drush_print(dt('The request could not be completed.')); + } + } +} + +/** + * Runs a check to ensure that the domain is responsive. + * + * @param Drupal\domain\DomainInterface $domain + * A domain entity. + * + * @return string + * An error message if the domain url does not validate. Else empty. + */ +function domain_drush_check_response(DomainInterface $domain) { + // Check the domain response. First, clear the path value. + if ($domain->validate_url) { + $domain->setPath(); + try { + $response = $domain->getResponse(); + } + // We cannot know which Guzzle Exception class will be returned; be generic. + catch (RequestException $e) { + watchdog_exception('domain', $e); + // File a general server failure. + $domain->setResponse(500); + } + // If validate_url is set, then we must receive a 200 response. + if ($domain->getResponse() != 200) { + if (empty($response)) { + $response = 500; + } + return dt('The server request to @url returned a @response response. To proceed, disable the test of the server response by leaving off the --validate flag.', ['@url' => $domain->getPath(), '@response' => $response]); + } + } +} + +/** + * Validates a domain. + * + * @param $hostname + * The domain name to validate for syntax and uniqueness. + * + * @return array + * An array of errors encountered. + * + * @see domain_validate() + */ +function domain_drush_validate_domain($hostname) { + /** @var \Drupal\domain\DomainValidatorInterface $validator */ + $validator = \Drupal::service('domain.validator'); + return $validator->validate($hostname); +} + +/** + * Deletes a domain record. + * + * @param $argument + * The domain_id to delete. Pass 'all' to delete all records. + */ +function drush_domain_delete($argument = NULL) { + if (is_null($argument)) { + drush_set_error('domain', dt('You must specify a domain to delete.')); + } + if ($argument == 'all') { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + if (empty($domains)) { + drush_print(dt('There are no domains to delete.')); + return; + } + $content = drush_choice(array(1 => dt('Delete all domains')), dt('This action may not be undone. Continue?'), '!value'); + if (empty($content)) { + return; + } + } + // Resolve the domain. + elseif ($domain = drush_domain_get_from_argument($argument)) { + if ($domain->isDefault()) { + return drush_set_error('domain', dt('The primary domain may not be deleted. Use drush domain-default to set a new default domain.')); + } + $domains = [$domain]; + } + else { + return; + } + foreach ($domains as $domain) { + $domain->delete(); + drush_print(dt('Domain record @domain deleted.', array('@domain' => $domain->getHostname()))); + } + + return; + + // TODO: Set options for re-assigning content. + $list = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + $options = array('0' => dt('Do not reassign')); + foreach ($list as $data) { + if ($data->id() != $domain->id()) { + $options[$data->getDomainId()] = $data->getHostname(); + } + } + $content = drush_choice($options, dt('Reassign content to:'), '!value'); + if (empty($content)) { + return; + } + $users = drush_choice($options, dt('Reassign users to:'), '!value'); + if (empty($users)) { + return; + } + $values['domain_access'] = (!empty($content)) ? $content : 'none'; + $values['domain_editor'] = (!empty($content)) ? $users : 'none'; + + domain_delete($domain, $values); + + drush_print(dt('Domain record deleted.')); +} + +/** + * Tests a domain record for the proper HTTP response. + * + * @param $argument + * The domain_id to test. Passing no value tests all records. + */ +function drush_domain_test($argument = NULL) { + // TODO: This won't work in a subdirectory without a parameter. + if ($base_path = drush_get_option('base_path')) { + $GLOBALS['base_path'] = '/' . $base_path . '/'; + } + if (is_null($argument)) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + } + else { + if ($domain = drush_domain_get_from_argument($argument)) { + $domains = array($domain); + } + else { + return; + } + } + foreach ($domains as $domain) { + if ($domain->getResponse() != 200) { + drush_print(dt('Fail: !error. Please pass a --uri parameter or a --base_path to retest.' , array('!error' => $domain->getResponse()))); + } + else { + drush_print(dt('Success: !url tested successfully.', array('!url' => $domain->getPath()))); + } + } +} + +/** + * Sets the default domain id. + */ +function drush_domain_default($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $validate = (drush_get_option('validate')) ? 1 : 0; + $domain->addProperty('validate_url', $validate); + if ($error = domain_drush_check_response($domain)) { + drush_set_error('domain', $error); + } + else { + $domain->saveDefault(); + drush_print(dt('!domain set to primary domain.', array('!domain' => $domain->getHostname()))); + } + } +} + +/** + * Disables a domain. + */ +function drush_domain_disable($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + if ($domain->status()) { + $domain->disable(); + drush_print(dt('!domain has been disabled.', array('!domain' => $domain->getHostname()))); + } + else { + drush_print(dt('!domain is already disabled.', array('!domain' => $domain->getHostname()))); + } + } +} + +/** + * Enables a domain. + */ +function drush_domain_enable($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + if (!$domain->status()) { + $domain->enable(); + drush_print(dt('!domain has been enabled.', array('!domain' => $domain->getHostname()))); + } + else { + drush_print(dt('!domain is already enabled.', array('!domain' => $domain->getHostname()))); + } + } +} + +/** + * Changes a domain name. + */ +function drush_domain_name($argument, $name) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $domain->saveProperty('name', $name); + } +} + +/** + * Changes a domain machine_name. + */ +function drush_domain_machine_name($argument, $machine_name) { + $machine_name = \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($machine_name); + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $results = \Drupal::entityTypeManager() + ->getStorage('domain') + ->loadByProperties(array('machine_name' => $machine_name)); + foreach ($results as $result) { + if ($result->id() == $machine_name) { + drush_print(dt('The machine_name @machine_name is being used by domain @hostname.', array('@machine_name' => $machine_name, '@hostname' => $result->getHostname()))); + return; + } + } + $domain->saveProperty('id', $machine_name); + } +} + +/** + * Changes a domain scheme. + */ +function drush_domain_scheme($argument) { + // Resolve the domain. + if ($domain = drush_domain_get_from_argument($argument)) { + $content = drush_choice(array(1 => dt('http'), 2 => dt('https')), dt('Select the default http scheme:'), '!value'); + if (empty($content)) { + return; + } + $scheme = 'http'; + if ($content == 2) { + $scheme = 'https'; + } + $domain->saveProperty('scheme', $scheme); + } +} + +/** + * Converts a domain string or domain_id to a $domain array. + * + * On failure, throws a drush error. + */ +function drush_domain_get_from_argument($argument) { + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($argument); + if (!$domain) { + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($argument); + } + if (!$domain) { + drush_set_error('domain', dt('Domain record not found.')); + return NULL; + } + return $domain; +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.info.yml b/sites/all/modules/contrib/admin/domain/domain/domain.info.yml new file mode 100644 index 000000000..04fa37fe4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.info.yml @@ -0,0 +1,15 @@ +name: Domain +description: 'Creates domain records within a Drupal installation.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - options +configure: domain.admin + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.install b/sites/all/modules/contrib/admin/domain/domain/domain.install new file mode 100644 index 000000000..9f986a5ad --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.install @@ -0,0 +1,38 @@ +setComponent(DOMAIN_ADMIN_FIELD, array( + 'type' => 'options_buttons', + 'weight' => 50, + )) + ->save(); +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.libraries.yml b/sites/all/modules/contrib/admin/domain/domain/domain.libraries.yml new file mode 100644 index 000000000..6c70e1411 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.libraries.yml @@ -0,0 +1,7 @@ +drupal.domain: + version: VERSION + js: + js/domain.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.links.action.yml b/sites/all/modules/contrib/admin/domain/domain/domain.links.action.yml new file mode 100644 index 000000000..e65a9b02c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.links.action.yml @@ -0,0 +1,5 @@ +entity.domain_add: + route_name: entity.domain.add + title: 'Add domain record' + appears_on: + - domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain/domain.links.menu.yml new file mode 100644 index 000000000..50e3f3971 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.links.menu.yml @@ -0,0 +1,18 @@ +domain.admin: + title: Domains + route_name: domain.admin + parent: system.admin_config + description: 'Domain configuration' + weight: 0 +domain.admin_list: + title: Domain records + route_name: domain.admin + parent: domain.admin + description: 'Manage the domain records used by the site.' + weight: -10 +domain.settings: + title: Domain settings + route_name: domain.settings + parent: domain.admin + description: 'Domain module settings' + weight: 0 diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.links.task.yml b/sites/all/modules/contrib/admin/domain/domain/domain.links.task.yml new file mode 100644 index 000000000..e66821094 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.links.task.yml @@ -0,0 +1,8 @@ +domain.admin: + title: Domain records + route_name: domain.admin + base_route: domain.admin +domain.settings: + title: Domain settings + route_name: domain.settings + base_route: domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.module b/sites/all/modules/contrib/admin/domain/domain/domain.module new file mode 100644 index 000000000..a4a0b6c73 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.module @@ -0,0 +1,162 @@ +getPath(), ['absolute' => TRUE]); +} + +/** + * Implements hook_entity_load(). + * + * The $domain->path and $domain->uri properties are derived from data in the + * {domain} table. We use the hook system to load that data to indicate that + * the data is not native to the object. + * + * This action is performed in hook_entity_load(), which precedes the running + * of hook_domain_load() and ensures that our data is present for other modules. + */ +function domain_entity_load(array $entities, $entity_type) { + if ($entity_type == 'domain') { + foreach ($entities as $domain) { + $domain->setPath(); + $domain->setUrl(); + } + } +} + +/** + * Implements hook_help(). + */ +function domain_help($route_name, RouteMatchInterface $route_match) { + switch ($route_name) { + case 'domain.admin': + $output = t('

The following domains have been created for your site. The currently active domain + is shown in boldface. You may click on a domain to change the currently active domain. +

'); + return $output; + } +} + +/** + * Implements hook_token_info(). + */ +function domain_token_info() { + return \Drupal::service('domain.token')->getTokenInfo(); +} + +/** + * Implements hook_tokens(). + */ +function domain_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) { + return \Drupal::service('domain.token')->getTokens($type, $tokens, $data, $options, $bubbleable_metadata); +} + + +/** + * Implements hook_preprocess_HOOK() for html.html.twig. + */ +function domain_preprocess_html(array &$variables) { + // Add class to body tag, if set. + $config = \Drupal::config('domain.settings'); + if ($string = $config->get('css_classes')) { + $token = \Drupal::token(); + // Prepare the classes proparly, with one class per string. + $classes = explode(' ', trim($string)); + foreach ($classes as $class) { + // Ensure no leading or trailing space. + $class = trim($class); + if (!empty($class)) { + $variables['attributes']['class'][] = Html::getClass($token->replace($class)); + } + } + } +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\user\UserForm. + * + * Handle settings that the user cannot access. + */ +function domain_form_user_form_alter(&$form, &$form_state, $form_id) { + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain.element_manager'); + $form = $manager->setFormOptions($form, $form_state, DOMAIN_ADMIN_FIELD); +} + +/** + * Implements hook_domain_references_alter(). + */ +function domain_domain_references_alter($query, $account, $context) { + // Restrict domains by assignment, being sure only to act on the admin field. + if ($context['field_type'] == 'admin' && $context['entity_type'] == 'user') { + if ($account->hasPermission('administer domains')) { + // Do nothing. + } + elseif ($account->hasPermission('assign domain administrators')) { + $allowed = \Drupal::service('domain.element_manager')->getFieldValues($account, DOMAIN_ADMIN_FIELD); + $query->condition('id', array_keys($allowed), 'IN'); + } + else { + // Remove all options. + $query->condition('id', '-no-possible-match-'); + } + } +} + +/** + * Implements hook_views_data_alter. + */ +function domain_views_data_alter(array &$data) { + $table = 'user__' . DOMAIN_ADMIN_FIELD; + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_ADMIN_FIELD]['relationship']); +} + +/** + * Implements hook_theme(). + */ +function domain_theme() { + return [ + 'domain_nav_block' => [ + 'render element' => 'items', + ], + ]; +} + +/** + * Prepares variables for block templates. + * + * Default template: domain-nav-block.html.twig. + * + * @param array $variables + * An associative array containing: + * - items: An array of labels and urls for use in the list. + * Properties used: 'label', 'url', 'active' + */ +function template_preprocess_domain_nav_block(&$variables) { + $variables['items'] = $variables['items']['#items']; +} diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.permissions.yml b/sites/all/modules/contrib/admin/domain/domain/domain.permissions.yml new file mode 100644 index 000000000..c3b2d2f80 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.permissions.yml @@ -0,0 +1,32 @@ +administer domains: + title: 'Administer all domain records' + description: 'View, create, edit, and delete domain records. Allows all permissions for the module.' + restrict access: true +access inactive domains: + title: 'Access inactive domains' + description: 'Access domain URLs for domains marked as inactive.' + restrict access: true +assign domain administrators: + title: 'Assign additional administrators to assigned domains' + restrict access: true +create domains: + title: 'Create domain records' + restrict access: true +edit assigned domains: + title: 'Edit assigned domain records' + restrict access: true +delete assigned domains: + title: 'Delete assigned domain records' + restrict access: true +use domain nav block: + title: 'Access the domain navigation block' +use domain switcher block: + title: 'Access the domain switcher block' + restrict access: true +view assigned domains: + title: 'View assigned domains in the administration list' +view domain list: + title: 'View all registered domains in the administration list' +view domain information: + title: 'View debugging information for domain handling' + restrict access: true diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.routing.yml b/sites/all/modules/contrib/admin/domain/domain/domain.routing.yml new file mode 100644 index 000000000..c230a9994 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.routing.yml @@ -0,0 +1,55 @@ +domain.admin: + path: '/admin/config/domain' + defaults: + _entity_list: 'domain' + _title: 'Domains' + _title_context: 'With modules' + requirements: + _custom_access: 'Drupal\domain\Access\DomainListCheck::viewDomainList' + +domain.settings: + path: '/admin/config/domain/settings' + defaults: + _title: 'Domain settings' + _form: '\Drupal\domain\Form\DomainSettingsForm' + requirements: + _permission: 'administer domains' + +entity.domain.add: + path: '/admin/config/domain/add' + defaults: + _entity_form: domain.edit + _title: 'Add domain record' + options: + _admin_route: TRUE + requirements: + _entity_create_access: domain + +entity.domain.edit_form: + path: '/admin/config/domain/edit/{domain}' + defaults: + _entity_form: domain.edit + _title: 'Edit domain record' + options: + _admin_route: TRUE + requirements: + _entity_access: domain.update + +entity.domain.delete_form: + path: '/admin/config/domain/delete/{domain}' + defaults: + _entity_form: domain.delete + _title: 'Delete' + options: + _admin_route: TRUE + requirements: + _entity_access: domain.delete + +domain.inline_action: + path: '/admin/config/domain/{op}/{domain}' + defaults: + _controller: 'Drupal\domain\Controller\DomainController::ajaxOperation' + requirements: + _entity_access: domain.update + _csrf_token: 'TRUE' + op: 'enable|disable|default' diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.services.yml b/sites/all/modules/contrib/admin/domain/domain/domain.services.yml new file mode 100644 index 000000000..8b87c6a5f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/domain.services.yml @@ -0,0 +1,41 @@ +services: + access_check.domain: + class: Drupal\domain\Access\DomainAccessCheck + tags: + - { name: access_check } + arguments: ['@domain.negotiator', '@config.factory', '@path.matcher'] + access_check.domain_route: + class: Drupal\domain\Access\DomainRouteCheck + tags: + - { name: access_check, applies_to: _domain } + arguments: ['@domain.negotiator'] + domain.current_domain_context: + class: Drupal\domain\ContextProvider\CurrentDomainContext + arguments: ['@domain.negotiator'] + tags: + - { name: 'context_provider' } + domain.element_manager: + class: Drupal\domain\DomainElementManager + arguments: ['@entity_type.manager'] + domain.negotiator: + class: Drupal\domain\DomainNegotiator + arguments: ['@request_stack', '@module_handler', '@entity_type.manager', '@config.factory'] + domain.subscriber: + class: Drupal\domain\EventSubscriber\DomainSubscriber + tags: + - { name: event_subscriber } + arguments: ['@domain.negotiator', '@entity_type.manager', '@access_check.domain', '@current_user'] + domain.token: + class: Drupal\domain\DomainToken + arguments: ['@entity_type.manager', '@domain.negotiator'] + domain.validator: + class: Drupal\domain\DomainValidator + arguments: ['@module_handler', '@config.factory', '@http_client', '@entity_type.manager'] + # @deprecated and will be removed for 8.x-1.0 release. + domain.creator: + class: Drupal\domain\DomainCreator + arguments: ['@domain.loader', '@domain.negotiator'] + # @deprecated and will be removed for 8.x-1.0 release. + domain.loader: + class: Drupal\domain\DomainLoader + arguments: ['@config.typed', '@config.factory'] diff --git a/sites/all/modules/contrib/admin/domain/domain/js/domain.js b/sites/all/modules/contrib/admin/domain/domain/js/domain.js new file mode 100644 index 000000000..be71eceeb --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/js/domain.js @@ -0,0 +1,42 @@ +/** + * @file + * Attaches behaviors for the Domain module. + */ +(function ($) { + + "use strict"; + + /** + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.domainSettingsSummaries = { + attach: function () { + // The drupalSetSummary method required for this behavior is not available + // on the Blocks administration page, so we need to make sure this + // behavior is processed only if drupalSetSummary is defined. + if (typeof jQuery.fn.drupalSetSummary === 'undefined') { + return; + } + + // There may be an easier way to do this. Right now, we just copy code + // from block module. + function checkboxesSummary(context) { + var vals = []; + var $checkboxes = $(context).find('input[type="checkbox"]:checked + label'); + var il = $checkboxes.length; + for (var i = 0; i < il; i++) { + vals.push($($checkboxes[i]).html()); + } + if (!vals.length) { + vals.push(Drupal.t('Not restricted')); + } + return vals.join(', '); + } + + $('[data-drupal-selector="edit-visibility-domain"]').drupalSetSummary(checkboxesSummary); + + } + }; + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml b/sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml new file mode 100644 index 000000000..ced2c7198 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml @@ -0,0 +1,17 @@ +id: d7_domain +label: Domain Records +migration_tags: + - Drupal 7 +source: + plugin: d7_domain +process: + id: machine_name + name: sitename + hostname: subdomain + weight: weight + is_default: is_default + scheme: scheme + path: subdomain + status: valid +destination: + plugin: entity:domain diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php new file mode 100644 index 000000000..a3cf475c6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php @@ -0,0 +1,93 @@ +domainNegotiator = $negotiator; + $this->configFactory = $config_factory; + $this->pathMatcher = $path_matcher; + } + + /** + * {@inheritdoc} + */ + public function applies(Route $route) { + return $this->checkPath($route->getPath()); + } + + /** + * {@inheritdoc} + */ + public function checkPath($path) { + $allowed_paths = $this->configFactory->get('domain.settings')->get('login_paths'); + return !$this->pathMatcher->matchPath($path, $allowed_paths); + } + + /** + * {@inheritdoc} + */ + public function access(AccountInterface $account) { + /** @var \Drupal\domain\DomainInterface $domain */ + $domain = $this->domainNegotiator->getActiveDomain(); + // Is the domain allowed? + // No domain, let it pass. + if (empty($domain)) { + return AccessResult::allowed()->setCacheMaxAge(0); + } + // Active domain, let it pass. + if ($domain->status()) { + return AccessResult::allowed()->setCacheMaxAge(0); + } + // Inactive domain, require permissions. + else { + $permissions = array('administer domains', 'access inactive domains'); + $operator = 'OR'; + return AccessResult::allowedIfHasPermissions($account, $permissions, $operator)->setCacheMaxAge(0); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainListCheck.php b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainListCheck.php new file mode 100644 index 000000000..a270f9c9b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainListCheck.php @@ -0,0 +1,28 @@ +hasPermission('administer domains') || $account->hasPermission('view domain list') || $account->hasPermission('view assigned domains')) { + return AccessResult::allowed(); + } + return AccessResult::forbidden(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainRouteCheck.php b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainRouteCheck.php new file mode 100644 index 000000000..d17a873e1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainRouteCheck.php @@ -0,0 +1,82 @@ +domainNegotiator = $negotiator; + } + + /** + * {@inheritdoc} + */ + public function applies(Route $route) { + return $route->hasRequirement($this->requirementsKey); + } + + /** + * Checks access to a route with a _domain requirement. + * + * @param \Symfony\Component\Routing\Route $route + * The route to check against. + * @param \Drupal\Core\Session\AccountInterface $account + * The currently logged in account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + * + * @see \Drupal\domain\Plugin\views\access\Domain + */ + public function access(Route $route, AccountInterface $account) { + // Requirements just allow strings, so this might be a comma separated list. + $string = $route->getRequirement($this->requirementsKey); + $domain = $this->domainNegotiator->getActiveDomain(); + // Since only one domain can be active per request, we only suport OR logic. + $allowed = array_filter(array_map('trim', explode('+', $string))); + if (!empty($domain) && in_array($domain->id(), $allowed)) { + return AccessResult::allowed()->addCacheContexts(['url.site']); + } + // If there is no allowed domain, give other access checks a chance. + return AccessResult::neutral()->addCacheContexts(['url.site']); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/ContextProvider/CurrentDomainContext.php b/sites/all/modules/contrib/admin/domain/domain/src/ContextProvider/CurrentDomainContext.php new file mode 100644 index 000000000..8c61ad582 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/ContextProvider/CurrentDomainContext.php @@ -0,0 +1,65 @@ +negotiator = $negotiator; + } + + /** + * {@inheritdoc} + */ + public function getRuntimeContexts(array $unqualified_context_ids) { + // Load the current domain. + $current_domain = $this->negotiator->getActiveDomain(); + // Set the context. + $context = new Context(new ContextDefinition('entity:domain', $this->t('Active domain')), $current_domain); + + // Allow caching. + $cacheability = new CacheableMetadata(); + $cacheability->setCacheContexts(['url.site']); + $context->addCacheableDependency($cacheability); + + // Prepare the result. + $result = [ + 'entity:domain' => $context, + ]; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getAvailableContexts() { + return $this->getRuntimeContexts([]); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainController.php b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainController.php new file mode 100644 index 000000000..c245e6977 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainController.php @@ -0,0 +1,76 @@ +saveDefault(); + $message = $this->t('Domain record set as default'); + if ($domain->isDefault()) { + $success = TRUE; + } + break; + + case 'enable': + $domain->enable(); + $message = $this->t('Domain record has been enabled.'); + if ($domain->status()) { + $success = TRUE; + } + break; + + case 'disable': + $domain->disable(); + $message = $this->t('Domain record has been disabled.'); + if (!$domain->status()) { + $success = TRUE; + } + break; + } + + // Set a message. + if ($success) { + drupal_set_message($message); + } + else { + drupal_set_message($this->t('The operation failed.')); + } + + // Return to the invoking page. + $url = Url::fromRoute('domain.admin', array(), array('absolute' => TRUE)); + return new RedirectResponse($url->toString(), 302); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainControllerBase.php b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainControllerBase.php new file mode 100644 index 000000000..f102bb69e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Controller/DomainControllerBase.php @@ -0,0 +1,59 @@ +domainStorage = $domain_storage; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('entity_type.manager') + ); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainAccessControlHandler.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainAccessControlHandler.php new file mode 100644 index 000000000..bffdff3a3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainAccessControlHandler.php @@ -0,0 +1,119 @@ +entityTypeManager = $entity_type_manager; + $this->domainElementManager = $domain_element_manager; + $this->userStorage = $user_storage; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity_type.manager'), + $container->get('domain.element_manager'), + $container->get('entity_type.manager')->getStorage('user') + ); + } + + /** + * {@inheritdoc} + */ + public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account = NULL) { + $account = $this->prepareUser($account); + // Check the global permission. + if ($account->hasPermission('administer domains')) { + return AccessResult::allowed(); + } + // @TODO: This may not be relevant. + if ($operation == 'create' && $account->hasPermission('create domains')) { + return AccessResult::allowed(); + } + // For view, we allow admins unless the domain is inactive. + $is_admin = $this->isDomainAdmin($entity, $account); + if ($operation == 'view' && ($entity->status() || $account->hasPermission('access inactive domains')) && ($is_admin || $account->hasPermission('view domain list'))) { + return AccessResult::allowed(); + } + // For other operations, check that the user is a domain admin. + if ($operation == 'update' && $account->hasPermission('edit assigned domains') && $is_admin) { + return AccessResult::allowed(); + } + if ($operation == 'delete' && $account->hasPermission('delete assigned domains') && $is_admin) { + return AccessResult::allowed(); + } + return AccessResult::forbidden(); + } + + /** + * Checks if a user can administer a specific domain. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to retrieve field data from. + * @param \Drupal\Core\Session\AccountInterface + * The user account. + * + * @return boolean + */ + public function isDomainAdmin(EntityInterface $entity, AccountInterface $account) { + $user = $this->userStorage->load($account->id()); + $user_domains = $this->domainElementManager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + return isset($user_domains[$entity->id()]); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainCreator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreator.php new file mode 100644 index 000000000..73ea320c0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreator.php @@ -0,0 +1,81 @@ +loader = $loader; + $this->negotiator = $negotiator; + } + + /** + * {@inheritdoc} + */ + public function createDomain(array $values = array()) { + $default = $this->loader->loadDefaultId(); + $domains = $this->loader->loadMultiple(); + if (empty($values)) { + $values['hostname'] = $this->createHostname(); + $values['name'] = \Drupal::config('system.site')->get('name'); + } + $values += array( + 'scheme' => \Drupal::service('entity_type.manager')->getStorage('domain')->getDefaultScheme(), + 'status' => 1, + 'weight' => count($domains) + 1, + 'is_default' => (int) empty($default), + ); + $domain = \Drupal::entityTypeManager()->getStorage('domain')->create($values); + + return $domain; + } + + /** + * {@inheritdoc} + */ + public function createHostname() { + return $this->negotiator->negotiateActiveHostname(); + } + + /** + * {@inheritdoc} + */ + public function createMachineName($hostname = NULL) { + if (empty($hostname)) { + $hostname = $this->createHostname(); + } + return preg_replace('/[^a-z0-9_]/', '_', $hostname); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainCreatorInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreatorInterface.php new file mode 100644 index 000000000..3eca77b37 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainCreatorInterface.php @@ -0,0 +1,47 @@ +entityTypeManager = $entity_type_manager; + $this->domainStorage = $entity_type_manager->getStorage('domain'); + } + + /** + * @inheritdoc + */ + public function setFormOptions(array $form, FormStateInterface $form_state, $field_name, $hide_on_disallow = FALSE) { + // There are cases, such as Entity Browser, where the form is partially + // invoked, but without our fields. + if (!isset($form[$field_name])) { + return $form; + } + $fields = $this->fieldList($field_name); + $disallowed = $this->disallowedOptions($form_state, $form[$field_name]); + $empty = empty($form[$field_name]['widget']['#options']); + + // If the domain form element is set as a group, and the field is not assigned to + // another group, then move it. See domain_access_form_node_form_alter(). + if (isset($form['domain']) && !isset($form[$field_name]['#group'])) { + $form[$field_name]['#group'] = 'domain'; + } + + // Check for domains the user cannot access or the absence of any options. + if (!empty($disallowed) || $empty) { + // @TODO: Potentially show this information to users with permission. + $form[$field_name . '_disallowed'] = array( + '#type' => 'value', + '#value' => $disallowed, + ); + $form['domain_hidden_fields'] = array( + '#type' => 'value', + '#value' => $fields, + ); + if ($hide_on_disallow || $empty) { + $form[$field_name]['#access'] = FALSE; + } + elseif (!empty($disallowed)) { + $form[$field_name]['widget']['#description'] .= $this->listDisallowed($disallowed); + } + // Call our submit function to merge in values. + // Account for all the submit buttons on the node form. + $buttons = ['preview', 'delete']; + $submit = $this->getSubmitHandler(); + foreach ($form['actions'] as $key => $action) { + if (!in_array($key, $buttons) && isset($form['actions'][$key]['#submit']) && !in_array($submit, $form['actions'][$key]['#submit'])) { + array_unshift($form['actions'][$key]['#submit'], $submit); + } + } + } + + return $form; + } + + /** + * @inheritdoc + */ + public static function submitEntityForm(array &$form, FormStateInterface $form_state) { + $fields = $form_state->getValue('domain_hidden_fields'); + foreach ($fields as $field) { + $entity_values = []; + $values = $form_state->getValue($field . '_disallowed'); + if (!empty($values)) { + $info = $form_state->getBuildInfo(); + $node = $form_state->getFormObject()->getEntity(); + $entity_values = $form_state->getValue($field); + } + foreach ($values as $value) { + $entity_values[]['target_id'] = $value; + } + // Prevent a fatal error caused by passing a NULL value. + // See https://www.drupal.org/node/2841962. + if (!empty($entity_values)) { + $form_state->setValue($field, $entity_values); + } + } + } + + /** + * @inheritdoc + */ + public function disallowedOptions(FormStateInterface $form_state, $field) { + $options = []; + $info = $form_state->getBuildInfo(); + $entity = $form_state->getFormObject()->getEntity(); + $entity_values = $this->getFieldValues($entity, $field['widget']['#field_name']); + if (isset($field['widget']['#options'])) { + $options = array_diff_key($entity_values, $field['widget']['#options']); + } + return array_keys($options); + } + + /** + * @inheritdoc + */ + public function fieldList($field_name) { + static $fields = []; + $fields[] = $field_name; + // Return only unique field names. AJAX requests can result in duplicates. + // See https://www.drupal.org/project/domain/issues/2930934. + return array_unique($fields); + } + + /** + * @inheritdoc + */ + public function getFieldValues($entity, $field_name) { + // @TODO: static cache. + $list = []; + // @TODO In tests, $entity is returning NULL. + if (is_null($entity)) { + return $list; + } + // Get the values of an entity. + $values = $entity->hasField($field_name) ? $entity->get($field_name) : NULL; + // Must be at least one item. + if (!empty($values)) { + foreach ($values as $item) { + if ($target = $item->getValue()) { + if ($domain = $this->domainStorage->load($target['target_id'])) { + $list[$domain->id()] = $domain->getDomainId(); + } + } + } + } + return $list; + } + + /** + * @inheritdoc + */ + public function getSubmitHandler() { + return '\\Drupal\\domain\\DomainElementManager::submitEntityForm'; + } + + /** + * Lists the disallowed domains in the user interface. + * + * @param array $disallowed + * An array of domain ids. + * + * @return string + * A string suitable for display. + */ + public function listDisallowed(array $disallowed) { + $domains = $this->domainStorage->loadMultiple($disallowed); + // @TODO: Proper theme function here. + $string = $this->t('The following domains are currently assigned and cannot be changed:'); + foreach ($domains as $domain) { + $items[] = $domain->label(); + } + $build = array( + '#theme' => 'item_list', + '#items' => $items, + ); + $string .= render($build); + return '
' . $string . '
'; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManagerInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManagerInterface.php new file mode 100644 index 000000000..31c8e47d8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManagerInterface.php @@ -0,0 +1,105 @@ +domainStorage = $domain_storage; + $this->renderer = $renderer; + $this->validator = $validator; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('renderer'), + $container->get('domain.validator'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = $this->entity; + + // Create defaults if this is the first domain. + $count_existing = $this->domainStorage->getQuery()->count()->execute(); + if (!$count_existing) { + $domain->addProperty('hostname', $this->domainStorage->createHostname()); + $domain->addProperty('name', $this->config('system.site')->get('name')); + } + $form['domain_id'] = array( + '#type' => 'value', + '#value' => $domain->getDomainId(), + ); + $form['hostname'] = array( + '#type' => 'textfield', + '#title' => $this->t('Hostname'), + '#size' => 40, + '#maxlength' => 80, + '#default_value' => $domain->getCanonical(), + '#description' => $this->t('The canonical hostname, using the full subdomain.example.com format. Leave off the http:// and the trailing slash and do not include any paths.
If this domain uses a custom http(s) port, you should specify it here, e.g.: subdomain.example.com:1234
The hostname may contain only lowercase alphanumeric characters, dots, dashes, and a colon (if using alternative ports).'), + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => !empty($domain->id()) ? $domain->id() : '', + '#disabled' => !empty($domain->id()), + '#machine_name' => array( + 'source' => array('hostname'), + 'exists' => array($this->domainStorage, 'load'), + ), + ); + $form['name'] = array( + '#type' => 'textfield', + '#title' => $this->t('Name'), + '#size' => 40, + '#maxlength' => 80, + '#default_value' => $domain->label(), + '#description' => $this->t('The human-readable name is shown in domain lists and may be used as the title tag.'), + ); + // Do not use the :// suffix when storing data. + $add_suffix = FALSE; + $form['scheme'] = array( + '#type' => 'radios', + '#title' => $this->t('Domain URL scheme'), + '#options' => array('http' => 'http://', 'https' => 'https://', 'variable' => 'Variable'), + '#default_value' => $domain->getRawScheme(), + '#description' => $this->t('This URL scheme will be used when writing links and redirects to this domain and its resources. Selecting Variable will inherit the current scheme of the web request.'), + ); + $form['status'] = array( + '#type' => 'radios', + '#title' => $this->t('Domain status'), + '#options' => array(1 => $this->t('Active'), 0 => $this->t('Inactive')), + '#default_value' => (int) $domain->status(), + '#description' => $this->t('"Inactive" domains are only accessible to user roles with that assigned permission.'), + ); + $form['weight'] = array( + '#type' => 'weight', + '#title' => $this->t('Weight'), + '#delta' => $count_existing + 1, + '#default_value' => $domain->getWeight(), + '#description' => $this->t('The sort order for this record. Lower values display first.'), + ); + $form['is_default'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Default domain'), + '#default_value' => $domain->isDefault(), + '#description' => $this->t('If a URL request fails to match a domain record, the settings for this domain will be used. Only one domain can be default.'), + ); + $form['validate_url'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Test server response'), + '#default_value' => TRUE, + '#description' => $this->t('Validate that url of the host is accessible to Drupal before saving.'), + ); + $required = $this->validator->getRequiredFields(); + foreach ($form as $key => $element) { + if (in_array($key, $required)) { + $form[$key]['#required'] = TRUE; + } + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\domain\DomainInterface $entity */ + $entity = $this->entity; + $hostname = $entity->getHostname(); + $errors = $this->validator->validate($hostname); + if (!empty($errors)) { + // Render errors to display as message. + $message = [ + '#theme' => 'item_list', + '#items' => $errors, + ]; + $message = $this->renderer->renderPlain($message); + $form_state->setErrorByName('hostname', $message); + } + // Validate if the same hostname exists. + // Do not use domain loader because it may change hostname. + $existing = $this->domainStorage->loadByProperties(['hostname' => $hostname]); + $existing = reset($existing); + // If we have already registered a hostname, make sure we don't create a duplicate. + // We cannot check id() here, as the machine name is editable. + if ($existing && $existing->getDomainId() != $entity->getDomainId()) { + $form_state->setErrorByName('hostname', $this->t('The hostname is already registered.')); + } + + // Check the domain response. First, clear the path value. + $entity->setPath(); + // Check the response. + $response = $this->validator->checkResponse($entity); + // If validate_url is set, then we must receive a 200 response. + if ($entity->validate_url && $response != 200) { + if (empty($response)) { + $response = 500; + } + $form_state->setErrorByName('hostname', $this->t('The server request to @url returned a @response response. To proceed, disable the Test server response in the form.', ['@url' => $entity->getPath(), '@response' => $response])); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $status = parent::save($form, $form_state); + if ($status == SAVED_NEW) { + drupal_set_message($this->t('Domain record created.')); + } + else { + drupal_set_message($this->t('Domain record updated.')); + } + $form_state->setRedirect('domain.admin'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainInterface.php new file mode 100644 index 000000000..3b9e8ffb9 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainInterface.php @@ -0,0 +1,241 @@ +get('entity_type.manager')->getStorage($entity_type->id()), + $container->get('current_user'), + $container->get('redirect.destination'), + $container->get('entity_type.manager'), + $container->get('module_handler'), + $container->get('domain.element_manager') + ); + } + + /** + * Constructs a new EntityListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\domain\DomainStorageInterface $domain_storage + * The domain storage class. + * @param \Drupal\Core\Session\AccountInterface $account + * The active user account. + * @param \Drupal\Core\Routing\RedirectDestinationInterface $destination + * The redirect destination helper. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\domain\DomainElementManager $domain_element_manager + * The domain field element manager. + */ + public function __construct(EntityTypeInterface $entity_type, DomainStorageInterface $domain_storage, AccountInterface $account, RedirectDestinationInterface $destination_handler, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, DomainElementManager $domain_element_manager) { + parent::__construct($entity_type, $domain_storage); + $this->entityTypeId = $entity_type->id(); + $this->domainStorage = $domain_storage; + $this->entityType = $entity_type; + $this->currentUser = $account; + $this->destinationHandler = $destination_handler; + $this->entityTypeManager = $entity_type_manager; + $this->accessHandler = $this->entityTypeManager->getAccessControlHandler('domain'); + $this->moduleHandler = $module_handler; + $this->domainElementManager = $domain_element_manager; + $this->userStorage = $this->entityTypeManager->getStorage('user'); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'domain_admin_overview_form'; + } + + /** + * {@inheritdoc} + */ + public function getOperations(EntityInterface $entity) { + $operations = parent::getOperations($entity); + $destination = $this->destinationHandler->getAsArray(); + $default = $entity->isDefault(); + $id = $entity->id(); + + // If the user cannot edit domains, none of these actions are permitted. + $access = $this->accessHandler->checkAccess($entity, 'update'); + if ($access->isForbidden()) { + return $operations; + } + + $super_admin = $this->currentUser->hasPermission('administer domains'); + if ($super_admin || $this->currentUser->hasPermission('access inactive domains')) { + if ($entity->status() && !$default) { + $operations['disable'] = array( + 'title' => $this->t('Disable'), + 'url' => Url::fromRoute('domain.inline_action', array('op' => 'disable', 'domain' => $id)), + 'weight' => 50, + ); + } + elseif (!$default) { + $operations['enable'] = array( + 'title' => $this->t('Enable'), + 'url' => Url::fromRoute('domain.inline_action', array('op' => 'enable', 'domain' => $id)), + 'weight' => 40, + ); + } + } + if (!$default && $super_admin) { + $operations['default'] = array( + 'title' => $this->t('Make default'), + 'url' => Url::fromRoute('domain.inline_action', array('op' => 'default', 'domain' => $id)), + 'weight' => 30, + ); + } + if (!$default && $this->accessHandler->checkAccess($entity, 'delete')->isAllowed()) { + $operations['delete'] = array( + 'title' => $this->t('Delete'), + 'url' => Url::fromRoute('entity.domain.delete_form', array('domain' => $id)), + 'weight' => 20, + ); + } + $operations += $this->moduleHandler->invokeAll('domain_operations', array($entity, $this->currentUser)); + foreach ($operations as $key => $value) { + if (isset($value['query']['token'])) { + $operations[$key]['query'] += $destination; + } + } + /** @var DomainInterface $default */ + $default = $this->domainStorage->loadDefaultDomain(); + + // Deleting the site default domain is not allowed. + if ($default && $id == $default->id()) { + unset($operations['delete']); + } + return $operations; + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header['label'] = $this->t('Name'); + $header['hostname'] = $this->t('Hostname'); + $header['status'] = $this->t('Status'); + $header['is_default'] = $this->t('Default'); + $header += parent::buildHeader(); + if (!$this->currentUser->hasPermission('administer domains')) { + unset($header['weight']); + } + return $header; + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + // If the user cannot view the domain, none of these actions are permitted. + $admin = $this->accessHandler->checkAccess($entity, 'view'); + if ($admin->isForbidden()) { + return; + } + + $row['label'] = $this->getLabel($entity); + $row['hostname'] = array('#markup' => $entity->getLink()); + if ($entity->isActive()) { + $row['hostname']['#prefix'] = ''; + $row['hostname']['#suffix'] = ''; + } + $row['status'] = array('#markup' => $entity->status() ? $this->t('Active') : $this->t('Inactive')); + $row['is_default'] = array('#markup' => ($entity->isDefault() ? $this->t('Yes') : $this->t('No'))); + $row += parent::buildRow($entity); + + if (!$this->currentUser->hasPermission('administer domains')) { + unset($row['weight']); + } + + return $row; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $form[$this->entitiesKey]['#domains'] = $this->entities; + $form['actions']['submit']['#value'] = $this->t('Save configuration'); + // Only super-admins may sort domains. + if (!$this->currentUser->hasPermission('administer domains')) { + $form['actions']['submit']['#access'] = FALSE; + unset($form['#tabledrag']); + } + // Delta is set after each row is loaded. + $count = count($this->domainStorage->loadMultiple()) + 1; + foreach (Element::children($form['domains']) as $key) { + if (isset($form['domains'][$key]['weight'])) { + $form['domains'][$key]['weight']['#delta'] = $count; + } + } + return $form; + } + + /** + * {@inheritdoc} + * + * Overrides the parent method to prevent saving bad data. + * + * @link https://www.drupal.org/project/domain/issues/2925798 + * @link https://www.drupal.org/project/domain/issues/2925629 + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + foreach ($form_state->getValue($this->entitiesKey) as $id => $value) { + if (isset($this->entities[$id]) && $this->entities[$id]->get($this->weightKey) != $value['weight']) { + // Reset weight properly. + $this->entities[$id]->set($this->weightKey, $value['weight']); + // Do not allow accidental hostname rewrites. + $this->entities[$id]->set('hostname', $this->entities[$id]->getCanonical()); + $this->entities[$id]->save(); + } + } + } + + + /** + * Internal sort method for form weights. + */ + private function sortByWeight($a, $b) { + if ($a['weight'] < $b['weight']) { + return 0; + } + return 1; + } + + /** + * {@inheritdoc} + * + * Builds the entity listing as a form with pagination. This method overrides both + * Drupal\Core\Config\Entity\DraggableListBuilder::render() and + * Drupal\Core\Entity\EntityListBuilder::render(). + */ + public function render() { + // Build the default form, which includes weights. + $form = $this->formBuilder()->getForm($this); + + // Only add the pager if a limit is specified. + if ($this->limit) { + $form['pager'] = array( + '#type' => 'pager', + ); + } + return $form; + } + + /** + * {@inheritdoc} + * + * Loads entity IDs using a pager sorted by the entity weight. The default behavior when + * using a limit is to sort by id. + * + * We also have to limit by assigned domains of the active user. + * + * See Drupal\Core\Entity\EntityListBuilder::getEntityIds() + * + * @return array + * An array of entity IDs. + */ + protected function getEntityIds() { + $query = $this->getStorage()->getQuery() + ->sort($this->entityType->getKey('weight')); + + // If the user cannot administer domains, we must filter the query further by + // assigned IDs. We don't have to check permissions here, because that is handled by + // the route system and buildRow(). There are two permissions that allow users to view + // the entire list. + if (!$this->currentUser->hasPermission('administer domains') && !$this->currentUser->hasPermission('view domain list')) { + $user = $this->userStorage->load($this->currentUser->id()); + $allowed = $this->domainElementManager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $query->condition('id', array_keys($allowed), 'IN'); + } + + // Only add the pager if a limit is specified. + if ($this->limit) { + $query->pager($this->limit); + } + return $query->execute(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainLoader.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoader.php new file mode 100644 index 000000000..b626e7fa5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoader.php @@ -0,0 +1,162 @@ +typedConfig = $typed_config; + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain.record.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function load($id, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache(array($id)); + } + return $controller->load($id); + } + + /** + * {@inheritdoc} + */ + public function loadDefaultId() { + $result = $this->loadDefaultDomain(); + if (!empty($result)) { + return $result->id(); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadDefaultDomain() { + $result = $this->getStorage()->loadByProperties(array('is_default' => TRUE)); + if (!empty($result)) { + return current($result); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadMultiple(array $ids = NULL, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache($ids); + } + return $controller->loadMultiple($ids); + } + + /** + * {@inheritdoc} + */ + public function loadMultipleSorted(array $ids = NULL) { + $domains = $this->loadMultiple($ids); + uasort($domains, array($this, 'sort')); + return $domains; + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $hostname = $this->prepareHostname($hostname); + $result = $this->getStorage()->loadByProperties(array('hostname' => $hostname)); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadOptionsList() { + $list = array(); + foreach ($this->loadMultipleSorted() as $id => $domain) { + $list[$id] = $domain->label(); + } + return $list; + } + + /** + * {@inheritdoc} + */ + public function sort(DomainInterface $a, DomainInterface $b) { + return $a->getWeight() > $b->getWeight(); + } + + /** + * Loads the storage controller. + * + * We use the loader very early in the request cycle. As a result, if we try + * to inject the storage container, we hit a circular dependency. Using this + * method at least keeps our code easier to update. + */ + protected function getStorage() { + $storage = \Drupal::entityTypeManager()->getStorage('domain'); + return $storage; + } + + /** + * {@inheritdoc} + */ + public function prepareHostname($hostname) { + // Strip www. prefix off the hostname. + $ignore_www = $this->configFactory->get('domain.settings')->get('www_prefix'); + if ($ignore_www && substr($hostname, 0, 4) == 'www.') { + $hostname = substr($hostname, 4); + } + return $hostname; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainLoaderInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoaderInterface.php new file mode 100644 index 000000000..5bacd4b28 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainLoaderInterface.php @@ -0,0 +1,118 @@ + label for use in forms. + */ + public function loadOptionsList(); + + /** + * Sorts domains by weight. + * + * For use by loadMultipleSorted(). + * + * @param DomainInterface $a + * The first Domain object to sort. + * @param DomainInterface $b + * The Domain object to compare against. + * + * @return bool + * Wether the first domain weight is greater or not. + */ + public function sort(DomainInterface $a, DomainInterface $b); + + /** + * Gets the entity field schema for domain records. + * + * @return array + * An array representing the field schema of the object. + */ + public function loadSchema(); + + /** + * Removes www. prefix from a hostname, if set. + * + * @param string $hostname + * A hostname. + * + * @return string + * The cleaned hostname. + */ + public function prepareHostname($hostname); + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php new file mode 100644 index 000000000..a45644448 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php @@ -0,0 +1,223 @@ +requestStack = $requestStack; + $this->moduleHandler = $module_handler; + $this->entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function setRequestDomain($httpHost, $reset = FALSE) { + // @TODO: Investigate caching methods. + $this->setHttpHost($httpHost); + // Try to load a direct match. + if ($domain = $this->domainStorage->loadByHostname($httpHost)) { + // If the load worked, set an exact match flag for the hook. + $domain->setMatchType(self::DOMAIN_MATCH_EXACT); + } + // If a straight load fails, create a base domain for checking. This data + // is required for hook_domain_request_alter(). + else { + $values = array('hostname' => $httpHost); + /** @var \Drupal\domain\Entity\DomainInterface $domain */ + $domain = $this->domainStorage->create($values); + $domain->setMatchType(self::DOMAIN_MATCH_NONE); + } + + // Make sure all modules are loaded and can alter the found domains. + // See https://www.drupal.org/node/2896434#comment-12267208. + $this->moduleHandler->reload(); + foreach ($this->moduleHandler->getImplementations('domain_request_alter') as $module) { + $this->moduleHandler->load($module); + } + // Now check with modules (like Domain Alias) that register alternate + // lookup systems with the main module. + $this->moduleHandler->alter('domain_request', $domain); + + // We must have registered a valid id, else the request made no match. + if (!empty($domain->id())) { + $this->setActiveDomain($domain); + } + // Fallback to default domain if no match. + elseif ($domain = $this->domainStorage->loadDefaultDomain()) { + $this->moduleHandler->alter('domain_request', $domain); + $domain->setMatchType(self::DOMAIN_MATCH_NONE); + if (!empty($domain->id())) { + $this->setActiveDomain($domain); + } + } + } + + /** + * {@inheritdoc} + */ + public function setActiveDomain(DomainInterface $domain) { + // @TODO: caching + $this->domain = $domain; + } + + /** + * Determine the active domain. + */ + protected function negotiateActiveDomain() { + $httpHost = $this->negotiateActiveHostname(); + $this->setRequestDomain($httpHost); + return $this->domain; + } + + /** + * {@inheritdoc} + */ + public function getActiveDomain($reset = FALSE) { + if ($reset) { + $this->negotiateActiveDomain(); + } + return $this->domain; + } + + /** + * {@inheritdoc} + */ + public function getActiveId() { + return $this->domain->id(); + } + + /** + * {@inheritdoc} + */ + public function negotiateActiveHostname() { + if ($request = $this->requestStack->getCurrentRequest()) { + $httpHost = $request->getHttpHost(); + } + else { + $httpHost = $_SERVER['HTTP_HOST']; + } + $hostname = !empty($httpHost) ? $httpHost : 'localhost'; + return $this->domainStorage->prepareHostname($hostname); + } + + /** + * {@inheritdoc} + */ + public function setHttpHost($httpHost) { + $this->httpHost = $httpHost; + } + + /** + * {@inheritdoc} + */ + public function getHttpHost() { + return $this->httpHost; + } + + /** + * {@inheritdoc} + */ + public function isRegisteredDomain($hostname) { + // Direct hostname match always passes. + if ($domain = $this->domainStorage->loadByHostname($hostname)) { + return TRUE; + } + // Check for registered alias matches. + $values = array('hostname' => $httpHost); + /** @var \Drupal\domain\Entity\DomainInterface $domain */ + $domain = $this->domainStorage->create($values); + $domain->setMatchType(self::DOMAIN_MATCH_NONE); + + // Now check with modules (like Domain Alias) that register alternate + // lookup systems with the main module. + $this->moduleHandler->alter('domain_request', $domain); + + // We must have registered a valid id, else the request made no match. + if (!empty($domain->id())) { + return TRUE; + } + return FALSE; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiatorInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiatorInterface.php new file mode 100644 index 000000000..c1c7cc437 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiatorInterface.php @@ -0,0 +1,91 @@ +getRequestContext()->getCompleteBaseUrl(); + return !UrlHelper::isExternal($url) || UrlHelper::externalIsLocal($url, $base_url) || $this->externalIsRegistered($url, $base_url); + } + + /** + * {@inheritdoc} + */ + protected function isSafe($url) { + return $this->isLocal($url); + } + + /** + * Returns the request context. + * + * @return \Drupal\Core\Routing\RequestContext + */ + protected function getRequestContext() { + if (!isset($this->requestContext)) { + $this->requestContext = \Drupal::service('router.request_context'); + } + return $this->requestContext; + } + + /** + * Sets the request context. + * + * @param \Drupal\Core\Routing\RequestContext $request_context + * The request context. + * + * @return $this + */ + public function setRequestContext(RequestContext $request_context) { + $this->requestContext = $request_context; + + return $this; + } + + /** + * Determines if an external URL points to this domain-aware installation. + * + * This method replaces the logic in + * Drupal\Component\Utility\UrlHelper::externalIsLocal(). Since that class is not + * directly extendable, we have to replace it. + * + * @param string $url + * A string containing an external URL, such as "http://example.com/foo". + * + * @return bool + * TRUE if the URL has the same domain and base path. + * @param string $base_url + * The base URL string to check against, such as "http://example.com/" + * + * @throws \InvalidArgumentException + * Exception thrown when $url is not fully qualified. + */ + public static function externalIsRegistered($url, $base_url) { + $url_parts = parse_url($url); + $base_parts = parse_url($base_url); + + if (empty($url_parts['host'])) { + throw new \InvalidArgumentException('A path was passed when a fully qualified domain was expected.'); + } + + // Check that the host name is registered with trusted hosts. + $trusted = self::checkTrustedHost($url_parts['host']); + if (!$trusted) { + return FALSE; + } + + // Check that the requested $url is registered. + $negotiator = \Drupal::service('domain.negotiator'); + $registered_domain = $negotiator->isRegisteredDomain($url_parts['host']); + + if (!isset($url_parts['path']) || !isset($base_parts['path'])) { + return $registered_domain; + } + else { + // When comparing base paths, we need a trailing slash to make sure a + // partial URL match isn't occurring. Since base_path() always returns + // with a trailing slash, we don't need to add the trailing slash here. + return ($registered_domain && stripos($url_parts['path'], $base_parts['path']) === 0); + } + } + + /** + * Checks that a host is registered with trusted_host_patterns. + * + * This method is cribbed from Symfony's Request::getHost() method. + * + * @param string $host + * The hostname to check. + * + * @return bool + * TRUE if the hostname matches the trusted_host_patterns. FALSE otherwise. + * It is the caller's responsibility to deal with this result securely. + */ + public static function checkTrustedHost($host) { + // See Request::setTrustedHosts(); + if (!isset(self::$trustedHostPatterns)) { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('#%s#i', $hostPattern); + }, Settings::get('trusted_host_patterns', [])); + // Reset the trusted host match array. + self::$trustedHosts = []; + } + + // Trim and remove port number from host. Host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // In the original Symfony code, hostname validation runs here. We have removed that + // portion because Domains are already validated on creation. + if (count(self::$trustedHostPatterns) > 0) { + // To avoid host header injection attacks, you should provide a list of trusted host patterns + if (in_array($host, self::$trustedHosts)) { + return TRUE; + } + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + return TRUE; + } + } + return FALSE; + } + // In cases where trusted_host_patterns are not set, allow all. This is flagged as a + // security issue by Drupal core in the Reports UI. + return TRUE; + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainStorage.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorage.php new file mode 100644 index 000000000..7a6f744cf --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorage.php @@ -0,0 +1,205 @@ +configFactory = $config_factory; + $this->uuidService = $uuid_service; + $this->languageManager = $language_manager; + $this->typedConfig = $typed_config; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('config.factory'), + $container->get('uuid'), + $container->get('language_manager'), + $container->get('config.typed') + ); + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain.record.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function loadDefaultId() { + $result = $this->loadDefaultDomain(); + if (!empty($result)) { + return $result->id(); + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadDefaultDomain() { + $result = $this->loadByProperties(array('is_default' => TRUE)); + if (!empty($result)) { + return current($result); + } + return NULL; + } + + + /** + * {@inheritdoc} + */ + public function loadMultipleSorted(array $ids = NULL) { + $domains = $this->loadMultiple($ids); + uasort($domains, array($this, 'sort')); + return $domains; + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $hostname = $this->prepareHostname($hostname); + $result = $this->loadByProperties(array('hostname' => $hostname)); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadOptionsList() { + $list = array(); + foreach ($this->loadMultipleSorted() as $id => $domain) { + $list[$id] = $domain->label(); + } + return $list; + } + + /** + * {@inheritdoc} + */ + public function sort(DomainInterface $a, DomainInterface $b) { + return $a->getWeight() > $b->getWeight(); + } + + /** + * {@inheritdoc} + */ + public function prepareHostname($hostname) { + // Strip www. prefix off the hostname. + $ignore_www = $this->configFactory->get('domain.settings')->get('www_prefix'); + if ($ignore_www && substr($hostname, 0, 4) == 'www.') { + $hostname = substr($hostname, 4); + } + return $hostname; + } + + /** + * {@inheritdoc} + */ + public function create(array $values = []) { + $default = $this->loadDefaultId(); + $domains = $this->loadMultiple(); + if (empty($values)) { + $values['hostname'] = $this->createHostname(); + $values['name'] = \Drupal::config('system.site')->get('name'); + } + $values += array( + 'scheme' => $this->getDefaultScheme(), + 'status' => 1, + 'weight' => count($domains) + 1, + 'is_default' => (int) empty($default), + ); + $domain = parent::create($values); + + return $domain; + } + + /** + * {@inheritdoc} + */ + public function createHostname() { + // We cannot inject the negotiator due to dependencies. + return \Drupal::service('domain.negotiator')->negotiateActiveHostname(); + } + + /** + * {@inheritdoc} + */ + public function createMachineName($hostname = NULL) { + if (empty($hostname)) { + $hostname = $this->createHostname(); + } + return preg_replace('/[^a-z0-9_]/', '_', $hostname); + } + + /** + * {@inheritdoc} + */ + public function getDefaultScheme() { + // Use the foundation request if possible/ + $request = \Drupal::request(); + if (!empty($request)) { + $scheme = $request->getScheme(); + } + // Else use the server variable. + elseif (!empty($_SERVER['https'])) { + $scheme = 'https'; + } + // Else fall through to default. + else { + $scheme = 'http'; + } + return $scheme; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainStorageInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorageInterface.php new file mode 100644 index 000000000..110bce7fd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainStorageInterface.php @@ -0,0 +1,124 @@ + label for use in forms. + */ + public function loadOptionsList(); + + /** + * Sorts domains by weight. + * + * For use by loadMultipleSorted(). + * + * @param DomainInterface $a + * The first Domain object to sort. + * @param DomainInterface $b + * The Domain object to compare against. + * + * @return bool + * Wether the first domain weight is greater or not. + */ + public function sort(DomainInterface $a, DomainInterface $b); + + /** + * Gets the entity field schema for domain records. + * + * @return array + * An array representing the field schema of the object. + */ + public function loadSchema(); + + /** + * Removes www. prefix from a hostname, if set. + * + * @param string $hostname + * A hostname. + * + * @return string + * The cleaned hostname. + */ + public function prepareHostname($hostname); + + /** + * Gets the hostname of the active request. + * + * @return string + * The hostname string of the current request. + */ + public function createHostname(); + + /** + * Creates a machine-name string from the hostname. + * + * This string is the primary key of the entity. + * + * @param string $hostname + * The hostname of the domain record. If empty, the current request will be + * used. + * + * @return string + * A string containing A-Z, a-z, 0-9, and _ characters. + */ + public function createMachineName($hostname = NULL); + + /** + * Returns the default http/https scheme for the site. + * + * This function helps us account for variable schemes across environments. + * + * @return $scheme + * A string representation of s scheme (http|https). + */ + public function getDefaultScheme(); + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainToken.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainToken.php new file mode 100644 index 000000000..7b4f23d01 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainToken.php @@ -0,0 +1,187 @@ +entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->negotiator = $negotiator; + } + + /** + * Implements hook_token_info(). + */ + public function getTokenInfo() { + // Domain token types. + $info['types']['domain'] = array( + 'name' => $this->t('Domains'), + 'description' => $this->t('Tokens related to domains.'), + 'needs-data' => 'domain', + ); + // These two types require the Token contrib module. + $info['types']['current-domain'] = array( + 'name' => $this->t('Current domain'), + 'description' => $this->t('Tokens related to the current domain.'), + 'type' => 'domain', + ); + $info['types']['default-domain'] = array( + 'name' => $this->t('Default domain'), + 'description' => $this->t('Tokens related to the default domain.'), + 'type' => 'domain', + ); + + // Domain tokens. + $info['tokens']['domain']['id'] = array( + 'name' => $this->t('Domain id'), + 'description' => $this->t('The domain\'s numeric ID.'), + ); + $info['tokens']['domain']['machine-name'] = array( + 'name' => $this->t('Domain machine name'), + 'description' => $this->t('The domain machine identifier.'), + ); + $info['tokens']['domain']['path'] = array( + 'name' => $this->t('Domain path'), + 'description' => $this->t('The base URL for the domain.'), + ); + $info['tokens']['domain']['name'] = array( + 'name' => $this->t('Domain name'), + 'description' => $this->t('The domain name.'), + ); + $info['tokens']['domain']['url'] = array( + 'name' => $this->t('Domain URL'), + 'description' => $this->t('The domain\'s URL for the current page request.'), + ); + $info['tokens']['domain']['hostname'] = array( + 'name' => $this->t('Domain hostname'), + 'description' => $this->t('The domain hostname.'), + ); + $info['tokens']['domain']['scheme'] = array( + 'name' => $this->t('Domain scheme'), + 'description' => $this->t('The domain scheme.'), + ); + $info['tokens']['domain']['status'] = array( + 'name' => $this->t('Domain status'), + 'description' => $this->t('The domain status.'), + ); + $info['tokens']['domain']['weight'] = array( + 'name' => $this->t('Domain weight'), + 'description' => $this->t('The domain weight.'), + ); + $info['tokens']['domain']['is_default'] = array( + 'name' => $this->t('Domain default'), + 'description' => $this->t('The domain is the default domain.'), + ); + + return $info; + } + + /** + * Implements hook_tokens(). + */ + public function getTokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) { + $replacements = array(); + + $domain = NULL; + + // Based on the type, get the proper domain context. + switch ($type) { + case 'domain': + if (!empty($data['domain'])) { + $domain = $data['domain']; + } + else { + $domain = $this->negotiator->getActiveDomain(); + } + break; + case 'current-domain': + $domain = $this->negotiator->getActiveDomain(); + break; + case 'default-domain': + $domain = $this->domainStorage->loadDefaultDomain(); + break; + } + + // Set the token information. + if (!empty($domain)) { + $callbacks = $this->getCallbacks(); + foreach ($tokens as $name => $original) { + if (isset($callbacks[$name])) { + $replacements[$original] = $domain->{$callbacks[$name]}(); + $bubbleable_metadata->addCacheableDependency($domain); + } + } + } + + return $replacements; + } + + /** + * Maps tokens to their entity callbacks. + * + * We assume that the token will call an instance of DomainInterface. + * + * @return array + */ + public function getCallbacks() { + return [ + 'id' => 'getDomainId', + 'machine-name' => 'id', + 'path' => 'getPath', + 'name' => 'label', + 'hostname' => 'getHostname', + 'scheme' => 'getScheme', + 'status' => 'status', + 'weight' => 'getWeight', + 'is_default' => 'isDefault', + 'url' => 'getUrl', + ]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php new file mode 100644 index 000000000..f0f8a3da5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php @@ -0,0 +1,154 @@ +moduleHandler = $module_handler; + $this->configFactory = $config_factory; + $this->httpClient = $http_client; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function validate($hostname) { + $error_list = array(); + // Check for at least one dot or the use of 'localhost'. + // Note that localhost can specify a port. + $localhost_check = explode(':', $hostname); + if (substr_count($hostname, '.') == 0 && $localhost_check[0] != 'localhost') { + $error_list[] = $this->t('At least one dot (.) is required, except when using localhost.'); + } + // Check for one colon only. + if (substr_count($hostname, ':') > 1) { + $error_list[] = $this->t('Only one colon (:) is allowed.'); + } + // If a colon, make sure it is only followed by numbers. + elseif (substr_count($hostname, ':') == 1) { + $parts = explode(':', $hostname); + $port = (int) $parts[1]; + if (strcmp($port, $parts[1])) { + $error_list[] = $this->t('The port protocol must be an integer.'); + } + } + // The domain cannot begin or end with a period. + if (substr($hostname, 0, 1) == '.') { + $error_list[] = $this->t('The domain must not begin with a dot (.)'); + } + // The domain cannot begin or end with a period. + if (substr($hostname, -1) == '.') { + $error_list[] = $this->t('The domain must not end with a dot (.)'); + } + // Check for valid characters, unless using non-ASCII domains. + $config = $this->configFactory->get('domain.settings'); + $non_ascii = $config->get('allow_non_ascii'); + if (!$non_ascii) { + $pattern = '/^[a-z0-9\.\-:]*$/i'; + if (!preg_match($pattern, $hostname)) { + $error_list[] = $this->t('Only alphanumeric characters, dashes, and a colon are allowed.'); + } + } + // Check for lower case. + if ($hostname != Unicode::strtolower($hostname)) { + $error_list[] = $this->t('Only lower-case characters are allowed.'); + } + // Check for 'www' prefix if redirection / handling is + // enabled under global domain settings. + // Note that www prefix handling must be set explicitly in the UI. + // See http://drupal.org/node/1529316 and http://drupal.org/node/1783042 + if ($config->get('www_prefix') && (substr($hostname, 0, strpos($hostname, '.')) == 'www')) { + $error_list[] = $this->t('WWW prefix handling: Domains must be registered without the www. prefix.'); + } + + // Allow modules to alter this behavior. + $this->moduleHandler->alter('domain_validate', $error_list, $hostname); + + return $error_list; + } + + /** + * {@inheritdoc} + */ + public function checkResponse(DomainInterface $domain) { + $url = $domain->getPath() . drupal_get_path('module', 'domain') . '/tests/200.png'; + try { + // GuzzleHttp no longer allows for bogus URL calls. + $request = $this->httpClient->get($url); + } + // We cannot know which Guzzle Exception class will be returned; be generic. + catch (RequestException $e) { + watchdog_exception('domain', $e); + // File a general server failure. + $domain->setResponse(500); + return; + } + // Expected result (i.e. no exception thrown.) + $domain->setResponse($request->getStatusCode()); + + return $domain->getResponse(); + } + + /** + * {@inheritdoc} + */ + public function getRequiredFields() { + return array('hostname', 'name', 'scheme', 'status', 'weight'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainValidatorInterface.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidatorInterface.php new file mode 100644 index 000000000..b1c349c20 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidatorInterface.php @@ -0,0 +1,45 @@ +getStorage('domain'); + $default = $domain_storage->loadDefaultId(); + $count = $storage_controller->getQuery()->count()->execute(); + $values += array( + 'scheme' => empty($GLOBALS['is_https']) ? 'http' : 'https', + 'status' => 1, + 'weight' => $count + 1, + 'is_default' => (int) empty($default), + ); + // Note that we have not created a domain_id, which is only used for + // node access control and will be added on save. + } + + /** + * {@inheritdoc} + */ + public function isActive() { + $negotiator = \Drupal::service('domain.negotiator'); + /** @var self $domain */ + $domain = $negotiator->getActiveDomain(); + if (empty($domain)) { + return FALSE; + } + return ($this->id() == $domain->id()); + } + + /** + * {@inheritdoc} + */ + public function addProperty($name, $value) { + if (!isset($this->{$name})) { + $this->{$name} = $value; + } + } + + /** + * {@inheritdoc} + */ + public function isDefault() { + return (bool) $this->is_default; + } + + /** + * {@inheritdoc} + */ + public function isHttps() { + return (bool) ($this->getScheme(FALSE) == 'https'); + } + + /** + * {@inheritdoc} + */ + public function saveDefault() { + if (!$this->isDefault()) { + // Swap the current default. + /** @var self $default */ + if ($default = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain()) { + $default->is_default = 0; + $default->setHostname($default->getCanonical()); + $default->save(); + } + // Save the new default. + $this->is_default = 1; + $this->setHostname($this->getCanonical()); + $this->save(); + } + else { + drupal_set_message($this->t('The selected domain is already the default.'), 'warning'); + } + } + + /** + * {@inheritdoc} + */ + public function enable() { + $this->setStatus(TRUE); + $this->setHostname($this->getCanonical()); + $this->save(); + } + + /** + * {@inheritdoc} + */ + public function disable() { + if (!$this->isDefault()) { + $this->setStatus(FALSE); + $this->setHostname($this->getCanonical()); + $this->save(); + } + else { + drupal_set_message($this->t('The default domain cannot be disabled.'), 'warning'); + } + } + + /** + * {@inheritdoc} + */ + public function saveProperty($name, $value) { + if (isset($this->{$name})) { + $this->{$name} = $value; + $this->setHostname($this->getCanonical()); + $this->save(); + drupal_set_message($this->t('The @key attribute was set to @value for domain @hostname.', array( + '@key' => $name, + '@value' => $value, + '@hostname' => $this->hostname, + ))); + } + else { + drupal_set_message($this->t('The @key attribute does not exist.', array('@key' => $name))); + } + } + + /** + * {@inheritdoc} + */ + public function setPath() { + $this->path = $this->getScheme() . $this->getHostname() . base_path(); + } + + /** + * {@inheritdoc} + */ + public function setUrl() { + $uri = \Drupal::request()->getRequestUri(); + $this->url = $this->getScheme() . $this->getHostname() . $uri; + } + + /** + * {@inheritdoc} + */ + public function getPath() { + if (!isset($this->path)) { + $this->setPath(); + } + return $this->path; + } + + /** + * Returns the raw path of the domain object, without the base url. + */ + public function getRawPath() { + return $this->getScheme() . $this->getHostname(); + } + + /** + * Builds a link from a known internal path. + * + * @param string $path + * A Drupal-formatted internal path, starting with /. Note that it is the + * caller's responsibility to handle the base_path(). + * + * @return string + * The built link. + */ + public function buildUrl($path) { + return $this->getRawPath() . $path; + } + + /** + * {@inheritdoc} + */ + public function getUrl() { + if (!isset($this->url)) { + $this->setUrl(); + } + return $this->url; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + // Sets the default domain properly. + /** @var self $default */ + $default = $storage->loadDefaultDomain(); + if (!$default) { + $this->is_default = 1; + } + elseif ($this->is_default && $default->getDomainId() != $this->getDomainId()) { + // Swap the current default. + $default->is_default = 0; + $default->save(); + } + // Ensures we have a proper domain_id. + if ($this->isNew()) { + $this->createDomainId(); + } + // Prevent duplicate hostname. + $hostname = $this->getHostname(); + // Do not use domain loader because it may change hostname. + $existing = $storage->loadByProperties(['hostname' => $hostname]); + $existing = reset($existing); + if ($existing && $this->getDomainId() != $existing->getDomainId()) { + throw new ConfigValueException("The hostname ($hostname) is already registered."); + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + // Invalidate cache tags relevant to domains. + \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']); + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageInterface $storage, array $entities) { + parent::postDelete($storage, $entities); + foreach ($entities as $entity) { + $actions = $storage->loadMultiple([ + 'domain_default_action.' . $entity->id(), + 'domain_delete_action.' . $entity->id(), + 'domain_disable_action.' . $entity->id(), + 'domain_enable_action.' . $entity->id(), + ]); + foreach ($actions as $action) { + $action->delete(); + } + } + // Invalidate cache tags relevant to domains. + \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']); + } + + /** + * {@inheritdoc} + */ + public function createDomainId() { + // We cannot reliably use sequences (1, 2, 3) because those can be different + // across environments. Instead, we use the crc32 hash function to create a + // unique numeric id for each domain. In some systems (Windows?) we have + // reports of crc32 returning a negative number. Issue #2794047. + // If we don't use hash(), then crc32() returns different results for 32- and 64-bit + // systems. On 32-bit systems, the number returned may also be too large for PHP. + // See #2908236. + $id = hash('crc32', $this->id()); + $id = abs(hexdec(substr($id, 0, -2))); + $this->createNumericId($id); + } + + /** + * Creates a unique numeric id for use in the {node_access} table. + * + * @param integer $id + * An integer to ue as the numeric id. + */ + public function createNumericId($id) { + // Ensure that this value is unique. + $storage = \Drupal::entityTypeManager()->getStorage('domain'); + $result = $storage->loadByProperties(array('domain_id' => $id)); + if (empty($result)) { + $this->domain_id = $id; + } + else { + $id++; + $this->createNumericId($id); + } + } + + /** + * {@inheritdoc} + */ + public function getScheme($add_suffix = TRUE) { + $scheme = $this->scheme; + if ($scheme == 'variable') { + $scheme = \Drupal::service('entity_type.manager')->getStorage('domain')->getDefaultScheme(); + } + elseif ($scheme != 'https') { + $scheme = 'http'; + } + $scheme .= ($add_suffix) ? '://' : ''; + + return $scheme; + } + + /** + * {@inheritdoc} + */ + public function getRawScheme() { + return $this->scheme; + } + + /** + * {@inheritdoc} + */ + public function getResponse() { + if (empty($this->response)) { + $validator = \Drupal::service('domain.validator'); + $validator->checkResponse($this); + } + return $this->response; + } + + /** + * {@inheritdoc} + */ + public function setResponse($response) { + $this->response = $response; + } + + /** + * {@inheritdoc} + */ + public function getLink($current_path = TRUE) { + $options = array('absolute' => TRUE, 'https' => $this->isHttps()); + if ($current_path) { + $url = Url::fromUri($this->getUrl(), $options); + } + else { + $url = Url::fromUri($this->getPath(), $options); + } + + return Link::fromTextAndUrl($this->getCanonical(), $url)->toString(); + } + + /** + * {@inheritdoc} + */ + public function getRedirect() { + return $this->redirect; + } + + /** + * {@inheritdoc} + */ + public function setRedirect($code = 302) { + $this->redirect = $code; + } + + /** + * {@inheritdoc} + */ + public function getHostname() { + return $this->hostname; + } + + /** + * {@inheritdoc} + */ + public function setHostname($hostname) { + $this->hostname = $hostname; + } + + /** + * {@inheritdoc} + */ + public function getDomainId() { + return $this->domain_id; + } + + /** + * {@inheritdoc} + */ + public function getWeight() { + return $this->weight; + } + + /** + * {@inheritdoc} + */ + public function setMatchType($match_type = DomainNegotiator::DOMAIN_MATCH_EXACT) { + $this->matchType = $match_type; + } + + /** + * {@inheritdoc} + */ + public function getMatchType() { + return $this->matchType; + } + + /** + * {@inheritdoc} + */ + public function getPort() { + $ports = explode(':', $this->getHostname()); + if (isset($ports[1])) { + return ':' . $ports[1]; + } + return ''; + } + + /** + * {@inheritdoc} + */ + public function setCanonical($hostname = NULL) { + if (is_null($hostname)) { + $this->canonical = $this->getHostname(); + } + else { + $this->canonical = $hostname; + } + } + + /** + * {@inheritdoc} + */ + public function getCanonical() { + if (empty($this->canonical)) { + $this->setCanonical(); + } + return $this->canonical; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/EventSubscriber/DomainSubscriber.php b/sites/all/modules/contrib/admin/domain/domain/src/EventSubscriber/DomainSubscriber.php new file mode 100644 index 000000000..244619d97 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/EventSubscriber/DomainSubscriber.php @@ -0,0 +1,140 @@ +domainNegotiator = $negotiator; + $this->entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->accessCheck = $access_check; + $this->account = $account; + } + + /** + * Sets the domain context of the request. + * + * This method also determines the redirect status for the http request. + * + * Specifically, here we determine if a redirect is required. That happens + * in one of two cases: an unauthorized request to an inactive domain is made; + * a domain alias is set to redirect to its primary domain record. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * The Event to process. + * + * @see domain_alias_domain_request_alter + */ + public function onKernelRequestDomain(GetResponseEvent $event) { + // Negotiate the request and set domain context. + /** @var \Drupal\domain\DomainInterface $domain */ + if ($domain = $this->domainNegotiator->getActiveDomain(TRUE)) { + $hostname = $domain->getHostname(); + $domain_url = $domain->getUrl(); + if ($domain_url) { + $redirect_status = $domain->getRedirect(); + $path = trim($event->getRequest()->getPathInfo(), '/'); + // If domain negotiation asked for a redirect, issue it. + if (is_null($redirect_status) && $this->accessCheck->checkPath($path)) { + // Else check for active domain or inactive access. + /** @var \Drupal\Core\Access\AccessResult $access */ + $access = $this->accessCheck->access($this->account); + // If the access check fails, reroute to the default domain. + // Note that Allowed, Neutral, and Failed are the options here. + // We insist on Allowed. + if (!$access->isAllowed()) { + /** @var \Drupal\domain\DomainInterface $default */ + $default = $this->domainStorage->loadDefaultDomain(); + $domain_url = $default->getUrl(); + $redirect_status = 302; + $hostname = $default->getHostname(); + } + } + } + if (!empty($redirect_status)) { + // Pass a redirect if necessary. + if (DomainRedirectResponse::checkTrustedHost($hostname)) { + $response = new TrustedRedirectResponse($domain_url, $redirect_status); + } + else { + // If the redirect is not to a registered hostname, reject the request. + $response = new Response('The provided host name is not a valid redirect.', 401); + } + $event->setResponse($response); + } + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + // This needs to fire very early in the stack, before accounts are cached. + $events[KernelEvents::REQUEST][] = array('onKernelRequestDomain', 50); + return $events; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainDeleteForm.php b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainDeleteForm.php new file mode 100644 index 000000000..d556a7c49 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainDeleteForm.php @@ -0,0 +1,45 @@ +t('Are you sure you want to delete %name?', array('%name' => $this->entity->label())); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('domain.admin'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->entity->delete(); + drupal_set_message($this->t('Domain %label has been deleted.', array('%label' => $this->entity->label()))); + \Drupal::logger('domain')->notice('Domain %label has been deleted.', array('%label' => $this->entity->label())); + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainSettingsForm.php b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainSettingsForm.php new file mode 100644 index 000000000..f5169aa6e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Form/DomainSettingsForm.php @@ -0,0 +1,92 @@ +config('domain.settings'); + $form['allow_non_ascii'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Allow non-ASCII characters in domains and aliases'), + '#default_value' => $config->get('allow_non_ascii'), + '#description' => $this->t('Domains may be registered with international character sets. Note that not all DNS server respect non-ascii characters.'), + ); + $form['www_prefix'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Ignore www prefix when negotiating domains'), + '#default_value' => $config->get('www_prefix'), + '#description' => $this->t('Domain negotiation will ignore any www prefixes for all requests.'), + ); + // Get the usable tokens for this field. + foreach (\Drupal::service('domain.token')->getCallbacks() as $key => $callback) { + $patterns[] = "[domain:$key]"; + } + $form['css_classes'] = array( + '#type' => 'textfield', + '#size' => 80, + '#title' => $this->t('Custom CSS classes'), + '#default_value' => $config->get('css_classes'), + '#description' => $this->t('Enter any CSS classes that should be added to the <body> tag. Available replacement patterns are: ' . implode(', ', $patterns)), + ); + $form['login_paths'] = array( + '#type' => 'textarea', + '#rows' => 5, + '#columns' => 40, + '#title' => $this->t('Paths that should be accessible for inactive domains'), + '#default_value' => $config->get('login_paths', "/user/login\r\n/user/password"), + '#description' => $this->t('Inactive domains are only accessible to users with permission. + Enter any paths that should be accessible, one per line. Normally, only the + login path will be allowed.'), + ); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $config = $this->config('domain.settings'); + foreach ($this->settingsKeys() as $key) { + $config->set($key, $form_state->getValue($key)); + } + $config->save(); + parent::submitForm($form, $form_state); + } + + /** + * Returns an array of settings keys. + */ + public function settingsKeys() { + return [ + 'allow_non_ascii', + 'www_prefix', + 'login_paths', + 'css_classes', + ]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DefaultDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DefaultDomain.php new file mode 100644 index 000000000..f44253bef --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DefaultDomain.php @@ -0,0 +1,47 @@ +saveDefault(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->saveDefault(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DeleteDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DeleteDomain.php new file mode 100644 index 000000000..0450a6d88 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DeleteDomain.php @@ -0,0 +1,47 @@ +delete(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->delete(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DisableDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DisableDomain.php new file mode 100644 index 000000000..f227f0604 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/DisableDomain.php @@ -0,0 +1,47 @@ +disable(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->disable(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/EnableDomain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/EnableDomain.php new file mode 100644 index 000000000..0b9deab27 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Action/EnableDomain.php @@ -0,0 +1,47 @@ +enable(); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $objects) { + foreach ($objects as $object) { + if ($object instanceof DomainInterface) { + $object->enable(); + } + } + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + $access_result = AccessResult::allowedIfHasPermission($account, 'administer domains'); + return $return_as_object ? $access_result : $access_result->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainBlockBase.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainBlockBase.php new file mode 100644 index 000000000..ccc8d0643 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainBlockBase.php @@ -0,0 +1,20 @@ +getSetting('link_options') == 'home') { + return ['url.site']; + } + return ['url']; + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $defaults = $this->defaultConfiguration(); + $elements['link_options'] = [ + '#title' => $this->t('Link paths'), + '#type' => 'radios', + '#required' => TRUE, + '#options' => ['active' => $this->t('Link to active url'), 'home' => $this->t('Link to site home page')], + '#default_value' => !empty($this->configuration['link_options']) ? $this->configuration['link_options'] : $defaults['link_options'], + '#description' => $this->t('Determines how links to each domain will be written. Note that some paths may not be accessible on all domains.'), + ]; + $options = array( + 'select' => t('JavaScript select list'), + 'menus' => t('Menu-style tab links'), + 'ul' => t('Unordered list of links'), + ); + $elements['link_theme'] = array( + '#type' => 'radios', + '#title' => t('Link theme'), + '#default_value' => !empty($this->configuration['link_theme']) ? $this->configuration['link_theme'] : $defaults['link_theme'], + '#options' => $options, + '#description' => $this->t('Select how to display the block output.'), + ); + $options = array( + 'name' => t('The domain display name'), + 'hostname' => t('The raw hostname'), + 'url' => t('The domain base URL'), + ); + $elements['link_label'] = array( + '#type' => 'radios', + '#title' => t('Link text'), + '#default_value' => !empty($this->configuration['link_label']) ? $this->configuration['link_label'] : $defaults['link_label'], + '#options' => $options, + '#description' => $this->t('Select the text to display for each link.'), + ); + return $elements; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + // Process the block's submission handling if no errors occurred only. + if (!$form_state->getErrors()) { + foreach (array_keys($this->defaultConfiguration()) as $element) { + $this->configuration[$element] = $form_state->getValue($element); + } + } + } + + /** + * Overrides \Drupal\block\BlockBase::access(). + */ + public function access(AccountInterface $account, $return_as_object = FALSE) { + $access = AccessResult::allowedIfHasPermissions($account, array('administer domains', 'use domain nav block'), 'OR'); + return $return_as_object ? $access : $access->isAllowed(); + } + + /** + * Build the output. + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $active_domain */ + $active_domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + $access_handler = \Drupal::service('entity_type.manager')->getAccessControlHandler('domain'); + $account = \Drupal::currentUser(); + + // Determine the visible domain list. + $items = []; + $add_path = ($this->getSetting('link_options') == 'active'); + /** @var \Drupal\domain\DomainInterface $domain */ + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted() as $domain) { + // Set the URL. + $options = ['absolute' => TRUE, 'https' => ($domain->getScheme() == 'https')]; + if ($add_path) { + $url = Url::fromUri($domain->getUrl(), $options); + } + else { + $url = Url::fromUri($domain->getPath(), $options); + } + // Set the label text. + $label = $domain->label(); + if ($this->getSetting('link_label') == 'hostname') { + $label = $domain->getHostname(); + } + elseif ($this->getSetting('link_label') == 'url') { + $label = $domain->getPath(); + } + // Handles menu links. + if ($domain->status() || $account->hasPermission('access inactive domains')) { + if ($this->getSetting('link_theme') == 'menus') { + // @TODO: active trail isn't working properly, likely because this + // isn't really a menu. + $items[] = [ + 'title' => $label, + 'url' => $url, + 'attributes' => [], + 'below' => [], + 'is_expanded' => FALSE, + 'is_collapsed' => FALSE, + 'in_active_trail' => $domain->isActive(), + ]; + } + elseif ($this->getSetting('link_theme') == 'select') { + $items[] = [ + 'label' => $label, + 'url' => $url->toString(), + 'active' => $domain->isActive(), + ]; + } + else { + $items[] = ['#markup' => Link::fromTextAndUrl($label, $url)->toString()]; + } + } + } + + // Set the proper theme. + switch ($this->getSetting('link_theme')) { + case 'select': + $build['#theme'] = 'domain_nav_block'; + $build['items'] = $items; + break; + case 'menus': + // Map the $items params to what menu.html.twig expects. + $build['#items'] = $items; + $build['#menu_name'] = 'domain-nav'; + $build['#sorted'] = TRUE; + $build['#theme'] = 'menu__' . strtr($build['#menu_name'], '-', '_'); + break; + case 'ul': + default: + $build['#theme'] = 'item_list'; + $build['#items'] = $items; + break; + } + + return $build; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'link_options' => 'home', + 'link_theme' => 'ul', + 'link_label' => 'name', + ]; + } + + /** + * Gets the configuration for the block, loading defaults if not set. + * + * @param $key + * The setting key to retrieve, a string + * + * @return + *. The setting value, a string. + */ + public function getSetting($key) { + if (isset($this->settings[$key])) { + return $this->settings[$key]; + } + $defaults = $this->defaultConfiguration(); + if (isset($this->configuration[$key])) { + $this->settings[$key] = $this->configuration[$key]; + } + else { + $this->settings[$key] = $defaults[$key]; + } + return $this->settings[$key]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php new file mode 100644 index 000000000..6929c273a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php @@ -0,0 +1,126 @@ +isAllowed(); + } + + /** + * Build the output. + * + * @TODO: abstract or theme this function? + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $domain */ + $domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + if (!$domain) { + return array( + '#markup' => $this->t('No domain record could be loaded.'), + ); + } + $header = array($this->t('Server'), $this->t('Value')); + $rows[] = array( + $this->t('HTTP_HOST request'), + Html::escape($_SERVER['HTTP_HOST']), + ); + // Check the response test. + $domain->getResponse(); + $check = \Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($_SERVER['HTTP_HOST']); + $match = $this->t('Exact match'); + if (!$check) { + // Specific check for Domain Alias. + if (isset($domain->alias)) { + $match = $this->t('ALIAS: Using alias %id', array('%id' => $domain->alias->getPattern())); + } + else { + $match = $this->t('FALSE: Using default domain.'); + } + } + $rows[] = array( + $this->t('Domain match'), + $match, + ); + $www = \Drupal::config('domain.settings')->get('www_prefix'); + $rows[] = array( + $this->t('Strip www prefix'), + !empty($www) ? $this->t('On') : $this->t('Off'), + ); + $list = $domain->toArray(); + ksort($list); + foreach ($list as $key => $value) { + if (is_null($value)) { + $value = $this->t('NULL'); + } + elseif ($value === TRUE) { + $value = $this->t('TRUE'); + } + elseif ($value === FALSE) { + $value = $this->t('FALSE'); + } + elseif ($key == 'status' || $key == 'is_default') { + $value = empty($value) ? $this->t('FALSE') : $this->t('TRUE'); + } + $rows[] = array( + Html::escape($key), + !is_array($value) ? Html::escape($value) : $this->printArray($value), + ); + } + return array( + '#theme' => 'table', + '#rows' => $rows, + '#header' => $header, + ); + } + + /** + * Prints array data for the server block. + * + * @param array $array + * An array of data. Note that we support two levels of nesting. + * + * @return string + * A suitable output string. + */ + public function printArray(array $array) { + $items = array(); + foreach ($array as $key => $val) { + if (!is_array($val)) { + $value = Html::escape($val); + } + else { + $list = array(); + foreach ($val as $k => $v) { + $list[] = $this->t('@key : @value', array('@key' => $k, '@value' => $v)); + } + $value = implode('
', $list); + } + $items[] = $this->t('@key : @value', array('@key' => $key, '@value' => $value)); + } + $variables['domain_server'] = array( + '#theme' => 'item_list', + '#items' => $items, + ); + return render($variables); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php new file mode 100644 index 000000000..1f45c9f0d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php @@ -0,0 +1,52 @@ +isAllowed(); + } + + /** + * Build the output. + * + * @TODO: abstract or theme this function? + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $active_domain */ + $active_domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + $items = array(); + /** @var \Drupal\domain\DomainInterface $domain */ + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted() as $domain) { + $string = $domain->getLink(); + if (!$domain->status()) { + $string .= '*'; + } + if ($domain->id() == $active_domain->id()) { + $string = '' . $string . ''; + } + $items[] = array('#markup' => $string); + } + return array( + '#theme' => 'item_list', + '#items' => $items, + ); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php new file mode 100644 index 000000000..f8f9965fc --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php @@ -0,0 +1,83 @@ +isAllowed(); + } + + /** + * Build the output. + * + * @TODO: abstract or theme this function? + */ + public function build() { + /** @var \Drupal\domain\DomainInterface $domain */ + $domain = \Drupal::service('domain.negotiator')->getActiveDomain(); + if (!$domain) { + return array( + '#markup' => $this->t('No domain record could be loaded.'), + ); + } + $header = array($this->t('Token'), $this->t('Value')); + return array( + '#theme' => 'table', + '#rows' => $this->renderTokens($domain), + '#header' => $header, + ); + } + + /** + * Generates available tokens for printing. + * + * @param Drupal\domain\DomainInterface $domain + * The active domain request. + * @return array + * An array keyed by token name, with value of replacement value. + */ + private function renderTokens(DomainInterface $domain) { + $rows = array(); + $token = \Drupal::token(); + $tokens = $token->getInfo(); + // The 'domain' token is supported by core. The others by Token module, + // so we cannot assume that Token module is present. + $domain_tokens = ['domain', 'current-domain', 'default-domain']; + foreach ($domain_tokens as $key) { + if (isset($tokens['tokens'][$key])) { + $data = []; + // Pass domain data to the default handler. + if ($key == 'domain') { + $data['domain'] = $domain; + } + foreach ($tokens['tokens'][$key] as $name => $info) { + $string = "[$key:$name]"; + $rows[] = [ + $string, + $token->replace($string, $data), + ]; + } + } + } + return $rows; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php new file mode 100644 index 000000000..95cc9ed8f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php @@ -0,0 +1,145 @@ +domainNegotiator = $domain_negotiator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('domain.negotiator'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['domains'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('When the following domains are active'), + '#default_value' => $this->configuration['domains'], + '#options' => array_map('\Drupal\Component\Utility\Html::escape', \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList()), + '#description' => $this->t('If you select no domains, the condition will evaluate to TRUE for all requests.'), + '#attached' => array( + 'library' => array( + 'domain/drupal.domain', + ), + ), + ); + return parent::buildConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array( + 'domains' => array(), + ) + parent::defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['domains'] = array_filter($form_state->getValue('domains')); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function summary() { + // Use the domain labels. They will be sanitized below. + $domains = array_intersect_key(\Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(), $this->configuration['domains']); + if (count($domains) > 1) { + $domains = implode(', ', $domains); + } + else { + $domains = reset($domains); + } + if ($this->isNegated()) { + return $this->t('Active domain is not @domains', array('@domains' => $domains)); + } + else { + return $this->t('Active domain is @domains', array('@domains' => $domains)); + } + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + $domains = $this->configuration['domains']; + if (empty($domains) && !$this->isNegated()) { + return TRUE; + } + // If the context did not load, derive from the request. + if (!$domain = $this->getContextValue('entity:domain')) { + $domain = $this->domainNegotiator->getActiveDomain(); + } + // No context found? + if (empty($domain)) { + return FALSE; + } + // NOTE: The context system handles negation for us. + return (bool) in_array($domain->id(), $domains); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + $contexts[] = 'url.site'; + return $contexts; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainAdminSelection.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainAdminSelection.php new file mode 100644 index 000000000..bd47b8a4d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/EntityReferenceSelection/DomainAdminSelection.php @@ -0,0 +1,36 @@ +currentUser->hasPermission('administer domains')) { + return $query; + } + // Can this user access inactive domains? + if (!$this->currentUser->hasPermission('access inactive domains')) { + $query->condition('status', 1); + } + // Filter domains by the user's assignments, which are controlled by other + // modules. Those modules must know what type of entity they are dealing + // with, so look up the entity type and bundle. + $info = $query->getMetaData('entity_reference_selection_handler'); + + if (!empty($info->configuration['entity'])) { + $context['entity_type'] = $info->configuration['entity']->getEntityTypeId(); + $context['bundle'] = $info->configuration['entity']->bundle(); + $context['field_type'] = $this->field_type; + + // Load the current user. + $account = User::load($this->currentUser->id()); + // Run the alter hook. + $this->moduleHandler->alter('domain_references', $query, $account, $context); + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $selection_handler_settings = $this->configuration['handler_settings']; + + // Merge-in default values. + $selection_handler_settings += array( + // For the 'target_bundles' setting, a NULL value is equivalent to "allow + // entities from any bundle to be referenced" and an empty array value is + // equivalent to "no entities from any bundle can be referenced". + 'target_bundles' => NULL, + 'sort' => array( + 'field' => 'weight', + 'direction' => 'ASC', + ), + 'auto_create' => FALSE, + 'default_selection' => 'current', + ); + + $form['target_bundles'] = array( + '#type' => 'value', + '#value' => NULL, + ); + + $fields = array( + 'weight' => $this->t('Weight'), + 'label' => $this->t('Name'), + 'hostname' => $this->t('Hostname'), + ); + + $form['sort']['field'] = array( + '#type' => 'select', + '#title' => $this->t('Sort by'), + '#options' => $fields, + '#ajax' => FALSE, + '#default_value' => $selection_handler_settings['sort']['field'], + ); + + $form['sort']['direction'] = array( + '#type' => 'select', + '#title' => $this->t('Sort direction'), + '#required' => TRUE, + '#options' => array( + 'ASC' => $this->t('Ascending'), + 'DESC' => $this->t('Descending'), + ), + '#default_value' => $selection_handler_settings['sort']['direction'], + ); + + return $form; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php new file mode 100644 index 000000000..4ceaa42f2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php @@ -0,0 +1,56 @@ +select('domain', 'd')->fields('d', $fields); + } + + /** + * {@inheritdoc} + */ + public function fields() { + return [ + 'domain_id' => $this->t('Domain ID.'), + 'subdomain' => $this->t('Subdomain.'), + 'sitename' => $this->t('Sitename.'), + 'scheme' => $this->t('Scheme.'), + 'valid' => $this->t('Valid.'), + 'weight' => $this->t('Weight.'), + 'is_default' => $this->t('Is default.'), + 'machine_name' => $this->t('Machine name.'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + return ['domain_id' => ['type' => 'int']]; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/views/access/Domain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/views/access/Domain.php new file mode 100644 index 000000000..63376a960 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/views/access/Domain.php @@ -0,0 +1,189 @@ +domainStorage = $domain_storage; + $this->domainNegotiator = $domain_negotiator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('domain.negotiator') + ); + } + + /** + * {@inheritdoc} + */ + public function access(AccountInterface $account) { + $id = $this->domainNegotiator->getActiveId(); + $options = array_filter($this->options['domain']); + return isset($options[$id]); + } + + /** + * {@inheritdoc} + */ + public function alterRouteDefinition(Route $route) { + if ($this->options['domain']) { + $route->setRequirement('_domain', (string) implode('+', $this->options['domain'])); + } + } + + /** + * {@inheritdoc} + */ + public function summaryTitle() { + $count = count($this->options['domain']); + if ($count < 1) { + return $this->t('No domain(s) selected'); + } + elseif ($count > 1) { + return $this->t('Multiple domains'); + } + else { + $domains = $this->domainStorage->loadOptionsList(); + $domain = reset($this->options['domain']); + return $domains[$domain]; + } + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $options['domain'] = array('default' => array()); + + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + parent::buildOptionsForm($form, $form_state); + $form['domain'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('Domain'), + '#default_value' => $this->options['domain'], + '#options' => $this->domainStorage->loadOptionsList(), + '#description' => $this->t('Only the checked domain(s) will be able to access this display.'), + ); + } + + /** + * {@inheritdoc} + */ + public function validateOptionsForm(&$form, FormStateInterface $form_state) { + $domain = $form_state->getValue(array('access_options', 'domain')); + $domain = array_filter($domain); + + if (!$domain) { + $form_state->setError($form['domain'], $this->t('You must select at least one domain if type is "by domain"')); + } + + $form_state->setValue(array('access_options', 'domain'), $domain); + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + $dependencies = parent::calculateDependencies(); + + foreach (array_keys($this->options['domain']) as $id) { + if ($domain = $this->domainStorage->load($id)) { + $dependencies[$domain->getConfigDependencyKey()][] = $domain->getConfigDependencyName(); + } + } + + return $dependencies; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['url.site']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Tests/DomainTestBase.php b/sites/all/modules/contrib/admin/domain/domain/src/Tests/DomainTestBase.php new file mode 100644 index 000000000..51cc0f3c4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Tests/DomainTestBase.php @@ -0,0 +1,102 @@ +base_hostname or the + * domainCreateTestDomains() method. + */ + public $base_hostname; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'node'); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create Basic page and Article node types. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + } + + // Set the base hostname for domains. + $this->setBaseHostname(); + } + + /** + * Returns whether a given user account is logged in. + * + * @param \Drupal\user\UserInterface $account + * The user account object to check. + * + * @return bool + */ + protected function drupalUserIsLoggedIn($account) { + // @TODO: This is a temporary hack for the test login fails when setting $cookie_domain. + if (!isset($account->session_id)) { + return (bool) $account->id(); + } + // The session ID is hashed before being stored in the database. + // @see \Drupal\Core\Session\SessionHandler::read() + return (bool) db_query("SELECT sid FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => Crypt::hashBase64($account->session_id)))->fetchField(); + } + + /** + * Login a user on a specific domain. + * + * @param \Drupal\domain\DomainInterface $domain + * The domain to log the user into. + * @param \Drupal\user\UserInterface $account + * The user account to login. + */ + public function domainLogin(DomainInterface $domain, UserInterface $account) { + if ($this->loggedInUser) { + $this->drupalLogout(); + } + + // Login. + $url = $domain->getPath() . 'user/login'; + $edit = ['name' => $account->getAccountName(), 'pass' => $account->passRaw]; + $this->drupalPostForm($url, $edit, t('Log in')); + + // @see WebTestBase::drupalUserIsLoggedIn() + if (isset($this->sessionId)) { + $account->session_id = $this->sessionId; + } + $pass = $this->assert($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', array('%name' => $account->getUsername())), 'User login'); + if ($pass) { + $this->loggedInUser = $account; + $this->container->get('current_user')->setAccount($account); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/templates/domain-nav-block.html.twig b/sites/all/modules/contrib/admin/domain/domain/templates/domain-nav-block.html.twig new file mode 100644 index 000000000..d2d4c0aca --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/templates/domain-nav-block.html.twig @@ -0,0 +1,32 @@ +{# +/** + * @file + * Custom theme implementation for a select-and-go menu list. + * + * Available variables: + * - items: A list of items containing an array of strings. + * - label: the label text + * - url: the link url string + * - active: indicates if the domain is currently active. + * + * @ingroup themeable + */ +#} +{%- if items -%} +
+
+ +
+
+{%- else -%} + {{- empty -}} +{%- endif -%} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/200.png b/sites/all/modules/contrib/admin/domain/domain/tests/200.png new file mode 100644 index 0000000000000000000000000000000000000000..d6152bd3c0dafa162b6bf93aeafa34f8bea930de GIT binary patch literal 593 zcmV-X0zW{8v8EKj1?C?pQ(!$r{ONp<)(A#R8x=DwwG>^zYh_tH9-}w6b zGK9BSlC-nO)NZfX0Cc_Y^!DiP^6vBYrohmDyWpL?!E@#|%09%|tm(Bohxa95gs>s@sxX-1- z)BtzDEqSIFdbiu|`rGUE1a-Shkh@2Ps{nAkJC@A=UzrblzW`sKxy{)}im|Q6)_1Pa zsm0ciwaXWGvjA?n2zR{F=JQXV&=q#FMTM;E@bq<}y#RB&{r>(>hp8nuVZ8tV0H#Sq zK~#9!VqgFP7H$SOkchF6gEBR=o#h4mB=taSeI*7XekLJlO;ZL2)@c7^OHM{cMjtf? z2Jg^tb|xU3ky+G`f!Caakpn1`As*($kiyK!o){>@#2mrK#}KdUt`m@CYv(HL8O#tA zq6#uoU5tUjm?1X74aind*79J8WZ+SBaddProperty('foo', 'bar'); + } +} + +/** + * Implements hook_domain_validate_alter(). + */ +function domain_test_domain_validate_alter(&$error_list, $hostname) { + // Deliberate test fail. + if ($hostname == 'fail.example.com') { + $error_list[] = 'Fail.example.com cannot be registered'; + } +} + +/** + * Implements hook_domain_request_alter(). + */ +function domain_test_domain_request_alter(DomainInterface &$domain) { + $domain->addProperty('foo1', 'bar1'); +} + +/** + * Implements hook_domain_operations + */ +function domain_test_domain_operations(DomainInterface $domain) { + $operations = []; + // Add aliases to the list. + $id = $domain->id(); + $operations['domain_test'] = array( + 'title' => t('Test'), + 'url' => Url::fromRoute('entity.domain.edit_form', array('domain' => $id)), + 'weight' => 80, + ); + return $operations; +} + +/** + * Implements hook_domain_references_alter + */ +function domain_test_domain_references_alter($query, $account, $context) { + if ($context['entity_type'] == 'node') { + $test = 'Test string'; + $query->addMetadata('domain_test', $test); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/Condition/DomainConditionTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/Condition/DomainConditionTest.php new file mode 100644 index 000000000..c0830285a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/Condition/DomainConditionTest.php @@ -0,0 +1,88 @@ +manager = $this->container->get('plugin.manager.condition'); + + // Create test domains. + $this->domainCreateTestDomains(5); + + // Get two sample domains. + $this->domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $this->test_domain = array_shift($this->domains); + $this->not_domain = array_shift($this->domains); + } + + /** + * Test the domain condition. + */ + public function testConditions() { + // Grab the domain condition and configure it to check against one domain. + $condition = $this->manager->createInstance('domain') + ->setConfig('domains', array($this->test_domain->id() => $this->test_domain->id())) + ->setContextValue('entity:domain', $this->not_domain); + $this->assertFalse($condition->execute(), 'Domain request condition fails on wrong domain.'); + + // Grab the domain condition and configure it to check against a null set. + $condition = $this->manager->createInstance('domain') + ->setConfig('domains', array($this->test_domain->id() => $this->test_domain->id())) + ->setContextValue('entity:domain', NULL); + $this->assertFalse($condition->execute(), 'Domain request condition fails when no context present.'); + + // Grab the domain condition and configure it to check against itself. + $condition = $this->manager->createInstance('domain') + ->setConfig('domains', array($this->test_domain->id() => $this->test_domain->id())) + ->setContextValue('entity:domain', $this->test_domain); + $this->assertTrue($condition->execute(), 'Domain request condition succeeds on matching domain.'); + + // Check for the proper summary. + // Summaries require an extra space due to negate handling in summary(). + $this->assertEqual($condition->summary(), 'Active domain is ' . $this->test_domain->label()); + + // Check the negated summary. + $condition->setConfig('negate', TRUE); + $this->assertEqual($condition->summary(), 'Active domain is not ' . $this->test_domain->label()); + + // Check the negated condition. + $this->assertFalse($condition->execute(), 'Domain request condition fails when condition negated.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainActionsTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainActionsTest.php new file mode 100644 index 000000000..756307d45 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainActionsTest.php @@ -0,0 +1,103 @@ +admin_user = $this->drupalCreateUser(array('administer domains', 'access administration pages')); + $this->drupalLogin($this->admin_user); + + $path = 'admin/config/domain'; + + // Create test domains. + $this->domainCreateTestDomains(4); + + // Visit the domain overview administration page. + $this->drupalGet($path); + $this->assertResponse(200); + + // Test the domains. + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $domains = $storage->loadMultiple(); + $this->assertTrue(count($domains) == 4, 'Four domain records found.'); + + // Check the default domain. + $default = $storage->loadDefaultId(); + $key = 'example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + // Test some text on the page. + foreach ($domains as $domain) { + $name = $domain->label(); + $this->assertText($name, 'Name found properly.'); + } + // Test the list of actions. + $actions = array('delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Check that all domains are active. + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Disable a domain and test the enable link. + $this->clickLink('Disable', 0); + $this->assertRaw('Inactive', 'Inactive domain found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($storage->loadMultiple() as $domain) { + if ($domain->id() == 'one_example_com') { + $this->assertEmpty($domain->status(), 'One domain inactive.'); + } + else { + $this->assertNotEmpty($domain->status(), 'Other domains active.'); + } + } + + // Test the list of actions. + $actions = array('enable', 'delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Re-enable the domain. + $this->clickLink('Enable', 0); + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($storage->loadMultiple() as $domain) { + $this->assertNotEmpty($domain->status(), 'All domains active.'); + } + + // Set a new default domain. + $this->clickLink('Make default', 0); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + // Check the default domain. + $storage->resetCache(); + $default = $storage->loadDefaultId(); + $key = 'one_example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + } + +} + diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainAdminElementTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainAdminElementTest.php new file mode 100644 index 000000000..5fac62914 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainAdminElementTest.php @@ -0,0 +1,142 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainAccessElement() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer users', + 'administer domains', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/people/create'); + $this->assertSession()->statusCodeEquals(200); + + // Create a user through the form. + $this->fillField('name', 'testuser'); + $this->fillField('mail', 'test@example.com'); + $this->fillField('pass[pass1]', 'test'); + $this->fillField('pass[pass2]', 'test'); + + // We expect to find 5 domain options. We set two as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $count = 0; + $ids = ['example_com', 'one_example_com', 'two_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + } + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + $storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $storage->load(3); + // Check that two values are set. + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 3, 'User saved with three domain records.'); + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array( + 'administer users', + 'assign domain administrators', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $tester = $storage->load($account->id()); + $values = $manager->getFieldValues($tester, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + $storage->resetCache(array($account->id())); + $this->drupalLogin($account); + + $this->drupalGet('user/' . $user->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + } + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the user. + $storage->resetCache(array($user->id())); + $user = $storage->load($user->id()); + // Check that two values are set. + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + // Test the case presented in https://www.drupal.org/node/2841962. + $config = \Drupal::configFactory()->getEditable('user.settings'); + $config->set('verify_mail', 0); + $config->set('register', USER_REGISTER_VISITORS); + $config->save(); + $this->drupalLogout(); + $this->drupalGet('user/register'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->responseNotContains('Domain administrator'); + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->assertSession()->fieldNotExists($locator); + } + // Create a user through the form. + $this->fillField('name', 'testuser2'); + $this->fillField('mail', 'test2@example.com'); + // In 8.3, this field is not present? + if (!empty($this->findField('pass[pass1]'))) { + $this->fillField('pass[pass1]', 'test'); + $this->fillField('pass[pass2]', 'test'); + } + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCSSTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCSSTest.php new file mode 100644 index 000000000..c3f67a39c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainCSSTest.php @@ -0,0 +1,77 @@ +install(array('bartik')); + } + + /** + * Tests the handling of an inbound request. + */ + public function testDomainNegotiator() { + // No domains should exist. + $this->domainTableIsEmpty(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + + // The test runner doesn't use a theme that contains the preprocess hook, + // so set to use Bartik. + $config = $this->config('system.theme'); + $config->set('default', 'bartik')->save(); + + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + $this->drupalGet($domain->getPath()); + $text = 'id() . '-class'); + $this->assertRaw($text, 'Custom CSS present.' . $text); + } + + // Set the css classes. + $config = $this->config('domain.settings'); + $config->set('css_classes', '[domain:machine-name]-class [domain:name]-class')->save(); + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + // The render cache trips up this test. In production, it may be + // necessary to add the url.site cache context. See README.md. + drupal_flush_all_caches(); + $this->drupalGet($domain->getPath()); + $text = 'id() . '"'; + $this->assertRaw($string, 'Found the domain option'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Try to post a node, assigned to the first two domains. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain[{$one}]"] = TRUE; + $edit["field_domain[{$two}]"] = TRUE; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + $values = $node->get('field_domain'); + + // Get the expected value count. + $this->assertTrue(count($values) == 2, 'Node saved with two domain records.'); + + } + + /** + * Creates a simple field for testing on the article content type. + * + * Note: This code is a model for auto-creation of fields. + */ + public function domainCreateTestField() { + $label = 'domain'; + $name = 'field_' . $label; + + $storage = array( + 'field_name' => $name, + 'entity_type' => 'node', + 'type' => 'entity_reference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'domain', + ), + ); + $field_storage_config = \Drupal::entityTypeManager()->getStorage('field_storage_config')->create($storage); + $field_storage_config->save(); + + $field = array( + 'field_name' => $name, + 'entity_type' => 'node', + 'label' => 'Domain test field', + 'bundle' => 'article', + 'settings' => array( + 'handler_settings' => array( + 'sort' => array('field' => 'weight', 'direction' => 'ASC'), + ), + ), + ); + $field_config = \Drupal::entityTypeManager()->getStorage('field_config')->create($field); + $field_config->save(); + + // Tell the form system how to behave. + entity_get_form_display('node', 'article', 'default') + ->setComponent($name, array( + 'type' => 'options_buttons', + )) + ->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainFormsTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainFormsTest.php new file mode 100644 index 000000000..be5575712 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainFormsTest.php @@ -0,0 +1,79 @@ +admin_user = $this->drupalCreateUser(array('administer domains', 'create domains')); + $this->drupalLogin($this->admin_user); + + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + + // No domains should exist. + $this->domainTableIsEmpty(); + + // Visit the main domain administration page. + $this->drupalGet('admin/config/domain'); + + // Check for the add message. + $this->assertText('There is no Domain record yet.', 'Text for no domains found.'); + // Visit the add domain administration page. + $this->drupalGet('admin/config/domain/add'); + + // Make a POST request on admin/config/domain/add. + $edit = $this->domainPostValues(); + $this->drupalPostForm('admin/config/domain/add', $edit, 'Save'); + + // Did it save correctly? + $default_id = $storage->loadDefaultId(); + $this->assertTrue(!empty($default_id), 'Domain record saved via form.'); + + // Does it load correctly? + $storage->resetCache([$default_id]); + $new_domain = $storage->load($default_id); + $this->assertTrue($new_domain->id() == $default_id, 'Domain loaded properly.'); + + // Has a UUID been set? + $this->assertTrue(!empty($new_domain->uuid()), 'Entity UUID set properly.'); + + // Visit the edit domain administration page. + $editUrl = 'admin/config/domain/edit/' . $new_domain->id(); + $this->drupalGet($editUrl); + + // Update the record. + $edit = []; + $edit['name'] = 'Foo'; + $edit['validate_url'] = 0; + $this->drupalPostForm($editUrl, $edit, 'Save'); + + // Check that the update succeeded. + $storage->resetCache([$default_id]); + $domain = $storage->load($default_id); + $this->assertTrue($domain->label() == 'Foo', 'Domain record updated via form.'); + + // Visit the delete domain administration page. + $deleteUrl = 'admin/config/domain/delete/' . $new_domain->id(); + $this->drupalGet($deleteUrl); + + // Delete the record. + $this->drupalPostForm($deleteUrl, array(), 'Delete'); + $storage->resetCache([$default_id]); + $domain = $storage->load($default_id); + $this->assertTrue(empty($domain), 'Domain record deleted.'); + + // No domains should exist. + $this->domainTableIsEmpty(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainGetResponseTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainGetResponseTest.php new file mode 100644 index 000000000..7360d894a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainGetResponseTest.php @@ -0,0 +1,44 @@ +domainTableIsEmpty(); + + // Create a new domain programmatically. + $this->domainCreateTestDomains(); + + // Check the created domain based on it's known id value. + $key = 'example_com'; + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($key); + + // Our testing server should be able to access the test PNG file. + $this->assertTrue($domain->getResponse() == 200, 'Server returned a 200 response.'); + + // Now create a bad domain. + $values = array( + 'hostname' => 'foo.bar', + 'id' => 'foo_bar', + 'name' => 'Foo', + ); + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->create($values); + + $domain->save(); + $this->assertTrue($domain->getResponse() == 500, 'Server test returned a 500 response.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainInactiveTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainInactiveTest.php new file mode 100644 index 000000000..c7d075f98 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainInactiveTest.php @@ -0,0 +1,98 @@ +config('system.site'); + $site_config->set('page.front', '/node')->save(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + + // Grab a known domain for testing. + $domain = $domains['two_example_com']; + $this->drupalGet($domain->getPath()); + $this->assertTrue($domain->status(), 'Tested domain is set to active.'); + $this->assertTrue($domain->getPath() == $this->getUrl(), 'Loaded the active domain.'); + + // Disable the domain and test for redirect. + $domain->disable(); + $default = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain(); + // Our postSave() cache tag clear should allow this to work properly. + $this->drupalGet($domain->getPath()); + + $this->assertFalse($domain->status(), 'Tested domain is set to inactive.'); + $this->assertTrue($default->getPath() == $this->getUrl(), 'Redirected an inactive domain to the default domain.'); + + // Check to see if the user can login. + $url = $domain->getPath() . 'user/login'; + $this->drupalGet($url); + $this->assertResponse(200, 'Request to login on inactive domain allowed.'); + // Check to see if the user can reset password. + $url = $domain->getPath() . 'user/password'; + $this->drupalGet($url); + $this->assertResponse(200, 'Request to reset password on inactive domain allowed.'); + + // Try to access with the proper permission. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('access inactive domains')); + // Must flush cache because we did not resave the domain. + drupal_flush_all_caches(); + $this->assertFalse($domain->status(), 'Tested domain is set to inactive.'); + $this->drupalGet($domain->getPath()); + + // Set up two additional domains. + $domain2 = $domains['one_example_com']; + $domain3 = $domains['three_example_com']; + + // Check against trusted host patterns. + $settings['settings']['trusted_host_patterns'] = (object) [ + 'value' => ['^' . $this->prepareTrustedHostname($domain->getHostname()) . '$', + '^' . $this->prepareTrustedHostname($domain2->getHostname()) . '$'], + 'required' => TRUE, + ]; + $this->writeSettings($settings); + + // Revoke the permission change. + user_role_revoke_permissions(RoleInterface::ANONYMOUS_ID, array('access inactive domains')); + + $domain2->saveDefault(); + + // Test the trusted host, which should redirect to default. + $this->drupalGet($domain->getPath()); + $this->assertTrue($domain2->getPath() == $this->getUrl(), 'Redirected from the inactive domain.'); + $this->assertResponse(200, 'Request to trusted host allowed.'); + + // The redirect is cached, so we must flush cache to test again. + drupal_flush_all_caches(); + + // Test another inactive domain that is not trusted. + // Disable the domain and test for redirect. + $domain3->saveDefault(); + $this->drupalGet($domain->getPath()); + $this->assertRaw('The provided host name is not a valid redirect.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListBuilderTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListBuilderTest.php new file mode 100644 index 000000000..1d43fbd5e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListBuilderTest.php @@ -0,0 +1,213 @@ +domainCreateTestDomains(150); + } + + /** + * Basic test setup. + */ + public function testDomainListBuilder() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + foreach ($this->getPaginatedDomains() as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + $this->assertSession()->linkByHrefExists($href, 0, 'Link found ' . $href); + $this->assertSession()->assertEscaped($domain->label()); + // Check for pagination. + $this->checkPagination(); + } + + // Go to page 2. + $this->clickLink('Next'); + foreach ($this->getPaginatedDomains(1) as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + $this->assertSession()->linkByHrefExists($href, 0, 'Link found ' . $href); + $this->assertSession()->assertEscaped($domain->label()); + // Check for pagination. + $this->checkPagination(); + } + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + 'edit assigned domains', + 'view domain list', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account->id()); + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + $this->drupalLogin($account); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + $path = 'admin/config/domain'; + $this->drupalGet($path); + foreach ($this->getPaginatedDomains() as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + $this->assertSession()->assertEscaped($domain->label()); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + $this->assertSession()->assertEscaped($domain->label()); + } + // Check for pagination. + $this->checkPagination(); + } + + // Check access to the pages/routes. + foreach ($this->getPaginatedDomains() as $domain) { + $path = 'admin/config/domain/edit/' . $domain->id(); + $this->drupalGet($path); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->statusCodeEquals(200); + } + else { + $this->assertSession()->statusCodeEquals(403); + } + } + + // Go to page 2. + $this->drupalGet('admin/config/domain'); + $this->clickLink('Next'); + foreach ($this->getPaginatedDomains(1) as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + $this->assertSession()->assertEscaped($domain->label()); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + $this->assertSession()->assertEscaped($domain->label()); + } + // Check for pagination. + $this->checkPagination(); + } + + // Now login as a user with more limited rights. + $account2 = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + 'edit assigned domains', + 'view assigned domains', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account2->id(), $ids, DOMAIN_ADMIN_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account2->id()); + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + $this->drupalLogin($account2); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that domains are listed and links are printed. + $path = 'admin/config/domain'; + $this->drupalGet($path); + foreach ($this->getPaginatedDomains() as $domain) { + $href = 'admin/config/domain/edit/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + $this->assertSession()->assertEscaped($domain->label()); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + $this->assertSession()->assertNoEscaped($domain->label()); + } + // Check for pagination. + $this->checkNoPagination(); + } + + // Check access to the pages/routes. + foreach ($this->getPaginatedDomains() as $domain) { + $path = 'admin/config/domain/edit/' . $domain->id(); + $this->drupalGet($path); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->statusCodeEquals(200); + } + else { + $this->assertSession()->statusCodeEquals(403); + } + } + + } + + /** + * Returns an array of domains, paginated and sorted by weight. + * + * @param int $page + * The page number to return. + */ + private function getPaginatedDomains($page = 0) { + $limit = 50; + $offset = $page * $limit; + return array_slice($this->getDomainsSorted(), $offset, $limit); + } + + /** + * Checks that pagination links appear, as expected. + */ + private function checkPagination() { + foreach (['?page=0', '?page=1', '?page=2'] as $href) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + } + } + + /** + * Checks that pagination links do not appear, as expected. + */ + private function checkNoPagination() { + foreach (['?page=0', '?page=1', '?page=2'] as $href) { + $this->assertSession()->linkByHrefNotExists($href, 0, 'Link not found'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListWeightTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListWeightTest.php new file mode 100644 index 000000000..bcb242a17 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainListWeightTest.php @@ -0,0 +1,110 @@ +domainCreateTestDomains(60); + } + + /** + * Basic test setup. + */ + public function testDomainWeight() { + // Test the default sort values. Should be 1 to 60. + $domains = $this->getDomainsSorted(); + $i = 1; + foreach ($domains as $domain) { + $this->assert($domain->getWeight() == $i, 'Weight set to ' . $i); + $i++; + } + // The last domain should be test59_example_com. + $this->assert($domain->id() == 'test59_example_com', 'Last domain is test59'); + $domains_old = $domains; + + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Set one weight to 61. + $locator = 'edit-domains-one-example-com-weight'; + $this->fillField($locator, 61); + + // Save the form. + $this->pressButton('edit-submit'); + + $domains = $this->getDomainsSorted(); + $i = 1; + foreach ($domains as $domain) { + // Weights should be the same one page 1 except for the one we changed. + if ($domain->id() == 'one_example_com') { + $this->assert($domain->getWeight() == 61, 'Weight set to 61 ' . $domain->getWeight()); + } + else { + $this->assert($domain->getWeight() == $domains_old[$domain->id()]->getWeight(). 'Weights unchanged'); + } + $i++; + } + // The last domain should be one_example_com. + $this->assert($domain->id() == 'one_example_com', 'Last domain is one'); + + // Go to page two. + $this->clickLink('Next'); + $this->assertSession()->statusCodeEquals(200); + // Set one weight to 2. + $locator = 'edit-domains-one-example-com-weight'; + $this->fillField($locator, 2); + // Save the form. + $this->pressButton('edit-submit'); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Go to page two. + $this->clickLink('Next'); + $this->assertSession()->statusCodeEquals(200); + + // Check the domain sort order. + $domains = $this->getDomainsSorted(); + $i = 1; + foreach ($domains as $domain) { + if ($domain->id() == 'one_example_com') { + $this->assert($domain->getWeight() == 2, 'Weight set to 2'); + } + else { + $this->assert($domain->getWeight() == $domains_old[$domain->id()]->getWeight(). 'Weights unchanged'); + } + } + // The last domain should be test59_example_com. + $this->assert($domain->id() == 'test59_example_com', 'Last domain is test59' . $domain->id()); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNavBlockTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNavBlockTest.php new file mode 100644 index 000000000..dbb88bc2b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNavBlockTest.php @@ -0,0 +1,97 @@ +domainCreateTestDomains(4); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + + // Place the nav block. + $block = $this->drupalPlaceBlock('domain_nav_block'); + + // Let the anon user view the block. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('use domain nav block')); + + // Load the homepage. All links should appear. + $this->drupalGet(''); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // Disable one of the domains. One link should not appear. + $disabled = $domains['one_example_com']; + $disabled->disable(); + + // Load the homepage. + $this->drupalGet(''); + // Confirm domain links. + foreach ($domains as $id => $domain) { + if ($id != 'one_example_com') { + $this->findLink($domain->label()); + } + else { + $this->assertNoRaw($domain->label()); + } + } + // Let the anon user view diabled domains. All links should appear. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('access inactive domains')); + + // Load the homepage. + $this->drupalGet(''); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // Now update the configuration and test again. + $this->config('block.block.' . $block->id()) + ->set('settings.link_options', 'active') + ->set('settings.link_label', 'hostname') + ->save(); + + // Load the the login page. + $this->drupalGet('user/login'); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->getHostname()); + $this->assertRaw($domain->buildUrl('/user/login')); + } + + // Now update the configuration and test again. + $this->config('block.block.' . $block->id()) + ->set('settings.link_options', 'home') + ->set('settings.link_theme', 'menu') + ->set('settings.link_label', 'url') + ->save(); + + // Load the the login page. + $this->drupalGet('user/login'); + // Confirm domain links. + foreach ($domains as $id => $domain) { + $this->findLink($domain->getPath()); + $this->assertRaw($domain->getPath()); + } + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNegotiatorTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNegotiatorTest.php new file mode 100644 index 000000000..d0f3a9cfa --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainNegotiatorTest.php @@ -0,0 +1,46 @@ +domainTableIsEmpty(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + + // Since we cannot read the service request, we place a block + // which shows the current domain information. + $this->drupalPlaceBlock('domain_server_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('view domain information')); + + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + $this->drupalGet($domain->getPath()); + $this->assertRaw($domain->label(), 'Loaded the proper domain.'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainReferencesTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainReferencesTest.php new file mode 100644 index 000000000..4bfb345ac --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainReferencesTest.php @@ -0,0 +1,216 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainReferences() { + // Create an admin user. This will be user 2. + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer users', + 'administer domains', + 'assign domain editors', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/people/create'); + $this->assertSession()->statusCodeEquals(200); + + // Create a user through the form. This will be user 3. + $this->fillField('name', 'testuser'); + $this->fillField('mail', 'test@example.com'); + $this->fillField('pass[pass1]', 'test'); + $this->fillField('pass[pass2]', 'test'); + + // We expect to find 5 domain options. We set three as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + + $ids = ['example_com', 'one_example_com', 'two_example_com']; + $edit_ids = ['example_com', 'one_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $edit_ids)) { + $this->checkField($locator); + } + } + + // Find the all affiliates field. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->findField($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Load our test user. + $storage = \Drupal::entityTypeManager()->getStorage('user'); + $testuser = $storage->load(3); + // Check that three values are set. + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($testuser, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 3, 'User saved with three domain admin records.'); + // Check that no access fields are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain access records.'); + + // Now login as a user with limited rights. This is user 4. + $account = $this->drupalCreateUser(array( + 'administer users', + 'assign domain administrators', + )); + // Set some domain assignments for this user. + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $limited_admin = $storage->load($account->id()); + $values = $manager->getFieldValues($limited_admin, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + // Check that no access fields are set. + $values = $manager->getFieldValues($limited_admin, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 0, 'User saved with no domain access records.'); + + // Now edit user 3 as user 4 with limited rights. + $this->drupalLogin($account); + $this->drupalGet('user/' . $testuser->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + // No Domain Access field rights exist for this user. + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->assertSession()->fieldNotExists($locator); + } + + // The all affiliates field should not be present.. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->assertSession()->fieldNotExists($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the user. + $storage->resetCache(array($testuser->id())); + $testuser = $storage->load($testuser->id()); + // Check that two values are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + // Check that no access fields are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain access records.'); + + // Now login as a user with different limited rights. This is user 5. + $new_account = $this->drupalCreateUser(array( + 'administer users', + 'assign domain administrators', + 'assign domain editors', + )); + $ids = ['example_com', 'one_example_com']; + $new_ids = ['one_example_com', 'four_example_com']; + $this->addDomainsToEntity('user', $new_account->id(), $ids, DOMAIN_ADMIN_FIELD); + $this->addDomainsToEntity('user', $new_account->id(), $new_ids, DOMAIN_ACCESS_FIELD); + + $new_admin = $storage->load($new_account->id()); + $values = $manager->getFieldValues($new_admin, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + $values = $manager->getFieldValues($new_admin, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain access records.'); + + // Now edit the user as someone with limited rights. + $storage->resetCache(array($new_admin->id())); + $this->drupalLogin($new_account); + + $this->drupalGet('user/' . $testuser->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ADMIN_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + // Some Domain Access field rights exist for this user. This adds + // one to the count. + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + if (in_array($domain->id(), $new_ids)) { + $this->findField($locator); + $this->checkField($locator); + } + else { + $this->assertSession()->fieldNotExists($locator); + } + } + + // The all affiliates field should not be present.. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->assertSession()->fieldNotExists($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the user. + $storage->resetCache(array($testuser->id())); + $testuser = $storage->load($testuser->id()); + // Check that two values are set. + $values = $manager->getFieldValues($testuser, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain admin records.'); + $values = $manager->getFieldValues($testuser, DOMAIN_ACCESS_FIELD); + $this->assert(count($values) == 3, 'User saved with three domain access records.'); + + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTestBase.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTestBase.php new file mode 100644 index 000000000..b1ab00a3d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTestBase.php @@ -0,0 +1,218 @@ +base_hostname or the + * domainCreateTestDomains() method. + */ + public $base_hostname; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'node'); + + /** + * We use the standard profile for testing. + * + * @var string + */ + protected $profile = 'standard'; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Set the base hostname for domains. + $this->base_hostname = \Drupal::service('entity_type.manager')->getStorage('domain')->createHostname(); + } + + /** + * The methods below are brazenly copied from Rules module. They are all + * helper methods that make writing tests a bit easier. + */ + + /** + * Finds link with specified locator. + * + * @param string $locator + * Link id, title, text or image alt. + * + * @return \Behat\Mink\Element\NodeElement|null + * The link node element. + */ + public function findLink($locator) { + return $this->getSession()->getPage()->findLink($locator); + } + + /** + * Confirms absence of link with specified locator. + * + * @param string $locator + * Link id, title, text or image alt. + * + * @return \Behat\Mink\Element\NodeElement|null + * The link node element. + */ + public function findNoLink($locator) { + return empty($this->getSession()->getPage()->hasLink($locator)); + } + + /** + * Finds field (input, textarea, select) with specified locator. + * + * @param string $locator + * Input id, name or label. + * + * @return \Behat\Mink\Element\NodeElement|null + * The input field element. + */ + public function findField($locator) { + return $this->getSession()->getPage()->findField($locator); + } + + /** + * Finds button with specified locator. + * + * @param string $locator + * Button id, value or alt. + * + * @return \Behat\Mink\Element\NodeElement|null + * The button node element. + */ + public function findButton($locator) { + return $this->getSession()->getPage()->findButton($locator); + } + + /** + * Presses button with specified locator. + * + * @param string $locator + * Button id, value or alt. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + */ + public function pressButton($locator) { + $this->getSession()->getPage()->pressButton($locator); + } + + /** + * Fills in field (input, textarea, select) with specified locator. + * + * @param string $locator + * Input id, name or label. + * @param string $value + * Value. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * + * @see \Behat\Mink\Element\NodeElement::setValue + */ + public function fillField($locator, $value) { + $this->getSession()->getPage()->fillField($locator, $value); + } + + /** + * Checks checkbox with specified locator. + * + * @param string $locator input id, name or label + * + * @throws ElementNotFoundException + */ + public function checkField($locator) { + $this->getSession()->getPage()->checkField($locator); + } + + /** + * Unchecks checkbox with specified locator. + * + * @param string $locator input id, name or label + * + * @throws ElementNotFoundException + */ + public function uncheckField($locator) { + $this->getSession()->getPage()->uncheckField($locator); + } + + /** + * Selects option from select field with specified locator. + * + * @param string $locator input id, name or label + * @param string $value option value + * @param Boolean $multiple select multiple options + * + * @throws ElementNotFoundException + * + * @see NodeElement::selectOption + */ + public function selectFieldOption($locator, $value, $multiple = false) { + $this->getSession()->getPage()->selectFieldOption($locator, $value, $multiple); + } + + /** + * Returns whether a given user account is logged in. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The user account object to check. + * + * @return bool + */ + protected function drupalUserIsLoggedIn(AccountInterface $account) { + // @TODO: This is a temporary hack for the test login fails when setting $cookie_domain. + if (!isset($account->session_id)) { + return (bool) $account->id(); + } + // The session ID is hashed before being stored in the database. + // @see \Drupal\Core\Session\SessionHandler::read() + return (bool) db_query("SELECT sid FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => Crypt::hashBase64($account->session_id)))->fetchField(); + } + + /** + * Login a user on a specific domain. + * + * @param \Drupal\domain\DomainInterface $domain + * The domain to log the user into. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account to login. + */ + public function domainLogin(DomainInterface $domain, AccountInterface $account) { + // Due to a quirk in session handling that we cannot directly access, it + // works if we login, then logout, and then login to a specific domain. + $this->drupalLogin($account); + if ($this->loggedInUser) { + $this->drupalLogout(); + } + + // Login. + $url = $domain->getPath() . 'user/login'; + $this->submitForm([ + 'name' => $account->getUsername(), + 'pass' => $account->passRaw, + ], t('Log in')); + + // @see BrowserTestBase::drupalUserIsLoggedIn() + $account->sessionId = $this->getSession()->getCookie($this->getSessionName()); + $this->assertTrue($this->drupalUserIsLoggedIn($account), 'User successfully logged in.'); + + $this->loggedInUser = $account; + $this->container->get('current_user')->setAccount($account); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTokenTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTokenTest.php new file mode 100644 index 000000000..b21ee6fd7 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainTokenTest.php @@ -0,0 +1,76 @@ +domainTableIsEmpty(); + + // Create four new domains programmatically. + $this->domainCreateTestDomains(4); + + // Since we cannot read the service request, we place a block + // which shows the current domain token information. + $this->drupalPlaceBlock('domain_token_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, array('view domain information')); + + // Test the response of the default home page. + foreach (\Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple() as $domain) { + $this->drupalGet($domain->getPath()); + $this->assertRaw($domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('Token', 'Token values printed.'); + foreach ($this->tokenList() as $token => $callback) { + $this->assertRaw("$token", "$token found correctly."); + // The URL token is sensitive to the path, which is /user, but that + // does not come across when making the callback outside of a request + // context. + $value = $domain->{$callback}(); + if ($token == '[domain:url]') { + $value = str_replace('user', '', $value); + if (substr($value, -1) != '/') { + $value .= '/'; + } + } + $this->assertRaw('' . $value . '', 'Value set correctly to ' . $value); + } + } + } + + /** + * Gets the list of tokens and value callbacks used by the test. + * + * @return array + * An array keyed by token string, with value of expected domain value. + */ + private function tokenList() { + $tokens = []; + foreach (\Drupal::service('domain.token')->getCallbacks() as $key => $callback) { + $name = "[domain:$key]"; + $tokens[$name] = $callback; + } + return $tokens; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainValidatorTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainValidatorTest.php new file mode 100644 index 000000000..96394e9f3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainValidatorTest.php @@ -0,0 +1,91 @@ +domainTableIsEmpty(); + $validator = \Drupal::service('domain.validator'); + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + + // Create a domain. + $this->domainCreateTestDomains(1, 'foo.com'); + // Check the created domain based on its known id value. + $key = 'foo.com'; + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = $storage->loadByHostname($key); + $this->assertTrue(!empty($domain), 'Test domain created.'); + + // Valid hostnames to test. Valid is the boolean value. + $hostnames = [ + 'localhost' => 1, + 'example.com' => 1, + 'www.example.com' => 1, // see www-prefix test, below. + 'one.example.com' => 1, + 'example.com:8080' => 1, + 'example.com::8080' => 0, // only one colon. + 'example.com:abc' => 0, // no letters after a colon. + '.example.com' => 0, // cannot begin with a dot. + 'example.com.' => 0, // cannot end with a dot. + 'EXAMPLE.com' => 0, // lowercase only. + 'éxample.com' => 0, // ascii-only. + ]; + foreach ($hostnames as $hostname => $valid) { + $errors = $validator->validate($hostname); + if ($valid) { + $this->assertTrue(empty($errors), 'Validation correct with no errors.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation correct with proper errors.'); + } + } + // Test duplicate hostname creation. + $test_hostname = 'foo.com'; + $test_domain = $storage->create([ + 'hostname' => $test_hostname, + 'name' => 'Test domain', + 'id' => 'test_domain', + ]); + try { + $test_domain->save(); + $this->fail('Duplicate hostname validation'); + } + catch (ConfigValueException $e) { + $expected_message = "The hostname ($test_hostname) is already registered."; + $this->assertEqual($expected_message, $e->getMessage()); + } + // Test the two configurable options. + $config = $this->config('domain.settings'); + $config->set('www_prefix', true); + $config->set('allow_non_ascii', true); + $config->save(); + // Valid hostnames to test. Valid is the boolean value. + $hostnames = [ + 'www.example.com' => 0, // no www-prefix allowed + 'éxample.com' => 1, // ascii-only allowed. + ]; + foreach ($hostnames as $hostname => $valid) { + $errors = $validator->validate($hostname); + if ($valid) { + $this->assertTrue(empty($errors),'Validation test correct with no errors.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation test correct with errors.'); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainViewsAccessTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainViewsAccessTest.php new file mode 100644 index 000000000..eae994075 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainViewsAccessTest.php @@ -0,0 +1,84 @@ +domainCreateTestDomains(5); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + // Enable the views. + $this->enableViewsTestModule(); + // Create a user. To test the area output was more difficult, so we just + // configured two views. The page shows the first, admin, user, and the + // block will show this new user name. + $this->user = $this->drupalCreateUser(array('administer domains', 'create domains')); + // Place the view block. + $this->drupalPlaceBlock('views_block:domain_views_access-block_1'); + + // The block and page should be visible on example_com and one_example_com. + $allowed = ['example_com', 'one_example_com']; + + foreach ($domains as $domain) { + $path = $domain->getPath() . 'domain-views-access'; + $this->DrupalGet($path); + if (in_array($domain->id(), $allowed)) { + $this->assertResponse('200', 'Access allowed'); + $this->assertRaw('admin'); + $this->assertRaw($this->user->getUsername()); + } + else { + $this->assertResponse('403', 'Access denied'); + $this->assertNoRaw('admin'); + $this->assertNoRaw($this->user->getUsername()); + } + // Test the block on another page. + $this->drupalGet($domain->getPath()); + if (in_array($domain->id(), $allowed)) { + $this->assertRaw($this->user->getUsername()); + } + else { + $this->assertNoRaw($this->user->getUsername()); + } + } + } + + /** + * Sets up the domain_test module. + * + * Because the schema of domain_test.module is dependent on the test + * using it, it cannot be enabled normally. + */ + protected function enableViewsTestModule() { + \Drupal::service('module_installer')->install(array('domain_test')); + $this->resetAll(); + $this->rebuildContainer(); + $this->container->get('module_handler')->reload(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainHookTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainHookTest.php new file mode 100644 index 000000000..2853f9685 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainHookTest.php @@ -0,0 +1,160 @@ +domainCreateTestDomains(); + + // Get the services. + $this->domainStorage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $this->currentUser = \Drupal::service('current_user'); + $this->moduleHandler = \Drupal::service('module_handler'); + } + + /** + * Tests domain loading. + */ + public function testHookDomainLoad() { + // Check the created domain based on its known id value. + $domain = $this->domainStorage->load($this->key); + + // Internal hooks. + $path = $domain->getPath(); + $url = $domain->getUrl(); + $this->assertTrue(isset($path), new FormattableMarkup('The path property was set to %path by hook_entity_load.', array('%path' => $path))); + $this->assertTrue(isset($url), new FormattableMarkup('The url property was set to %url by hook_entity_load.', array('%url' => $url))); + + // External hooks. + $this->assertTrue($domain->foo == 'bar', 'The foo property was set to bar by hook_domain_load.'); + } + + /** + * Tests domain validation. + */ + public function testHookDomainValidate() { + $validator = \Drupal::service('domain.validator'); + // Test a good domain. + $errors = $validator->validate('one.example.com'); + $this->assertEmpty($errors, 'No errors returned for example.com'); + + // Test our hook implementation, which denies fail.example.com explicitly. + $errors = $validator->validate('fail.example.com'); + $this->assertNotEmpty($errors, 'Errors returned for fail.example.com'); + $this->assertTrue(current($errors) == 'Fail.example.com cannot be registered', 'Error message returned correctly.'); + } + + /** + * Tests domain request alteration. + */ + public function testHookDomainRequestAlter() { + // Set the request. + $negotiator = \Drupal::service('domain.negotiator'); + $negotiator->setRequestDomain($this->base_hostname); + + // Check that the property was added by our hook. + $domain = $negotiator->getActiveDomain(); + $this->assertTrue($domain->foo1 == 'bar1', 'The foo1 property was set to bar1 by hook_domain_request_alter'); + } + + /** + * Tests domain operations hook. + */ + public function testHookDomainOperations() { + $domain = $this->domainStorage->load($this->key); + + // Set the request. + $operations = $this->moduleHandler->invokeAll('domain_operations', array($domain, $this->currentUser)); + + // Test that our operations were added by the hook. + $this->assertTrue(isset($operations['domain_test']), 'Domain test operation loaded.'); + } + + /** + * Tests domain references alter hook. + */ + public function testHookDomainReferencesAlter() { + $domain = $this->domainStorage->load($this->key); + + // Set the request. + $manager = \Drupal::service('entity.manager'); + $target_type = 'domain'; + + // Build a node entity selection query. + $query = $manager->getStorage($target_type)->getQuery(); + $context = [ + 'entity_type' => 'node', + 'bundle' => 'article', + 'field_type' => 'editor', + ]; + + // Run the alteration, which should add metadata to the query for nodes. + $this->moduleHandler->alter('domain_references', $query, $this->currentUser, $context); + $this->assertTrue($query->getMetaData('domain_test') == 'Test string', 'Domain test query altered.'); + + // Build a user entity selection query. + $query = $manager->getStorage($target_type)->getQuery(); + $context = [ + 'entity_type' => 'user', + 'bundle' => 'user', + 'field_type' => 'admin', + ]; + + // Run the alteration, which does not add metadata for user queries. + $this->moduleHandler->alter('domain_references', $query, $this->currentUser, $context); + $this->assertEmpty($query->getMetaData('domain_test'), 'Domain test query not altered.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainVariableSchemeTest.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainVariableSchemeTest.php new file mode 100644 index 000000000..ad8979fcd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Kernel/DomainVariableSchemeTest.php @@ -0,0 +1,72 @@ +domainCreateTestDomains(); + + // Get the services. + $this->domainStorage = \Drupal::service('entity_type.manager')->getStorage('domain'); + } + + /** + * Tests domain loading. + */ + public function testDomainScheme() { + // Set our testing parameters. + $default_scheme = \Drupal::request()->getScheme(); + $alt_scheme = ($default_scheme == 'https') ? 'http' : 'https'; + $add_suffix = FALSE; + + // Our created domain should have a scheme that matches the request. + $domain = $this->domainStorage->load($this->key); + $this->assertTrue($domain->getScheme($add_suffix) == $default_scheme); + + // Swtich the scheme and see if that works. + $domain->set('scheme', $alt_scheme); + $domain->save(); + $domain = $this->domainStorage->load($this->key); + $this->assertTrue($domain->getScheme($add_suffix) == $alt_scheme); + + // Set the scheme to variable, and that should match the default. + $domain->set('scheme', 'variable'); + $domain->save(); + $this->assertTrue($domain->getScheme($add_suffix) == $default_scheme); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/src/Traits/DomainTestTrait.php b/sites/all/modules/contrib/admin/domain/domain/tests/src/Traits/DomainTestTrait.php new file mode 100644 index 000000000..314fefeef --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/tests/src/Traits/DomainTestTrait.php @@ -0,0 +1,158 @@ +getStorage('domain')->loadMultiple(NULL, TRUE); + if (empty($base_hostname)) { + $base_hostname = $this->base_hostname; + } + // Note: these domains are rigged to work on my test server. + // For proper testing, yours should be set up similarly, but you can pass a + // $list array to change the default. + if (empty($list)) { + $list = array('', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'); + } + for ($i = 0; $i < $count; $i++) { + if ($i === 0) { + $hostname = $base_hostname; + $machine_name = 'example.com'; + $name = 'Example'; + } + elseif (!empty($list[$i])) { + $hostname = $list[$i] . '.' . $base_hostname; + $machine_name = $list[$i] . '.example.com'; + $name = 'Test ' . ucfirst($list[$i]); + } + // These domains are not setup and are just for UX testing. + else { + $hostname = 'test' . $i . '.' . $base_hostname; + $machine_name = 'test' . $i . '.example.com'; + $name = 'Test ' . $i; + } + // Create a new domain programmatically. + $values = array( + 'hostname' => $hostname, + 'name' => $name, + 'id' => \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($machine_name), + ); + $domain = \Drupal::entityTypeManager()->getStorage('domain')->create($values); + $domain->save(); + } + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + } + + /** + * Adds a test domain to an entity. + * + * @param string $entity_type + * The entity type being acted upon. + * @param int $entity_id + * The entity id. + * @param array $ids + * An array of ids to add. + * @param string $field + * The name of the domain field used to attach to the entity. + */ + public function addDomainsToEntity($entity_type, $entity_id, $ids, $field) { + if ($entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id)) { + $entity->set($field, $ids); + $entity->save(); + } + } + + /** + * Returns an uncached list of all domains. + * + * @return array + * An array of domain entities. + */ + public function getDomains() { + \Drupal::service('entity_type.manager')->getStorage('domain')->resetCache(); + return \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + } + + /** + * Returns an uncached list of all domains, sorted by weight. + * + * @return array + * An array of domain entities. + */ + public function getDomainsSorted() { + \Drupal::service('entity_type.manager')->getStorage('domain')->resetCache(); + return \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted(); + } + + /** + * Converts a domain hostname to a trusted host pattern. + * + * @param $hostname + * A hostname string. + * + * @return + * A regex-safe hostname, without delimiters. + */ + public function prepareTrustedHostname($hostname) { + $hostname = strtolower(preg_replace('/:\d+$/', '', trim($hostname))); + return preg_quote($hostname); + } + + /** + * Set the base hostname for this test. + */ + public function setBaseHostname() { + $this->base_hostname = \Drupal::service('entity_type.manager')->getStorage('domain')->createHostname(); + } + + /** + * Reusable test function for checking initial / empty table status. + */ + public function domainTableIsEmpty() { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(NULL, TRUE); + $this->assertTrue(empty($domains), 'No domains have been created.'); + $default_id = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultId(); + $this->assertTrue(empty($default_id), 'No default domain has been set.'); + } + + /** + * Creates domain record for use with POST request tests. + */ + public function domainPostValues() { + $edit = array(); + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->create(); + $required = \Drupal::service('domain.validator')->getRequiredFields(); + foreach ($required as $key) { + $edit[$key] = $domain->get($key); + } + // URL validation has issues on Travis, so only do it when requested. + $edit['validate_url'] = 0; + $edit['id'] = \Drupal::service('entity_type.manager')->getStorage('domain')->createMachineName($edit['hostname']); + return $edit; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/domain_access.settings.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/domain_access.settings.yml new file mode 100644 index 000000000..2f245f810 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/domain_access.settings.yml @@ -0,0 +1 @@ +node_advanced_tab: true diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_access.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_access.yml new file mode 100644 index 000000000..d4cdf636b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_access.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - domain + - node + enforced: + module: + - domain_access +id: node.field_domain_access +field_name: field_domain_access +entity_type: node +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_all_affiliates.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_all_affiliates.yml new file mode 100644 index 000000000..392f68940 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.node.field_domain_all_affiliates.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + module: + - domain + - node + enforced: + module: + - domain_access +id: node.field_domain_all_affiliates +field_name: field_domain_all_affiliates +entity_type: node +type: boolean +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_access.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_access.yml new file mode 100644 index 000000000..25d0b0944 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_access.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - domain + - user + enforced: + module: + - domain_access +id: user.field_domain_access +field_name: field_domain_access +entity_type: user +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_all_affiliates.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_all_affiliates.yml new file mode 100644 index 000000000..e8f9888fc --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/field.storage.user.field_domain_all_affiliates.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + module: + - domain + - user + enforced: + module: + - domain_access +id: user.field_domain_all_affiliates +field_name: field_domain_all_affiliates +entity_type: user +type: boolean +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_all_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_all_action.yml new file mode 100644 index 000000000..d932320f8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_all_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_all_action +label: 'Assign content to all affiliates' +type: node +plugin: domain_access_all_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_all_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_all_action.yml new file mode 100644 index 000000000..926e2d55b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_all_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_edit_all_action +label: 'Assign editors to all affiliates' +type: user +plugin: domain_access_edit_all_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_none_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_none_action.yml new file mode 100644 index 000000000..33e7adf10 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_edit_none_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_edit_none_action +label: 'Remove editors from all affiliates' +type: user +plugin: domain_access_edit_none_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_none_action.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_none_action.yml new file mode 100644 index 000000000..4accb23cd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/install/system.action.domain_access_none_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - node +id: domain_access_none_action +label: 'Remove content from all affiliates' +type: node +plugin: domain_access_none_action +configuration: { } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.schema.yml b/sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.schema.yml new file mode 100644 index 000000000..dcdcc11da --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.schema.yml @@ -0,0 +1,47 @@ +domain_access.settings: + type: config_object + label: 'Domain Access settings' + mapping: + node_advanced_tab: + type: boolean + label: 'Move domain access field to a tab in the advanced settings on nodes.' +action.configuration.domain_access_all_action: + type: action_configuration_default + label: 'Assign content to all affiliates' +action.configuration.domain_access_edit_all_action: + type: action_configuration_default + label: 'Assign editors to all affiliates' +action.configuration.domain_access_edit_none_action: + type: action_configuration_default + label: 'Remove editors from all affiliates' +action.configuration.domain_access_none_action: + type: action_configuration_default + label: 'Remove content from all affiliates' +action.configuration.domain_access_add_action: + type: mapping + label: 'Configuration for the add domain(s) to content action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to add' +action.configuration.domain_access_remove_action: + type: mapping + label: 'Configuration for the remove domain(s) from content action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to remove' +action.configuration.domain_access_add_editor_action: + type: mapping + label: 'Configuration for the add domain(s) to users action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to add' +action.configuration.domain_access_remove_editor_action: + type: mapping + label: 'Configuration for the remove domain(s) from users action' + mapping: + domain_id: + type: string + label: 'The ID(s) of the domain(s) to remove' diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml new file mode 100644 index 000000000..2a6f1bac6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml @@ -0,0 +1,15 @@ +name: Domain Access +description: 'Domain-based access control for content.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + - node + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.install b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.install new file mode 100644 index 000000000..8633b4dec --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.install @@ -0,0 +1,36 @@ +getStorage('node_type')->loadMultiple(); + foreach ($node_types as $type => $info) { + $list[$type] = 'node'; + } + // Install our fields. + foreach ($list as $bundle => $entity_type) { + domain_access_confirm_fields($entity_type, $bundle); + } + // Install our actions. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + domain_access_domain_insert($domain); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.menu.yml new file mode 100644 index 000000000..92ad9905b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.menu.yml @@ -0,0 +1,6 @@ +domain_access.settings: + title: Domain Access settings + route_name: domain_access.settings + parent: domain.admin + description: 'Domain Access settings' + weight: 10 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.task.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.task.yml new file mode 100644 index 000000000..02cc8734d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.links.task.yml @@ -0,0 +1,4 @@ +domain_access.settings: + title: Domain Access settings + route_name: domain_access.settings + base_route: domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.module b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.module new file mode 100755 index 000000000..b27340b46 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.module @@ -0,0 +1,735 @@ +getActiveDomain(); + + if (empty($active)) { + $active = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain(); + } + + // No domains means no permissions. + if (empty($active)) { + return $grants; + } + + $id = $active->getDomainId(); + // Advanced grants for edit/delete require permissions. + /** @var \Drupal\user\UserInterface $user */ + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($user); + // Grants for view are simple. Use the active domain and all affiliates. + // Note that "X to any domain" is a global permission designed for admins. + if ($op == 'view') { + $grants['domain_id'][] = $id; + $grants['domain_site'][] = 0; + if ($user->hasPermission('view unpublished domain content')) { + if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + $grants['domain_unpublished'][] = $id; + } + } + } + elseif ($op == 'update' && $user->hasPermission('edit domain content')) { + if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + $grants['domain_id'][] = $id; + } + } + elseif ($op == 'delete' && $user->hasPermission('delete domain content')) { + if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + $grants['domain_id'][] = $id; + } + } + return $grants; +} + +/** + * Implements hook_node_access_records(). + */ +function domain_access_node_access_records(NodeInterface $node) { + $grants = array(); + // Create grants for each translation of the node. See the report at + // https://www.drupal.org/node/2825419 for the logic here. Note that right + // now, grants may not be the same for all languages. + $translations = $node->getTranslationLanguages(); + foreach ($translations as $langcode => $language) { + $translation = $node->getTranslation($langcode); + // If there are no domains set, use the current one. + $domains = \Drupal::service('domain_access.manager')->getAccessValues($translation); + /** @var \Drupal\domain\DomainInterface $active */ + if (empty($domains) && $active = \Drupal::service('domain.negotiator')->getActiveDomain()) { + $domains[$active->id()] = $active->getDomainId(); + } + foreach ($domains as $id => $domainId) { + /** @var \Drupal\domain\DomainInterface $domain */ + if ($domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($id)) { + $grants[] = array( + 'realm' => ($translation->isPublished()) ? 'domain_id' : 'domain_unpublished', + 'gid' => $domain->getDomainId(), + 'grant_view' => 1, + 'grant_update' => 1, + 'grant_delete' => 1, + 'langcode' => $langcode, + ); + } + } + // Set the domain_site grant. + if (!empty($translation->get(DOMAIN_ACCESS_ALL_FIELD)->value) && $translation->isPublished()) { + $grants[] = array( + 'realm' => 'domain_site', + 'gid' => 0, + 'grant_view' => 1, + 'grant_update' => 0, + 'grant_delete' => 0, + 'langcode' => $langcode, + ); + } + // Because of language translation, we must save a record for each language. + // even if that record adds no permissions, as this one does. + else { + $grants[] = array( + 'realm' => 'domain_site', + 'gid' => 1, + 'grant_view' => 0, + 'grant_update' => 0, + 'grant_delete' => 0, + 'langcode' => $langcode, + ); + } + } + return $grants; +} + +/** + * Implements hook_ENTITY_TYPE_presave(). + * + * Fires only if Devel Generate module is present, to assign test nodes to + * domains. + */ +function domain_access_node_presave(EntityInterface $node) { + domain_access_presave_generate($node); +} + +/** + * Implements hook_ENTITY_TYPE_presave(). + * + * Fires only if Devel Generate module is present, to assign test nodes to + * domains. + */ +function domain_access_user_presave(EntityInterface $account) { + domain_access_presave_generate($account); +} + +/** + * Handles presave operations for devel generate. + */ +function domain_access_presave_generate(EntityInterface $entity) { + // There is a core bug https://www.drupal.org/node/2609252 that causes a + // fatal database errors if the boolean DOMAIN_ACCESS_ALL_FIELD is set when + // a user cannot access the field. See domain_access_entity_field_access(). + // To overcome this issue, we cast the boolean to integer, which prevents the + // failure. + $value = (int) $entity->get(DOMAIN_ACCESS_ALL_FIELD)->value; + $entity->set(DOMAIN_ACCESS_ALL_FIELD, $value); + + // Handle devel module settings. + $exists = \Drupal::moduleHandler()->moduleExists('devel_generate'); + $values = []; + if ($exists && isset($entity->devel_generate)) { + // If set by the form. + if (isset($entity->devel_generate['domain_access'])) { + $selection = array_filter($entity->devel_generate['domain_access']); + if (isset($selection['random-selection'])) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $values[DOMAIN_ACCESS_FIELD] = array_rand($domains, ceil(rand(1, count($domains)))); + } + else { + $values[DOMAIN_ACCESS_FIELD] = array_keys($selection); + } + } + if (isset($entity->devel_generate['domain_all'])) { + $selection = $entity->devel_generate['domain_all']; + if ($selection == 'random-selection') { + $values[DOMAIN_ACCESS_ALL_FIELD] = rand(0, 1); + } + else { + $values[DOMAIN_ACCESS_ALL_FIELD] = ($selection = 'yes' ? 1 : 0); + } + } + foreach ($values as $name => $value) { + $entity->set($name, $value); + } + } + +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Add options for domains when using Devel Generate. + */ +function domain_access_form_devel_generate_form_content_alter(&$form, &$form_state, $form_id) { + // Add our element to the Devel generate form. + $form['submit']['#weight'] = 10; + $list = ['random-selection' => t('Random selection')]; + $list += \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + $form['domain_access'] = array( + '#title' => t('Domains'), + '#type' => 'checkboxes', + '#options' => $list, + '#weight' => 2, + '#multiple' => TRUE, + '#size' => count($list) > 5 ? 5 : count($list), + '#default_value' => ['random-selection'], + '#description' => t('Sets the domains for created nodes. Random selection overrides other choices.'), + ); + $form['domain_all'] = array( + '#title' => t('Send to all affiliates'), + '#type' => 'radios', + '#options' => [ + 'random-selection' => t('Random selection'), + 'yes' => t('Yes'), + 'no' => t('No'), + ], + '#default_value' => 'random-selection', + '#weight' => 3, + '#description' => t('Sets visibility across all affiliates.'), + ); +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Add options for domains when using Devel Generate. + */ +function domain_access_form_devel_generate_form_user_alter(&$form, &$form_state, $form_id) { + domain_access_form_devel_generate_form_content_alter($form, $form_state, $form_id); +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. + * + * Move Domain Access fields to an advanced tab like other node settings. + */ +function domain_access_form_node_form_alter(&$form, FormState $form_state, $form_id) { + $move_enabled = \Drupal::config('domain_access.settings')->get('node_advanced_tab'); + if ( + $move_enabled && isset($form[DOMAIN_ACCESS_FIELD]) && + isset($form[DOMAIN_ACCESS_ALL_FIELD]) && + empty($form[DOMAIN_ACCESS_FIELD]['#group']) && + empty($form[DOMAIN_ACCESS_ALL_FIELD]['#group']) + ) { + // Move to the tabs on the entity form + $form[DOMAIN_ACCESS_FIELD]['#group'] = 'domain'; + $form[DOMAIN_ACCESS_ALL_FIELD]['#group'] = 'domain'; + $form['domain'] = [ + '#type' => 'details', + '#title' => t('Domain settings'), + '#group' => 'advanced', + '#attributes' => [ + 'class' => ['node-form-options'] + ], + '#attached' => [ + 'library' => ['node/drupal.node'], + ], + '#weight' => 100, + '#optional' => TRUE + ]; + } + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain.element_manager'); + $form = $manager->setFormOptions($form, $form_state, DOMAIN_ACCESS_FIELD); +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\user\UserForm. + * + * Handle settings that the user cannot access. + */ +function domain_access_form_user_form_alter(&$form, &$form_state, $form_id) { + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain.element_manager'); + $form = $manager->setFormOptions($form, $form_state, DOMAIN_ACCESS_FIELD); +} + +/** + * Implements hook_domain_references_alter(). + */ +function domain_access_domain_references_alter($query, $account, $context) { + // Restrict domains by editorial assignment. + if ($context['field_type'] != 'editor') { + return; + } + switch ($context['entity_type']) { + case 'node': + if ($account->hasPermission('publish to any domain')) { + break; + } + elseif ($account->hasPermission('publish to any assigned domain')) { + if (!empty($account->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + break; + } + $allowed = \Drupal::service('domain_access.manager')->getAccessValues($account); + $query->condition('id', array_keys($allowed), 'IN'); + } + else { + // Remove all options. + $query->condition('id', '-no-possible-match-'); + } + break; + + case 'user': + if ($account->hasPermission('assign editors to any domain')) { + // Do nothing. + } + elseif ($account->hasPermission('assign domain editors')) { + if (!empty($account->get(DOMAIN_ACCESS_ALL_FIELD)->value)) { + break; + } + $allowed = \Drupal::service('domain_access.manager')->getAccessValues($account); + $query->condition('id', array_keys($allowed), 'IN'); + } + else { + // Remove all options. + $query->condition('id', '-no-possible-match-'); + } + break; + + default: + // No action taken. + break; + } +} + +/** + * Implements hook_node_access(). + */ +function domain_access_node_access(NodeInterface $node, $op, AccountInterface $account) { + static $active_domain; + if (!isset($active_domain)) { + // Ensure that the loader has run. In some tests, the kernel event has not. + $active = \Drupal::service('domain.negotiator')->getActiveDomain(); + if (empty($active)) { + $active = \Drupal::service('domain.negotiator')->getActiveDomain(TRUE); + } + $active_domain = $active; + } + // Check to see that we have a valid active domain. + // Without one, we cannot assert an opinion about access. + if (empty($active_domain->getDomainId())) { + return AccessResult::neutral(); + } + + $type = $node->bundle(); + $manager = \Drupal::service('domain_access.manager'); + $allowed = FALSE; + + // In order to access update or delete, the user must be able to View. + if ($op == 'view' && $manager->checkEntityAccess($node, $account)) { + /** @var \Drupal\user\UserInterface $user */ + if ($node->isPublished()) { + $allowed = TRUE; + } + elseif ($account->hasPermission('view unpublished domain content')) { + $allowed = TRUE; + } + } + + if ($op == 'update') { + if ($account->hasPermission('update ' . $type . ' content on assigned domains') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + elseif ($account->hasPermission('edit domain content') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + } + + if ($op == 'delete') { + if ($account->hasPermission('delete ' . $type . ' content on assigned domains') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + elseif ($account->hasPermission('delete domain content') && $manager->checkEntityAccess($node, $account)) { + $allowed = TRUE; + } + } + + if ($allowed) { + return AccessResult::allowed() + ->cachePerPermissions() + ->cachePerUser() + ->addCacheableDependency($node); + } + + // No opinion. + return AccessResult::neutral(); +} + +/** + * Implements hook_node_create_access(). + * + * @link https://www.drupal.org/node/2348203 + */ +function domain_access_node_create_access(AccountInterface $account, $context, $entity_bundle) { + // Check to see that we have a valid active domain. + // Without one, we cannot assert an opinion about access. + /** @var \Drupal\domain\DomainInterface $active */ + if ($active = \Drupal::service('domain.negotiator')->getActiveDomain()) { + $id = $active->getDomainId(); + } + else { + return AccessResult::neutral(); + } + // Load the full user record. + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($user); + if (($account->hasPermission('create ' . $entity_bundle . ' content on assigned domains') + || $account->hasPermission('create domain content')) + && in_array($id, $user_domains)) { + // Note the cache context here! + return AccessResult::allowed()->addCacheContexts(['user.permissions', 'url.site']); + } + // No opinion. + return AccessResult::neutral(); +} + +/** + * Implements hook_entity_field_access(). + */ +function domain_access_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { + // Hide the domain access fields from the entity add/edit forms + // when the user cannot access them. + if ($operation != 'edit') { + return AccessResult::neutral(); + } + + // The entity the field is attached to. + $entity = $items->getEntity(); + + if ($field_definition->getName() == DOMAIN_ACCESS_FIELD) { + if ($entity instanceof AccountInterface) { + $access = AccessResult::allowedIfHasPermissions($account, [ + 'assign domain editors', + 'assign editors to any domain', + ], 'OR'); + } + elseif ($entity instanceof NodeInterface) { + // Treat any other entity as content. + $access = AccessResult::allowedIfHasPermissions($account, [ + 'publish to any domain', + 'publish to any assigned domain', + ], 'OR'); + } + // allowedIfHasPermissions returns allowed() or neutral(). + // In this case, we want it to be forbidden, + // if user doesn't have the permissions above. + if (isset($access) && !$access->isAllowed()) { + return AccessResult::forbidden(); + } + } + // Check permissions on the All Affiliates field. + elseif ($field_definition->getName() == DOMAIN_ACCESS_ALL_FIELD) { + if ($entity instanceof AccountInterface) { + return AccessResult::forbiddenIf(!$account->hasPermission('assign editors to any domain')); + } + elseif ($entity instanceof NodeInterface) { + // Treat any other entity as content. + return AccessResult::forbiddenIf(!$account->hasPermission('publish to any domain')); + } + } + + return AccessResult::neutral(); +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + * + * Creates our fields when new node types are created. + */ +function domain_access_node_type_insert(EntityInterface $entity) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + if (!$entity->isSyncing()) { + // Do not fire hook when config sync in progress. + domain_access_confirm_fields('node', $entity->id()); + } +} + +/** + * Creates our fields for an entity bundle. + * + * @param string $entity_type + * The entity type being created. Node and user are supported. + * @param string $bundle + * The bundle being created. + * @param array $text + * The text to use for the field. Keys are: + * 'name' -- the lower-case, human-readable name of the entity. + * 'label' -- the form label for the all affiliates field. + * 'description' -- the help text for the all affiliates field. + * + * If calling this function for entities other than user or node, it is the + * caller's responsibility to provide this text. + * + * This function is here for convenience during installation. It is not really + * an API function. Modules wishing to add fields to non-node entities must + * provide their own field storage. See the field storage YML sample in + * tests/modules/domain_access_test for an example of field storage definitions. + * + * @see domain_access_node_type_insert() + * @see domain_access_install() + */ +function domain_access_confirm_fields($entity_type, $bundle, $text = array()) { + // We have reports that importing config causes this function to fail. + try { + $text['node'] = [ + 'name' => 'content', + 'label' => 'Send to all affiliates', + 'description' => 'Make this content available on all domains.', + ]; + $text['user'] = [ + 'name' => 'user', + 'label' => 'Editor for all affiliates', + 'description' => 'Make this user an editor on all domains.', + ]; + + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_FIELD; + + $field_storage = \Drupal::entityTypeManager()->getStorage('field_config'); + if (!$field = $field_storage->load($id)) { + $field = array( + 'field_name' => DOMAIN_ACCESS_FIELD, + 'entity_type' => $entity_type, + 'label' => 'Domain Access', + 'bundle' => $bundle, + // Users should not be required to be a domain editor. + 'required' => $entity_type !== 'user', + 'description' => 'Select the affiliate domain(s) for this ' . $text[$entity_type]['name'], + 'default_value_callback' => 'Drupal\domain_access\DomainAccessManager::getDefaultValue', + 'settings' => array( + 'handler' => 'default:domain', + // Handler_settings are deprecated but seem to be necessary here. + 'handler_settings' => [ + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ], + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ), + ); + $field_config = $field_storage->create($field); + $field_config->save(); + } + // Assign the all affiliates field. + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_ALL_FIELD; + if (!$field = $field_storage->load($id)) { + $field = array( + 'field_name' => DOMAIN_ACCESS_ALL_FIELD, + 'entity_type' => $entity_type, + 'label' => $text[$entity_type]['label'], + 'bundle' => $bundle, + 'required' => FALSE, + 'description' => $text[$entity_type]['description'], + ); + $field_config = $field_storage->create($field); + $field_config->save(); + } + // Tell the form system how to behave. Default to radio buttons. + // @TODO: This function is deprecated, but using the OO syntax is causing + // test fails. + entity_get_form_display($entity_type, $bundle, 'default') + ->setComponent(DOMAIN_ACCESS_FIELD, array( + 'type' => 'options_buttons', + 'weight' => 40, + )) + ->setComponent(DOMAIN_ACCESS_ALL_FIELD, array( + 'type' => 'boolean_checkbox', + 'settings' => array('display_label' => 1), + 'weight' => 41, + )) + ->save(); + } + catch (Exception $e) { + \Drupal::logger('domain_access')->notice('Field installation failed.'); + } +} + +/** + * Implements hook_views_data_alter(). + */ +function domain_access_views_data_alter(array &$data) { + $table = 'node__' . DOMAIN_ACCESS_FIELD; + $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument'; + + // Current domain filter. + $data[$table]['current_all'] = array( + 'title' => t('Current domain'), + 'group' => t('Domain'), + 'filter' => array( + 'field' => DOMAIN_ACCESS_FIELD . '_target_id', + 'id' => 'domain_access_current_all_filter', + 'title' => t('Available on current domain'), + 'help' => t('Filters out nodes not available on current domain (published to current domain or all affiliates).'), + ), + ); + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']); + + // Set the user data. + $table = 'user__' . DOMAIN_ACCESS_FIELD; + $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter'; + $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument'; + + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']); +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + */ +function domain_access_domain_insert($entity) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + if ($entity->isSyncing()) { + // Do not fire hook when config sync in progress. + return; + } + $id = 'domain_access_add_action.' . $entity->id(); + $controller = \Drupal::entityTypeManager()->getStorage('action'); + if (!$controller->load($id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $id, + 'type' => 'node', + 'label' => t('Add selected content to the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_add_action', + )); + $action->trustData()->save(); + } + $remove_id = 'domain_access_remove_action.' . $entity->id(); + if (!$controller->load($remove_id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $remove_id, + 'type' => 'node', + 'label' => t('Remove selected content from the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_remove_action', + )); + $action->trustData()->save(); + } + $id = 'domain_access_add_editor_action.' . $entity->id(); + if (!$controller->load($id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $id, + 'type' => 'user', + 'label' => t('Add editors to the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_add_editor_action', + )); + $action->trustData()->save(); + } + $remove_id = 'domain_access_remove_editor_action.' . $entity->id(); + if (!$controller->load($remove_id)) { + /** @var \Drupal\system\Entity\Action $action */ + $action = $controller->create(array( + 'id' => $remove_id, + 'type' => 'user', + 'label' => t('Remove editors from the @label domain', array('@label' => $entity->label())), + 'configuration' => array( + 'domain_id' => $entity->id(), + ), + 'plugin' => 'domain_access_remove_editor_action', + )); + $action->trustData()->save(); + } +} + +/** + * Implements hook_ENTITY_TYPE_delete(). + */ +function domain_access_domain_delete(EntityInterface $entity) { + $controller = \Drupal::entityTypeManager()->getStorage('action'); + $actions = $controller->loadMultiple(array( + 'domain_access_add_action.' . $entity->id(), + 'domain_access_remove_action.' . $entity->id(), + 'domain_access_add_editor_action.' . $entity->id(), + 'domain_access_remove_editor_action.' . $entity->id(), + )); + foreach ($actions as $action) { + $action->delete(); + } +} + +/** + * Implements hook_form_alter(). + * + * Find forms that contain the domain access field and allow those to handle + * default values properly. Note that here we just care if the form saves an + * entity. We then pass that entity to a helper function. + * + * @see domain_access_default_form_values(). + */ +function domain_access_form_alter(&$form, &$form_state, $form_id) { + if ($object = $form_state->getFormObject() && !empty($object) && is_callable([$object, 'getEntity']) && $entity = $object->getEntity()) { + domain_access_default_form_values($form, $form_state, $entity); + } +} + +/** + * Defines default values for domain access field. + * + * This function is a workaround for a core bug. When the domain access field + * is not accessible to some users, the existing values are not preserved. + * + * @see domain_access_entity_field_access(). + */ +function domain_access_default_form_values(&$form, &$form_state, $entity) { + // Set domain access default value when the user does not have access + // to edit the field. This seems to work fine for all affiliates, which + // suggests a core bug in entity reference handling. + if (!$entity->isNew() && + isset($form['field_domain_access']) && + !$form['field_domain_access']['#access'] && + empty($form['field_domain_access']['widget']['#default_value']) + ) { + // Set the default values correctly. + $values = \Drupal::service('domain_access.manager')->getAccessValues($entity); + $form['field_domain_access']['widget']['#default_value'] = array_keys($values); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.permissions.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.permissions.yml new file mode 100644 index 000000000..b05ebf58b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.permissions.yml @@ -0,0 +1,2 @@ +permission_callbacks: + - Drupal\domain_access\DomainAccessPermissions::permissions diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.routing.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.routing.yml new file mode 100644 index 000000000..fb3071dab --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.routing.yml @@ -0,0 +1,7 @@ +domain_access.settings: + path: '/admin/config/domain/domain_access' + defaults: + _title: 'Domain Access settings' + _form: '\Drupal\domain_access\Form\DomainAccessSettingsForm' + requirements: + _permission: 'administer domains' diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.services.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.services.yml new file mode 100644 index 000000000..bb9fefb32 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.services.yml @@ -0,0 +1,9 @@ +services: + domain_access.manager: + class: Drupal\domain_access\DomainAccessManager + arguments: ['@domain.negotiator'] + access_check.domain_access_views: + class: Drupal\domain_access\Access\DomainAccessViewsAccess + arguments: ['@entity_type.manager', '@domain_access.manager'] + tags: + - { name: access_check, applies_to: _domain_access_views } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Access/DomainAccessViewsAccess.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Access/DomainAccessViewsAccess.php new file mode 100644 index 000000000..c9f3ceb7b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Access/DomainAccessViewsAccess.php @@ -0,0 +1,101 @@ +entityTypeManager = $entity_type_manager; + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + $this->userStorage = $this->entityTypeManager->getStorage('user'); + $this->manager = $manager; + } + + /** + * {@inheritdoc} + */ + public function access(Route $route, AccountInterface $account, $arg_0 = NULL) { + // Permissions are stored on the route defaults. + $permission = $route->getDefault('domain_permission'); + $allPermission = $route->getDefault('domain_all_permission'); + + // Users with this permission can see any domain content lists, and it is + // required to view all affiliates. + if ($account->hasPermission($allPermission)) { + return AccessResult::allowed(); + } + + // Load the domain from the passed argument. In testing, this passed NULL + // in some instances. + if (!is_null($arg_0)) { + $domain = $this->domainStorage->load($arg_0); + } + + // Domain found, check user permissions. + if (!empty($domain)) { + if ($this->manager->hasDomainPermissions($account, $domain, [$permission])) { + return AccessResult::allowed(); + } + } + return AccessResult::forbidden(); + } + + /** + * {@inheritdoc} + */ + public function applies(Route $route) { + return $route->hasRequirement($this->requirementsKey); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManager.php b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManager.php new file mode 100644 index 000000000..7cf30b4a5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManager.php @@ -0,0 +1,140 @@ +negotiator = $negotiator; + } + + /** + * @inheritdoc + */ + public static function getAccessValues(EntityInterface $entity, $field_name = DOMAIN_ACCESS_FIELD) { + // @TODO: static cache. + $list = []; + // @TODO In tests, $entity is returning NULL. + if (is_null($entity)) { + return $list; + } + // Get the values of an entity. + $values = $entity->hasField($field_name) ? $entity->get($field_name) : NULL; + // Must be at least one item. + if (!empty($values)) { + foreach ($values as $item) { + if ($target = $item->getValue()) { + if ($domain = \Drupal::entityTypeManager()->getStorage('domain')->load($target['target_id'])) { + $list[$domain->id()] = $domain->getDomainId(); + } + } + } + } + return $list; + } + + /** + * @inheritdoc + */ + public static function getAllValue(EntityInterface $entity) { + return $entity->hasField(DOMAIN_ACCESS_ALL_FIELD) ? $entity->get(DOMAIN_ACCESS_ALL_FIELD)->value : NULL; + } + + /** + * @inheritdoc + */ + public function checkEntityAccess(EntityInterface $entity, AccountInterface $account) { + $entity_domains = $this->getAccessValues($entity); + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + if (!empty($this->getAllValue($user)) && !empty($entity_domains)) { + return TRUE; + } + $user_domains = $this->getAccessValues($user); + return (bool) !empty(array_intersect($entity_domains, $user_domains)); + } + + /** + * @inheritdoc + */ + public static function getDefaultValue(FieldableEntityInterface $entity, FieldDefinitionInterface $definition) { + $item = []; + if (!$entity->isNew()) { + // If set, ensure we do not drop existing data. + foreach (self::getAccessValues($entity) as $id) { + $item[] = $id; + } + } + // When creating a new entity, populate if required. + elseif ($entity->getFieldDefinition(DOMAIN_ACCESS_FIELD)->isRequired()) { + /** @var \Drupal\domain\DomainInterface $active */ + if ($active = \Drupal::service('domain.negotiator')->getActiveDomain()) { + $item[0]['target_uuid'] = $active->uuid(); + } + } + return $item; + } + + /** + * @inheritdoc + */ + public function hasDomainPermissions(AccountInterface $account, DomainInterface $domain, array $permissions, $conjunction = 'AND') { + // Assume no access. + $access = FALSE; + + // In the case of multiple AND permissions, assume access and then deny if + // any check fails. + if ($conjunction == 'AND' && !empty($permissions)) { + $access = TRUE; + foreach ($permissions as $permission) { + if (!($permission_access = $account->hasPermission($permission))) { + $access = FALSE; + break; + } + } + } + // In the case of multiple OR permissions, assume deny and then allow if any + // check passes. + else { + foreach ($permissions as $permission) { + if ($permission_access = $account->hasPermission($permission)) { + $access = TRUE; + break; + } + } + } + // Validate that the user is assigned to the domain. If not, deny. + $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + $allowed = $this->getAccessValues($user); + if (!isset($allowed[$domain->id()]) && empty($this->getAllValue($user))) { + $access = FALSE; + } + + return $access; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManagerInterface.php b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManagerInterface.php new file mode 100644 index 000000000..d95933a36 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/DomainAccessManagerInterface.php @@ -0,0 +1,85 @@ + array( + 'title' => $this->t('Assign additional editors to assigned domains'), + 'restrict access' => TRUE, + ), + 'assign editors to any domain' => array( + 'title' => $this->t('Assign additional editors to any domains'), + 'restrict access' => TRUE, + ), + 'publish to any domain' => array( + 'title' => $this->t('Publish to any domain'), + ), + 'publish to any assigned domain' => array( + 'title' => $this->t('Publish content to any assigned domain'), + ), + 'create domain content' => array( + 'title' => $this->t('Create any content on assigned domains'), + ), + 'edit domain content' => array( + 'title' => $this->t('Edit any content on assigned domains'), + ), + 'delete domain content' => array( + 'title' => $this->t('Delete any content on assigned domains'), + ), + 'view unpublished domain content' => array( + 'title' => $this->t('View unpublished content on assigned domains'), + 'restrict access' => TRUE, + ), + ); + + // Generate standard node permissions for all applicable node types. + foreach (NodeType::loadMultiple() as $type) { + $permissions += $this->nodePermissions($type); + } + + return $permissions; + } + + /** + * Helper method to generate standard node permission list for a given type. + * + * Shamelessly lifted from node_list_permissions(). + * + * @param NodeType $type + * The node type object. + * + * @return array + * An array of permission names and descriptions. + */ + private function nodePermissions(NodeType $type) { + // Build standard list of node permissions for this type. + $id = $type->id(); + $perms = array( + "create $id content on assigned domains" => array( + 'title' => $this->t('%type_name: Create new content on assigned domains', array('%type_name' => $type->label())), + ), + "update $id content on assigned domains" => array( + 'title' => $this->t('%type_name: Edit any content on assigned domains', array('%type_name' => $type->label())), + ), + "delete $id content on assigned domains" => array( + 'title' => $this->t('%type_name: Delete any content on assigned domains', array('%type_name' => $type->label())), + ), + ); + + return $perms; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Form/DomainAccessSettingsForm.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Form/DomainAccessSettingsForm.php new file mode 100644 index 000000000..f7cb2310a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Form/DomainAccessSettingsForm.php @@ -0,0 +1,53 @@ +config('domain_access.settings'); + $form['node_advanced_tab'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Move Domain Access fields to advanced node settings.'), + '#default_value' => $config->get('node_advanced_tab'), + '#description' => $this->t('When checked the Domain Access fields will be shown as a tab in the advanced settings on node edit form. However, if you have placed the fields in a field group already, they will not be moved.'), + ); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->config('domain_access.settings') + ->set('node_advanced_tab', $form_state->getValue('node_advanced_tab')) + ->save(); + + parent::submitForm($form, $form_state); + } + + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessActionBase.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessActionBase.php new file mode 100644 index 000000000..8bc8d000c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessActionBase.php @@ -0,0 +1,100 @@ +entityType = $entity_type; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager')->getDefinition('domain') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return array( + 'domain_id' => '', + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + $form['domain_id'] = array( + '#type' => 'checkboxes', + '#title' => t('Domain'), + '#options' => $domains, + '#default_value' => $this->configuration['id'], + '#required' => TRUE, + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['domain_id'] = $form_state->getValue('domain_id'); + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + if (!empty($this->configuration['domain_id'])) { + $prefix = $this->entityType->getConfigPrefix() . '.'; + $this->addDependency('config', $prefix . $this->configuration['domain_id']); + } + return $this->dependencies; + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\user\UserInterface $object */ + // @TODO: fix this logic. + $access = $object->access('update', $account, TRUE); + + return $return_as_object ? $access : $access->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAdd.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAdd.php new file mode 100644 index 000000000..5d388405e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAdd.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $node_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Add domain assignment if not present. + if ($entity !== FALSE && !isset($node_domains[$id])) { + $node_domains[$id] = $id; + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($node_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAddEditor.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAddEditor.php new file mode 100644 index 000000000..e6e963de9 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAddEditor.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Skip adding the role to the user if they already have it. + if ($entity !== FALSE && !isset($user_domains[$id])) { + $user_domains[$id] = $id; + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($user_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAll.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAll.php new file mode 100644 index 000000000..8ccd91397 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessAll.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 1); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditAll.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditAll.php new file mode 100644 index 000000000..dc2dfa772 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditAll.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 1); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditNone.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditNone.php new file mode 100644 index 000000000..ee3ba9855 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessEditNone.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 0); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessNone.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessNone.php new file mode 100644 index 000000000..53d670a2b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessNone.php @@ -0,0 +1,24 @@ +set(DOMAIN_ACCESS_ALL_FIELD, 0); + $entity->save(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemove.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemove.php new file mode 100644 index 000000000..4bbfe4a4e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemove.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $node_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Remove domain assignment if present. + if ($entity !== FALSE && isset($node_domains[$id])) { + unset($node_domains[$id]); + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($node_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemoveEditor.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemoveEditor.php new file mode 100644 index 000000000..0c2e27905 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/Action/DomainAccessRemoveEditor.php @@ -0,0 +1,31 @@ +configuration['domain_id']; + $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($entity); + + // Skip adding the role to the user if they already have it. + if ($entity !== FALSE && isset($user_domains[$id])) { + unset($user_domains[$id]); + $entity->set(DOMAIN_ACCESS_FIELD, array_keys($user_domains)); + $entity->save(); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessContent.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessContent.php new file mode 100644 index 000000000..7111e2389 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessContent.php @@ -0,0 +1,169 @@ +domainStorage = $domain_storage; + $this->userStorage = $user_storage; + $this->manager = $manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager')->getStorage('domain'), + $container->get('entity_type.manager')->getStorage('user'), + $container->get('domain_access.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function summaryTitle() { + return $this->t('Domain editor'); + } + + /** + * {@inheritdoc} + */ + public function access(AccountInterface $account) { + // Users with this permission can see any domain content lists, and it is + // required to view all affiliates. + if ($account->hasPermission($this->allPermission)) { + return TRUE; + } + + // The routine below determines what domain (if any) was passed to the View. + if (isset($this->view->element['#arguments'])) { + foreach ($this->view->element['#arguments'] as $value) { + if ($domain = $this->domainStorage->load($value)) { + break; + } + } + } + + // Domain found, check user permissions. + if (!empty($domain)) { + return $this->manager->hasDomainPermissions($account, $domain, [$this->permission]); + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function alterRouteDefinition(Route $route) { + if ($domains = $this->domainStorage->loadMultiple()) { + $list = array_keys($domains); + } + $list[] = 'all_affiliates'; + $route->setRequirement('_domain_access_views', (string) implode('+', $list)); + $route->setDefault('domain_permission', $this->permission); + $route->setDefault('domain_all_permission', $this->allPermission); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['user']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessEditor.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessEditor.php new file mode 100644 index 000000000..f5cefb183 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/access/DomainAccessEditor.php @@ -0,0 +1,26 @@ +getStorage('domain')->load($this->argument)) { + return $domain->label(); + } + return parent::title(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/field/DomainAccessField.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/field/DomainAccessField.php new file mode 100644 index 000000000..30456088e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/field/DomainAccessField.php @@ -0,0 +1,50 @@ +options['settings']['link'])) { + foreach ($items as &$item) { + $object = $item['raw']; + $entity = $object->getEntity(); + $url = $entity->toUrl()->toString(); + $domain = $item['rendered']['#options']['entity']; + $item['rendered']['#type'] = 'markup'; + $item['rendered']['#markup'] = '' . $domain->label() . ''; + } + uasort($items, array($this, 'sort')); + } + + return $items; + } + + /** + * Sort the domain list, if possible. + */ + private function sort($a, $b) { + $domainA = isset($a['rendered']['#options']['entity']) ? $a['rendered']['#options']['entity'] : 0; + $domainB = isset($b['rendered']['#options']['entity']) ? $b['rendered']['#options']['entity'] : 0; + if ($domainA !== 0) { + return $domainA->getWeight() > $domainB->getWeight(); + } + // We don't have a domain object so sort as best we can. + return $a['rendered']['#plain_text'] > $b['rendered']['#plain_text']; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessCurrentAllFilter.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessCurrentAllFilter.php new file mode 100644 index 000000000..d23f6e2af --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessCurrentAllFilter.php @@ -0,0 +1,73 @@ +value_value = t('Available on current domain'); + } + + /** + * {@inheritdoc} + */ + public function getValueOptions() { + $this->valueOptions = array(1 => $this->t('Yes'), 0 => $this->t('No')); + } + + /** + * {@inheritdoc} + */ + protected function operators() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function query() { + $this->ensureMyTable(); + // @TODO: Proper abstraction of table and field name. + $all_table = $this->query->ensureTable('node__field_domain_all_affiliates'); + $current_domain = \Drupal::service('domain.negotiator')->getActiveId(); + if (empty($this->value)) { + // @TODO proper handling of NULL? + $where = "$this->tableAlias.$this->realField <> '$current_domain'"; + $where = '(' . $where . " OR $this->tableAlias.$this->realField IS NULL)"; + $where = '(' . $where . " AND ($all_table.field_domain_all_affiliates_value = 0 OR $all_table.field_domain_all_affiliates_value IS NULL))"; + } + else { + $where = "($this->tableAlias.$this->realField = '$current_domain' OR $all_table.field_domain_all_affiliates_value = 1)"; + } + $this->query->addWhereExpression($this->options['group'], $where); + // This filter causes duplicates. + $this->query->options['distinct'] = TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = parent::getCacheContexts(); + + $contexts[] = 'url.site'; + + return $contexts; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessFilter.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessFilter.php new file mode 100644 index 000000000..0869ef8c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/filter/DomainAccessFilter.php @@ -0,0 +1,26 @@ +valueOptions)) { + $this->valueTitle = $this->t('Domains'); + $this->valueOptions = \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + } + return $this->valueOptions; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_access.yml b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_access.yml new file mode 100644 index 000000000..a37af529c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_access.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - domain + - taxonomy +id: taxonomy_term.field_domain_access +field_name: field_domain_access +entity_type: taxonomy_term +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_all_affiliates.yml b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_all_affiliates.yml new file mode 100644 index 000000000..407d58ca3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/config/install/field.storage.taxonomy_term.field_domain_all_affiliates.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - domain + - taxonomy +id: taxonomy_term.field_domain_all_affiliates +field_name: field_domain_all_affiliates +entity_type: taxonomy_term +type: boolean +settings: { } +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml new file mode 100644 index 000000000..5e85d249f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml @@ -0,0 +1,18 @@ +name: "Domain Access module tests" +description: "Support module for domain access testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: + - domain + - domain_access + - taxonomy + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessAllAffiliatesTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessAllAffiliatesTest.php new file mode 100644 index 000000000..0264efd30 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessAllAffiliatesTest.php @@ -0,0 +1,103 @@ +admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($this->admin_user); + + // Visit the article field administration page. + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->assertResponse(200, 'Manage fields page accessed.'); + + // Check for the field. + $this->assertText($label, 'Domain form field found.'); + + // Visit the article field display administration page. + $this->drupalGet('admin/structure/types/manage/article/display'); + $this->assertResponse(200, 'Manage field display page accessed.'); + + // Check for the field. + $this->assertText($label, 'Domain form field found.'); + } + + /** + * Tests the storage of the domain access field. + */ + public function testDomainAccessAllFieldStorage() { + $label = 'Send to all affiliates'; + $this->admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText($label, 'Found the domain field instance.'); + + // We expect to find 5 domain options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $string = 'value="' . $domain->id() . '"'; + $this->assertRaw($string, 'Found the domain option.'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Try to post a node, assigned to the first two domains. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain_access[{$one}]"] = TRUE; + $edit["field_domain_access[{$two}]"] = TRUE; + $edit["field_domain_all_affiliates[value]"] = 1; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + // Check that two values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 2, 'Node saved with two domain records.'); + // Check that all affiliates is set. + $this->assertTrue(!empty($node->get(DOMAIN_ACCESS_ALL_FIELD)->value), 'Node assigned to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessDefaultValueTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessDefaultValueTest.php new file mode 100644 index 000000000..cc56b9926 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessDefaultValueTest.php @@ -0,0 +1,99 @@ +admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText('Domain Access', 'Found the domain field instance.'); + $this->assertRaw('name="field_domain_access[example_com]" value="example_com" checked="checked"', 'Default domain selected.'); + // Check the all affiliates field. + $this->assertText('Send to all affiliates', 'Found the all affiliates field instance.'); + $this->assertNoRaw('name="field_domain_all_affiliates[value]" value="1" checked="checked"', 'All affiliates not selected.'); + + // Now save the node with the values set. + $edit = [ + 'title[0][value]' => 'Test node', + 'field_domain_access[example_com]' => 'example_com', + ]; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + + // Load the node. + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + $this->assertTrue($node, 'Article node created.'); + // Check that the values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 1, 'Node saved with one domain record.'); + $allValue = \Drupal::service('domain_access.manager')->getAllValue($node); + $this->assertTrue(empty($allValue), 'Not sent to all affiliates.'); + + // Logout the admin user. + $this->drupalLogout(); + + // Create a limited value user. + $this->test_user = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + )); + + // Login and try to edit the created node. + $this->drupalLogin($this->test_user); + + $this->drupalGet('node/1/edit'); + $this->assertResponse(200); + + // Now save the node with the values set. + $edit = [ + 'title[0][value]' => 'Test node update', + ]; + $this->drupalPostForm('node/1/edit', $edit, 'Save'); + + // Load the node. + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + $this->assertTrue($node, 'Article node created.'); + // Check that the values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 1, 'Node saved with one domain record.'); + $allValue = \Drupal::service('domain_access.manager')->getAllValue($node); + $this->assertTrue(empty($allValue), 'Not sent to all affiliates.'); + + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessElementTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessElementTest.php new file mode 100644 index 000000000..ea511c3fa --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessElementTest.php @@ -0,0 +1,130 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainAccessElement() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($admin); + + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + + // Set the title, so the node can be saved. + $this->fillField('title[0][value]', 'Test node'); + + // We expect to find 5 domain options. We set two as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $count = 0; + $ids = ['example_com', 'one_example_com', 'two_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + } + // Find the all affiliates field. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->findField($locator); + + // Set all affiliates to TRUE. + $this->checkField($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + $storage = \Drupal::entityTypeManager()->getStorage('node'); + $node = $storage->load(1); + // Check that two values are set. + $manager = \Drupal::service('domain_access.manager'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 3, 'Node saved with three domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array('create article content', 'edit any article content', 'publish to any assigned domain')); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ACCESS_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account->id()); + $values = $manager->getAccessValues($user); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + $value = $manager->getAllValue($user); + $this->assert($value == 0, 'User not saved to all affiliates.'); + + $this->drupalLogin($account); + + $this->drupalGet('node/' . $node->id() . '/edit'); + $this->assertSession()->statusCodeEquals(200); + + foreach ($domains as $domain) { + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if ($domain->id() == 'example_com') { + $this->checkField($locator); + } + elseif ($domain->id() == 'one_example_com') { + $this->uncheckField($locator); + } + + else { + $this->assertSession()->fieldNotExists($locator); + } + } + + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->assertSession()->fieldNotExists($locator); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Now, check the node. + $storage->resetCache(array($node->id())); + $node = $storage->load(1); + // Check that two values are set. + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 2, 'Node saved with two domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityFieldTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityFieldTest.php new file mode 100644 index 000000000..d98c2ca1a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityFieldTest.php @@ -0,0 +1,71 @@ +domainCreateTestDomains(5); + } + + /** + * Tests that the fields are accessed properly. + */ + public function testDomainAccessEntityFields() { + $label = 'Send to all affiliates'; + // Create a vocabulary. + $vocabulary = entity_create('taxonomy_vocabulary', array( + 'name' => 'Domain vocabulary', + 'description' => 'Test taxonomy for Domain Access', + 'vid' => 'domain_access', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'weight' => 100, + )); + $vocabulary->save(); + $text['taxonomy_term'] = [ + 'name' => 'term', + 'label' => 'Send to all affiliates', + 'description' => 'Make this term available on all domains.', + ]; + domain_access_confirm_fields('taxonomy_term', 'domain_access', $text); + $this->admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + 'administer taxonomy', + 'administer taxonomy_term fields', + 'administer taxonomy_term form display', + )); + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/structure/taxonomy/manage/domain_access/overview/fields'); + $this->assertResponse(200, 'Manage fields page accessed.'); + + // Check for a domain field. + $this->assertText('Domain Access', 'Domain form field found.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityReferenceTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityReferenceTest.php new file mode 100644 index 000000000..8e9a277dd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessEntityReferenceTest.php @@ -0,0 +1,98 @@ +admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($this->admin_user); + + // Visit the article field administration page. + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->assertResponse(200, 'Manage fields page accessed.'); + + // Check for a domain field. + $this->assertText('Domain Access', 'Domain form field found.'); + + // Visit the article field display administration page. + $this->drupalGet('admin/structure/types/manage/article/display'); + $this->assertResponse(200, 'Manage field display page accessed.'); + + // Check for a domain field. + $this->assertText('Domain Access', 'Domain form field found.'); + } + + /** + * Tests the storage of the domain access field. + */ + public function testDomainAccessFieldStorage() { + $this->admin_user = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText('Domain Access', 'Found the domain field instance.'); + + // We expect to find 5 domain options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $string = 'value="' . $domain->id() . '"'; + $this->assertRaw($string, 'Found the domain option.'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Try to post a node, assigned to the first two domains. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain_access[{$one}]"] = TRUE; + $edit["field_domain_access[{$two}]"] = TRUE; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = \Drupal::entityTypeManager()->getStorage('node')->load(1); + // Check that two values are set. + $values = \Drupal::service('domain_access.manager')->getAccessValues($node); + $this->assertTrue(count($values) == 2, 'Node saved with two domain records.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php new file mode 100644 index 000000000..339942937 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php @@ -0,0 +1,190 @@ +domainCreateTestDomains(5); + } + + /** + * Tests that the fields are accessed properly. + */ + public function testDomainAccessFields() { + $label = 'Send to all affiliates'; + + // Test a user who can access all domain settings. + $user1 = $this->drupalCreateUser(['create article content', 'publish to any domain']); + $this->drupalLogin($user1); + + // Visit the article creation page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200, 'Article creation found.'); + + // Check for the form options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $this->assertText($domain->label(), 'Domain form item found.'); + } + $this->assertText($label, 'All affiliates field found.'); + + // Test a user who can access some domain settings. + $user2 = $this->drupalCreateUser(['create article content', 'publish to any assigned domain']); + $active_domain = array_rand($domains, 1); + $this->addDomainsToEntity('user', $user2->id(), $active_domain, DOMAIN_ACCESS_FIELD); + $this->drupalLogin($user2); + + // Visit the article creation page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200, 'Article creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + if ($domain->id() == $active_domain) { + $this->assertRaw('>' . $domain->label() . '', 'Domain form item found.'); + } + else { + $this->assertNoRaw('>' . $domain->label() . '', 'Domain form item not found.'); + } + } + $this->assertNoText($label, 'All affiliates field not found.'); + + // Test a user who can access no domain settings. + $user3 = $this->drupalCreateUser(array('create article content')); + $this->drupalLogin($user3); + + // Visit the article creation page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200, 'Article creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + $this->assertNoText($domain->label(), 'Domain form item not found.'); + } + $this->assertNoText($label, 'All affiliates field not found.'); + + // Attempt saving the node. + // The domain/domain affiliates fields are not accessible to this user. + // The save will fail with an EntityStorageException until + // https://www.drupal.org/node/2609252 is fixed. + $edit = array(); + $edit['title[0][value]'] = $this->randomMachineName(8); + $edit['body[0][value]'] = $this->randomMachineName(16); + $this->drupalPostForm('node/add/article', $edit, t('Save')); + + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit['title[0][value]']); + $this->assertTrue($node, 'Node found in database.'); + + // Test a user who can assign users to domains. + $user4 = $this->drupalCreateUser(array('administer users', 'assign editors to any domain')); + $this->drupalLogin($user4); + + // Visit the account creation page. + $this->drupalGet('admin/people/create'); + $this->assertResponse(200, 'User creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + $this->assertText($domain->label(), 'Domain form item found.'); + } + + // Test a user who can assign users to some domains. + $user5 = $this->drupalCreateUser(array('administer users', 'assign domain editors')); + $active_domain = array_rand($domains, 1); + $this->addDomainsToEntity('user', $user5->id(), $active_domain, DOMAIN_ACCESS_FIELD); + $this->drupalLogin($user5); + + // Visit the account creation page. + $this->drupalGet('admin/people/create'); + $this->assertResponse(200, 'User creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + if ($domain->id() == $active_domain) { + $this->assertRaw('>' . $domain->label() . '', 'Domain form item found.'); + } + else { + $this->assertNoRaw('>' . $domain->label() . '', 'Domain form item not found.'); + } + } + + // Test a user who can access no domain settings. + $user6 = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($user6); + + // Visit the account creation page. + $this->drupalGet('admin/people/create'); + $this->assertResponse(200, 'User creation found.'); + + // Check for the form options. + foreach ($domains as $domain) { + $this->assertNoText($domain->label(), 'Domain form item not found.'); + } + + // Test a user who can access all domain settings. + $user7 = $this->drupalCreateUser(array('bypass node access', 'publish to any domain')); + $this->drupalLogin($user7); + + // Create a new content type and test that the fields are created. + // Create a content type programmatically. + $type = $this->drupalCreateContentType(); + + $type_exists = (bool) NodeType::load($type->id()); + $this->assertTrue($type_exists, 'The new content type has been created in the database.'); + + // Visit the article creation page. + $this->drupalGet('node/add/' . $type->id()); + $this->assertResponse(200, $type->id() . ' creation found.'); + + // Check for the form options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $this->assertText($domain->label(), 'Domain form item found.'); + } + $this->assertText($label, 'All affiliates field found.'); + + // Test user without access to affiliates field editing their user page. + $user8 = $this->drupalCreateUser(array('change own username')); + $this->drupalLogin($user8); + + $user_edit_page = 'user/' . $user8->id() . '/edit'; + $this->drupalGet($user_edit_page); + // Check for the form options. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $this->assertNoText($domain->label(), 'Domain form item not found.'); + } + + $this->assertNoText($label, 'All affiliates field not found.'); + + // Change own username. + $edit = array(); + $edit['name'] = $this->randomMachineName(); + + $this->drupalPostForm($user_edit_page, $edit, t('Save')); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessGrantsTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessGrantsTest.php new file mode 100644 index 000000000..e669f080e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessGrantsTest.php @@ -0,0 +1,88 @@ +delete('node_access')->execute(); + } + + /** + * Creates a node and tests the creation of node access rules. + */ + public function testDomainAccessGrants() { + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + // Assign a node to a random domain. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $active_domain = array_rand($domains, 1); + $domain = $domains[$active_domain]; + // Create an article node. + $node1 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + )); + $this->assertTrue($node_storage->load($node1->id()), 'Article node created.'); + + // Test the response of the node on each site. Should allow access only to + // the selected site. + foreach ($domains as $domain) { + $path = $domain->getPath() . 'node/' . $node1->id(); + $this->drupalGet($path); + if ($domain->id() == $active_domain) { + $this->assertResponse(200); + $this->assertRaw($node1->getTitle(), 'Article found on domain.'); + } + else { + $this->assertResponse(403); + } + } + + // Create an article node. + $node2 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + DOMAIN_ACCESS_ALL_FIELD => 1, + )); + $this->assertTrue($node_storage->load($node2->id()), 'Article node created.'); + // Test the response of the node on each site. Should allow access on all. + foreach ($domains as $domain) { + $path = $domain->getPath() . 'node/' . $node2->id(); + $this->drupalGet($path); + $this->assertResponse(200); + $this->assertRaw($node2->getTitle(), 'Article found on domain.'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessLanguageSaveTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessLanguageSaveTest.php new file mode 100644 index 000000000..0896d30d2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessLanguageSaveTest.php @@ -0,0 +1,82 @@ +domainCreateTestDomains(5); + + // Add Hungarian and Afrikaans. + ConfigurableLanguage::createFromLangcode('hu')->save(); + ConfigurableLanguage::createFromLangcode('af')->save(); + + // Enable content translation for the current entity type. + \Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE); + } + + /** + * Basic test setup. + */ + public function testDomainAccessSave() { + $storage = \Drupal::entityTypeManager()->getStorage('node'); + // Save a node programatically. + $node = $storage->create([ + 'type' => 'article', + 'title' => 'Test node', + 'uid' => '1', + 'status' => 1, + DOMAIN_ACCESS_FIELD => ['example_com'], + DOMAIN_ACCESS_ALL_FIELD => 1, + ]); + $node->save(); + + // Load the node. + $node = $storage->load(1); + + // Check that two values are set properly. + $manager = \Drupal::service('domain_access.manager'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 1, 'Node saved with one domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + + // Create an Afrikaans translation assigned to domain 2. + $translation = $node->addTranslation('af'); + $translation->title->value = $this->randomString(); + $translation->{DOMAIN_ACCESS_FIELD} = ['example_com', 'one_example_com']; + $translation->{DOMAIN_ACCESS_ALL_FIELD} = 0; + $translation->status = 1; + $node->save(); + + // Load and check the translated node. + $parent_node = $storage->load(1); + $node = $parent_node->getTranslation('af'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 2, 'Node saved with two domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 0, 'Node not saved to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessPermissionsTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessPermissionsTest.php new file mode 100644 index 000000000..67cdd3440 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessPermissionsTest.php @@ -0,0 +1,295 @@ +config('user.role.' . RoleInterface::AUTHENTICATED_ID)->set('permissions', array())->save(); + // Create Basic page node type. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array( + 'type' => 'page', + 'name' => 'Basic page', + 'display_submitted' => FALSE, + )); + $this->drupalCreateContentType(array( + 'type' => 'article', + 'name' => 'Article', + 'display_submitted' => FALSE, + )); + } + $this->accessHandler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); + $this->manager = \Drupal::service('domain_access.manager'); + $this->userStorage = \Drupal::entityTypeManager()->getStorage('user'); + // Create 5 domains. + $this->domainCreateTestDomains(5); + $this->domains = $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + } + + /** + * Runs basic tests for node_access function. + */ + public function testDomainAccessPermissions() { + // Note that these are hook_node_access() rules. Node Access system tests + // are in DomainAccessRecordsTest. + // We expect to find 5 domain options. Set two for later use. + foreach ($this->domains as $domain) { + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + + // Assign our user to domain $two. Test on $one and $two. + $domain_user1 = $this->drupalCreateUser(array( + 'access content', + 'edit domain content', + 'delete domain content', + )); + $this->addDomainsToEntity('user', $domain_user1->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user1 = $this->userStorage->load($domain_user1->id()); + $assigned = $this->manager->getAccessValues($domain_user1); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + // Assign one node to default domain, and one to our test domain. + $domain_node1 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$one])); + $domain_node2 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$two])); + $assigned = $this->manager->getAccessValues($domain_node1); + $this->assertTrue(isset($assigned[$one]), 'Node1 assigned to proper test domain.'); + $assigned = $this->manager->getAccessValues($domain_node2); + $this->assertTrue(isset($assigned[$two]), 'Node2 assigned to proper test domain.'); + + // Tests 'edit domain content' to edit content assigned to their domains. + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ), $domain_node1, $domain_user1); + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ), $domain_node2, $domain_user1); + + // Tests 'edit domain TYPE content'. + // Assign our user to domain $two. Test on $one and $two. + $domain_user3 = $this->drupalCreateUser(array( + 'access content', + 'update page content on assigned domains', + 'delete page content on assigned domains', + )); + $this->addDomainsToEntity('user', $domain_user3->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user3 = $this->userStorage->load($domain_user3->id()); + $assigned = $this->manager->getAccessValues($domain_user3); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + + // Assign two different node types to our test domain. + $domain_node3 = $this->drupalCreateNode(array('type' => 'article', DOMAIN_ACCESS_FIELD => [$two])); + $domain_node4 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$two])); + $assigned = $this->manager->getAccessValues($domain_node3); + $this->assertTrue(isset($assigned[$two]), 'Node3 assigned to proper test domain.'); + $assigned = $this->manager->getAccessValues($domain_node4); + $this->assertTrue(isset($assigned[$two]), 'Node4 assigned to proper test domain.'); + + // Tests 'edit TYPE content on assigned domains'. + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ), $domain_node3, $domain_user3); + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ), $domain_node4, $domain_user3); + + // @TODO: Test edit and delete for user with 'all affiliates' permission. + // Tests 'edit domain TYPE content'. + // Assign our user to domain $two. Test on $one and $two. + $domain_user4 = $this->drupalCreateUser(array( + 'access content', + 'update page content on assigned domains', + 'delete page content on assigned domains', + )); + $this->addDomainsToEntity('user', $domain_user4->id(), $two, DOMAIN_ACCESS_FIELD); + $this->addDomainsToEntity('user', $domain_user4->id(), 1, DOMAIN_ACCESS_ALL_FIELD); + $domain_user4 = $this->userStorage->load($domain_user4->id()); + $assigned = $this->manager->getAccessValues($domain_user4); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + $this->assertTrue(!empty($domain_user4->get(DOMAIN_ACCESS_ALL_FIELD)->value), 'User assign to all affiliates.'); + + // Assign two different node types to our test domain. + $domain_node5 = $this->drupalCreateNode(array('type' => 'article', DOMAIN_ACCESS_FIELD => [$one])); + $domain_node6 = $this->drupalCreateNode(array('type' => 'page', DOMAIN_ACCESS_FIELD => [$one])); + $assigned = $this->manager->getAccessValues($domain_node5); + $this->assertTrue(isset($assigned[$one]), 'Node5 assigned to proper test domain.'); + $assigned = $this->manager->getAccessValues($domain_node6); + $this->assertTrue(isset($assigned[$one]), 'Node6 assigned to proper test domain.'); + + // Tests 'edit TYPE content on assigned domains'. + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => FALSE, + 'delete' => FALSE, + ), $domain_node5, $domain_user4); + $this->assertNodeAccess(array( + 'view' => TRUE, + 'update' => TRUE, + 'delete' => TRUE, + ), $domain_node6, $domain_user4); + + } + + public function testDomainAccessCreatePermissions() { + foreach ($this->domains as $domain) { + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + // Tests create permissions. Any content on assigned domains. + $domain_account5 = $this->drupalCreateUser(array('access content', 'create domain content')); + $this->addDomainsToEntity('user', $domain_account5->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user5 = $this->userStorage->load($domain_account5->id()); + $assigned = $this->manager->getAccessValues($domain_user5); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + // This test is domain sensitive. + foreach ($this->domains as $domain) { + $this->domainLogin($domain, $domain_account5); + $url = $domain->getPath() . 'node/add/page'; + $this->drupalGet($url); + if ($domain->id() == $two) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + // The user should be allowed to create articles. + $url = $domain->getPath() . 'node/add/article'; + $this->drupalGet($url); + if ($domain->id() == $two) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + } + + } + + public function testDomainAccessLimitedCreatePermissions() { + foreach ($this->domains as $domain) { + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + } + } + // Tests create permissions. Any content on assigned domains. + $domain_account6 = $this->drupalCreateUser(array('access content', 'create page content on assigned domains')); + $this->addDomainsToEntity('user', $domain_account6->id(), $two, DOMAIN_ACCESS_FIELD); + $domain_user6 = $this->userStorage->load($domain_account6->id()); + $assigned = $this->manager->getAccessValues($domain_user6); + $this->assertTrue(count($assigned) == 1, 'User assigned to one domain.'); + $this->assertTrue(isset($assigned[$two]), 'User assigned to proper test domain.'); + // This test is domain sensitive. + foreach ($this->domains as $domain) { + $this->domainLogin($domain, $domain_account6); + $url = $domain->getPath() . 'node/add/page'; + $this->drupalGet($url); + if ($domain->id() == $two) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + // The user should not be allowed to create articles. + $url = $domain->getPath() . 'node/add/article'; + $this->drupalGet($url); + $this->assertResponse(403); + } + } + + + /** + * Asserts that node access correctly grants or denies access. + * + * @param array $ops + * An associative array of the expected node access grants for the node + * and account, with each key as the name of an operation (e.g. 'view', + * 'delete') and each value a Boolean indicating whether access to that + * operation should be granted. + * @param \Drupal\node\Entity\NodeInterface $node + * The node object to check. + * @param \Drupal\Core\Session\AccountInterface $account + * The user account for which to check access. + */ + public function assertNodeAccess(array $ops, NodeInterface $node, AccountInterface $account) { + foreach ($ops as $op => $result) { + $this->assertEqual($result, $this->accessHandler->access($node, $op, $account), 'Expected result returned.'); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessRecordsTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessRecordsTest.php new file mode 100644 index 000000000..77950315f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessRecordsTest.php @@ -0,0 +1,80 @@ +domainCreateTestDomains(5); + // Assign a node to a random domain. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $active_domain = array_rand($domains, 1); + $domain = $domains[$active_domain]; + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + + // Create an article node. + $node1 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + DOMAIN_ACCESS_ALL_FIELD => 0, + )); + $this->assertTrue($node_storage->load($node1->id()), 'Article node created.'); + + // Check to see if grants added by domain_node_access_records made it in. + $query = 'SELECT realm, gid, grant_view, grant_update, grant_delete FROM {node_access} WHERE nid = :nid'; + $records = Database::getConnection() + ->query($query, array(':nid' => $node1->id())) + ->fetchAll(); + + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'domain_id', 'Grant with domain_id acquired for node.'); + $this->assertEqual($records[0]->gid, $domain->getDomainId(), 'Grant with proper id acquired for node.'); + $this->assertEqual($records[0]->grant_view, 1, 'Grant view stored.'); + $this->assertEqual($records[0]->grant_update, 1, 'Grant update stored.'); + $this->assertEqual($records[0]->grant_delete, 1, 'Grant delete stored.'); + + // Create another article node. + $node2 = $this->drupalCreateNode(array( + 'type' => 'article', + DOMAIN_ACCESS_FIELD => array($domain->id()), + DOMAIN_ACCESS_ALL_FIELD => 1, + )); + $this->assertTrue($node_storage->load($node2->id()), 'Article node created.'); + // Check to see if grants added by domain_node_access_records made it in. + $query = 'SELECT realm, gid, grant_view, grant_update, grant_delete FROM {node_access} WHERE nid = :nid ORDER BY realm'; + $records = Database::getConnection() + ->query($query, array(':nid' => $node2->id())) + ->fetchAll(); + $this->assertEqual(count($records), 2, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->realm, 'domain_id', 'Grant with domain_id acquired for node.'); + $this->assertEqual($records[0]->gid, $domain->getDomainId(), 'Grant with proper id acquired for node.'); + $this->assertEqual($records[0]->grant_view, 1, 'Grant view stored.'); + $this->assertEqual($records[0]->grant_update, 1, 'Grant update stored.'); + $this->assertEqual($records[0]->grant_delete, 1, 'Grant delete stored.'); + $this->assertEqual($records[1]->realm, 'domain_site', 'Grant with domain_site acquired for node.'); + $this->assertEqual($records[1]->gid, 0, 'Grant with proper id acquired for node.'); + $this->assertEqual($records[1]->grant_view, 1, 'Grant view stored.'); + $this->assertEqual($records[1]->grant_update, 0, 'Grant update stored.'); + $this->assertEqual($records[1]->grant_delete, 0, 'Grant delete stored.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessSaveTest.php b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessSaveTest.php new file mode 100644 index 000000000..fd06f9eb8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessSaveTest.php @@ -0,0 +1,76 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainAccessSave() { + $storage = \Drupal::entityTypeManager()->getStorage('node'); + // Save a node programatically. + $node = $storage->create([ + 'type' => 'article', + 'title' => 'Test node', + 'uid' => '1', + 'status' => 1, + DOMAIN_ACCESS_FIELD => ['example_com'], + DOMAIN_ACCESS_ALL_FIELD => 1, + ]); + $node->save(); + + // Load the node. + $node = $storage->load(1); + + // Check that two values are set properly. + $manager = \Drupal::service('domain_access.manager'); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 1, 'Node saved with one domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 1, 'Node saved to all affiliates.'); + + // Save a node with different values. + $node = $storage->create([ + 'type' => 'article', + 'title' => 'Test node', + 'uid' => '1', + 'status' => 1, + DOMAIN_ACCESS_FIELD => ['example_com', 'one_example_com'], + DOMAIN_ACCESS_ALL_FIELD => 0, + ]); + $node->save(); + + // Load and check the node. + $node = $storage->load(2); + $values = $manager->getAccessValues($node); + $this->assert(count($values) == 2, 'Node saved with two domain records.'); + $value = $manager->getAllValue($node); + $this->assert($value == 0, 'Node not saved to all affiliates.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/README.md b/sites/all/modules/contrib/admin/domain/domain_alias/README.md new file mode 100644 index 000000000..eea1623b8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/README.md @@ -0,0 +1,182 @@ +About aliases +==== + +Aliases define unique domains or patterns that map to existing domain records. +For example, you may want to have the following setup: + +* example.com (primary domain) +* my.example.com (active domain record) +* myexample.com (alias of the my.example.com record) + +In effect, myexample.com and my.example.com behave the same way from a Drupal +perspective. Aliases may also be made to redirect to their parent domain, if +preferred. + +Alias Patterns +==== + +The Drupal 8 version of Domain Alias supports multi-character wildcards as part +of the hostname. You can register an alias to a domain record in any of the +following patterns. +``` + *.example.com + example.*.com + example.com.* + *.*.example.com + example.*.*.com + example.* + example.*.* + example.*.*.* + *.com + *.example.* + example.com:8080 + example.com:* + example.*:* + *.com:* +``` +A maximum of three wildcards are supported. At least one element must not be a +wildcard. Ports may be wildcards as well, but are optional. + +Alias Matching +==== + +When a request is sent to Drupal, the domain negotiation system will look for a +matching record. The order of precedence is as follows. + +Example request: `one.example.com` + +1. Exact domain record match (`one.example.com`) +1. Exact alias match (`one.example.com`) +1. Wildcard match +``` + one.example.com + one.example.* + *.example.com + one.*.com + *.example.* + *.*.com + one.*.*, +``` +Note that wildcard matching happens _in the listed order_. The number of +wildcards is equal to the number of hostname parts minus 1. That is, you cannot register +an alias that is all wildcards. + +Port Matching +=== + +Port matching (e.g. example.com:8080) works exactly as hostname matching, with +one significant change. If the inbound request is on port 80, matches to the +base hostname are permitted, since port 80 is the default port for HTTP. + +For example, a request to example.com:80 will match the following aliases: +``` + example.com:80 + example.com + example.com:* + example.* + example.*:80 + example.*:* + *.com + *.com:80 + *.com:* +``` +Whereas a request to example.com:8080 will not match the hostname without a port +specified. +``` + example.com:8080 + example.com:* + example.*:8080 + example.*:* + *.com:8080 + *.com:* +``` + +Development Workflow +==== + +Aliases can be used to support development across different environments, with unique +URLs. To support this feature, there is now an `environment` field for each alias. The +default environment list is: + +* default +* local +* development +* staging +* testing + +This list may be overridden by setting the `domain_alias.environments` configuration in +settings.php. + +The operation of these environments is as follows: + +* If alias matching the environment is `default`, no changes occur. +* Else, matching aliases are loaded for all domains, so that links are rewritten to be +specific to the specified environment. (See `domain_alias_domain_load()` for the logic.) + +For instance, consider the following configuration. Your site's canonical domains are: + +* example.com +* foo.example.com +* bar.example.com + +When developing locally, developers use `.local` instead of `.com`. These should be +aliased to each domain as set as the `local` environment. + +* example.local > alias to example.com +* foo.example.local > alias to foo.example.com +* bar.example.local > alias to bar.example.com + +When pushing changes to the cloud, we use a development server. These are tied to a +specific cloud host (dev.mycloud.com). You can alias these to the `development` +environment. + +* example.dev.mycloud.com > alias to example.com +* foo.example.dev.mycloud.com > alias to foo.example.com +* bar.example.dev.mycloud.com > alias to bar.example.com + +The pattern can repeat for each of the environments listed above. The intended use of the +default set of environments is: + +* default -- indicates a canonical URL. No changes will be made. +* local -- for local development environments. +* development -- for a development integration server (such as those provided by Acquia, +Pantheon, and Platform.sh) +* staging -- for a pre-deployment server (such as those provided by Acquia, Panthon, and +Platform.sh) +* testing -- for continuous integration services (such as TravisCI or CircleCI). + +None of these environments are required. You may safely set all aliases to default if +your workflow does not span multiple server environments. + +How does it work? +---- + +This feature works by mapping each alias to an environment. If the active request matches +an alias that is set as an environment other than `default`, then matching environment +aliases are loaded for each domain. If a match is found, the `hostname` value for each +domain is overwritten. + +This overwrite affects the base path and request url that Domain module (and Domain Source) +use for writing links. + +Because the environments are specific to hostnames, this feature will only work if the +site's cache recognizes `url.site` as a required cache context. Without that, the render +system will cache the output of a request incorrectly. + +Configuration +---- + +To use this feature, the following steps must be followed: + +* `url.site` must be added as a required_cache_context to your `services.yml` file. +* Aliases must be mapped to a server environment. Default value is `default`. +* All aliases should be listed as part of `trusted_host_settings` in `settings.php`. + +Technical Notes +==== + +The matching follows an explicit sort order shown in DomainAliasSortTest. + +The code will attempt to match domains of different lengths when doing wildcard +matching in an environment. That is, an alias to `example.*` assigned to `local` should +return `example.local` if the active domain is `one.example.local`, assigned to `local`. diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/config/install/domain_alias.settings.yml b/sites/all/modules/contrib/admin/domain/domain_alias/config/install/domain_alias.settings.yml new file mode 100644 index 000000000..41b02b54b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/config/install/domain_alias.settings.yml @@ -0,0 +1,6 @@ +environments: + - default + - local + - development + - staging + - testing diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/config/schema/domain_alias.schema.yml b/sites/all/modules/contrib/admin/domain/domain_alias/config/schema/domain_alias.schema.yml new file mode 100644 index 000000000..8b0320581 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/config/schema/domain_alias.schema.yml @@ -0,0 +1,34 @@ +# Schema for the configuration files of the Domain Alias module. +domain_alias.settings: + type: config_object + label: 'Domain Alias settings' + mapping: + environments: + type: sequence + label: 'Development environments' + sequence: + type: string + label: 'Environment' + +domain_alias.alias.*: + type: config_entity + label: 'Domain alias' + mapping: + id: + type: string + label: 'ID' + uuid: + type: string + label: 'UUID' + domain_id: + type: string + label: 'Domain ID' + pattern: + type: string + label: 'Pattern' + redirect: + type: integer + label: 'Redirect' + environment: + type: string + label: 'Environment' diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml new file mode 100644 index 000000000..d4d57d043 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml @@ -0,0 +1,14 @@ +name: Domain Alias +description: 'Maps multiple host requests to a single domain record.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.install b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.install new file mode 100644 index 000000000..adb2f66a3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.install @@ -0,0 +1,32 @@ +getEditable('domain_alias.settings'); + // Set and save new message value. + $config->set('environments', ['default', 'local', 'development', 'staging', 'testing'])->save(); +} + +/** + * Updates domain_alias schema. + */ +function domain_alias_update_8001() { + $manager = \Drupal::entityDefinitionUpdateManager(); + // Regenerate entity type indexes. + $manager->updateEntityType($manager->getEntityType('domain_alias')); +} + +/** + * Adds domain_alias environment settings. + */ +function domain_alias_update_8002() { + // Set the default environments variable. + domain_alias_set_environments(); +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.action.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.action.yml new file mode 100644 index 000000000..e8ad0ebce --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.action.yml @@ -0,0 +1,5 @@ +domain_alias_add: + route_name: domain_alias.add + title: 'Add domain alias' + appears_on: + - domain_alias.admin diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.task.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.task.yml new file mode 100644 index 000000000..842ea90c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.links.task.yml @@ -0,0 +1,4 @@ +domain_alias.list: + title: 'Aliases' + route_name: domain_alias.admin + tab_root_id: entity.domain.edit_form diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.module b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.module new file mode 100644 index 000000000..569c4ae87 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.module @@ -0,0 +1,168 @@ +getMatchType() == DomainNegotiator::DOMAIN_MATCH_EXACT) { + return; + } + // If no exact match, then run the alias load routine. + $hostname = $domain->getHostname(); + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + /** @var \Drupal\domain_alias\Entity\DomainAlias $alias */ + if ($alias = $alias_storage->loadByHostname($hostname)) { + /** @var \Drupal\domain\Entity\Domain $domain */ + if ($domain = $domain_storage->load($alias->getDomainId())) { + $domain->addProperty('alias', $alias); + $domain->setMatchType(DomainNegotiator::DOMAIN_MATCH_ALIAS); + $redirect = $alias->getRedirect(); + if (!empty($redirect)) { + $domain->setRedirect($redirect); + } + } + else { + // If the domain did not load, report an error. + \Drupal::logger('domain_alias')->error('Found matching alias %alias for host request %hostname, but failed to load matching domain with id %id.', + ['%alias' => $alias->getPattern(), + '%hostname' => $hostname, + '%id' => $alias->getDomainId(), + ]); + } + } +} + +/** + * Implements hook_domain_operations(). + */ +function domain_alias_domain_operations(DomainInterface $domain, AccountInterface $account) { + $operations = []; + // Check permissions. The user must be a super-admin or assigned to the domain. + $is_domain_admin = $domain->access('update', $account); + if ($account->hasPermission('administer domain aliases') || ($is_domain_admin && $account->hasPermission('view domain aliases'))) { + // Add aliases to the list of operations. + $operations['domain_alias'] = array( + 'title' => t('Aliases'), + 'url' => Url::fromRoute('domain_alias.admin', array('domain' => $domain->id())), + 'weight' => 60, + ); + } + return $operations; +} + +/** + * Implements hook_ENTITY_TYPE_load(). + */ +function domain_alias_domain_load($entities) { + static $enabled; + // We can only perform meaningful actions if url.site is a cache context. Otherwise, + // the render process ignores our changes. + if (!isset($enabled)) { + $required_cache_contexts = \Drupal::getContainer()->getParameter('renderer.config')['required_cache_contexts']; + if (!in_array('url.site', $required_cache_contexts) && !in_array('url', $required_cache_contexts)) { + $enabled = FALSE; + return; + } + $enabled = TRUE; + } + + // We cannot run before the negotiator service has fired. + $negotiator = \Drupal::service('domain.negotiator'); + $active = $negotiator->getActiveDomain(); + + // Do nothing if no domain is active. + if (is_null($active)) { + return; + } + + // Load and rewrite environement-specific aliases. + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + if (isset($active->alias) && $active->alias->getEnvironment() != 'default') { + foreach ($entities as $id => $domain) { + if ($environment_aliases = $alias_storage->loadByEnvironmentMatch($domain, $active->alias->getEnvironment())) { + foreach($environment_aliases as $environment_alias) { + $pattern = $environment_alias->getPattern(); + // Add a canonical property. + $domain->setCanonical(); + // Override the domain hostname and path. We always prefer a string match. + if (substr_count($pattern, '*') < 1) { + $domain->setHostname($pattern); + $domain->setPath(); + $domain->setUrl(); + break; + } + else { + // Do a wildcard replacement based on the current request. + $request = $negotiator->negotiateActiveHostname(); + // First, check for a wildcard port. + if (substr_count($pattern, ':*') > 0) { + // Do not replace ports unless they are nonstandard. See + // \Symfony\Component\HttpFoundation\Request\getHttpHost(). + if (substr_count($request, ':') > 0) { + $search = explode(':', $pattern); + $replace = explode(':', $request); + if (!empty($search[1]) && !empty($replace[1])) { + $pattern = str_replace(':' . $search[1], ':' . $replace[1], $pattern); + } + } + // If no port wildcard, then remove the port entirely. + else { + $pattern = str_replace(':*', '', $pattern); + } + } + + $replacements = ['.' => '\.', '*' => '(.+?)']; + $regex = '/^' . strtr($active->alias->getPattern(), $replacements) . '$/'; + if (preg_match($regex, $request, $matches) && isset($matches[1])) { + $pattern = str_replace('*', $matches[1], $pattern); + } + + // Do not let the domain loop back on itself. + if ($pattern != $domain->getCanonical()) { + $domain->setHostname($pattern); + $domain->setPath(); + $domain->setUrl(); + } + } + } + } + } + } +} + +/** + * Implements hook_ENTITY_TYPE_delete(). + */ +function domain_alias_domain_delete(EntityInterface $entity) { + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $properties = ['domain_id' => $entity->id()]; + foreach ($alias_storage->loadByProperties($properties) as $alias) { + $alias->delete(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.permissions.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.permissions.yml new file mode 100644 index 000000000..88fd57655 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.permissions.yml @@ -0,0 +1,14 @@ +administer domain aliases: + title: 'Administer all domain aliases' + restrict access: true +create domain aliases: + title: 'Create domain aliases for assigned domains' + restrict access: true +edit domain aliases: + title: 'Edit domain aliases for assigned domains' + restrict access: true +delete domain aliases: + title: 'Delete domain aliases for assigned domains' + restrict access: true +view domain aliases: + title: 'View aliases for assigned domains' diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.routing.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.routing.yml new file mode 100644 index 000000000..8ac59e418 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.routing.yml @@ -0,0 +1,34 @@ +domain_alias.admin: + path: '/admin/config/domain/alias/{domain}' + defaults: + _controller: '\Drupal\domain_alias\Controller\DomainAliasController::listing' + _title: 'Domain aliases' + _title_context: 'With modules' + requirements: + _entity_access: domain.update + _permission: 'view domain aliases' + +entity.domain_alias.edit_form: + path: '/admin/config/domain/alias/edit/{domain_alias}' + defaults: + _entity_form: domain_alias.edit + _title: 'Edit domain alias' + requirements: + _entity_access: domain_alias.update + +entity.domain_alias.delete_form: + path: '/admin/config/domain/alias/delete/{domain_alias}' + defaults: + _entity_form: domain_alias.delete + _title: 'Delete domain alias' + requirements: + _entity_access: domain_alias.delete + +domain_alias.add: + path: '/admin/config/domain/alias/{domain}/add' + defaults: + _controller: '\Drupal\domain_alias\Controller\DomainAliasController::addAlias' + _title: 'Add domain alias' + requirements: + _entity_access: domain.update + _permission: 'create domain aliases' diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.services.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.services.yml new file mode 100644 index 000000000..104563a94 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.services.yml @@ -0,0 +1,8 @@ +services: + domain_alias.validator: + class: Drupal\domain_alias\DomainAliasValidator + arguments: ['@config.factory', '@entity_type.manager'] + # @deprecated and will be removed for 8.x-1.0 release. + domain_alias.loader: + class: Drupal\domain_alias\DomainAliasLoader + arguments: ['@config.typed'] diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/Controller/DomainAliasController.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/Controller/DomainAliasController.php new file mode 100644 index 000000000..93d1693a1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/Controller/DomainAliasController.php @@ -0,0 +1,48 @@ +id(); + + // Create the stub alias with reference to the parent domain. + $alias = $this->entityTypeManager()->getStorage('domain_alias')->create($values); + + return $this->entityFormBuilder()->getForm($alias); + } + + /** + * Provides the listing page for aliases. + * + * @param \Drupal\domain\DomainInterface $domain + * An domain record entity. + * + * @return array + * A render array as expected by drupal_render(). + */ + public function listing(DomainInterface $domain) { + $list = $this->entityTypeManager()->getListBuilder('domain_alias'); + $list->setDomain($domain); + return $list->render(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasAccessControlHandler.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasAccessControlHandler.php new file mode 100644 index 000000000..512b3ec2e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasAccessControlHandler.php @@ -0,0 +1,45 @@ +prepareUser($account); + // Check the global permission. + if ($account->hasPermission('administer domain aliases')) { + return AccessResult::allowed(); + } + // For other actions we allow admin if they can administer the parent domains. + $domain = $entity->getDomain(); + // If this account can administer the domain, allow access to actions based on + // permission. + if ($is_admin = $this->isDomainAdmin($domain, $account)) { + if ($operation == 'view' && $account->hasPermission('view domain aliases')) { + return AccessResult::allowed(); + } + if ($operation == 'create' && $account->hasPermission('create domain aliases')) { + return AccessResult::allowed(); + } + if ($operation == 'update' && $account->hasPermission('edit domain aliases')) { + return AccessResult::allowed(); + } + if ($operation == 'delete' && $account->hasPermission('delete domain aliases')) { + return AccessResult::allowed(); + } + } + return AccessResult::forbidden(); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasForm.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasForm.php new file mode 100644 index 000000000..b046ab998 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasForm.php @@ -0,0 +1,235 @@ +validator = $validator; + $this->config = $config; + $this->entityTypeManager = $entity_type_manager; + $this->aliasStorage = $alias_storage; + $this->domainStorage = $domain_storage; + // Not loaded directly since it is not an interface. + $this->accessHandler = $this->entityTypeManager->getAccessControlHandler('domain'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('domain_alias.validator'), + $container->get('config.factory'), + $container->get('entity_type.manager'), + $container->get('entity_type.manager')->getStorage('domain_alias'), + $container->get('entity_type.manager')->getStorage('domain') + ); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + /** @var \Drupal\domain_alias\DomainAliasInterface $alias */ + $alias = $this->entity; + + $form['domain_id'] = array( + '#type' => 'value', + '#value' => $alias->getDomainId(), + ); + $form['pattern'] = array( + '#type' => 'textfield', + '#title' => $this->t('Pattern'), + '#size' => 40, + '#maxlength' => 80, + '#default_value' => $alias->getPattern(), + '#description' => $this->t('The matching pattern for this alias.'), + '#required' => TRUE, + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $alias->id(), + '#machine_name' => array( + 'source' => array('pattern'), + 'exists' => '\Drupal\domain_alias\Entity\DomainAlias::load', + ), + ); + $form['redirect'] = array( + '#type' => 'select', + '#options' => $this->redirectOptions(), + '#default_value' => $alias->getRedirect(), + '#description' => $this->t('Set an optional redirect directive when this alias is invoked.'), + ); + $environments = $this->environmentOptions(); + $form['environment'] = array( + '#type' => 'select', + '#options' => $environments, + '#default_value' => $alias->getEnvironment(), + '#description' => $this->t('Map the alias to a development environment.'), + ); + $form['environment_help'] = [ + '#type' => 'details', + '#open' => FALSE, + '#collapsed' => TRUE, + '#title' => $this->t('Environment list'), + '#description' => $this->t('The table below shows the registered aliases for each environment.'), + ]; + + $domains = $this->domainStorage->loadMultipleSorted(); + $rows = []; + foreach ($domains as $domain) { + // If the user cannot edit the domain, then don't show in the list. + $access = $this->accessHandler->checkAccess($domain, 'update'); + if ($access->isForbidden()) { + continue; + } + $row = []; + $row[] = $domain->label(); + foreach ($environments as $environment) { + $match_output = []; + if ($environment == 'default') { + $match_output[] = $domain->getCanonical(); + } + $matches = $this->aliasStorage->loadByEnvironmentMatch($domain, $environment); + foreach ($matches as $match) { + $match_output[] = $match->getPattern(); + } + $output = [ + '#items' => $match_output, + '#theme' => 'item_list', + ]; + $row[] = \Drupal::service('renderer')->render($output); + } + $rows[] = $row; + } + + $form['environment_help']['table'] = [ + '#type' => 'table', + '#header' => array_merge([$this->t('Domain')], $environments), + '#rows' => $rows, + ]; + + return parent::form($form, $form_state); + } + + /** + * Returns a list of valid redirect options for the form. + * + * @return array + * A list of valid redirect options. + */ + public function redirectOptions() { + return array( + 0 => $this->t('Do not redirect'), + 301 => $this->t('301 redirect: Moved Permanently'), + 302 => $this->t('302 redirect: Found'), + ); + } + + /** + * Returns a list of valid environement options for the form. + * + * @return array + * A list of valid environment options. + */ + public function environmentOptions() { + $list = $this->config->get('domain_alias.settings')->get('environments'); + foreach ($list as $item) { + $environments[$item] = $item; + } + return $environments; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $errors = $this->validator->validate($this->entity); + if (!empty($errors)) { + $form_state->setErrorByName('pattern', $errors); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\domain_alias\DomainAliasInterface $alias */ + $alias = $this->entity; + $edit_link = $alias->toLink($this->t('Edit'), 'edit-form')->toString(); + if ($alias->save() == SAVED_NEW) { + drupal_set_message($this->t('Created new domain alias.')); + $this->logger('domain_alias')->notice('Created new domain alias %name.', array('%name' => $alias->label(), 'link' => $edit_link)); + } + else { + drupal_set_message($this->t('Updated domain alias.')); + $this->logger('domain_alias')->notice('Updated domain alias %name.', array('%name' => $alias->label(), 'link' => $edit_link)); + } + $form_state->setRedirect('domain_alias.admin', array('domain' => $alias->getDomainId())); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasInterface.php new file mode 100644 index 000000000..b4f341ab8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasInterface.php @@ -0,0 +1,44 @@ +t('Pattern'); + $header['redirect'] = $this->t('Redirect'); + $header['environment'] = $this->t('Environment'); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + $row = array(); + + $row['label'] = $entity->label(); + $redirect = $entity->getRedirect(); + $row['redirect'] = empty($redirect) ? $this->t('None') : $redirect; + $row['environment'] = $entity->getEnvironment(); + $row += parent::buildRow($entity); + + return $row; + } + + /** + * {@inheritdoc} + */ + public function render() { + $build = array( + '#theme' => 'table', + '#header' => $this->buildHeader(), + '#rows' => array(), + '#empty' => $this->t('No aliases have been created for this domain.'), + ); + foreach ($this->load() as $entity) { + if ($row = $this->buildRow($entity)) { + $build['#rows'][$entity->id()] = $row; + } + } + return $build; + } + + /** + * Loads entity IDs using a pager sorted by the entity id. + * + * @return array + * An array of entity IDs. + */ + protected function getEntityIds() { + $query = $this->getStorage()->getQuery() + ->condition('domain_id', $this->getDomainId()) + ->sort($this->entityType->getKey('id')); + + // Only add the pager if a limit is specified. + if ($this->limit) { + $query->pager($this->limit); + } + return $query->execute(); + } + + /** + * Sets the domain context for this list. + * + * @param \Drupal\domain\DomainInterface $domain + * The domain to set as context for the list. + */ + public function setDomain(DomainInterface $domain) { + $this->domain = $domain; + } + + /** + * Gets the domain context for this list. + * + * @return \Drupal\domain\DomainInterface $domain + * The domain that is context for this list. + */ + public function getDomainId() { + // @TODO: check for a use-case where we might need to derive the id? + return !empty($this->domain) ? $this->domain->id() : NULL; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoader.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoader.php new file mode 100644 index 000000000..c9e3e0d78 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoader.php @@ -0,0 +1,258 @@ +typedConfig = $typed_config; + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain_alias.alias.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function load($id, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache(array($id)); + } + return $controller->load($id); + } + + /** + * {@inheritdoc} + */ + public function loadMultiple($ids = NULL, $reset = FALSE) { + $controller = $this->getStorage(); + if ($reset) { + $controller->resetCache($ids); + } + return $controller->loadMultiple($ids); + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $patterns = $this->getPatterns($hostname); + foreach ($patterns as $pattern) { + if ($alias = $this->loadByPattern($pattern)) { + return $alias; + } + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadByPattern($pattern) { + $result = $this->getStorage()->loadByProperties(['pattern' => $pattern]); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironment($environment) { + $result = $this->getStorage()->loadByProperties(['environment' => $environment]); + if (empty($result)) { + return NULL; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironmentMatch(DomainInterface $domain, $environment) { + $result = $this->getStorage()->loadByProperties(['domain_id' => $domain->id(), 'environment' => $environment]); + if (empty($result)) { + return []; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function sort($a, $b) { + // Fewer wildcards is a closer match. + // A longer string indicates a closer match. + if ((substr_count($a, '*') > substr_count($b, '*')) || (strlen($a) < strlen($b))) { + return 1; + } + return 0; + } + + /** + * Returns an array of eligible matching patterns. + * + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array + */ + public function getPatterns($hostname) { + $parts = explode('.', $hostname); + $count = count($parts); + // Account for ports. + if (substr_count($hostname, ':') > 0) { + $ports = explode(':', $parts[$count - 1]); + $parts[$count - 1] = preg_replace('/:(\d+)/', '', $parts[$count - 1]); + $parts[] = $ports[1]; + } + // Build the list of possible matching patterns. + $patterns = $this->buildPatterns($parts); + // Pattern lists are sorted based on the fewest wildcards. That gives us + // more precise matches first. + uasort($patterns, array($this, 'sort')); + array_unshift($patterns, $hostname); + + // Account for ports. + if (isset($ports)) { + $patterns = $this->buildPortPatterns($patterns, $hostname); + } + + // Return unique patters. + return array_unique($patterns); + } + + /** + * Builds a list of matching patterns. + * + * @param array $parts + * The hostname of the request, as an array split by dots. + * + * @return array $patterns + * An array of eligible matching patterns. + */ + private function buildPatterns(array $parts) { + $count = count($parts); + for ($i = 0; $i < $count; $i++) { + // Basic replacement of each value. + $temp = $parts; + $temp[$i] = '*'; + $patterns[] = implode('.', $temp); + // Advanced multi-value wildcards. + // Pattern *.* + if (count($temp) > 2 && $i < ($count - 1)) { + $temp[$i + 1] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern foo.bar.* + if ($count > 3 && $i < ($count - 2)) { + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*. + if ($count > 3 && $i < 2) { + $temp = $parts; + $temp[$i] = '*'; + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*.* + if ($count > 2) { + $temp = array_fill(0, $count, '*'); + $temp[$i] = $parts[$i]; + $patterns[] = implode('.', $temp); + } + } + return $patterns; + } + + /** + * Builds a list of matching patterns, including ports. + * + * @param array $patterns + * An array of eligible matching patterns. + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array $patterns + * An array of eligible matching patterns, modified by port. + */ + private function buildPortPatterns(array $patterns, $hostname) { + foreach ($patterns as $index => $pattern) { + // Make a pattern for port wildcards. + if (substr_count($pattern, ':') < 1) { + $new = explode('.', $pattern); + $port = (int) array_pop($new); + $allow = FALSE; + // Do not allow *.* or *:*. + foreach ($new as $item) { + if ($item != '*') { + $allow = TRUE; + } + } + if ($allow) { + // For port 80, allow bare hostname matches. + if ($port == 80) { + // Base hostname with port. + $patterns[] = str_replace(':' . $port, '', $hostname); + // Base hostname is allowed. + $patterns[] = implode('.', $new); + } + // Base hostname with wildcard port. + $patterns[] = str_replace(':' . $port, ':*', $hostname); + // Pattern with exact port. + $patterns[] = implode('.', $new) . ':' . $port; + // Pattern with wildcard port. + $patterns[] = implode('.', $new) . ':*'; + } + unset($patterns[$index]); + } + } + return $patterns; + } + + /** + * Loads the storage controller. + * + * We use the loader very early in the request cycle. As a result, if we try + * to inject the storage container, we hit a circular dependency. Using this + * method at least keeps our code easier to update. + */ + protected function getStorage() { + $storage = \Drupal::entityTypeManager()->getStorage('domain_alias'); + return $storage; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoaderInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoaderInterface.php new file mode 100644 index 000000000..42ebb2c6e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasLoaderInterface.php @@ -0,0 +1,104 @@ +configFactory = $config_factory; + $this->uuidService = $uuid_service; + $this->languageManager = $language_manager; + $this->typedConfig = $typed_config; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('config.factory'), + $container->get('uuid'), + $container->get('language_manager'), + $container->get('config.typed') + ); + } + + /** + * {@inheritdoc} + */ + public function loadSchema() { + $fields = $this->typedConfig->getDefinition('domain_alias.alias.*'); + return isset($fields['mapping']) ? $fields['mapping'] : array(); + } + + /** + * {@inheritdoc} + */ + public function loadByHostname($hostname) { + $patterns = $this->getPatterns($hostname); + foreach ($patterns as $pattern) { + if ($alias = $this->loadByPattern($pattern)) { + return $alias; + } + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function loadByPattern($pattern) { + $result = $this->loadByProperties(['pattern' => $pattern]); + if (empty($result)) { + return NULL; + } + return current($result); + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironment($environment) { + $result = $this->loadByProperties(['environment' => $environment]); + if (empty($result)) { + return NULL; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function loadByEnvironmentMatch(DomainInterface $domain, $environment) { + $result = $this->loadByProperties(['domain_id' => $domain->id(), 'environment' => $environment]); + if (empty($result)) { + return []; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function sort($a, $b) { + // Fewer wildcards is a closer match. + // A longer string indicates a closer match. + if ((substr_count($a, '*') > substr_count($b, '*')) || (strlen($a) < strlen($b))) { + return 1; + } + return 0; + } + + /** + * Returns an array of eligible matching patterns. + * + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array + */ + public function getPatterns($hostname) { + $parts = explode('.', $hostname); + $count = count($parts); + // Account for ports. + if (substr_count($hostname, ':') > 0) { + $ports = explode(':', $parts[$count - 1]); + $parts[$count - 1] = preg_replace('/:(\d+)/', '', $parts[$count - 1]); + $parts[] = $ports[1]; + } + // Build the list of possible matching patterns. + $patterns = $this->buildPatterns($parts); + // Pattern lists are sorted based on the fewest wildcards. That gives us + // more precise matches first. + uasort($patterns, array($this, 'sort')); + array_unshift($patterns, $hostname); + + // Account for ports. + if (isset($ports)) { + $patterns = $this->buildPortPatterns($patterns, $hostname); + } + + // Return unique patters. + return array_unique($patterns); + } + + /** + * Builds a list of matching patterns. + * + * @param array $parts + * The hostname of the request, as an array split by dots. + * + * @return array $patterns + * An array of eligible matching patterns. + */ + private function buildPatterns(array $parts) { + $count = count($parts); + for ($i = 0; $i < $count; $i++) { + // Basic replacement of each value. + $temp = $parts; + $temp[$i] = '*'; + $patterns[] = implode('.', $temp); + // Advanced multi-value wildcards. + // Pattern *.* + if (count($temp) > 2 && $i < ($count - 1)) { + $temp[$i + 1] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern foo.bar.* + if ($count > 3 && $i < ($count - 2)) { + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*. + if ($count > 3 && $i < 2) { + $temp = $parts; + $temp[$i] = '*'; + $temp[$i + 2] = '*'; + $patterns[] = implode('.', $temp); + } + // Pattern *.foo.*.* + if ($count > 2) { + $temp = array_fill(0, $count, '*'); + $temp[$i] = $parts[$i]; + $patterns[] = implode('.', $temp); + } + } + return $patterns; + } + + /** + * Builds a list of matching patterns, including ports. + * + * @param array $patterns + * An array of eligible matching patterns. + * @param string $hostname + * A hostname string, in the format example.com. + * + * @return array $patterns + * An array of eligible matching patterns, modified by port. + */ + private function buildPortPatterns(array $patterns, $hostname) { + foreach ($patterns as $index => $pattern) { + // Make a pattern for port wildcards. + if (substr_count($pattern, ':') < 1) { + $new = explode('.', $pattern); + $port = (int) array_pop($new); + $allow = FALSE; + // Do not allow *.* or *:*. + foreach ($new as $item) { + if ($item != '*') { + $allow = TRUE; + } + } + if ($allow) { + // For port 80, allow bare hostname matches. + if ($port == 80) { + // Base hostname with port. + $patterns[] = str_replace(':' . $port, '', $hostname); + // Base hostname is allowed. + $patterns[] = implode('.', $new); + } + // Base hostname with wildcard port. + $patterns[] = str_replace(':' . $port, ':*', $hostname); + // Pattern with exact port. + $patterns[] = implode('.', $new) . ':' . $port; + // Pattern with wildcard port. + $patterns[] = implode('.', $new) . ':*'; + } + unset($patterns[$index]); + } + } + return $patterns; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorageInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorageInterface.php new file mode 100644 index 000000000..8d6c69330 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasStorageInterface.php @@ -0,0 +1,76 @@ +configFactory = $config_factory; + $this->entityTypeManager = $entity_type_manager; + $this->aliasStorage = $this->entityTypeManager->getStorage('domain_alias'); + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + } + + /** + * Validates the rules for a domain alias. + * + * @param \Drupal\domain_alias\DomainAliasInterface $alias + * The Domain Alias to validate. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup | NULL + * A validation error message, if any. + */ + public function validate(DomainAliasInterface $alias) { + $pattern = $alias->getPattern(); + + // 1) Check for at least one dot or the use of 'localhost'. + // Note that localhost can specify a port. + $localhost_check = explode(':', $pattern); + if (substr_count($pattern, '.') == 0 && $localhost_check[0] != 'localhost') { + return $this->t('At least one dot (.) is required, except when using localhost.'); + } + + // 2) Check that the alias only has one wildcard. + $count = substr_count($pattern, '*') + substr_count($pattern, '?'); + if ($count > 1) { + return $this->t('You may only have one wildcard character in each alias.'); + } + // 3) Only one colon allowed, and it must be followed by an integer. + $count = substr_count($pattern, ':'); + if ($count > 1) { + return $this->t('You may only have one colon ":" character in each alias.'); + } + elseif ($count == 1) { + $int = substr($pattern, strpos($pattern, ':') + 1); + if (!is_numeric($int)) { + return $this->t('A colon may only be followed by an integer indicating the proper port.'); + } + } + // 4) Check that the alias doesn't contain any invalid characters. + // Check for valid characters, unless using non-ASCII domains. + $non_ascii = $this->configFactory->get('domain.settings')->get('allow_non_ascii'); + if (!$non_ascii) { + $check = preg_match('/^[a-z0-9\.\+\-\*\?:]*$/', $pattern); + if ($check == 0) { + return $this->t('The pattern contains invalid characters.'); + } + } + // 5) The alias cannot begin or end with a period. + if (substr($pattern, 0, 1) == '.') { + return $this->t('The pattern cannot begin with a dot.'); + } + if (substr($pattern, -1) == '.') { + return $this->t('The pattern cannot end with a dot.'); + } + + // 6) Check that the alias is not a direct match for a registered domain. + $check = preg_match('/[a-z0-9\.\+\-:]*$/', $pattern); + if ($check == 1 && $this->domainStorage->loadByHostname($pattern)) { + return $this->t('The pattern matches an existing domain record.'); + } + // 7) Check that the alias is unique across all records. + if ($alias_check = $this->aliasStorage->loadByPattern($pattern)) { + /** @var \Drupal\domain_alias\DomainAliasInterface $alias_check */ + if ($alias_check->id() != $alias->id()) { + return $this->t('The pattern already exists.'); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidatorInterface.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidatorInterface.php new file mode 100644 index 000000000..2fbfcd1f4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/DomainAliasValidatorInterface.php @@ -0,0 +1,21 @@ +pattern; + } + + /** + * {@inheritdoc} + */ + public function getEnvironment() { + return !empty($this->environment) ? $this->environment : 'default'; + } + + /** + * {@inheritdoc} + */ + public function getDomainId() { + return $this->domain_id; + } + + /** + * {@inheritdoc} + */ + public function getDomain() { + $storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $domains = $storage->loadByProperties(['domain_id' => $this->domain_id]); + return $domains ? current($domains) : NULL; + } + + /** + * {@inheritdoc} + */ + public function getRedirect() { + return $this->redirect; + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + // Invalidate cache tags relevant to domains. + \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/Form/DomainAliasDeleteForm.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/Form/DomainAliasDeleteForm.php new file mode 100644 index 000000000..cd8cc6220 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/Form/DomainAliasDeleteForm.php @@ -0,0 +1,22 @@ + $this->entity->getDomainId(), + ]); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/src/Tests/DomainAliasTestBase.php b/sites/all/modules/contrib/admin/domain/domain_alias/src/Tests/DomainAliasTestBase.php new file mode 100644 index 000000000..c957cc4d0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/src/Tests/DomainAliasTestBase.php @@ -0,0 +1,33 @@ +admin_user = $this->drupalCreateUser(array('administer domains', 'access administration pages')); + $this->drupalLogin($this->admin_user); + + // Create test domains. + $this->domainCreateTestDomains(3); + + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultiple(); + + // Save these for later testing. + $original_domains = $domains; + + $base = $this->base_hostname; + $hostnames = [$base, 'one.' . $base, 'two.'. $base]; + + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['*.' . $base, 'four.' . $base, 'five.' . $base]; + $i = 0; + foreach ($domains as $domain) { + $this->assert($domain->getHostname() == $hostnames[$i], 'Hostnames set correctly'); + $this->assert($domain->getCanonical() == $hostnames[$i], 'Canonical domains set correctly'); + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + $i++; + } + + $path = $domain->getScheme() . 'five.' . $base . '/admin/config/domain'; + + // Visit the domain overview administration page. + $this->drupalGet($path); + $this->assertResponse(200); + + // Test the domains. + $domains = $domain_storage->loadMultiple(); + $this->assertTrue(count($domains) == 3, 'Three domain records found.'); + + // Check the default domain. + $default = $domain_storage->loadDefaultId(); + $key = 'example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + // Test some text on the page. + foreach ($domains as $domain) { + $name = $domain->label(); + $this->assertText($name, 'Name found properly.'); + } + // Test the list of actions. + $actions = array('delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Check that all domains are active. + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Disable a domain and test the enable link. + $this->clickLink('Disable', 0); + $this->assertRaw('Inactive', 'Inactive domain found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($domain_storage->loadMultiple() as $domain) { + if ($domain->id() == 'one_example_com') { + $this->assertEmpty($domain->status(), 'One domain inactive.'); + } + else { + $this->assertNotEmpty($domain->status(), 'Other domains active.'); + } + } + + // Test the list of actions. + $actions = array('enable', 'delete', 'disable', 'default'); + foreach ($actions as $action) { + $this->assertRaw("/domain/{$action}/", 'Actions found properly.'); + } + // Re-enable the domain. + $this->clickLink('Enable', 0); + $this->assertNoRaw('Inactive', 'Inactive domain not found.'); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + foreach ($domain_storage->loadMultiple() as $domain) { + $this->assertNotEmpty($domain->status(), 'All domains active.'); + } + + // Set a new default domain. + $this->clickLink('Make default', 0); + + // Visit the domain overview administration page to clear cache. + $this->drupalGet($path); + $this->assertResponse(200); + + // Check the default domain. + $domain_storage->resetCache(); + $default = $domain_storage->loadDefaultId(); + $key = 'one_example_com'; + $this->assertTrue($default == $key, 'Default domain set correctly.'); + + // Did the hostnames change accidentally? + foreach ($domain_storage->loadMultiple() as $id => $domain) { + $this->assertTrue($domain->getHostname() == $original_domains[$id]->getHostname(), 'Hostnames match.'); + } + + } + +} + diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasEnvironmentTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasEnvironmentTest.php new file mode 100644 index 000000000..bf25f66f8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasEnvironmentTest.php @@ -0,0 +1,95 @@ +domainCreateTestDomains(3); + } + + /** + * Test for environment matching. + */ + public function testDomainAliasEnvironments() { + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultipleSorted(NULL, TRUE); + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['*.example.com', 'four.example.com', 'five.example.com']; + foreach ($domains as $domain) { + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + } + // Test the environment loader. + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 3, 'Three aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $match = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($match) == 1, 'One environment match loaded'); + $alias = current($match); + $this->assert($alias->getPattern() == 'five.example.com', 'Proper pattern match loaded.'); + + // Set one alias to a different environment + $alias->set('environment', 'testing')->save(); + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 2, 'Two aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $matches = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($matches) == 0, 'No environment matches loaded'); + + // Test the environment matcher. $domain here is one.example.com + $domain = $domain_storage->load('one_example_com'); + $matches = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($matches) == 1, 'One environment match loaded'); + $alias = current($matches); + $this->assert($alias->getPattern() == 'four.example.com', 'Proper pattern match loaded.'); + + // Now load a page and check things. + // Since we cannot read the service request, we place a block + // which shows links to all domains. + $this->drupalPlaceBlock('domain_switcher_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + // For a non-aliased request, the url list should be normal. + $this->drupalGet($domain->getPath()); + foreach ($domains as $domain) { + $this->assertSession()->assertEscaped($domain->getHostname()); + $this->assertSession()->linkByHrefExists($domain->getPath(), 0, 'Link found: ' . $domain->getPath()); + } + // For an aliased request (four.example.com), the list should be aliased. + $url = $domain->getScheme() . $alias->getPattern() . $domain->getPort(); + $this->drupalGet($url); + foreach ($matches as $match) { + $this->assertSession()->assertEscaped($match->getPattern()); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListBuilderTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListBuilderTest.php new file mode 100644 index 000000000..c75929296 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListBuilderTest.php @@ -0,0 +1,103 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainListBuilder() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'administer domain aliases', + 'view domain aliases', + )); + $this->drupalLogin($admin); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + foreach ($this->getDomains() as $domain) { + $href = 'admin/config/domain/alias/' . $domain->id(); + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + } + + // Now login as a user with limited rights. + $account = $this->drupalCreateUser(array( + 'create article content', + 'edit any article content', + 'edit assigned domains', + 'view domain list', + 'view domain aliases', + 'edit domain aliases', + )); + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $account->id(), $ids, DOMAIN_ADMIN_FIELD); + $user_storage = \Drupal::entityTypeManager()->getStorage('user'); + $user = $user_storage->load($account->id()); + $manager = \Drupal::service('domain.element_manager'); + $values = $manager->getFieldValues($user, DOMAIN_ADMIN_FIELD); + $this->assert(count($values) == 2, 'User saved with two domain records.'); + + $this->drupalLogin($account); + + $this->drupalGet('admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Check that links are printed. + $path = 'admin/config/domain/'; + $this->drupalGet($path); + foreach ($this->getDomains() as $domain) { + $href = 'admin/config/domain/alias/' . $domain->id(); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->linkByHrefExists($href, 0, 'Link found'); + } + else { + $this->assertSession()->linkByHrefNotExists($href, 'Link not found'); + } + } + + // Check access to the pages/routes. + foreach ($this->getDomains() as $domain) { + $path = 'admin/config/domain/alias/' . $domain->id(); + $this->drupalGet($path); + if (in_array($domain->id(), $ids)) { + $this->assertSession()->statusCodeEquals(200); + } + else { + $this->assertSession()->statusCodeEquals(403); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListHostnameTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListHostnameTest.php new file mode 100644 index 000000000..e1582ff54 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasListHostnameTest.php @@ -0,0 +1,85 @@ +domainCreateTestDomains(3); + } + + /** + * Test for environment matching. + */ + public function testDomainAliasEnvironments() { + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultiple(); + + $base = $this->base_hostname; + $hostnames = [$base, 'one.' . $base, 'two.'. $base]; + + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['*.' . $base, 'four.' . $base, 'five.' . $base]; + $i = 0; + foreach ($domains as $domain) { + $this->assert($domain->getHostname() == $hostnames[$i], 'Hostnames set correctly'); + $this->assert($domain->getCanonical() == $hostnames[$i], 'Canonical domains set correctly'); + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + $i++; + } + // Test the environment loader. + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 3, 'Three aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $match = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($match) == 1, 'One environment match loaded'); + $alias = current($match); + $this->assert($alias->getPattern() == 'five.' . $base, 'Proper pattern match loaded.'); + + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($admin); + + // Load an aliased domain. + $this->drupalGet($domain->getScheme() . 'five.' . $base . '/admin/config/domain'); + $this->assertSession()->statusCodeEquals(200); + + // Save the form. + $this->pressButton('edit-submit'); + // Ensure the values haven't changed. + $i = 0; + $domains = $domain_storage->loadMultiple(); + foreach ($domains as $domain) { + $this->assert($domain->getHostname() == $hostnames[$i], 'Hostnames set correctly'); + $this->assert($domain->getCanonical() == $hostnames[$i], 'Canonical domains set correctly'); + $i++; + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasNegotiatorTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasNegotiatorTest.php new file mode 100644 index 000000000..1b3e924c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasNegotiatorTest.php @@ -0,0 +1,111 @@ +domainTableIsEmpty(); + + // Create two new domains programmatically. + $this->domainCreateTestDomains(2); + + // Since we cannot read the service request, we place a block + // which shows the current domain information. + $this->drupalPlaceBlock('domain_server_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + + // Set the storage handles. + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + + // Set known prefixes that work with our tests. This will give us domains + // 'example.com' and 'one.example.com' aliased to 'two.example.com' and + // 'three.example.com'. + $prefixes = ['two', 'three']; + // Test the response of each home page. + /** @var \Drupal\domain\Entity\Domain $domain */ + foreach ($domain_storage->loadMultiple() as $domain) { + $alias_domains[] = $domain; + $this->drupalGet($domain->getPath()); + $this->assertRaw($domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('Exact match', 'Direct domain match.'); + } + + // Now, test an alias for each domain. + foreach ($alias_domains as $index => $alias_domain) { + $prefix = $prefixes[$index]; + // Set a known pattern. + $pattern = $prefix . '.' . $this->base_hostname; + $this->domainAliasCreateTestAlias($alias_domain, $pattern); + $alias = $alias_storage->loadByPattern($pattern); + // Set the URL for the request. Note that this is not saved, it is just + // URL generation. + $alias_domain->set('hostname', $pattern); + $alias_domain->setPath(); + $url = $alias_domain->getPath(); + $this->drupalGet($url); + $this->assertRaw($alias_domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('ALIAS:', 'No direct domain match.'); + $this->assertRaw($alias->getPattern(), 'Alias match.'); + + // Test redirections. + // @TODO: This could be much more elegant: the redirects break assertRaw() + $alias->set('redirect', 301); + $alias->save(); + $this->drupalGet($url); + $alias->set('redirect', 302); + $alias->save(); + $this->drupalGet($url); + } + // Test a wildcard alias. + // @TODO: Refactor this test to merge with the above. + $alias_domain = $domain_storage->loadDefaultDomain(); + $pattern = '*.' . $this->base_hostname; + $this->domainAliasCreateTestAlias($alias_domain, $pattern); + $alias = $alias_storage->loadByPattern($pattern); + // Set the URL for the request. Note that this is not saved, it is just + // URL generation. + $alias_domain->set('hostname', 'four.' . $this->base_hostname); + $alias_domain->setPath(); + $url = $alias_domain->getPath(); + $this->drupalGet($url); + $this->assertRaw($alias_domain->label(), 'Loaded the proper domain.'); + $this->assertRaw('ALIAS:', 'No direct domain match.'); + $this->assertRaw($alias->getPattern(), 'Alias match.'); + + // Test redirections. + // @TODO: This could be much more elegant: the redirects break assertRaw() + $alias->set('redirect', 301); + $alias->save(); + $this->drupalGet($url); + $alias->set('redirect', 302); + $alias->save(); + $this->drupalGet($url); + + // Revoke the permission change. + user_role_revoke_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasSortTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasSortTest.php new file mode 100644 index 000000000..f517fdd73 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasSortTest.php @@ -0,0 +1,86 @@ +sortList(); + + $storage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + foreach ($list as $key => $values) { + $patterns = $storage->getPatterns($key); + $this->assertTrue(empty(array_diff($values, $patterns)), 'Pattern matched as expected for ' . $key); + } + + } + + /** + * An array of expected matches to specific domains. + */ + private function sortList() { + return array( + 'example.com' => [ + 'example.com', + 'example.*', + '*.com', + ], + 'one.example.com' => [ + 'one.example.com', + 'one.example.*', + '*.example.com', + 'one.*.com', + '*.example.*', + '*.*.com', + 'one.*.*', + ], + 'one.two.example.com' => [ + 'one.two.example.com', + '*.two.example.com', + 'one.two.example.*', + 'one.*.example.com', + '*.*.example.com', + 'one.two.*.com', + 'one.*.example.*', + 'one.*.*.com', + '*.two.*.com', + 'one.two.*.*', + '*.*.example.com', + '*.*.*.com', + 'one.*.*.*', + '*.two.*.*', + ], + 'example.com:80' => [ + 'example.com:80', + 'example.com', + 'example.*', + 'example.com:*', + 'example.*:80', + 'example.*:*', + '*.com', + '*.com:80', + '*.com:*', + ], + 'example.com:8080' => [ + 'example.com:8080', + 'example.com:*', + 'example.*:8080', + 'example.*:*', + '*.com:8080', + '*.com:*', + ], + ); + } + +} + diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasTestBase.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasTestBase.php new file mode 100644 index 000000000..2057cc88e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasTestBase.php @@ -0,0 +1,29 @@ +domainTableIsEmpty(); + $validator = \Drupal::service('domain_alias.validator'); + + // Create a domain. + $this->domainCreateTestDomains(1, 'foo.com'); + // Check the created domain based on it's known id value. + $key = 'foo.com'; + /** @var \Drupal\domain\Entity\Domain $domain */ + $domain = \Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($key); + $this->assertTrue(!empty($domain), 'Test domain created.'); + + // Valid patterns to test. Valid is the boolean value. + $patterns = [ + 'localhost' => 1, + 'example.com' => 1, + 'www.example.com' => 1, // see www-prefix test, below. + '*.example.com' => 1, + 'one.example.com' => 1, + 'example.com:8080' => 1, + 'foobar' => 0, // must have a dot or be localhost + '*.*.example.com' => 0, // only one wildcard. + 'example.com::8080' => 0, // only one colon. + 'example.com:abc' => 0, // no letters after a colon. + '.example.com' => 0, // cannot begin with a dot. + 'example.com.' => 0, // cannot end with a dot. + 'EXAMPLE.com' => 0, // lowercase only. + 'éxample.com' => 0, // ascii-only. + 'foo.com' => 0, // duplicate. + ]; + foreach ($patterns as $pattern => $valid) { + $alias = $this->domainAliasCreateTestAlias($domain, $pattern, 0, 'default', FALSE); + $errors = $validator->validate($alias); + if ($valid) { + $this->assertTrue(empty($errors), 'Validation test success.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation test success.'); + } + } + // Test the configurable option. + $config = $this->config('domain.settings'); + $config->set('allow_non_ascii', true)->save(); + // Valid hostnames to test. Valid is the boolean value. + $patterns = [ + 'éxample.com' => 1, // ascii-only allowed. + ]; + foreach ($patterns as $pattern => $valid) { + $alias = $this->domainAliasCreateTestAlias($domain, $pattern, 0, 'default', FALSE); + $errors = $validator->validate($alias); + if ($valid) { + $this->assertTrue(empty($errors), 'Validation test success.'); + } + else { + $this->assertTrue(!empty($errors), 'Validation test success.'); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasWildcardTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasWildcardTest.php new file mode 100644 index 000000000..12dc5e88b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Functional/DomainAliasWildcardTest.php @@ -0,0 +1,87 @@ +domainCreateTestDomains(3); + } + + /** + * Test for environment matching. + */ + public function testDomainAliasWildcards() { + $domain_storage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $alias_loader = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + $domains = $domain_storage->loadMultipleSorted(NULL, TRUE); + // Our patterns should map to example.com, one.example.com, two.example.com. + $patterns = ['example.*', 'four.example.*', 'five.example.*']; + foreach ($domains as $domain) { + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => array_shift($patterns), + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + } + // Test the environment loader. + $local = $alias_loader->loadByEnvironment('local'); + $this->assert(count($local) == 3, 'Three aliases set to local'); + // Test the environment matcher. $domain here is two.example.com + $match = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($match) == 1, 'One environment match loaded'); + $alias = current($match); + $this->assert($alias->getPattern() == 'five.example.*', 'Proper pattern match loaded.'); + + // Test the environment matcher. $domain here is one.example.com + $domain = $domain_storage->load('example_com'); + $matches = $alias_loader->loadByEnvironmentMatch($domain, 'local'); + $this->assert(count($matches) == 1, 'One environment match loaded'); + $alias = current($matches); + $this->assert($alias->getPattern() == 'example.*', 'Proper pattern match loaded.'); + + // Now load a page and check things. + // Since we cannot read the service request, we place a block + // which shows links to all domains. + $this->drupalPlaceBlock('domain_switcher_block'); + + // To get around block access, let the anon user view the block. + user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, array('administer domains')); + // For a non-aliased request, the url list should be normal. + $this->drupalGet($domain->getPath()); + foreach ($domains as $domain) { + $this->assertSession()->assertEscaped($domain->getHostname()); + $this->assertSession()->linkByHrefExists($domain->getPath(), 0, 'Link found: ' . $domain->getPath()); + } + // For an aliased request (four.example.com), the list should be aliased. + $url = $domain->getScheme() . str_replace('*', 'com', $alias->getPattern()) . $domain->getPort(); + $this->drupalGet($url); + foreach ($matches as $match) { + $this->assertSession()->assertEscaped(str_replace('*', 'com', $match->getPattern())); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Kernel/DomainAliasDomainDeleteTest.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Kernel/DomainAliasDomainDeleteTest.php new file mode 100644 index 000000000..1c68f76d6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Kernel/DomainAliasDomainDeleteTest.php @@ -0,0 +1,83 @@ +domainCreateTestDomains(2); + + // Get the services. + $this->domainStorage = \Drupal::service('entity_type.manager')->getStorage('domain'); + $this->aliasStorage = \Drupal::service('entity_type.manager')->getStorage('domain_alias'); + } + + /** + * Tests alias deletion on domain deletion. + */ + public function testDomainDelete() { + $domains = $this->domainStorage->loadMultiple(); + $patterns = [ + 'example_com' => '*.example.com', + 'one_example_com' => 'foo.example.com' + ]; + + // Create an alias. + foreach ($domains as $id => $domain) { + $values = [ + 'domain_id' => $domain->id(), + 'pattern' => $patterns[$id], + 'redirect' => 0, + 'environment' => 'local', + ]; + $this->createDomainAlias($values); + $alias = $this->aliasStorage->loadByPattern($patterns[$id]); + $this->assertTrue(!empty($alias), 'Alias saved properly'); + } + + // Delete one domain. + $domain->delete(); + $alias = $this->aliasStorage->loadByPattern($patterns[$id]); + $this->assertTrue(empty($alias), 'Alias deleted properly'); + + // Check the remaining domain, which should still have an alias. + $domain = $this->domainStorage->load('example_com'); + $alias = $this->aliasStorage->loadByPattern($patterns[$domain->id()]); + $this->assertTrue(!empty($alias), 'Alias retained properly'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Traits/DomainAliasTestTrait.php b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Traits/DomainAliasTestTrait.php new file mode 100644 index 000000000..0631a04bd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alias/tests/src/Traits/DomainAliasTestTrait.php @@ -0,0 +1,67 @@ +getStorage('domain_alias')->create($values); + if ($save) { + $alias->save(); + } + + return $alias; + } + + /** + * Creates an alias for testing without passing values. + * + * @param \Drupal\domain\DomainInterface $domain + * A domain entity. + * @param string $pattern + * An optional alias pattern. + * @param int $redirect + * An optional redirect (301 or 302). + * @param int $environment + * An optional environment string. + * @param boolean $save + * Whether to save the alias or return for validation. + * + * @return \Drupal\domain_alias\Entity\DomainAlias + * A domain alias entity. + */ + public function domainAliasCreateTestAlias(DomainInterface $domain, $pattern = NULL, $redirect = 0, $environment = 'default', $save = TRUE) { + if (empty($pattern)) { + $pattern = '*.' . $domain->getHostname(); + } + $values = array( + 'domain_id' => $domain->id(), + 'pattern' => $pattern, + 'redirect' => $redirect, + 'environment' => $environment, + ); + + return $this->createDomainAlias($values, $save); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml new file mode 100644 index 000000000..201f1540a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml @@ -0,0 +1,14 @@ +name: Domain Alpha Updates +description: 'Provides per-release updates for critical changes.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.install b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.install new file mode 100644 index 000000000..e51238168 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.install @@ -0,0 +1,92 @@ +getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + /** @var $domain \Drupal\domain\Entity\Domain */ + // Existing id. + $id = $domain->getDomainId(); + // New id. + $domain->createDomainId(); + $new_id = $domain->getDomainId(); + // Check to see if this update is needed. + if ($id != $new_id) { + $domain->save(); + $rebuild = TRUE; + } + } + if ($rebuild) { + // Trigger permissions rebuild action. + node_access_needs_rebuild(TRUE); + } +} + +/** + * Provide a new update for 8001, for users who never ran 8001. + * + * See https://github.com/agentrickard/domain/issues/310. + */ +function domain_alpha_update_8002(&$sandbox) { + domain_alpha_update_8001($sandbox); +} + +/** + * Set the Domain Admin field to use the proper plugin. + */ +function domain_alpha_update_8003(&$sandbox) { + $id = 'user.user.field_domain_admin'; + $storage = \Drupal::entityTypeManager()->getStorage('field_config'); + if ($field = $storage->load($id)) { + $new_field = $field->toArray(); + if ($new_field['settings']['handler'] != 'domain:domain') { + $new_field['settings']['handler'] = 'domain:domain'; + $field_config = $storage->create($new_field); + $field_config->original = $field; + $field_config->enforceIsNew(FALSE); + $field_config->save(); + } + } +} + +/** + * Make all affiliates default value configurable. + */ +function domain_alpha_update_8004() { + $config = \Drupal::configFactory(); + $field_name = '.' . DOMAIN_ACCESS_ALL_FIELD; + // Iterate over all node & user fields. + foreach (['node', 'user'] as $type) { + foreach ($config->listAll('field.field.' . $type . '.') as $field_id) { + if (substr($field_id, -strlen($field_name)) === $field_name) { + $field = $config->getEditable($field_id); + if ($field->get('default_value_callback') === 'Drupal\domain_access\DomainAccessManager::getDefaultAllValue') { + $field + ->clear('default_value_callback') + ->save(TRUE); + } + } + } + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/README.md b/sites/all/modules/contrib/admin/domain/domain_config/README.md new file mode 100644 index 000000000..5e9b48060 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/README.md @@ -0,0 +1,111 @@ +Domain Config +============= + +This module provides a per-domain configuration option so that, for each +domain, you can change configuration items of any type, such as settings like +the site name. + +By default, these overrides are also language-aware. An override may exist per +domain, or per-language per domain, based on the prefixes used in the config +file. + +Usage +===== + +Currently, there is no user interface for changing settings, but you can load +in new files that contain configuration per domain. + +The pattern for override files is +`domain.config.DOMAIN_MACHINE_NAME.LANGCODE.item.name`, with the backup for +language-insensitive files as `domain.config.DOMAIN_MACHINE_NAME.item.name`, +where `item.name` is the name of the configuration item being overridden. + +To override the site name, for instance, you begin by exporting the +corresponding configuration item at the Configuration Synchronization screen +for a single export: +`admin/config/development/configuration/single/export` + +In this case, the type of configuration to be exported is "Simple +configuration" and the particular item is named `system.site`. + +You should have a file like the following: + +```YAML +uuid: 536db351-7aec-407e-a746-70ba9cc9f190 +name: Example +mail: admin@example.com +slogan: '' +page: + 403: '' + 404: '' + front: /user +admin_compact_mode: false +weight_select_max: 100 +langcode: en +default_langcode: en +``` + +An override file should contain only the specific data that you want to override. +To override the name, the only line needed is the one beginning ```name:```. +The resulting override looks like this: + +```YAML +name: Three +``` + +To override both the name and the slogan: + +```YAML +name: Three +slogan: 'My site slogan' +``` + +*Warning: do not copy over the `uuid` value into the new file. Doing so may +cause the configuration import to fail.* + +We want this to apply when the domain `three.example.com` is active and English +is the active language. Therefore, this file would be named +`domain.config.three_example_com.en.system.site`. If we wanted this file to +apply whenever the domain `three.example.com` is active, we would leave off the +language prefix: `domain.config.three_example_com.system.site`. + +For further examples, see the files in +`\domain\domain_config\tests\modules\domain_config_test\config\install`. + +Import that file's contents at the Configuration Synchronization screen for a +single import: +`admin/config/development/configuration/single/import` + +Note that, unlike when you exported the item, here you always select "Simple +configuration" as the configuration type to import, independent of the type of +configuration you're overriding. + +settings.php overrides +---------------------- + +For environment-specific or sensitive overrides, use the settings.php method. +In the above case, add +`$conf['domain.config.three_example_com.en.system.site']['name'] = "My special site";` +to your local settings.php file. This will ensure that the `three.example.com` +domain gets the correct value regardless of other module overrides. + +Read more about the "Configuration override system" +at https://www.drupal.org/node/1928898. + +Installation +============ + +If some variable changes are not picked up when the page renders, you may need +add domain-sensitivity to the site's cache. + +To do so, clone `default.services.yml` to `services.yml` and change the +`required_cache_contexts` value to: + +```YAML + required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions', 'url.site'] +``` + +Dependencies +============ + +- Domain diff --git a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml new file mode 100644 index 000000000..8271c474c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml @@ -0,0 +1,14 @@ +name: Domain Configuration +description: 'Allows domain specific configuration.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.module b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.module new file mode 100644 index 000000000..bb26c0250 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.module @@ -0,0 +1,6 @@ +storage = $storage; + } + + /** + * {@inheritdoc} + */ + public function loadOverrides($names) { + $overrides = array(); + // loadOverrides() runs on config entities, which means that if we try + // to run this routine on our own data, then we end up in an infinite loop. + // So ensure that we are _not_ looking up a domain.record.*. + $check = current($names); + $list = explode('.', $check); + if (isset($list[0]) && isset($list[1]) && $list[0] == 'domain' && $list[1] == 'record') { + return $overrides; + } + if (empty($this->contextSet)) { + $this->initiateContext(); + } + if (!empty($this->domain)) { + foreach ($names as $name) { + $config_name = $this->getDomainConfigName($name, $this->domain); + // Check to see if the config storage has an appropriately named file + // containing override data. + if ($override = $this->storage->read($config_name['langcode'])) { + $overrides[$name] = $override; + } + // Check to see if we have a file without a specific language. + elseif ($override = $this->storage->read($config_name['domain'])) { + $overrides[$name] = $override; + } + + // Apply any settings.php overrides. + if (isset($GLOBALS['config'][$config_name['langcode']])) { + $overrides[$name] = $GLOBALS['config'][$config_name['langcode']]; + } + elseif (isset($GLOBALS['config'][$config_name['domain']])) { + $overrides[$name] = $GLOBALS['config'][$config_name['domain']]; + } + } + } + return $overrides; + } + + /** + * Get configuration name for this hostname. + * + * It will be the same name with a prefix depending on domain and language: + * @code domain.config.DOMAIN_ID.LANGCODE @endcode + * + * @param string $name + * The name of the config object. + * @param \Drupal\domain\DomainInterface $domain + * The domain object. + * + * @return array + * The domain-language, and domain-specific config names. + */ + protected function getDomainConfigName($name, DomainInterface $domain) { + return [ + 'langcode' => 'domain.config.' . $domain->id() . '.' . $this->language->getId() . '.' . $name, + 'domain' => 'domain.config.' . $domain->id() . '.' . $name, + ]; + } + + /** + * {@inheritdoc} + */ + public function getCacheSuffix() { + $suffix = $this->domain ? $this->domain->id() : ''; + $suffix .= $this->language ? $this->language->getId() : ''; + return ($suffix) ? $suffix : NULL; + } + + /** + * {@inheritdoc} + */ + public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($name) { + if (empty($this->contextSet)) { + $this->initiateContext(); + } + $metadata = new CacheableMetadata(); + if (!empty($this->domain)) { + $metadata->addCacheContexts(['url.site', 'languages:language_interface']); + } + return $metadata; + } + + /** + * Sets domain and language contexts for the request. + * + * We wait to do this in order to avoid circular dependencies + * with the locale module. + */ + protected function initiateContext() { + // Prevent infinite lookups by caching the request. Since the _construct() + // is called for each lookup, this is more efficient. + $this->contextSet = TRUE; + + // Get the language context. Note that injecting the language manager + // into the service created a circular dependency error, so we load from + // the core service manager. + $this->languageManager = \Drupal::languageManager(); + $this->language = $this->languageManager->getCurrentLanguage(); + + // The same issue is true for the domainNegotiator. + $this->domainNegotiator = \Drupal::service('domain.negotiator'); + // Get the domain context. + $this->domain = $this->domainNegotiator->getActiveDomain(); + // If we have fired too early in the bootstrap, we must force the routine to run. + if (empty($this->domain)) { + $this->domain = $this->domainNegotiator->getActiveDomain(TRUE); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigServiceProvider.php b/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigServiceProvider.php new file mode 100644 index 000000000..a7841188e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigServiceProvider.php @@ -0,0 +1,38 @@ +getDefinition('router.route_provider'); + $definition->setClass(DomainRouteProvider::class); + + if ($container->hasParameter('renderer.config')) { + $renderer_config = $container->getParameter('renderer.config'); + + if (!in_array('url.site', $renderer_config['required_cache_contexts'])) { + $renderer_config['required_cache_contexts'][] = 'url.site'; + } + + $container->setParameter('renderer.config', $renderer_config); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/src/Routing/DomainRouteProvider.php b/sites/all/modules/contrib/admin/domain/domain_config/src/Routing/DomainRouteProvider.php new file mode 100644 index 000000000..e0c623a66 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/src/Routing/DomainRouteProvider.php @@ -0,0 +1,51 @@ +getHost() . ':' . $request->getPathInfo() . ':' . $request->getQueryString(); + if ($cached = $this->cache->get($cid)) { + $this->currentPath->setPath($cached->data['path'], $request); + $request->query->replace($cached->data['query']); + return $cached->data['routes']; + } + else { + // Just trim on the right side. + $path = $request->getPathInfo(); + $path = $path === '/' ? $path : rtrim($request->getPathInfo(), '/'); + $path = $this->pathProcessor->processInbound($path, $request); + $this->currentPath->setPath($path, $request); + // Incoming path processors may also set query parameters. + $query_parameters = $request->query->all(); + $routes = $this->getRoutesByPath(rtrim($path, '/')); + $cache_value = [ + 'path' => $path, + 'query' => $query_parameters, + 'routes' => $routes, + ]; + $this->cache->set($cid, $cache_value, CacheBackendInterface::CACHE_PERMANENT, ['route_match']); + return $routes; + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/src/Tests/DomainConfigTestBase.php b/sites/all/modules/contrib/admin/domain/domain_config/src/Tests/DomainConfigTestBase.php new file mode 100644 index 000000000..784662b88 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/src/Tests/DomainConfigTestBase.php @@ -0,0 +1,71 @@ + 'Spanish'); + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'language', 'domain_config_test', 'domain_config'); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + $this->drupalLogin($admin_user); + + // Add language. + $edit = array( + 'predefined_langcode' => 'es', + ); + $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); + + // Enable URL language detection and selection. + $edit = array('language_interface[enabled][language-url]' => '1'); + $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + + $this->drupalLogout(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + $es = \Drupal::entityTypeManager()->getStorage('configurable_language')->load('es'); + $this->assertTrue(!empty($es), 'Created test language.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/install/domain_config_middleware_test.settings.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/install/domain_config_middleware_test.settings.yml new file mode 100644 index 000000000..03c0bfdd1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/install/domain_config_middleware_test.settings.yml @@ -0,0 +1 @@ +test_setting: false diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/schema/domain_config_middleware_test.schema.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/schema/domain_config_middleware_test.schema.yml new file mode 100644 index 000000000..7fddd2369 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/config/schema/domain_config_middleware_test.schema.yml @@ -0,0 +1,8 @@ +# Schema for the configuration files of the Domain module. +domain_config_middleware_test.settings: + type: config_object + label: 'Domain config middleware settings' + mapping: + test_setting: + type: boolean + label: 'A test setting' diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml new file mode 100644 index 000000000..09be4495d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml @@ -0,0 +1,17 @@ +name: "Domain config middleware module tests" +description: "Support module for domain config middleware response testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: + - domain + - domain_config + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.services.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.services.yml new file mode 100644 index 000000000..ecaa375c2 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.services.yml @@ -0,0 +1,8 @@ +services: + domain_config_test.middleware: + class: Drupal\domain_config_middleware_test\Middleware + arguments: ['@config.factory'] + tags: + # Ensure to come before page caching, so you don't serve cached pages to + # banned users. + - { name: http_middleware, priority: 250 } diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/src/Middleware.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/src/Middleware.php new file mode 100644 index 000000000..08066aee1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/src/Middleware.php @@ -0,0 +1,50 @@ +httpKernel = $http_kernel; + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + // This line should break hooks in our code. See https://www.drupal.org/node/2896434. + $config = $this->configFactory->get('domain_config_middleware_test.settings'); + return $this->httpKernel->handle($request, $type, $catch); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.four_example_com.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.four_example_com.system.site.yml new file mode 100644 index 000000000..320f9b0fe --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.four_example_com.system.site.yml @@ -0,0 +1,3 @@ +name: 'Four' +page: + front: /node/2 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.one_example_com.en.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.one_example_com.en.system.site.yml new file mode 100644 index 000000000..479549188 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.one_example_com.en.system.site.yml @@ -0,0 +1,10 @@ +name: 'One' +mail: admin@example.com +slogan: '' +page: + 403: '' + 404: '' + front: /node/1 +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.three_example_com.en.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.three_example_com.en.system.site.yml new file mode 100644 index 000000000..4c7e58f3b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.three_example_com.en.system.site.yml @@ -0,0 +1,9 @@ +mail: admin@example.com +slogan: '' +page: + 403: '' + 404: '' + front: /node +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.en.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.en.system.site.yml new file mode 100644 index 000000000..03791fb87 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.en.system.site.yml @@ -0,0 +1,10 @@ +name: 'Two' +mail: admin@example.com +slogan: 'I have it' +page: + 403: '' + 404: '' + front: /node +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.es.system.site.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.es.system.site.yml new file mode 100644 index 000000000..33f2ed30c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/config/install/domain.config.two_example_com.es.system.site.yml @@ -0,0 +1,10 @@ +name: 'Dos' +mail: admin@example.com +slogan: 'Yo la tengo' +page: + 403: '' + 404: '' + front: /node +admin_compact_mode: false +weight_select_max: 100 +langcode: en diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml new file mode 100644 index 000000000..7438443d0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml @@ -0,0 +1,17 @@ +name: "Domain config module tests" +description: "Support module for domain config testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: + - domain + - domain_config + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.module b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.module new file mode 100644 index 000000000..a80ab50a7 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.module @@ -0,0 +1,25 @@ +addProperty('config_test', 'aye'); +} + +/** + * Implements hook_page_attachments_alter(). + */ +function domain_config_test_page_attachments_alter(array &$attachments) { + /** @var \Drupal\domain\DomainNegotiatorInterface $Negotiator */ + $negotiator = \Drupal::service('domain.negotiator'); + $domain = $negotiator->getActiveDomain(); + if (!empty($domain) && $domain->get('config_test') == 'aye') { + $attachments['#attached']['http_header'][] = [ + 'X-Domain-Config-Test-page-attachments-hook', + 'invoked' + ]; + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigAlterHookTest.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigAlterHookTest.php new file mode 100644 index 000000000..a3594a6db --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigAlterHookTest.php @@ -0,0 +1,63 @@ +domainCreateTestDomains(); + + // Get the services. + $this->negotiator = \Drupal::service('domain.negotiator'); + $this->moduleHandler = \Drupal::service('module_handler'); + } + + /** + * Tests domain request alteration. + */ + public function testHookDomainRequestAlter() { + // Check for the count of hook implementations. + $hooks = $this->moduleHandler->getImplementations('domain_request_alter'); + $this->assertCount(1, $hooks, 'One hook implementation found.'); + + // Assert that the hook is also called on a request with a HTTP Middleware + // that requests config thus triggering an early hook invocation (before + // modules are loaded by the kernel). + $this->drupalGet(''); + $this->assertEquals('invoked', $this->drupalGetHeader('X-Domain-Config-Test-page-attachments-hook')); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHomepageTest.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHomepageTest.php new file mode 100644 index 000000000..9f58f2a33 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHomepageTest.php @@ -0,0 +1,71 @@ +config('system.site'); + $site_config->set('page.front', '/node')->save(); + + // No domains should exist. + $this->domainTableIsEmpty(); + // Create four new domains programmatically. + $this->domainCreateTestDomains(5); + // Get the domain list. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $this->drupalCreateNode(array( + 'type' => 'article', + 'title' => 'Node 1', + 'promoted' => TRUE, + )); + $this->drupalCreateNode(array( + 'type' => 'article', + 'title' => 'Node 2', + 'promoted' => TRUE, + )); + $homepages = $this->getHomepages(); + foreach ($domains as $domain) { + $home = $this->drupalGet($domain->getPath()); + + // Check if this setting is picked up. + $expected = $domain->getPath() . $homepages[$domain->id()]; + $expected_home = $this->drupalGet($expected); + + $this->assertTrue($home == $expected_home, 'Proper home page loaded (' . $domain->id() . ').'); + } + } + + /** + * Returns the expected homepage paths for each domain. + */ + private function getHomepages() { + $homepages = array( + 'example_com' => 'node', + 'one_example_com' => 'node/1', + 'two_example_com' => 'node', + 'three_example_com' => 'node', + 'four_example_com' => 'node/2', + ); + return $homepages; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigOverriderTest.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigOverriderTest.php new file mode 100644 index 000000000..3537e11f8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigOverriderTest.php @@ -0,0 +1,111 @@ +domainTableIsEmpty(); + // Create five new domains programmatically. + $this->domainCreateTestDomains(5); + // Get the domain list. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + // Except for the default domain, the page title element should match what + // is in the override files. + // With a language context, based on how we have our files setup, we + // expect the following outcomes: + // - example.com name = 'Drupal' for English, 'Drupal' for Spanish. + // - one.example.com name = 'One' for English, 'Drupal' for Spanish. + // - two.example.com name = 'Two' for English, 'Dos' for Spanish. + // - three.example.com name = 'Drupal' for English, 'Drupal' for Spanish. + // - four.example.com name = 'Four' for English, 'Four' for Spanish. + foreach ($domains as $domain) { + // Test the login page, because our default homepages do not exist. + foreach ($this->langcodes as $langcode => $language) { + $path = $domain->getPath() . $langcode . '/user/login'; + $this->drupalGet($path); + if ($domain->isDefault()) { + $this->assertRaw('Log in | Drupal', 'Loaded the proper site name.'); + } + else { + $this->assertRaw('Log in | ' . $this->expectedName($domain, $langcode) . '', 'Loaded the proper site name.' . 'Log in | ' . $this->expectedName($domain, $langcode) . ''); + } + } + } + } + + /** + * Tests that domain-specific variable overrides in settings.php works. + */ + public function testDomainConfigOverriderFromSettings() { + // Set up overrides. + $settings = []; + $settings['config']['domain.config.one_example_com.en.system.site']['name'] = (object) [ + 'value' => 'First', + 'required' => TRUE, + ]; + $settings['config']['domain.config.four_example_com.system.site']['name'] = (object) [ + 'value' => 'Four overridden in settings', + 'required' => TRUE, + ]; + $this->writeSettings($settings); + + // Create five new domains programmatically. + $this->domainCreateTestDomains(5); + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(['one_example_com', 'four_example_com']); + + $domain_one = $domains['one_example_com']; + $this->drupalGet($domain_one->getPath() . 'user/login'); + $this->assertRaw('Log in | First', 'Found overridden slogan for one.example.com.'); + + $domain_four = $domains['four_example_com']; + $this->drupalGet($domain_four->getPath() . 'user/login'); + $this->assertRaw('Log in | Four overridden in settings', 'Found overridden slogan for four.example.com.'); + } + /** + * Returns the expected site name value from our test configuration. + * + * @param DomainInterface $domain + * The Domain object. + * @param $langcode + * A two-digit language code. + * + * @return string + * The expected name. + */ + private function expectedName(DomainInterface $domain, $langcode = NULL) { + $name = ''; + + switch ($domain->id()) { + case 'one_example_com': + $name = ($langcode == 'es') ? 'Drupal' : 'One'; + break; + + case 'two_example_com': + $name = ($langcode == 'es') ? 'Dos' : 'Two'; + break; + + case 'three_example_com': + $name = 'Drupal'; + break; + + case 'four_example_com': + $name = 'Four'; + break; + } + + return $name; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigTestBase.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigTestBase.php new file mode 100644 index 000000000..5c2f045df --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigTestBase.php @@ -0,0 +1,66 @@ + 'Spanish'); + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('domain', 'language', 'domain_config_test', 'domain_config'); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + $this->drupalLogin($admin_user); + + // Add language. + $edit = array( + 'predefined_langcode' => 'es', + ); + $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); + + // Enable URL language detection and selection. + $edit = array('language_interface[enabled][language-url]' => '1'); + $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + + $this->drupalLogout(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + $es = \Drupal::entityTypeManager()->getStorage('configurable_language')->load('es'); + $this->assertTrue(!empty($es), 'Created test language.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml new file mode 100644 index 000000000..4eff610de --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml @@ -0,0 +1,1301 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_domain_access + - field.storage.node.field_domain_all_affiliates + module: + - domain_access + - node + - user +id: affiliated_content +label: 'Affiliated content' +module: node +description: 'Find and manage content.' +tag: domain +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_options: + access: + type: domain_content_editor + options: { } + cache: + type: tag + query: + type: views_query + options: + disable_sql_rewrite: true + distinct: true + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 50 + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: true + caption: '' + summary: '' + description: '' + columns: + node_bulk_form: node_bulk_form + title: title + type: type + name: name + status: status + changed: changed + operations: operations + field_domain_access: field_domain_access + field_domain_all_affiliates: field_domain_all_affiliates + info: + node_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + title: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + operations: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + field_domain_access: + align: '' + separator: '' + empty_column: false + responsive: '' + field_domain_all_affiliates: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: true + row: + type: fields + fields: + node_bulk_form: + id: node_bulk_form + table: node + field: node_bulk_form + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + action_title: 'With selection' + include_exclude: exclude + selected_actions: { } + plugin_id: node_bulk_form + entity_type: node + title: + id: title + table: node_field_data + field: title + label: Title + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: title + type: string + settings: + link_to_entity: true + plugin_id: field + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + label: 'Content Type' + 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 + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: type + plugin_id: field + field_domain_access: + id: field_domain_access + table: node__field_domain_access + field: field_domain_access + relationship: none + group_type: group + admin_label: '' + label: Domain(s) + 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 + click_sort_column: target_id + type: entity_reference_label + settings: + link: 1 + group_column: target_id + group_columns: { } + group_rows: 1 + delta_limit: '0' + delta_offset: '0' + delta_reversed: 0 + delta_first_last: 0 + multi_type: separator + separator: ', ' + field_api_classes: 0 + plugin_id: domain_access_field + field_domain_all_affiliates: + id: field_domain_all_affiliates + table: node__field_domain_all_affiliates + field: field_domain_all_affiliates + relationship: none + group_type: group + admin_label: '' + label: 'All affiliates' + 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 + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + name: + id: name + table: users_field_data + field: name + relationship: uid + label: Author + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: field + type: user_name + entity_type: user + entity_field: name + status: + id: status + table: node_field_data + field: status + label: Status + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: boolean + settings: + format: custom + format_custom_true: Published + format_custom_false: Unpublished + plugin_id: field + entity_type: node + entity_field: status + changed: + id: changed + table: node_field_data + field: changed + label: Updated + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + plugin_id: field + entity_type: node + entity_field: changed + operations: + id: operations + table: node + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + 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 + destination: true + plugin_id: entity_operations + filters: + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: Type + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + field_domain_all_affiliates_value: + id: field_domain_all_affiliates_value + table: node__field_domain_all_affiliates + field: field_domain_all_affiliates_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + sorts: { } + title: 'Content assigned to All affiliates' + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + empty: true + content: 'No content available.' + plugin_id: text_custom + arguments: + field_domain_access_target_id: + id: field_domain_access_target_id + table: node__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + default_action: 'not found' + exception: + value: all + title_enable: false + title: All + title_enable: true + title: '{{ arguments.field_domain_access_target_id }} content' + 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: true + validate: + type: 'entity:domain' + fail: 'not found' + validate_options: + operation: view + multiple: 0 + access: false + bundles: null + glossary: false + limit: 0 + case: none + path_case: none + transform_dash: false + break_phrase: false + plugin_id: string + relationships: + uid: + id: uid + table: node_field_data + field: uid + admin_label: author + required: true + plugin_id: standard + show_admin_links: false + filter_groups: + operator: AND + groups: + 1: AND + display_extenders: { } + display_plugin: default + display_title: Master + id: default + position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + max-age: 0 + tags: + - 'config:field.storage.node.field_domain_access' + - 'config:field.storage.node.field_domain_all_affiliates' + page_1: + display_options: + path: admin/content/domain-content/% + menu: + type: none + title: Content + description: '' + expanded: false + parent: '' + weight: -10 + context: '0' + menu_name: admin + tab_options: + type: normal + title: Content + description: 'Find and manage content' + menu_name: admin + weight: -10 + display_extenders: { } + display_description: 'Default page for content per domain' + filters: + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: Type + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + defaults: + filters: false + filter_groups: false + title: false + filter_groups: + operator: AND + groups: + 1: AND + title: Content + display_plugin: page + display_title: 'Domain content page' + id: page_1 + position: 1 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + max-age: 0 + tags: + - 'config:field.storage.node.field_domain_access' + - 'config:field.storage.node.field_domain_all_affiliates' + page_2: + display_plugin: page + id: page_2 + display_title: 'All affiliates page' + position: 2 + display_options: + display_extenders: { } + display_description: 'Lists content assigned to all affiliates' + arguments: { } + defaults: + arguments: false + filters: false + filter_groups: false + path: admin/content/domain-content/all_affiliates + filters: + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: Type + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + field_domain_all_affiliates_value: + id: field_domain_all_affiliates_value + table: node__field_domain_all_affiliates + field: field_domain_all_affiliates_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + field_domain_access_target_id: + id: field_domain_access_target_id + table: node__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: field_domain_access_target_id_op + label: Domain + description: '' + use_operator: false + operator: field_domain_access_target_id_op + identifier: field_domain_access_target_id + required: false + remember: true + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: 0 + 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: domain_access_filter + filter_groups: + operator: AND + groups: + 1: AND + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + tags: + - 'config:field.storage.node.field_domain_access' + - 'config:field.storage.node.field_domain_all_affiliates' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml new file mode 100644 index 000000000..0dbc4c89c --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml @@ -0,0 +1,1454 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.user.field_domain_access + - field.storage.user.field_domain_all_affiliates + module: + - domain_access + - user +id: affiliated_editors +label: 'Affiliated editors' +module: user +description: 'Find and manage people interacting with your site.' +tag: domain +base_table: users_field_data +base_field: uid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: domain_content_admin + options: { } + cache: + type: tag + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: true + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: 0 + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + 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 + quantity: 9 + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + summary: '' + columns: + user_bulk_form: user_bulk_form + name: name + status: status + rid: rid + created: created + access: access + edit_node: edit_node + dropbutton: dropbutton + info: + user_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + rid: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + access: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + edit_node: + align: '' + separator: '' + empty_column: false + responsive: priority-low + dropbutton: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: created + empty_table: true + row: + type: fields + fields: + user_bulk_form: + id: user_bulk_form + table: users + field: user_bulk_form + relationship: none + group_type: group + admin_label: '' + label: 'Bulk update' + 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 + plugin_id: user_bulk_form + entity_type: user + name: + id: name + table: users_field_data + field: name + relationship: none + group_type: group + admin_label: '' + label: Username + 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 + plugin_id: field + type: user_name + entity_type: user + entity_field: name + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + label: Status + 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 + plugin_id: field + type: boolean + settings: + format: custom + format_custom_true: Active + format_custom_false: Blocked + entity_type: user + entity_field: status + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + label: Roles + 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 + type: ul + separator: ', ' + plugin_id: user_roles + field_domain_access: + id: field_domain_access + table: user__field_domain_access + field: field_domain_access + relationship: none + group_type: group + admin_label: '' + label: Domain(s) + 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 + click_sort_column: target_id + type: entity_reference_label + settings: + link: 0 + group_column: target_id + group_columns: { } + group_rows: 1 + delta_limit: '0' + delta_offset: '0' + delta_reversed: 0 + delta_first_last: 0 + multi_type: separator + separator: ', ' + field_api_classes: 0 + plugin_id: domain_access_field + field_domain_all_affiliates: + id: field_domain_all_affiliates + table: user__field_domain_all_affiliates + field: field_domain_all_affiliates + relationship: none + group_type: group + admin_label: '' + label: 'All affiliates' + 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 + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + label: 'Member for' + 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 + type: timestamp_ago + settings: + future_format: '@interval' + past_format: '@interval' + granularity: 2 + plugin_id: field + entity_type: user + entity_field: created + access: + id: access + table: users_field_data + field: access + relationship: none + group_type: group + admin_label: '' + label: 'Last access' + 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 + type: timestamp_ago + settings: + future_format: '@interval hence' + past_format: '@interval ago' + granularity: 2 + plugin_id: field + entity_type: user + entity_field: access + operations: + id: operations + table: users + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + 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 + destination: true + entity_type: user + plugin_id: entity_operations + mail: + id: mail + table: users_field_data + field: mail + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: basic_string + settings: { } + 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 + plugin_id: field + entity_type: user + entity_field: mail + filters: + combine: + id: combine + table: views + field: combine + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: combine_op + label: 'Name or email contains' + description: '' + use_operator: false + operator: combine_op + identifier: user + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + fields: + name: name + mail: mail + plugin_id: combine + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: roles_target_id_op + label: Role + description: '' + use_operator: false + operator: roles_target_id_op + identifier: role + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_roles + permission: + id: permission + table: user__roles + field: permission + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: permission_op + label: Permission + description: '' + use_operator: false + operator: permission_op + identifier: permission + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_permissions + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: true + group_info: + label: Status + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Active + operator: '=' + value: '1' + 2: + title: Blocked + operator: '=' + value: '0' + plugin_id: boolean + entity_type: user + entity_field: status + default_langcode: + id: default_langcode + table: users_field_data + field: default_langcode + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: user + entity_field: default_langcode + plugin_id: boolean + uid_raw: + id: uid_raw + table: users_field_data + field: uid_raw + relationship: none + group_type: group + admin_label: '' + operator: '!=' + value: + min: '' + max: '' + value: '0' + group: 1 + exposed: false + expose: + operator_id: '0' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + entity_type: user + sorts: + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + granularity: second + plugin_id: date + entity_type: user + entity_field: created + title: People + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'No people available.' + plugin_id: text_custom + use_more: false + use_more_always: false + use_more_text: more + display_comment: '' + use_ajax: false + hide_attachment_summary: false + show_admin_links: true + group_by: false + link_url: '' + link_display: page_1 + css_class: '' + filter_groups: + operator: AND + groups: + 1: AND + display_extenders: { } + arguments: + field_domain_access_target_id: + id: field_domain_access_target_id + table: user__field_domain_access + field: field_domain_access_target_id + plugin_id: string + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + max-age: 0 + tags: + - 'config:field.storage.user.field_domain_access' + - 'config:field.storage.user.field_domain_all_affiliates' + page_1: + display_plugin: page + id: page_1 + display_title: 'Domain content editors' + position: 1 + display_options: + path: admin/content/domain-editors/% + show_admin_links: false + menu: + type: none + title: List + description: 'Find and manage people interacting with your site.' + expanded: false + parent: '' + weight: -10 + context: '0' + menu_name: admin + tab_options: + type: normal + title: People + description: 'Manage user accounts, roles, and permissions.' + menu_name: admin + weight: 0 + defaults: + show_admin_links: false + arguments: false + display_extenders: { } + display_description: 'Default page for domain content editor lists.' + arguments: + field_domain_access_target_id: + id: field_domain_access_target_id + table: user__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + default_action: 'not found' + exception: + value: all + title_enable: false + title: All + title_enable: true + title: '{{ arguments.field_domain_access_target_id }} editors' + 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: true + validate: + type: 'entity:domain' + fail: 'not found' + validate_options: + operation: view + multiple: 0 + access: false + bundles: null + glossary: false + limit: 0 + case: none + path_case: none + transform_dash: false + break_phrase: false + plugin_id: string + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + max-age: 0 + tags: + - 'config:field.storage.user.field_domain_access' + - 'config:field.storage.user.field_domain_all_affiliates' + page_2: + display_plugin: page + id: page_2 + display_title: 'All affiliates editors' + position: 2 + display_options: + display_extenders: { } + path: admin/content/domain-editors/all_affiliates + display_description: 'Lists editors with the All affiliates permission.' + filters: + combine: + id: combine + table: views + field: combine + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: combine_op + label: 'Name or email contains' + description: '' + use_operator: false + operator: combine_op + identifier: user + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + fields: + name: name + mail: mail + plugin_id: combine + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: roles_target_id_op + label: Role + description: '' + use_operator: false + operator: roles_target_id_op + identifier: role + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_roles + permission: + id: permission + table: user__roles + field: permission + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: permission_op + label: Permission + description: '' + use_operator: false + operator: permission_op + identifier: permission + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + plugin_id: user_permissions + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: true + group_info: + label: Status + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Active + operator: '=' + value: '1' + 2: + title: Blocked + operator: '=' + value: '0' + plugin_id: boolean + entity_type: user + entity_field: status + default_langcode: + id: default_langcode + table: users_field_data + field: default_langcode + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: user + entity_field: default_langcode + plugin_id: boolean + uid_raw: + id: uid_raw + table: users_field_data + field: uid_raw + relationship: none + group_type: group + admin_label: '' + operator: '!=' + value: + min: '' + max: '' + value: '0' + group: 1 + exposed: false + expose: + operator_id: '0' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + entity_type: user + field_domain_access_target_id: + id: field_domain_access_target_id + table: user__field_domain_access + field: field_domain_access_target_id + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: field_domain_access_target_id_op + label: Domain + description: '' + use_operator: false + operator: field_domain_access_target_id_op + identifier: field_domain_access_target_id + required: false + remember: true + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: 0 + 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: domain_access_filter + field_domain_all_affiliates_value: + id: field_domain_all_affiliates_value + table: user__field_domain_all_affiliates + field: field_domain_all_affiliates_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + defaults: + filters: false + filter_groups: false + filter_groups: + operator: AND + groups: + 1: AND + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + tags: + - 'config:field.storage.user.field_domain_access' + - 'config:field.storage.user.field_domain_all_affiliates' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml new file mode 100644 index 000000000..50c86ce06 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml @@ -0,0 +1,18 @@ +name: Domain Content +description: 'Review content by assigned domain.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + - domain_access + - node + - user + - views + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.install b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.install new file mode 100644 index 000000000..cc15028ed --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.install @@ -0,0 +1,17 @@ +getStorage('view'); + $entities = []; + foreach (['affiliated_content', 'affiliated_editors'] as $id) { + if ($view = $storage->load($id)) { + $entities[$id] = $view; + } + } + if (!empty($entities)) { + $storage->delete($entities); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.links.menu.yml new file mode 100644 index 000000000..5a4a025b6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.links.menu.yml @@ -0,0 +1,12 @@ +domain_content.content_list: + title: Affiliated content + route_name: domain_content.content_list + parent: system.admin_content + description: 'Lists content by domain assignment.' + weight: 10 +domain_content.editors_list: + title: Affiliated editors + route_name: domain_content.editors_list + parent: system.admin_content + description: 'Lists editors by domain assignment.' + weight: 10 diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.module b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.module new file mode 100644 index 000000000..727f39147 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.module @@ -0,0 +1,71 @@ +getStorage('user')->load($account->id()); + $allowed = \Drupal::service('domain_access.manager')->getAccessValues($user); + $id = $domain->id(); + if ($account->hasPermission('publish to any domain') || ($account->hasPermission('publish to any assigned domain') && isset($allowed[$domain->id()]))) { + $operations['domain_content'] = array( + 'title' => t('Content'), + 'url' => Url::fromUri("internal:/admin/content/domain-content/$id"), + // Core operations start at 0 and increment by 10. + 'weight' => 120, + ); + } + if ($account->hasPermission('assign editors to any domain') || ($account->hasPermission('assign domain editors') && isset($allowed[$domain->id()]))) { + $operations['domain_users'] = array( + 'title' => t('Editors'), + 'url' => Url::fromUri("internal:/admin/content/domain-editors/$id"), + // Core operations start at 0 and increment by 10. + 'weight' => 120, + ); + } + + return $operations; +} + +/** + * Implements hook_requirements. + */ +function domain_content_requirements($phase) { + $requirements = []; + $allow = TRUE; + if ($phase == 'install') { + $list['user'] = 'user'; + $node_types = \Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple(); + foreach ($node_types as $type => $info) { + $list[$type] = 'node'; + } + // Check for required fields. + foreach ($list as $bundle => $entity_type) { + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_FIELD; + if (!$field = \Drupal::entityTypeManager()->getStorage('field_config')->load($id)) { + $allow = FALSE; + break; + } + $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_ALL_FIELD; + if (!$field = \Drupal::entityTypeManager()->getStorage('field_config')->load($id)) { + $allow = FALSE; + break; + } + } + } + if (!$allow) { + $requirements['domain_content'] = [ + 'title' => t('Domain content'), + 'description' => t('Domain content cannot be enabled until Domain access has installed its required fields.'), + 'severity' => REQUIREMENT_ERROR, + ]; + } + return $requirements; +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.permissions.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.permissions.yml new file mode 100644 index 000000000..629da9e91 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.permissions.yml @@ -0,0 +1,4 @@ +access domain content: + title: 'Access domain content pages' +access domain content editors: + title: 'Access domain editors pages' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.routing.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.routing.yml new file mode 100644 index 000000000..e352d0590 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.routing.yml @@ -0,0 +1,14 @@ +domain_content.content_list: + path: '/admin/content/domain-content' + defaults: + _controller: 'Drupal\domain_content\Controller\DomainContentController::contentList' + _title: 'Affiliated content' + requirements: + _permission: 'access domain content' +domain_content.editors_list: + path: '/admin/content/domain-editors' + defaults: + _controller: 'Drupal\domain_content\Controller\DomainContentController::editorsList' + _title: 'Affiliated editors' + requirements: + _permission: 'access domain content editors' diff --git a/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php b/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php new file mode 100644 index 000000000..ead35f356 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php @@ -0,0 +1,125 @@ +getUser(); + $build = [ + '#theme' => 'table', + '#header' => [$this->t('Domain'), $options['column_header']], + ]; + if ($account->hasPermission($options['all_permission'])) { + $build['#rows'][] = [ + Link::fromTextAndUrl($this->t('All affiliates'), Url::fromUri('internal:/admin/content/' . $options['path'] . '/' . '/all_affiliates')), + $this->getCount($options['type']), + ]; + } + // Loop through domains. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultipleSorted(); + $manager = \Drupal::service('domain_access.manager'); + /** @var \Drupal\domain\DomainInterface $domain */ + foreach ($domains as $domain) { + if ($account->hasPermission($options['all_permission']) || $manager->hasDomainPermissions($account, $domain, [$options['permission']])) { + $row = [ + Link::fromTextAndUrl($domain->label(), Url::fromUri('internal:/admin/content/' . $options['path'] . '/' . $domain->id())), + $this->getCount($options['type'], $domain), + ]; + $build['#rows'][] = $row; + } + } + return $build; + } + + /** + * Generates a list of content by domain. + */ + public function contentList() { + $options = [ + 'type' => 'node', + 'column_header' => $this->t('Content count'), + 'permission' => 'publish to any assigned domain', + 'all_permission' => 'publish to any domain', + 'path' => 'domain-content', + ]; + + return $this->buildList($options); + } + + /** + * Generates a list of editors by domain. + */ + public function editorsList() { + $options = [ + 'type' => 'user', + 'column_header' => $this->t('Editor count'), + 'permission' => 'assign domain editors', + 'all_permission' => 'assign editors to any domain', + 'path' => 'domain-editors', + ]; + + return $this->buildList($options); + } + + /** + * Counts the content for a domain. + * + * @param string $entity_type + * The entity type. + * @param DomainInterface $domain + * The domain to query. If passed NULL, checks status for all affiliates. + * + * @return int + * The content count for the given domain. + */ + protected function getCount($entity_type = 'node', DomainInterface $domain = NULL) { + if (is_null($domain)) { + $field = DOMAIN_ACCESS_ALL_FIELD; + $value = 1; + } + else { + $field = DOMAIN_ACCESS_FIELD; + $value = $domain->id(); + } + // Note that we ignore node access so these queries work on any domain. + $query = \Drupal::entityQuery($entity_type) + ->condition($field, $value) + ->accessCheck(FALSE); + + return count($query->execute()); + } + + /** + * Returns a fully loaded user object for the current request. + * + * @return AccountInterface + * The current user object. + */ + protected function getUser() { + $account = $this->currentUser(); + // Advanced grants for edit/delete require permissions. + return \Drupal::entityTypeManager()->getStorage('user')->load($account->id()); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainContentAccess.php b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainContentAccess.php new file mode 100644 index 000000000..10b1ff40f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainContentAccess.php @@ -0,0 +1,30 @@ +setRequirement('_permission', 'access domain content'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainEditorAccess.php b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainEditorAccess.php new file mode 100644 index 000000000..eed22abb1 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/src/Plugin/views/access/DomainEditorAccess.php @@ -0,0 +1,30 @@ +setRequirement('_permission', 'access domain content editors'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentActionsTest.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentActionsTest.php new file mode 100644 index 000000000..50e40180b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentActionsTest.php @@ -0,0 +1,79 @@ +admin_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + 'bypass node access' // Edit access is required. This is fastest. + ]); + $this->drupalLogin($this->admin_user); + + // Create users and content. + $this->createDomainContent(); + + $url = 'admin/content/domain-content/all_affiliates'; + + $this->drupalGet($url); + + // All the content should be on domain one. + $old_domain = $this->domains['one_example_com']; + $new_domain = $this->domains['two_example_com']; + + // Domains are linked in the output. + $this->assertRaw($old_domain->label() . ''); + $this->assertNoRaw($new_domain->label() . ''); + + // Add some content to domain two. + $edit = [ + 'node_bulk_form[0]' => TRUE, + 'node_bulk_form[1]' => TRUE, + 'action' => 'domain_access_add_action.two_example_com', + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + + // Both domains should be present. + $this->assertRaw($old_domain->label() . ''); + $this->assertRaw($new_domain->label() . ''); + + // Remove some content from domain two. + $edit = [ + 'node_bulk_form[0]' => TRUE, + 'node_bulk_form[1]' => TRUE, + 'action' => 'domain_access_remove_action.two_example_com', + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + + // Domains are linked properly in the output. + $this->assertRaw($old_domain->label() . ''); + $this->assertNoRaw($new_domain->label() . ''); + + // There should be five elements. + $this->assertRaw('node_bulk_form[4]'); + + // Remove one from all affiliates. + $edit = [ + 'node_bulk_form[0]' => TRUE, + 'action' => 'domain_access_none_action', + ]; + $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); + + // There should be four elements. + $this->assertRaw('node_bulk_form[3]'); + $this->assertNoRaw('node_bulk_form[4]'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentCountTest.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentCountTest.php new file mode 100644 index 000000000..9cf54a6fd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentCountTest.php @@ -0,0 +1,49 @@ +admin_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + ]); + $this->drupalLogin($this->admin_user); + + // Create users and content. + $this->createDomainContent(); + $this->createDomainUsers(); + + // Base Urls for our views. + $urls = [ + 'admin/content/domain-content', + 'admin/content/domain-editors', + ]; + // Test the overview pages. + foreach ($urls as $url) { + $content = $this->drupalGet($url); + $this->assertResponse(200); + // Find the links. + $this->findLink('All affiliates'); + foreach ($this->domains as $id => $domain) { + $this->findLink($domain->label()); + $string = $domain->label() . "5"; + $this->checkContent($content, $string); + } + $string = 'All affiliates5'; + $this->checkContent($content, $string); + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentPermissionsTest.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentPermissionsTest.php new file mode 100644 index 000000000..1ed8b40a8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentPermissionsTest.php @@ -0,0 +1,173 @@ +admin_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + ]); + $this->drupalLogin($this->admin_user); + + // Create users and content. + $this->createDomainContent(); + $this->createDomainUsers(); + + // Base Urls for our views. + $urls = [ + 'admin/content/domain-content', + 'admin/content/domain-editors', + ]; + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $this->drupalGet($url); + $this->assertResponse(200); + // Find the links. + $this->findLink('All affiliates'); + foreach ($this->domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // All affiliates link. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(200); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + $this->assertResponse(200); + } + } + // This user should be able to see everything but all affiliates + $this->limited_user = $this->drupalCreateUser([ + 'administer domains', + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any assigned domain', + 'assign domain editors', + ]); + $this->addDomainsToEntity('user', $this->limited_user->id(), array_keys($this->domains), DOMAIN_ACCESS_FIELD); + + $this->drupalLogin($this->limited_user); + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $this->drupalGet($url); + $this->assertResponse(200); + // Find the links. + $this->assertNoRaw('All affiliates'); + foreach ($this->domains as $id => $domain) { + $this->findLink($domain->label()); + } + + // All affiliates link. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(403); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + $this->assertResponse(200); + } + } + + // This user should be able to see everything but all affiliates and nothing + // for editor assignments. + $this->editor_user = $this->drupalCreateUser([ + 'access administration pages', + 'access domain content', + 'publish to any assigned domain', + ]); + $this->addDomainsToEntity('user', $this->editor_user->id(), array_keys($this->domains), DOMAIN_ACCESS_FIELD); + + $this->drupalLogin($this->editor_user); + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $expected = 200; + if ($url == 'admin/content/domain-editors') { + $expected = 403; + } + $this->drupalGet($url); + $this->assertResponse($expected); + // Find the links. + $this->assertNoRaw('All affiliates'); + foreach ($this->domains as $id => $domain) { + if ($expected == 200) { + $this->findLink($domain->label()); + } + else { + $this->findNoLink($domain->label()); + } + } + + // All affiliates link will fail for both paths. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(403); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + $this->assertResponse($expected); + } + } + + // This user should be able to see one domain for editor assignments. + $this->assign_user = $this->drupalCreateUser([ + 'access administration pages', + 'access domain content editors', + 'assign domain editors', + ]); + $ids = array_keys($this->domains); + $assigned_id = end($ids); + $this->addDomainsToEntity('user', $this->assign_user->id(), [$assigned_id], DOMAIN_ACCESS_FIELD); + + $this->drupalLogin($this->assign_user); + // Test the overview and domain-specific pages. + foreach ($urls as $url) { + $expected = 200; + if ($url == 'admin/content/domain-content') { + $expected = 403; + } + $this->drupalGet($url); + $this->assertResponse($expected); + // Find the links. + $this->assertNoRaw('All affiliates'); + foreach ($this->domains as $id => $domain) { + if ($expected == 200 && $id == $assigned_id) { + $this->findLink($domain->label()); + } + else { + $this->findNoLink($domain->label()); + } + } + + // All affiliates link will fail for both paths. + $this->drupalGet($url . '/all_affiliates'); + $this->assertResponse(403); + + // Individual domain pages. + foreach ($this->domains as $id => $domain) { + $this->drupalGet($url . '/' . $id); + if ($expected == 200 && $id == $assigned_id) { + $this->assertResponse(200); + } + else { + $this->assertResponse(403); + } + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentTestBase.php b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentTestBase.php new file mode 100644 index 000000000..44786cfb8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_content/tests/src/Functional/DomainContentTestBase.php @@ -0,0 +1,109 @@ +domainCreateTestDomains(5); + + $this->domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + } + + /** + * Creates dummy content for testing. + * + * 25 nodes, 5 per domain and 5 to all affiliates. + */ + public function createDomainContent() { + foreach ($this->domains as $id => $domain) { + for ($i = 0; $i < 5; $i++) { + $this->drupalCreateNode([ + 'type' => 'article', + DOMAIN_ACCESS_FIELD => [$id], + DOMAIN_ACCESS_ALL_FIELD => ($id == 'one_example_com') ? 1 : 0, + ]); + } + } + // Rebuild node access rules. + node_access_rebuild(); + } + + /** + * Creates dummy content for testing. + * + * 25 users, 5 per domain and 5 to all affiliates. + */ + public function createDomainUsers() { + foreach ($this->domains as $id => $domain) { + for ($i = 0; $i < 5; $i++) { + $account[$id] = $this->drupalCreateUser([ + 'access administration pages', + 'access domain content', + 'access domain content editors', + 'publish to any domain', + 'assign editors to any domain', + ]); + $this->addDomainsToEntity('user', $account[$id]->id(), $id, DOMAIN_ACCESS_FIELD); + if ($id == 'one_example_com') { + $this->addDomainsToEntity('user', $account[$id]->id(), 1, DOMAIN_ACCESS_ALL_FIELD); + } + } + } + } + + /** + * Strips whitespace from a page response and runs assertRaw() equivalent. + * + * In tests, we were having difficulty with spacing in tables. This method + * takes some concepts from Mink and rearranges them to work for our tests. + * Notably, we don't pull page content from the session request. + * + * @param $content + * The generated HTML, such as from drupalGet(). + * @param $text + * The text string to search for. + */ + public function checkContent($content, $text) { + // Convert all whitespace to spaces. + $content = preg_replace('/\s+/u', ' ', $content); + // Strip all whitespace between tags. + $content = preg_replace('@>\\s+<@', '><', $content); + $regex = '/' . preg_quote($text, '/') . '/ui'; + $message = sprintf('The text "%s" was found in the text of the current page.', $text); + $this->assert((bool) preg_match($regex, $content), $message); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/README.md b/sites/all/modules/contrib/admin/domain/domain_source/README.md new file mode 100644 index 000000000..7508eeba6 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/README.md @@ -0,0 +1,58 @@ +Domain Source +====== + +Allows content to be assigned a canonical domain when writing URLs. Domain Source will +ensure that content that appears on multiple domains always links to one URL. + +When links are written to content from another domain, the links +will go to the "source" domain specified for the node. + +Domain Source does not issue redirects. It rewrites links. The only time redirects may +be involved is when content is saved. + +Domain Source Fields +----- + +Domain Source adds a field to each content type that works with or without Domain Access. +The field is optional. If Domain Access is present, only a selected domain may be +assigned as the source domain. + +Domain Source configuration +----- + +The module has a configuration option that allows administrators to disable link rewrites +for specific Drupal paths. These paths are defined by Drupal's routing system. The default' +set of routes are: + +* `canonical` -- the View page of a node +* `delete_form` -- the delete form +* `edit_form` -- the edit form +* `version_history` -- the revisions page +* `revision` -- the revision edit form +* `create` -- not enforced + +Additional routes may be defined by other modules. Most notable Content Translation, +which adds: + +* `content_translation_overview` -- the overview page +* `content_translation_add` -- the add translation link +* `content_translation_edit` -- the edit translation form +* `content_translation_delete` -- the delete translation form + +Additional Entities +----- + +Domain Source is designed to work for content (nodes) but should work with other content +entities. You will need to configure the field for each entity type manually. + +Developer Notes +----- + +Domain Source changes core's `redirect_response_subscriber` service to the +`DomainSourceRedirectResponseSubscriber` class. This allows us to issue redirects to +registered domains and aliases that would otherwise not be recognizes as internal Drupal +links. These redirects typically occur on entity save when the source domain varies from +the current domain. + +Domain Source also implements `OutboundPathProcessorInterface` to rewrite links to an +entity assigned to a source domain. diff --git a/sites/all/modules/contrib/admin/domain/domain_source/config/install/domain_source.settings.yml b/sites/all/modules/contrib/admin/domain/domain_source/config/install/domain_source.settings.yml new file mode 100644 index 000000000..d63da0eb3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/config/install/domain_source.settings.yml @@ -0,0 +1 @@ +exclude_routes: {} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/config/install/field.storage.node.field_domain_source.yml b/sites/all/modules/contrib/admin/domain/domain_source/config/install/field.storage.node.field_domain_source.yml new file mode 100644 index 000000000..9ae9937de --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/config/install/field.storage.node.field_domain_source.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - domain + - node + enforced: + module: + - domain_source +id: node.field_domain_source +field_name: field_domain_source +entity_type: node +type: entity_reference +settings: + target_type: domain +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.schema.yml b/sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.schema.yml new file mode 100644 index 000000000..bbde9a45a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.schema.yml @@ -0,0 +1,10 @@ +domain_source.settings: + type: config_object + label: 'Domain Source settings' + mapping: + exclude_routes: + type: sequence + label: 'Disable link rewrites for specific entity routes.' + sequence: + type: string + label: 'Route' diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.api.php b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.api.php new file mode 100644 index 000000000..a1cf72dcb --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.api.php @@ -0,0 +1,64 @@ +getStorage('domain')->loadDefaultDomain(); +} + +/** + * Allows modules to specify the target link for a Drupal path. + * + * Note: This hook is not meant to be used for node or entity paths, which + * are handled by hook_domain_source_alter(). This hook is split + * from hook_domain_source_alter() for better performance. + * + * Note that hook_domain_source_alter() only paths that are not content entities. + * + * Currently, no modules in the package implement this hook. + * + * There is no return value for this hook. Modify $source by reference by + * loading a valid domain record or set $source = NULL to discard an existing + * $source value and not rewrite the path. + * + * @param array &$source + * The domain array from domain_get_node_match(), passed by reference. + * @param string $path + * The outbound path request. + * @param array $options + * The options for the url, as defined by + * \Drupal\Core\PathProcessor\OutboundPathProcessorInterface. + */ +function hook_domain_source_path_alter(&$source, $path, $options) { + // Always make admin links go to the primary domain. + $parts = explode('/', $path); + if (isset($parts[0]) && $parts[0] == 'admin') { + $source = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml new file mode 100644 index 000000000..24ae2905b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml @@ -0,0 +1,15 @@ +name: Domain Source +description: 'Domain-based path rewrites for content.' +type: module +package: Domain +# version: VERSION +# core: 8.x +dependencies: + - domain + - node + +# Information added by Drupal.org packaging script on 2017-12-19 +version: '8.x-1.0-alpha11' +core: '8.x' +project: 'domain' +datestamp: 1513718589 diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.install b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.install new file mode 100644 index 000000000..654216932 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.install @@ -0,0 +1,30 @@ +getStorage('node_type')->loadMultiple(); + foreach ($node_types as $type => $info) { + $list[$type] = 'node'; + } + // Install our fields. + foreach ($list as $bundle => $entity_type) { + domain_source_confirm_fields($entity_type, $bundle); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.libraries.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.libraries.yml new file mode 100644 index 000000000..a61d45d8e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.libraries.yml @@ -0,0 +1,7 @@ +drupal.domain_source: + version: VERSION + js: + js/domain_source.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.menu.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.menu.yml new file mode 100644 index 000000000..e40865771 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.menu.yml @@ -0,0 +1,6 @@ +domain_source.settings: + title: Domain Source settings + route_name: domain_source.settings + parent: domain.admin + description: 'Domain Source settings' + weight: 15 diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.task.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.task.yml new file mode 100644 index 000000000..a636149e3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.links.task.yml @@ -0,0 +1,4 @@ +domain_source.settings: + title: Domain Source settings + route_name: domain_source.settings + base_route: domain.admin diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module new file mode 100644 index 000000000..d87e2360e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module @@ -0,0 +1,265 @@ +getStorage('field_config'); + + if (!$field = $field_config_storage->load($id)) { + $field = array( + 'field_name' => DOMAIN_SOURCE_FIELD, + 'entity_type' => $entity_type, + 'label' => 'Domain Source', + 'bundle' => $bundle, + 'required' => FALSE, + 'description' => 'Select the canonical domain for this content.', + 'settings' => array( + 'handler' => 'default:domain', + // Handler_settings are deprecated but seem to be necessary here. + 'handler_settings' => [ + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ], + 'target_bundles' => NULL, + 'sort' => ['field' => 'weight', 'direction' => 'ASC'], + ), + ); + $field_config = $field_config_storage->create($field); + $field_config->save(); + } + + // Tell the form system how to behave. Default to radio buttons. + // @TODO: This function is deprecated, but using the OO syntax is causing + // test fails. + entity_get_form_display($entity_type, $bundle, 'default') + ->setComponent(DOMAIN_SOURCE_FIELD, array( + 'type' => 'options_select', + 'weight' => 42, + )) + ->save(); +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + * + * Creates our fields when new node types are created. + * + * @TODO: Make this possible for all entity types. + */ +function domain_source_node_type_insert(EntityInterface $entity) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + if (!$entity->isSyncing()) { + // Do not fire hook when config sync in progress. + domain_source_confirm_fields('node', $entity->id()); + } +} + +/** + * Returns the source domain associated to an entity. + * + * @param Drupal\Core\Entity\EntityInterface $entity + * The entity to check. + * + * @return string|NULL + * The value assigned to the entity, either a domain id string or NULL. + */ +function domain_source_get(EntityInterface $entity) { + $source = NULL; + + if (!isset($entity->{DOMAIN_SOURCE_FIELD})) { + return $source; + } + + $value = $entity->get(DOMAIN_SOURCE_FIELD)->offsetGet(0); + if (!empty($value)) { + $target_id = $value->target_id; + if ($domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($target_id)) { + $source = $domain->id(); + } + } + return $source; +} + +/** + * Implements hook_form_alter(). + * + * Find forms that contain the domain source field and allow those to handle + * redirects properly. + */ +function domain_source_form_alter(&$form, &$form_state, $form_id) { + $object = $form_state->getFormObject(); + // Set up our TrustedRedirect handler for form saves. + if (isset($form[DOMAIN_SOURCE_FIELD]) && !empty($object) && is_callable([$object, 'getEntity']) && $entity = $object->getEntity()) { + foreach ($form['actions'] as $key => $element) { + // Redirect submit handlers, but not the preview button. + if ($key != 'preview' && isset($element['#type']) && $element['#type'] == 'submit') { + $form['actions'][$key]['#submit'][] = 'domain_source_form_submit'; + } + } + } +} + +/** + * Validate form submissions. + */ +function domain_source_form_validate($element, \Drupal\Core\Form\FormStateInterface $form_state) { + $values = $form_state->getValues(); + // This is only run if Domain Access is present. + $access_values = $values[DOMAIN_ACCESS_FIELD]; + $source_value = current($values[DOMAIN_SOURCE_FIELD]); + // If no value is selected, that's acceptable. Else run through a check. + $source_set = empty($source_value); + foreach ($access_values as $value) { + if ($value == $source_value) { + $source_set = TRUE; + } + } + if (!$source_set) { + $form_state->setError($element, t('The source domain must be selected as a publishing option.')); + } +} + +/** + * Redirect form submissions to other domains. + */ +function domain_source_form_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) { + // Ensure that we have saved an entity. + if ($object = $form_state->getFormObject()) { + $url = $object->getEntity()->url(); + } + // Validate that the URL will be considered "external" by Drupal, which means + // that a scheme value will be present. + if (!empty($url)) { + $uri_parts = parse_url($url); + // If necessary and secure, issue a TrustedRedirectResponse to the new URL. + if (!empty($uri_parts['host'])) { + // Pass a redirect if necessary. + if (DomainRedirectResponse::checkTrustedHost($uri_parts['host'])) { + $response = new TrustedRedirectResponse($url); + $form_state->setResponse($response); + } + } + } +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. + */ +function domain_source_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) { + // Add the options hidden from the user silently to the form. + $manager = \Drupal::service('domain_source.element_manager'); + $hide = TRUE; + $form = $manager->setFormOptions($form, $form_state, DOMAIN_SOURCE_FIELD, $hide); + // Add validation if Domain Access is installed. + if (defined('DOMAIN_ACCESS_FIELD') && isset($form[DOMAIN_ACCESS_FIELD])) { + $form[DOMAIN_SOURCE_FIELD]['#element_validate'] = array('domain_source_form_validate'); + // If using a select field, load the JS to show/hide options. + if ($form[DOMAIN_SOURCE_FIELD]['widget']['#type'] == 'select') { + $form['#attached']['library'][] = 'domain_source/drupal.domain_source'; + } + } +} + +/** + * Implements hook_views_data_alter. + */ +function domain_source_views_data_alter(array &$data) { + $table = 'node__' . DOMAIN_SOURCE_FIELD; + $data[$table][DOMAIN_SOURCE_FIELD]['field']['id'] = 'domain_source'; + $data[$table][DOMAIN_SOURCE_FIELD . '_target_id']['filter']['id'] = 'domain_source'; + // Since domains are not stored in the database, relationships cannot be used. + unset($data[$table][DOMAIN_SOURCE_FIELD]['relationship']); +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Add options for domain source when using Devel Generate. + */ +function domain_source_form_devel_generate_form_content_alter(&$form, &$form_state, $form_id) { + // Add our element to the Devel generate form. + $list = ['_derive' => t('Derive from domain selection')]; + $list += \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + $form['domain_source'] = array( + '#title' => t('Domain source'), + '#type' => 'checkboxes', + '#options' => $list, + '#weight' => 4, + '#multiple' => TRUE, + '#size' => count($list) > 5 ? 5 : count($list), + '#default_value' => ['_derive'], + '#description' => t('Sets the source domain for created nodes.'), + ); +} + +/** + * Implements hook_ENTITY_TYPE_presave(). + * + * Fires only if Devel Generate module is present, to assign test nodes to + * domains. + */ +function domain_source_node_presave(EntityInterface $node) { + domain_source_presave_generate($node); +} + +/** + * Handles presave operations for devel generate. + */ +function domain_source_presave_generate(EntityInterface $entity) { + // Handle devel module settings. + $exists = \Drupal::moduleHandler()->moduleExists('devel_generate'); + $values = []; + $selections = []; + if ($exists && isset($entity->devel_generate)) { + // If set by the form. + if (isset($entity->devel_generate['domain_access'])) { + $selection = array_filter($entity->devel_generate['domain_access']); + if (isset($selection['random-selection'])) { + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $selections = array_rand($domains, ceil(rand(1, count($domains)))); + } + else { + $selections = array_keys($selection); + } + } + if (isset($entity->devel_generate['domain_source'])) { + $selection = $entity->devel_generate['domain_source']; + if ($selection == '_derive') { + if (!empty($selections)) { + $values[DOMAIN_SOURCE_FIELD] = current($selections); + } + else { + $values[DOMAIN_SOURCE_FIELD] = NULL; + } + } + foreach ($values as $name => $value) { + $entity->set($name, $value); + } + } + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.routing.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.routing.yml new file mode 100644 index 000000000..551e37400 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.routing.yml @@ -0,0 +1,7 @@ +domain_source.settings: + path: '/admin/config/domain/domain_source' + defaults: + _title: 'Domain Source settings' + _form: '\Drupal\domain_source\Form\DomainSourceSettingsForm' + requirements: + _permission: 'administer domains' diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.services.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.services.yml new file mode 100644 index 000000000..127264fd4 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.services.yml @@ -0,0 +1,9 @@ +services: + domain_source.element_manager: + class: Drupal\domain_source\DomainSourceElementManager + arguments: ['@entity_type.manager'] + domain_source.path_processor: + class: Drupal\domain_source\HttpKernel\DomainSourcePathProcessor + arguments: ['@domain.negotiator', '@module_handler', '@entity_type.manager', '@path.alias_manager', '@config.factory'] + tags: + - { name: path_processor_outbound, priority: 200 } diff --git a/sites/all/modules/contrib/admin/domain/domain_source/js/domain_source.js b/sites/all/modules/contrib/admin/domain/domain_source/js/domain_source.js new file mode 100644 index 000000000..6e549663a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/js/domain_source.js @@ -0,0 +1,59 @@ +/** + * @file + * Attaches behaviors for the Domain Source module. + * + * If Domain Access is present, we show/hide selected publishing domains. This approach + * currently only works with a select field. + */ +(function ($) { + + "use strict"; + + /** + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.domainSourceAllowed = { + attach: function () { + + // Get the initial setting so that it can be reset. + var initialOption = $("#edit-field-domain-source").val(); + + // Onload, fire initial show/hide. + getDomains(); + + // Get the domains selected by the domain access field. + function getDomains() { + var domains = new Array(); + $("#edit-field-domain-access :checked").each(function(index, obj) { + domains.push(obj.value); + }); + setOptions(domains); + } + + // Based on selected domains, show/hide the selection options. + function setOptions(domains) { + $("#edit-field-domain-source option").each(function(index, obj) { + if (jQuery.inArray(obj.value, domains) == -1 && obj.value != '_none') { + // If the current selection is removed, reset the selection to _none. + if ($("#edit-field-domain-source").val() == obj.value) { + $("#edit-field-domain-source").val('_none'); + } + $("#edit-field-domain-source option[value=" + obj.value + "]").hide(); + } + else { + $("#edit-field-domain-source option[value=" + obj.value + "]").show(); + // If we reselected the initial value, reset the select option. + if (obj.value == initialOption) { + $("#edit-field-domain-source").val(obj.value); + } + } + }); + } + + // When the selections change, recalculate the select options. + $( "#edit-field-domain-access" ).on( "change", getDomains ); + } + }; + +})(jQuery); diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceElementManager.php b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceElementManager.php new file mode 100644 index 000000000..b70d448b9 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceElementManager.php @@ -0,0 +1,27 @@ +getBuildInfo(); + $entity = $form_state->getFormObject()->getEntity(); + $entity_values = $entity->get(DOMAIN_SOURCE_FIELD)->offsetGet(0); + if (isset($field['widget']['#options']) && !empty($entity_values)) { + $value = $entity_values->getValue('target_id'); + $options = array_diff_key(array_flip($value), $field['widget']['#options']); + } + return array_keys($options); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceServiceProvider.php b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceServiceProvider.php new file mode 100644 index 000000000..7baea2ba5 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/DomainSourceServiceProvider.php @@ -0,0 +1,20 @@ +getDefinition('redirect_response_subscriber'); + $definition->setClass('Drupal\domain_source\EventSubscriber\DomainSourceRedirectResponseSubscriber'); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php b/sites/all/modules/contrib/admin/domain/domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php new file mode 100644 index 000000000..7570b5582 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/EventSubscriber/DomainSourceRedirectResponseSubscriber.php @@ -0,0 +1,69 @@ +getResponse(); + if ($response instanceof RedirectResponse) { + $request = $event->getRequest(); + + // Let the 'destination' query parameter override the redirect target. + // If $response is already a SecuredRedirectResponse, it might reject the + // new target as invalid, in which case proceed with the old target. + $destination = $request->query->get('destination'); + if ($destination) { + // The 'Location' HTTP header must always be absolute. + $destination = $this->getDestinationAsAbsoluteUrl($destination, $request->getSchemeAndHttpHost()); + try { + $response->setTargetUrl($destination); + } + catch (\InvalidArgumentException $e) { + } + } + + // Regardless of whether the target is the original one or the overridden + // destination, ensure that all redirects are safe. + if (!($response instanceof SecuredRedirectResponse)) { + try { + // SecuredRedirectResponse is an abstract class that requires a + // concrete implementation. Default to DomainRedirectResponse, which + // considers only redirects to sites registered via Domain. + $safe_response = DomainRedirectResponse::createFromRedirectResponse($response); + $safe_response->setRequestContext($this->requestContext); + } + catch (\InvalidArgumentException $e) { + // If the above failed, it's because the redirect target wasn't + // local. Do not follow that redirect. Display an error message + // instead. We're already catching one exception, so trigger_error() + // rather than throw another one. + // We don't throw an exception, because this is a client error rather than a + // server error. + $message = 'Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.'; + trigger_error($message, E_USER_ERROR); + $safe_response = new Response($message, 400); + } + $event->setResponse($safe_response); + } + } + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/Form/DomainSourceSettingsForm.php b/sites/all/modules/contrib/admin/domain/domain_source/src/Form/DomainSourceSettingsForm.php new file mode 100644 index 000000000..7b69811ff --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/Form/DomainSourceSettingsForm.php @@ -0,0 +1,60 @@ +getDefinition('node')->getLinkTemplates(); + + foreach ($routes as $route => $path) { + // Some parts of the system prepend drupal:, which the routing + // system doesn't use. The routing system also uses underscores instead + // of dashes. Because Drupal. + $route = str_replace(['-', 'drupal:'], ['_', ''], $route); + $options[$route] = $route; + } + $config = $this->config('domain_source.settings'); + $form['exclude_routes'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('Disable link rewrites for the selected routes.'), + '#default_value' => $config->get('exclude_routes', []), + '#options' => $options, + '#description' => $this->t('Check the routes to disable. Any entity URL with a Domain Source field will be rewritten unless its corresponding route is disabled.'), + ); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->config('domain_source.settings') + ->set('exclude_routes', $form_state->getValue('exclude_routes')) + ->save(); + + parent::submitForm($form, $form_state); + } + + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php b/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php new file mode 100644 index 000000000..c5ed83679 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php @@ -0,0 +1,262 @@ +negotiator = $negotiator; + $this->moduleHandler = $module_handler; + $this->entityTypeManager = $entity_type_manager; + $this->aliasManager = $alias_manager; + $this->configFactory = $config_factory; + $this->domainStorage = $entity_type_manager->getStorage('domain'); + } + + /** + * {@inheritdoc} + */ + public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { + // Load the active domain. + $active_domain = $this->getActiveDomain(); + + // Only act on valid internal paths and when a domain loads. + if (empty($active_domain) || empty($path) || !empty($options['external'])) { + return $path; + } + + // Set the default source information. + $source = NULL; + $options['active_domain'] = $active_domain; + + // Get the current language. + $langcode = NULL; + if (!empty($options['language'])) { + $langcode = $options['language']->getId(); + } + // Get the URL object for this request. + $alias = $this->aliasManager->getPathByAlias($path, $langcode); + $url = Url::fromUserInput($alias, $options); + + // Check the route, if available. Entities can be configured to + // only rewrite specific routes. + if ($url->isRouted() && $this->allowedRoute($url->getRouteName())) { + // Load the entity to check + if (!empty($options['entity'])) { + $entity = $options['entity']; + } + else { + $parameters = $url->getRouteParameters(); + if (!empty($parameters)) { + $entity = $this->getEntity($parameters); + } + } + } + // One hook for entities. + if (!empty($entity)) { + // Enmsure we send the right translation. + if (!empty($langcode) && $entity->hasTranslation($langcode) && $translation = $entity->getTranslation($langcode)) { + $entity = $translation; + } + if ($target_id = domain_source_get($entity)) { + $source = $this->domainStorage->load($target_id); + } + $options['entity'] = $entity; + $options['entity_type'] = $entity->getEntityTypeId(); + $this->moduleHandler->alter('domain_source', $source, $path, $options); + } + // One for other, because the latter is resource-intensive. + else { + $this->moduleHandler->alter('domain_source_path', $source, $path, $options); + } + // If a source domain is specified, rewrite the link. + if (!empty($source)) { + // Note that url rewrites add a leading /, which getPath() also adds. + $options['base_url'] = trim($source->getPath(), '/'); + $options['absolute'] = TRUE; + } + return $path; + } + + /** + * Derive entity data from a given route's parameters. + * + * @param $parameters + * An array of route parameters. + * + * @return $entity|NULL + */ + public function getEntity($parameters) { + $entity = NULL; + $entity_type = key($parameters); + $entity_types = $this->getEntityTypes(); + foreach ($parameters as $entity_type => $value) { + if (!empty($entity_type) && isset($entity_types[$entity_type])) { + $entity = $this->entityTypeManager->getStorage($entity_type)->load($value); + } + } + return $entity; + } + + /** + * Checks that a route's common name is not disallowed. + * + * Looks at the name (e.g. canonical) of the route without regard for + * the entity type. + * + * @parameter $name + * The route name being checked. + * + * @return boolean + */ + public function allowedRoute($name) { + $excluded = $this->getExcludedRoutes(); + $parts = explode('.', $name); + $route_name = end($parts); + // Config is stored as an array. Empty items are not excluded. + return !isset($excluded[$route_name]); + } + + /** + * Gets an array of content entity types, keyed by type. + * + * @return array + */ + public function getEntityTypes() { + if (!isset($this->entityTypes)) { + foreach ($this->entityTypeManager->getDefinitions() as $type => $definition) { + if ($definition->getGroup() == 'content') { + $this->entityTypes[$type] = $type; + } + } + } + return $this->entityTypes; + } + + /** + * Gets the settings for domain source path rewrites. + * + * @return array + */ + public function getExcludedRoutes() { + if (!isset($this->excludedRoutes)) { + $config = $this->configFactory->get('domain_source.settings'); + $routes = $config->get('exclude_routes'); + if (is_array($routes)) { + $this->excludedRoutes = array_flip($routes); + } + else { + $this->excludedRoutes = []; + } + } + return $this->excludedRoutes; + } + + /** + * Gets the settings for domain source path rewrites. + * + * @return array + */ + public function getActiveDomain() { + if (!isset($this->activeDomain)) { + // Ensure that the loader has run. + // In some tests, the kernel event has not. + $active = $this->negotiator->getActiveDomain(); + if (empty($active)) { + $active = $this->negotiator->getActiveDomain(TRUE); + } + $this->activeDomain = $active; + } + return $this->activeDomain; + } +} + diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/field/DomainSource.php b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/field/DomainSource.php new file mode 100644 index 000000000..756acbe4f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/field/DomainSource.php @@ -0,0 +1,50 @@ +options['settings']['link'])) { + foreach ($items as &$item) { + $object = $item['raw']; + $entity = $object->getEntity(); + $url = $entity->toUrl()->toString(); + $domain = $item['rendered']['#options']['entity']; + $item['rendered']['#type'] = 'markup'; + $item['rendered']['#markup'] = '' . $domain->label() . ''; + } + uasort($items, array($this, 'sort')); + } + + return $items; + } + + /** + * Sort the domain list, if possible. + */ + private function sort($a, $b) { + $domainA = isset($a['rendered']['#options']['entity']) ? $a['rendered']['#options']['entity'] : 0; + $domainB = isset($b['rendered']['#options']['entity']) ? $b['rendered']['#options']['entity'] : 0; + if ($domainA !== 0) { + return $domainA->getWeight() > $domainB->getWeight(); + } + // We don't have a domain object so sort as best we can. + return $a['rendered']['#plain_text'] > $b['rendered']['#plain_text']; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/filter/DomainSource.php b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/filter/DomainSource.php new file mode 100644 index 000000000..a449ae495 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/Plugin/views/filter/DomainSource.php @@ -0,0 +1,41 @@ +valueOptions)) { + $this->valueTitle = $this->t('Domains'); + $this->valueOptions = [ + '_active' => $this->t('Active domain'), + ] + \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList(); + } + return $this->valueOptions; + } + + /** + * {@inheritdoc} + */ + public function query() { + $active_index = array_search('_active', (array)$this->value); + if ($active_index !== FALSE) { + $active_id = \Drupal::service('domain.negotiator')->getActiveId(); + $this->value[$active_index] = $active_id; + } + + parent::query(); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php new file mode 100644 index 000000000..2dac3aba0 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php @@ -0,0 +1,94 @@ +domainCreateTestDomains(5); + } + + /** + * Basic test setup. + */ + public function testDomainSourceElement() { + $admin = $this->drupalCreateUser(array( + 'bypass node access', + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'publish to any domain', + )); + $this->drupalLogin($admin); + + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + + // Set the title, so the node can be saved. + $this->fillField('title[0][value]', 'Test node'); + + // We expect to find 5 domain options. We set two as selected. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $count = 0; + $ids = ['example_com', 'one_example_com', 'two_example_com']; + foreach ($domains as $domain) { + $locator = DOMAIN_ACCESS_FIELD . '[' . $domain->id() . ']'; + $this->findField($locator); + if (in_array($domain->id(), $ids)) { + $this->checkField($locator); + } + } + // Find the all affiliates field. + $locator = DOMAIN_ACCESS_ALL_FIELD . '[value]'; + $this->findField($locator); + + // Set all affiliates to TRUE. + $this->checkField($locator); + + // Find the Domain Source field. + $locator = DOMAIN_SOURCE_FIELD; + $this->findField($locator); + // Set it to one_example_com. + $this->selectFieldOption($locator, 'one_example_com'); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->statusCodeEquals(200); + + // Edit the node. + $this->drupalGet('node/1/edit'); + $this->assertSession()->statusCodeEquals(200); + + // Set the domain source field to an unselected domain. + $this->selectFieldOption($locator, 'three_example_com'); + + // Save the form. + $this->pressButton('edit-submit'); + $this->assertSession()->assertEscaped('The source domain must be selected as a publishing option.'); + + // Check the URL. + $url = $this->geturl(); + $this->assert(strpos($url, 'node/1/edit') > 0, 'Form not submitted.'); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceEntityReferenceTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceEntityReferenceTest.php new file mode 100644 index 000000000..63cd4b649 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceEntityReferenceTest.php @@ -0,0 +1,167 @@ +admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + )); + $this->drupalLogin($this->admin_user); + + // Visit the article field administration page. + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->assertResponse(200, 'Manage fields page sourceed.'); + + // Check for a domain field. + $this->assertText('Domain Source', 'Domain form field found.'); + + // Visit the article field display administration page. + $this->drupalGet('admin/structure/types/manage/article/display'); + $this->assertResponse(200, 'Manage field display page sourceed.'); + + // Check for a domain field. + $this->assertText('Domain Source', 'Domain form field found.'); + } + + /** + * Tests the storage of the domain source field. + */ + public function testDomainSourceFieldStorage() { + $this->admin_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer node fields', + 'administer node display', + 'administer domains', + 'administer menu', + )); + $this->drupalLogin($this->admin_user); + + // Create 5 domains. + $this->domainCreateTestDomains(5); + + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + + // Check the new field exists on the page. + $this->assertText('Domain Source', 'Found the domain field instance.'); + + // We expect to find 5 domain options + none. + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + foreach ($domains as $domain) { + $string = 'value="' . $domain->id() . '"'; + $this->assertRaw($string, 'Found the domain option.'); + if (!isset($one)) { + $one = $domain->id(); + continue; + } + if (!isset($two)) { + $two = $domain->id(); + $two_path = $domain->getPath(); + } + } + $this->assertRaw('value="_none"', 'Found the _none_ option.'); + + $node_storage = \Drupal::entityTypeManager()->getStorage('node'); + + // Try to post a node, assigned to the second domain. + $edit['title[0][value]'] = 'Test node'; + $edit['field_domain_source'] = $two; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = $node_storage->load(1); + // Check that the value is set. + $value = domain_source_get($node); + $this->assertTrue($value == $two, 'Node saved with proper source record.'); + + // Test the URL. + $url = $node->toUrl()->toString(); + $expected_url = $two_path . 'node/1'; + $this->assertTrue($expected_url == $url, 'URL rewritten correctly.'); + + // Try to post a node, assigned to no domain. + $edit['title[0][value]'] = 'Test node'; + $edit["field_domain_source"] = '_none'; + $this->drupalPostForm('node/add/article', $edit, 'Save'); + $this->assertResponse(200); + $node = $node_storage->load(2); + // Check that the value is set. + $value = domain_source_get($node); + $this->assertNull($value, 'Node saved with proper source record.'); + + // Test the url. + $url = $node->toUrl()->toString(); + $expected_url = base_path() . 'node/2'; + $this->assertTrue($expected_url == $url, 'URL rewritten correctly.'); + + // Place the menu block. + $this->drupalPlaceBlock('system_menu_block:main'); + + // Enable main menu as available menu. + $edit = array( + 'menu_options[main]' => 1, + 'menu_parent' => 'main:', + ); + $this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type')); + + // Create a third node that is assigned to a menu. + $edit = array( + 'title[0][value]' => 'Node 3', + 'menu[enabled]' => 1, + 'menu[title]' => 'Test preview', + 'field_domain_source' => $two, + ); + $this->drupalPostForm('node/add/article', $edit, 'Save'); + // Test the URL against expectations, and the rendered menu link. + $node = $node_storage->load(3); + $url = $node->toUrl()->toString(); + $expected_url = $two_path . 'node/3'; + $this->assertTrue($expected_url == $url, 'URL rewritten correctly.'); + // Load the page with a menu and check that link. + $this->drupalGet('node/3'); + $this->assertRaw('href="' . $url, 'Menu link rewritten correctly.'); + + // Remove the field from the node type and make sure nothing breaks. + // See https://www.drupal.org/node/2892612 + $id = 'node.article.field_domain_source'; + if ($field = \Drupal::entityManager()->getStorage('field_config')->load($id)) { + $field->delete(); + field_purge_batch(10, $field->uuid()); + drupal_flush_all_caches(); + } + // Visit the article field display administration page. + $this->drupalGet('node/add/article'); + $this->assertResponse(200); + // Try to post a node, assigned to no domain. + $edit2['title[0][value]'] = 'Test node'; + $this->drupalPostForm('node/add/article', $edit2, 'Save'); + // Test the URL against expectations, and the rendered menu link. + $node = $node_storage->load(4); + $url = $node->toUrl()->toString(); + $expected_url = '/node/4'; + $this->assertTrue($expected_url == $url, 'No URL rewrite performed.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceExcludeTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceExcludeTest.php new file mode 100644 index 000000000..33ec0d7e8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceExcludeTest.php @@ -0,0 +1,90 @@ +createNode(['type' => 'page', 'title' => 'foo', DOMAIN_SOURCE_FIELD => $id]); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + + // Exclude the edit path from rewrites. + $config = $this->config('domain_source.settings'); + $config->set('exclude_routes', ['edit_form' => 'edit_form'])->save(); + + // Variables for our tests. + $path = 'node/1/edit'; + $expected = '/' . $path; + $route_name = 'entity.node.edit_form'; + $route_parameters = ['node' => 1]; + $uri = 'internal:/' . $path; + $uri_path = '/' . $path; + $options = []; + + // Because of path cache, we have to flush here. + drupal_flush_all_caches(); + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute' . $url); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceLanguageTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceLanguageTest.php new file mode 100644 index 000000000..1adf48bcd --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceLanguageTest.php @@ -0,0 +1,117 @@ +save(); + ConfigurableLanguage::createFromLangcode('af')->save(); + + // Enable content translation for the current entity type. + \Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE); + + } + + public function testDomainSourceLanguage() { + // Create a node, assigned to a source domain. + $id = 'one_example_com'; + // Create one node with no language. + $node = $this->drupalCreateNode([ + 'body' => [[]], + 'status' => 1, + DOMAIN_SOURCE_FIELD => $id, + ]); + + // Programmatically create a translation. + $storage = \Drupal::entityTypeManager()->getStorage('node'); + // Reload the node. + $node = $storage->load(1); + // Create an Afrikaans translation assigned to domain 2. + $id2 = 'two_example_com'; + $translation = $node->addTranslation('af'); + $translation->title->value = $this->randomString(); + $translation->{DOMAIN_SOURCE_FIELD} = $id2; + $translation->status = 1; + $node->save(); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + + // Now test the same for the Arfrikaans translation. + $path = 'node/1'; + $source = $domains[$id2]; + $expected = $source->getPath() . 'af/' . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $language = \Drupal::entityTypeManager()->getStorage('configurable_language')->load('af'); + $options = ['language' => $language]; + + $translation = $node->getTranslation('af'); + $this->assertTrue(domain_source_get($translation) == $id2, domain_source_get($translation)); + + // Because of path cache, we have to flush here. + drupal_flush_all_caches(); + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceTrustedHostTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceTrustedHostTest.php new file mode 100644 index 000000000..ccc22b93a --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceTrustedHostTest.php @@ -0,0 +1,76 @@ +createNode(['type' => 'page', 'title' => 'foo', DOMAIN_SOURCE_FIELD => $id]); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Set up two additional domains. + $domain2 = $domains['two_example_com']; + + // Check against trusted host patterns. + $settings['settings']['trusted_host_patterns'] = (object) [ + 'value' => ['^' . $this->prepareTrustedHostname($domain2->getHostname()) . '$'], + 'required' => TRUE, + ]; + $this->writeSettings($settings); + // This URL should fail due to trusted host omission. + $this->drupalGet($url); + $this->assertRaw('The provided host name is not valid for this server.'); + + // Now switch the node to a domain that is trusted. + $node->{DOMAIN_SOURCE_FIELD} = $domain2->id(); + $node->save(); + // Get the link using Url::fromRoute(). + $expected = $domain2->getPath() . $path; + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + // Assert that the URL is what we expect. + $this->assertTrue($url == $expected, 'fromRoute'); + $this->drupalGet($url); + $this->assertResponse(200, 'Url is validated by trusted host settings.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceUrlTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceUrlTest.php new file mode 100644 index 000000000..302f2745d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceUrlTest.php @@ -0,0 +1,62 @@ +createNode(['type' => 'page', 'title' => 'foo', DOMAIN_SOURCE_FIELD => $id]); + + // Variables for our tests. + $path = 'node/1'; + $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple(); + $source = $domains[$id]; + $expected = $source->getPath() . $path; + $route_name = 'entity.node.canonical'; + $route_parameters = ['node' => 1]; + $uri = 'entity:' . $path; + $uri_path = '/' . $path; + $options = []; + + // Get the link using Url::fromRoute(). + $url = URL::fromRoute($route_name, $route_parameters, $options)->toString(); + $this->assertTrue($url == $expected, 'fromRoute'); + + // Get the link using Url::fromUserInput() + $url = URL::fromUserInput($uri_path, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUserInput'); + + // Get the link using Url::fromUri() + $url = URL::fromUri($uri, $options)->toString(); + $this->assertTrue($url == $expected, 'fromUri'); + } + +} diff --git a/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js b/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js index 15a1140e3..07b8ba093 100644 --- a/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js +++ b/sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js @@ -24,17 +24,21 @@ var _canvas = _$canvas[0]; var _ctx = _canvas.getContext('2d'); var _canvas_props = { - 'margin-top':100, - 'margin-bottom':100 + 'margin_top':100, + 'margin_right':0, + 'margin_bottom':100, + 'margin_left':0 }; var _physics = new Physics(); // var _stage = new createjs.Stage('edlp-map'); var _nodes = []; // var _particules = []; - var _p_radius = 6; + var _p_radius = 3; // nodes radius (real radius, not diametre) var _p_velocity_factor = 0.5; var _m_pos = {x:0, y:0}; var _node_hover_id = -1; + var _$entrees_block = $('#block-edlpentreesblock'); + var _$entrees_block_termlinks = $('a.term-link', _$entrees_block); var _entrees_colors = { "130":"rgb(75, 143, 126)", "121":"rgb(134, 142, 36)", @@ -69,15 +73,14 @@ console.log("EdlpCorpus init()"); // console.log("document", document); initMap(); + }; function initMap(){ console.log("EdlpCorpus initMap()"); // console.log('_physics',_physics); initCanvas(); - initEvents(); loadCorpus(); - }; // ______ @@ -93,38 +96,13 @@ function onResizeCanvas() { _canvas.width = window.innerWidth; - _canvas.height = window.innerHeight - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; - _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; - _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; + _canvas.height = window.innerHeight; + // - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; + // _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; + // _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; }; - // ______ __ - // / ____/ _____ ____ / /______ - // / __/ | | / / _ \/ __ \/ __/ ___/ - // / /___ | |/ / __/ / / / /_(__ ) - // /_____/ |___/\___/_/ /_/\__/____/ - function initEvents(){ - _$container - .on('mousemove', function(event) { - event.preventDefault(); - // console.log("onMouseMove", event); - _m_pos.x = event.originalEvent.clientX; - _m_pos.y = event.originalEvent.clientY; - // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); - // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); - }) - .on('click', function(event) { - if(event.target.tagName != "A"){ - console.log("Corpus : click", event); - event.preventDefault(); - if(_node_hover_id != -1){ - console.log("click on node", _nodes[_node_hover_id]); - } - } - }); - }; - // ___ _ // / | (_)___ __ __ // / /| | / / __ `/ |/_/ @@ -135,13 +113,16 @@ // console.log('drupalSettings',drupalSettings); // console.log('window.location', window.location); var path = window.location.origin + drupalSettings.basepath + "edlp/corpus"; - $.getJSON(path, {}, function(data){ - console.log('corpus loaded : data', data); - // console.log('first node', data.nodes[0]); - // buildParticles(data.nodes); - buildNodes(data.nodes); - startAnime(); - }); + $.getJSON(path, {}, onCorpusLoaded); + }; + + function onCorpusLoaded(data){ + console.log('corpus loaded : data', data); + // console.log('first node', data.nodes[0]); + // buildParticles(data.nodes); + buildNodes(data.nodes); + initEvents(); + startAnime(); }; // _ __ __ @@ -155,29 +136,35 @@ var x,y,d; for (var i in nodes) { // console.log('i',i); - x = _p_radius*2 + Math.random()*(_canvas.width - _p_radius*4); - y = _p_radius*2 + Math.random()*(_canvas.height - _p_radius*4); d = i < 1 ? true : false; - _nodes.push(new Node(i,nodes[i],x,y,d)); - // if(i==4) + _nodes.push(new Node(i,nodes[i],d)); + // if(i==0) // break; } }; - function Node(i,n,x,y,d){ + function Node(i,node,d){ this.id = i; - for(key in n) - this[key] = n[key]; + for(key in node) + this[key] = node[key]; this.debug = d; - this.radius = _p_radius; this.mass = 8; + this.base_radius = _p_radius; this.g = { l : 1, // drawing line width s : 1 // drawing space between squares + }; + this.real_radius = this.base_radius + (this.g.l+this.g.s)*this.entrees.length; + this.wall_limits = { + top: _canvas_props.margin_top +this.real_radius, + right: _canvas.width -_canvas_props.margin_right -this.real_radius, + bottom: _canvas.height -_canvas_props.margin_bottom -this.real_radius, + left: _canvas_props.margin_left +this.real_radius } - this.x = x; - this.y = y; + this.x = this.wall_limits.left + Math.random()*(this.wall_limits.right - this.wall_limits.left); + this.y = this.wall_limits.top + Math.random()*(this.wall_limits.bottom - this.wall_limits.top); + this.hover = false; // physics this.p = _physics.makeParticle(this.mass, this.x, this.y); @@ -202,17 +189,16 @@ }; Node.prototype.checkWallBouncing = function(){ - // TODO: correct waal bouncing with centered squares if( - (this.x < 0 && this.p.velocity.x < 0) - || (this.x > _canvas.width-this.radius && this.p.velocity.x > 0) + (this.x < this.wall_limits.left && this.p.velocity.x < 0) + || (this.x > this.wall_limits.right && this.p.velocity.x > 0) ){ // console.log("bounce x"); this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75); } if( - (this.y < 0 && this.p.velocity.y < 0) - || (this.y > _canvas.height-this.radius && this.p.velocity.y > 0) + (this.y < this.wall_limits.top && this.p.velocity.y < 0) + || (this.y > this.wall_limits.bottom && this.p.velocity.y > 0) ){ // console.log("bounce y"); this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75); @@ -225,36 +211,51 @@ }; Node.prototype.checkMouse = function(){ - // TODO: get the real w and h of node (with entrees squares) - if( _m_pos.x > this.x - && _m_pos.x < this.x+this.radius - && _m_pos.y > this.y - && _m_pos.y < this.y+this.radius){ + + if( _m_pos.x > this.x - this.real_radius + && _m_pos.x < this.x + this.real_radius + && _m_pos.y > this.y - this.real_radius + && _m_pos.y < this.y + this.real_radius){ if(_node_hover_id == -1 || _node_hover_id == this.id){ console.log("Node hover", this.id); this.hover = true; + this.highlightEntries(); _node_hover_id = this.id; } }else{ this.hover = false; if (_node_hover_id == this.id) { _node_hover_id = -1; + _$entrees_block_termlinks.removeClass('highlighted'); } } }; - Node.prototype.draw = function(){ - // TODO: center the squares around the pos - _ctx.beginPath(); - _ctx.lineWidth = this.g.l; + Node.prototype.highlightEntries = function(){ + _$entrees_block_termlinks.removeClass('highlighted'); + var entree; for (var i = 0; i < this.entrees.length; i++) { - _ctx.strokeStyle = this.hover ? "red" : _entrees_colors[this.entrees[i]]; - _ctx.strokeRect(this.x-4*i, this.y-4*i, this.radius+8*i, this.radius+8*i); + entree = this.entrees[i]; + _$entrees_block_termlinks.filter(function(){ + return $(this).attr('tid') == entree; + }).addClass('highlighted'); + } + } + + Node.prototype.draw = function(){ + _ctx.beginPath(); + _ctx.lineWidth = this.hover ? this.g.l*2 : this.g.l; + var r,d; + for (var i = 0; i < this.entrees.length; i++) { + _ctx.strokeStyle = _entrees_colors[this.entrees[i]]; + r = this.base_radius + (this.g.l+this.g.s)*i; // radius + d = r*2; // diametre + _ctx.strokeRect(this.x - r,this.y - r,d,d); } _ctx.closePath(); // _ctx.beginPath(); // _ctx.fillStyle = this.hover ? "red" : this.debug ? "blue" : "black"; - // _ctx.fillRect(this.x, this.y, this.radius, this.radius); + // _ctx.fillRect(this.x, this.y, this.base_radius, this.base_radius); // _ctx.closePath(); }; @@ -321,6 +322,42 @@ } }; + // ______ __ + // / ____/ _____ ____ / /______ + // / __/ | | / / _ \/ __ \/ __/ ___/ + // / /___ | |/ / __/ / / / /_(__ ) + // /_____/ |___/\___/_/ /_/\__/____/ + function initEvents(){ + _$container + .on('mousemove', function(event) { + event.preventDefault(); + // console.log("onMouseMove", event); + _m_pos.x = event.originalEvent.clientX; + _m_pos.y = event.originalEvent.clientY; + // console.log("/ _ / - / _ /"); + // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); + // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); + }) + .on('click', function(event) { + if(event.target.tagName != "A"){ + console.log("Corpus : click", event); + event.preventDefault(); + if(_node_hover_id != -1){ + console.log("click on node", _nodes[_node_hover_id]); + } + } + }); + + _$entrees_block_termlinks.on('click', function(event) { + event.preventDefault(); + + _$entrees_block_termlinks.parents('li').removeClass('opened'); + $(this).parents('li').addClass('opened'); + return false; + }); + }; + + // ____ __ // / __ \___ ____ ____/ /__ _____ // / /_/ / _ \/ __ \/ __ / _ \/ ___/ @@ -332,6 +369,11 @@ for (var i = 0; i < _nodes.length; i++) { _nodes[i].onUpdate(); } + if(_node_hover_id != -1){ + document.body.style.cursor = 'pointer'; + }else{ + document.body.style.cursor = 'auto'; + } }; function startAnime(){ diff --git a/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js b/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js index 15a1140e3..07b8ba093 100644 --- a/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js +++ b/sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js @@ -24,17 +24,21 @@ var _canvas = _$canvas[0]; var _ctx = _canvas.getContext('2d'); var _canvas_props = { - 'margin-top':100, - 'margin-bottom':100 + 'margin_top':100, + 'margin_right':0, + 'margin_bottom':100, + 'margin_left':0 }; var _physics = new Physics(); // var _stage = new createjs.Stage('edlp-map'); var _nodes = []; // var _particules = []; - var _p_radius = 6; + var _p_radius = 3; // nodes radius (real radius, not diametre) var _p_velocity_factor = 0.5; var _m_pos = {x:0, y:0}; var _node_hover_id = -1; + var _$entrees_block = $('#block-edlpentreesblock'); + var _$entrees_block_termlinks = $('a.term-link', _$entrees_block); var _entrees_colors = { "130":"rgb(75, 143, 126)", "121":"rgb(134, 142, 36)", @@ -69,15 +73,14 @@ console.log("EdlpCorpus init()"); // console.log("document", document); initMap(); + }; function initMap(){ console.log("EdlpCorpus initMap()"); // console.log('_physics',_physics); initCanvas(); - initEvents(); loadCorpus(); - }; // ______ @@ -93,38 +96,13 @@ function onResizeCanvas() { _canvas.width = window.innerWidth; - _canvas.height = window.innerHeight - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; - _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; - _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; + _canvas.height = window.innerHeight; + // - _canvas_props['margin-top'] - _canvas_props['margin-bottom']; + // _canvas.style['margin-top'] = _canvas_props['margin-top']+"px"; + // _canvas.style['margin-bottom'] = _canvas_props['margin-bottom']+"px"; }; - // ______ __ - // / ____/ _____ ____ / /______ - // / __/ | | / / _ \/ __ \/ __/ ___/ - // / /___ | |/ / __/ / / / /_(__ ) - // /_____/ |___/\___/_/ /_/\__/____/ - function initEvents(){ - _$container - .on('mousemove', function(event) { - event.preventDefault(); - // console.log("onMouseMove", event); - _m_pos.x = event.originalEvent.clientX; - _m_pos.y = event.originalEvent.clientY; - // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); - // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); - }) - .on('click', function(event) { - if(event.target.tagName != "A"){ - console.log("Corpus : click", event); - event.preventDefault(); - if(_node_hover_id != -1){ - console.log("click on node", _nodes[_node_hover_id]); - } - } - }); - }; - // ___ _ // / | (_)___ __ __ // / /| | / / __ `/ |/_/ @@ -135,13 +113,16 @@ // console.log('drupalSettings',drupalSettings); // console.log('window.location', window.location); var path = window.location.origin + drupalSettings.basepath + "edlp/corpus"; - $.getJSON(path, {}, function(data){ - console.log('corpus loaded : data', data); - // console.log('first node', data.nodes[0]); - // buildParticles(data.nodes); - buildNodes(data.nodes); - startAnime(); - }); + $.getJSON(path, {}, onCorpusLoaded); + }; + + function onCorpusLoaded(data){ + console.log('corpus loaded : data', data); + // console.log('first node', data.nodes[0]); + // buildParticles(data.nodes); + buildNodes(data.nodes); + initEvents(); + startAnime(); }; // _ __ __ @@ -155,29 +136,35 @@ var x,y,d; for (var i in nodes) { // console.log('i',i); - x = _p_radius*2 + Math.random()*(_canvas.width - _p_radius*4); - y = _p_radius*2 + Math.random()*(_canvas.height - _p_radius*4); d = i < 1 ? true : false; - _nodes.push(new Node(i,nodes[i],x,y,d)); - // if(i==4) + _nodes.push(new Node(i,nodes[i],d)); + // if(i==0) // break; } }; - function Node(i,n,x,y,d){ + function Node(i,node,d){ this.id = i; - for(key in n) - this[key] = n[key]; + for(key in node) + this[key] = node[key]; this.debug = d; - this.radius = _p_radius; this.mass = 8; + this.base_radius = _p_radius; this.g = { l : 1, // drawing line width s : 1 // drawing space between squares + }; + this.real_radius = this.base_radius + (this.g.l+this.g.s)*this.entrees.length; + this.wall_limits = { + top: _canvas_props.margin_top +this.real_radius, + right: _canvas.width -_canvas_props.margin_right -this.real_radius, + bottom: _canvas.height -_canvas_props.margin_bottom -this.real_radius, + left: _canvas_props.margin_left +this.real_radius } - this.x = x; - this.y = y; + this.x = this.wall_limits.left + Math.random()*(this.wall_limits.right - this.wall_limits.left); + this.y = this.wall_limits.top + Math.random()*(this.wall_limits.bottom - this.wall_limits.top); + this.hover = false; // physics this.p = _physics.makeParticle(this.mass, this.x, this.y); @@ -202,17 +189,16 @@ }; Node.prototype.checkWallBouncing = function(){ - // TODO: correct waal bouncing with centered squares if( - (this.x < 0 && this.p.velocity.x < 0) - || (this.x > _canvas.width-this.radius && this.p.velocity.x > 0) + (this.x < this.wall_limits.left && this.p.velocity.x < 0) + || (this.x > this.wall_limits.right && this.p.velocity.x > 0) ){ // console.log("bounce x"); this.p.velocity.multiplyScalarXY(-1, 1).multiplyScalar(0.75); } if( - (this.y < 0 && this.p.velocity.y < 0) - || (this.y > _canvas.height-this.radius && this.p.velocity.y > 0) + (this.y < this.wall_limits.top && this.p.velocity.y < 0) + || (this.y > this.wall_limits.bottom && this.p.velocity.y > 0) ){ // console.log("bounce y"); this.p.velocity.multiplyScalarXY(1,-1).multiplyScalar(0.75); @@ -225,36 +211,51 @@ }; Node.prototype.checkMouse = function(){ - // TODO: get the real w and h of node (with entrees squares) - if( _m_pos.x > this.x - && _m_pos.x < this.x+this.radius - && _m_pos.y > this.y - && _m_pos.y < this.y+this.radius){ + + if( _m_pos.x > this.x - this.real_radius + && _m_pos.x < this.x + this.real_radius + && _m_pos.y > this.y - this.real_radius + && _m_pos.y < this.y + this.real_radius){ if(_node_hover_id == -1 || _node_hover_id == this.id){ console.log("Node hover", this.id); this.hover = true; + this.highlightEntries(); _node_hover_id = this.id; } }else{ this.hover = false; if (_node_hover_id == this.id) { _node_hover_id = -1; + _$entrees_block_termlinks.removeClass('highlighted'); } } }; - Node.prototype.draw = function(){ - // TODO: center the squares around the pos - _ctx.beginPath(); - _ctx.lineWidth = this.g.l; + Node.prototype.highlightEntries = function(){ + _$entrees_block_termlinks.removeClass('highlighted'); + var entree; for (var i = 0; i < this.entrees.length; i++) { - _ctx.strokeStyle = this.hover ? "red" : _entrees_colors[this.entrees[i]]; - _ctx.strokeRect(this.x-4*i, this.y-4*i, this.radius+8*i, this.radius+8*i); + entree = this.entrees[i]; + _$entrees_block_termlinks.filter(function(){ + return $(this).attr('tid') == entree; + }).addClass('highlighted'); + } + } + + Node.prototype.draw = function(){ + _ctx.beginPath(); + _ctx.lineWidth = this.hover ? this.g.l*2 : this.g.l; + var r,d; + for (var i = 0; i < this.entrees.length; i++) { + _ctx.strokeStyle = _entrees_colors[this.entrees[i]]; + r = this.base_radius + (this.g.l+this.g.s)*i; // radius + d = r*2; // diametre + _ctx.strokeRect(this.x - r,this.y - r,d,d); } _ctx.closePath(); // _ctx.beginPath(); // _ctx.fillStyle = this.hover ? "red" : this.debug ? "blue" : "black"; - // _ctx.fillRect(this.x, this.y, this.radius, this.radius); + // _ctx.fillRect(this.x, this.y, this.base_radius, this.base_radius); // _ctx.closePath(); }; @@ -321,6 +322,42 @@ } }; + // ______ __ + // / ____/ _____ ____ / /______ + // / __/ | | / / _ \/ __ \/ __/ ___/ + // / /___ | |/ / __/ / / / /_(__ ) + // /_____/ |___/\___/_/ /_/\__/____/ + function initEvents(){ + _$container + .on('mousemove', function(event) { + event.preventDefault(); + // console.log("onMouseMove", event); + _m_pos.x = event.originalEvent.clientX; + _m_pos.y = event.originalEvent.clientY; + // console.log("/ _ / - / _ /"); + // console.log("Node pos: ", {x:_nodes[0].x, y:_nodes[0].y}); + // console.log("Mouse pos: ", {x:_m_pos.x, y:_m_pos.y}); + }) + .on('click', function(event) { + if(event.target.tagName != "A"){ + console.log("Corpus : click", event); + event.preventDefault(); + if(_node_hover_id != -1){ + console.log("click on node", _nodes[_node_hover_id]); + } + } + }); + + _$entrees_block_termlinks.on('click', function(event) { + event.preventDefault(); + + _$entrees_block_termlinks.parents('li').removeClass('opened'); + $(this).parents('li').addClass('opened'); + return false; + }); + }; + + // ____ __ // / __ \___ ____ ____/ /__ _____ // / /_/ / _ \/ __ \/ __ / _ \/ ___/ @@ -332,6 +369,11 @@ for (var i = 0; i < _nodes.length; i++) { _nodes[i].onUpdate(); } + if(_node_hover_id != -1){ + document.body.style.cursor = 'pointer'; + }else{ + document.body.style.cursor = 'auto'; + } }; function startAnime(){ diff --git a/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php b/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php index 8e28d21f2..daa2db5b0 100644 --- a/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php +++ b/sites/all/modules/figli/edlp_corpus/src/Plugin/Block/BlockEntrees.php @@ -30,13 +30,66 @@ class BlockEntrees extends BlockBase { $terms = entity_load_multiple('taxonomy_term', $tids); // dsm($terms); foreach ($terms as $term) { + // dpm($term->toArray()); + $tid = $term->id(); $name = $term->getName(); - $url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); - $link = Link::fromTextAndUrl($name, $url); - $link = $link->toRenderable(); - $link["#attributes"]["class"] = 'term-'.$term->id(); - $entrees[] = $link; + + $term_url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); + $term_link = Link::fromTextAndUrl($name, $term_url)->toRenderable(); + $term_link['#prefix'] = ''; + $term_link['#suffix'] = ""; + $term_link["#attributes"] = array( + "class" => array('term-'.$term->id(), 'term-link'), + "tid" => $term->id() + ); + + $index_url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); + $index_link = Link::fromTextAndUrl('index', $index_url)->toRenderable(); + $index_link['#prefix'] = ''; + $index_link['#suffix'] = ""; + $index_link['#attributes'] = array( + 'class' => array('index-link'), + "tid" => $term->id() + ); + + + $notice_url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $tid]); + $notice_link = Link::fromTextAndUrl('notice', $notice_url)->toRenderable(); + $notice_link['#prefix'] = ''; + $notice_link['#suffix'] = ""; + $notice_link['#attributes'] = array( + 'class' => array('notice-link'), + "tid" => $term->id() + ); + + $description = $term->get('description')->value; + // dpm($description); + + $entree = array( + '#wrapper_attributes' => array( + 'class' => array('term-'.$term->id(), 'entree'), + 'tid' => $term->id() + ), + 'term_link' => $term_link, + 'entree_content' => array( + '#type' => 'container', + '#attributes' => array( + 'class' => 'entree-content' + ), + 'index_link' => $index_link, + 'notice_link' => $notice_link, + 'description' => array( + '#type' => 'container', + '#markup' => $description, + '#attributes' => array('class'=>'term-description') + ) + ) + ); +// + // dpm($entree); + + $entrees[] = $entree; } return array ( diff --git a/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css b/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css index 322cf5caf..4ef79124f 100644 --- a/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css +++ b/sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css @@ -32,29 +32,22 @@ main[role="main"] { background-color: rgba(255, 255, 255, 0.9); } footer[role="contentinfo"] { - outline: 1px solid pink; position: fixed; bottom: 0; width: 100%; } -canvas#edlp-map { - outline: 1px solid grey; } - footer { - text-align: center; } + display: table; + padding: 0 0 0.5em 0; } footer > .region { - outline: 1px dotted pink; } + display: table-cell; } footer .region-footer-left { - float: left; text-align: left; } footer .region-footer-center { - display: inline-block; - margin: 0 auto; text-align: center; } footer .region-footer-right { - float: right; text-align: right; - padding-right: 5em; } + min-width: 30px; } footer nav.block-menu { display: inline-block; } footer nav.block-menu ul { @@ -86,7 +79,8 @@ footer { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotateZ(-45deg); - transform: rotateZ(-45deg); } + transform: rotateZ(-45deg); + white-space: nowrap; } footer .block-language ul li { margin: 0; padding: 0; @@ -106,7 +100,10 @@ footer { position: relative; list-style: none; margin: 0 1.5em 0 0; - padding: 0; } + padding: 0; + margin: 0; + white-space: nowrap; + pointer-events: none; } footer .block-block-edlp-entrees ul li a { position: absolute; -webkit-transform-origin: left bottom; @@ -118,48 +115,148 @@ footer { text-transform: uppercase; font-size: 0.756em; white-space: nowrap; } - footer .block-block-edlp-entrees ul li a:before { - content: ""; + footer .block-block-edlp-entrees ul li span.oblique-wrapper { display: inline-block; - width: 7px; - height: 7px; - border: 1px solid #000; - margin-right: 0.5em; } - footer .block-block-edlp-entrees ul li a.term-134:before { - border-color: #2b8f2f; } - footer .block-block-edlp-entrees ul li a.term-121:before { - border-color: #3a33b6; } - footer .block-block-edlp-entrees ul li a.term-125:before { - border-color: #2c9f57; } - footer .block-block-edlp-entrees ul li a.term-119:before { - border-color: #c48978; } - footer .block-block-edlp-entrees ul li a.term-132:before { - border-color: #5270bb; } - footer .block-block-edlp-entrees ul li a.term-122:before { - border-color: #fb54d3; } - footer .block-block-edlp-entrees ul li a.term-129:before { - border-color: #e07483; } - footer .block-block-edlp-entrees ul li a.term-120:before { - border-color: #655845; } - footer .block-block-edlp-entrees ul li a.term-130:before { - border-color: #7e0868; } - footer .block-block-edlp-entrees ul li a.term-118:before { - border-color: #0e7121; } - footer .block-block-edlp-entrees ul li a.term-127:before { - border-color: #dabd42; } - footer .block-block-edlp-entrees ul li a.term-133:before { - border-color: #0399bb; } - footer .block-block-edlp-entrees ul li a.term-128:before { - border-color: #399a1c; } - footer .block-block-edlp-entrees ul li a.term-124:before { - border-color: #708540; } - footer .block-block-edlp-entrees ul li a.term-116:before { - border-color: #191bff; } - footer .block-block-edlp-entrees ul li a.term-117:before { - border-color: #279d84; } - footer .block-block-edlp-entrees ul li a.term-131:before { - border-color: #5219ab; } - footer .block-block-edlp-entrees ul li a.term-126:before { - border-color: #d49cb6; } - footer .block-block-edlp-entrees ul li a.term-123:before { - border-color: #497715; } + position: relative; + width: 1.5em; } + footer .block-block-edlp-entrees ul li span.oblique-wrapper a { + text-transform: none; } + footer .block-block-edlp-entrees ul li a.term-link { + pointer-events: all; } + footer .block-block-edlp-entrees ul li a.term-link:before { + content: ""; + display: inline-block; + width: 7px; + height: 7px; + border: 1px solid #000; + margin-right: 0.5em; } + footer .block-block-edlp-entrees ul li a.term-link[tid='134']:before { + border-color: #2b8f2f; + background-color: #2b8f2f; } + footer .block-block-edlp-entrees ul li a.term-link[tid='121']:before { + border-color: #3a33b6; + background-color: #3a33b6; } + footer .block-block-edlp-entrees ul li a.term-link[tid='125']:before { + border-color: #2c9f57; + background-color: #2c9f57; } + footer .block-block-edlp-entrees ul li a.term-link[tid='119']:before { + border-color: #c48978; + background-color: #c48978; } + footer .block-block-edlp-entrees ul li a.term-link[tid='132']:before { + border-color: #5270bb; + background-color: #5270bb; } + footer .block-block-edlp-entrees ul li a.term-link[tid='122']:before { + border-color: #fb54d3; + background-color: #fb54d3; } + footer .block-block-edlp-entrees ul li a.term-link[tid='129']:before { + border-color: #e07483; + background-color: #e07483; } + footer .block-block-edlp-entrees ul li a.term-link[tid='120']:before { + border-color: #655845; + background-color: #655845; } + footer .block-block-edlp-entrees ul li a.term-link[tid='130']:before { + border-color: #7e0868; + background-color: #7e0868; } + footer .block-block-edlp-entrees ul li a.term-link[tid='118']:before { + border-color: #0e7121; + background-color: #0e7121; } + footer .block-block-edlp-entrees ul li a.term-link[tid='127']:before { + border-color: #dabd42; + background-color: #dabd42; } + footer .block-block-edlp-entrees ul li a.term-link[tid='133']:before { + border-color: #0399bb; + background-color: #0399bb; } + footer .block-block-edlp-entrees ul li a.term-link[tid='128']:before { + border-color: #399a1c; + background-color: #399a1c; } + footer .block-block-edlp-entrees ul li a.term-link[tid='124']:before { + border-color: #708540; + background-color: #708540; } + footer .block-block-edlp-entrees ul li a.term-link[tid='116']:before { + border-color: #191bff; + background-color: #191bff; } + footer .block-block-edlp-entrees ul li a.term-link[tid='117']:before { + border-color: #279d84; + background-color: #279d84; } + footer .block-block-edlp-entrees ul li a.term-link[tid='131']:before { + border-color: #5219ab; + background-color: #5219ab; } + footer .block-block-edlp-entrees ul li a.term-link[tid='126']:before { + border-color: #d49cb6; + background-color: #d49cb6; } + footer .block-block-edlp-entrees ul li a.term-link[tid='123']:before { + border-color: #497715; + background-color: #497715; } + footer .block-block-edlp-entrees ul li .entree-content { + display: inline-block; + width: 0; + overflow: hidden; + opacity: 0; + -webkit-transition: all 300ms ease-in-out; + transition: all 300ms ease-in-out; + -webkit-transition-property: width,opacity; + transition-property: width,opacity; } + footer .block-block-edlp-entrees ul li .entree-content .term-description { + display: inline-block; + margin-left: 2em; + text-align: left; + width: 250px; + white-space: normal; + background-color: rgba(255, 255, 255, 0.9); + padding: 0.5em; } + footer .block-block-edlp-entrees ul li .entree-content .term-description p { + font-size: 0.65em; } + footer .block-block-edlp-entrees ul li:not(.opened) a.term-link:not(:hover):not(.highlighted):before { + background-color: #fff !important; } + footer .block-block-edlp-entrees ul li.opened .entree-content { + width: 350px; + opacity: 1; } + footer #block-userlogin { + position: relative; + width: 20px; + height: 20px; } + footer #block-userlogin h2 { + position: relative; + width: 20px; + height: 20px; + background-image: url(../../img/user.svg); + background-size: contain; + text-indent: 40px; + margin: 0; + overflow: hidden; + z-index: 1; + cursor: pointer; } + footer #block-userlogin .block-content { + z-index: 0; + position: absolute; + right: 0; + bottom: 20px; + padding: 0.5em; + padding-bottom: 20px; + background-color: rgba(255, 255, 255, 0.9); + overflow: hidden; + -webkit-box-sizing: border-box; + box-sizing: border-box; + height: 0px; + opacity: 0; + pointer-events: none; + -webkit-transition: all 0.5s ease-in-out; + transition: all 0.5s ease-in-out; + -webkit-transition-property: height,opacity; + transition-property: height,opacity; } + footer #block-userlogin .block-content form { + font-size: 0.75em; } + footer #block-userlogin .block-content .item-list ul { + margin: 0; } + footer #block-userlogin .block-content .item-list li { + margin: 0; + list-style: none; } + footer #block-userlogin .block-content .item-list li a { + color: inherit; + text-decoration: none; + font-size: 0.75em; + white-space: nowrap; } + footer #block-userlogin:hover .block-content { + height: 200px; + opacity: 1; + pointer-events: auto; } diff --git a/sites/all/themes/custom/edlptheme/assets/img/search.svg b/sites/all/themes/custom/edlptheme/assets/img/search.svg new file mode 100644 index 000000000..00e0d9f68 --- /dev/null +++ b/sites/all/themes/custom/edlptheme/assets/img/search.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/sites/all/themes/custom/edlptheme/assets/img/user.svg b/sites/all/themes/custom/edlptheme/assets/img/user.svg new file mode 100644 index 000000000..cd34e6dee --- /dev/null +++ b/sites/all/themes/custom/edlptheme/assets/img/user.svg @@ -0,0 +1,39 @@ + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/sites/all/themes/custom/edlptheme/assets/styles/app.scss b/sites/all/themes/custom/edlptheme/assets/styles/app.scss index da840f951..9c750b4a2 100644 --- a/sites/all/themes/custom/edlptheme/assets/styles/app.scss +++ b/sites/all/themes/custom/edlptheme/assets/styles/app.scss @@ -24,7 +24,6 @@ body, html{ padding:0; } - div.layout-container{ position: relative; width:100%; height:100%; @@ -40,18 +39,19 @@ header[role="banner"]{ main[role="main"]{ outline:1px solid green; - background-color: rgba(255,255,255, 0.9); + background-color: $transparent-bg; + // display: none; } footer[role="contentinfo"]{ - outline: 1px solid pink; + // outline: 1px solid pink; position: fixed; bottom:0; width:100%; } canvas#edlp-map{ - outline: 1px solid grey; + // outline: 1px solid grey; } @mixin oblique-list { @@ -63,6 +63,7 @@ canvas#edlp-map{ padding: 0; a{ position: absolute; + // display:inline-block; transform-origin: left bottom; transform: rotateZ(-45deg); color: #000; @@ -74,23 +75,23 @@ canvas#edlp-map{ } footer{ - text-align: center; + // text-align: center; + display: table; + padding: 0 0 0.5em 0; >.region{ - outline: 1px dotted pink; + display: table-cell; + // outline: 1px dotted purple; + // position: relative; } .region-footer-left{ - float: left; text-align: left; } .region-footer-center{ - display: inline-block; - margin: 0 auto; text-align: center; } .region-footer-right{ - float: right; text-align: right; - padding-right: 5em; + min-width: 30px; } nav.block-menu{ @@ -112,6 +113,7 @@ footer{ padding:0 0 0 0.5em; transform-origin: left bottom; transform: rotateZ(-45deg); + white-space: nowrap; li{ margin:0; padding:0; list-style: none; @@ -133,10 +135,25 @@ footer{ .block-block-edlp-entrees{ display: inline-block; + // vertical-align: top; ul{ li{ @include oblique-list; - a{ + margin:0; + white-space: nowrap; + pointer-events: none; + span.oblique-wrapper{ + display: inline-block; + position: relative; + width:1.5em; + a{ + text-transform: none; + } + } + + a.term-link{ + pointer-events: all; + // bottom: 0; &:before{ content: ""; display:inline-block; @@ -145,26 +162,132 @@ footer{ border: 1px solid #000; margin-right: 0.5em; } - &.term-134:before{border-color: $entree-color-134;} - &.term-121:before{border-color: $entree-color-121;} - &.term-125:before{border-color: $entree-color-125;} - &.term-119:before{border-color: $entree-color-119;} - &.term-132:before{border-color: $entree-color-132;} - &.term-122:before{border-color: $entree-color-122;} - &.term-129:before{border-color: $entree-color-129;} - &.term-120:before{border-color: $entree-color-120;} - &.term-130:before{border-color: $entree-color-130;} - &.term-118:before{border-color: $entree-color-118;} - &.term-127:before{border-color: $entree-color-127;} - &.term-133:before{border-color: $entree-color-133;} - &.term-128:before{border-color: $entree-color-128;} - &.term-124:before{border-color: $entree-color-124;} - &.term-116:before{border-color: $entree-color-116;} - &.term-117:before{border-color: $entree-color-117;} - &.term-131:before{border-color: $entree-color-131;} - &.term-126:before{border-color: $entree-color-126;} - &.term-123:before{border-color: $entree-color-123;} + &[tid='134']:before{border-color: $e-col-134;background-color: $e-col-134;} + &[tid='121']:before{border-color: $e-col-121;background-color: $e-col-121;} + &[tid='125']:before{border-color: $e-col-125;background-color: $e-col-125;} + &[tid='119']:before{border-color: $e-col-119;background-color: $e-col-119;} + &[tid='132']:before{border-color: $e-col-132;background-color: $e-col-132;} + &[tid='122']:before{border-color: $e-col-122;background-color: $e-col-122;} + &[tid='129']:before{border-color: $e-col-129;background-color: $e-col-129;} + &[tid='120']:before{border-color: $e-col-120;background-color: $e-col-120;} + &[tid='130']:before{border-color: $e-col-130;background-color: $e-col-130;} + &[tid='118']:before{border-color: $e-col-118;background-color: $e-col-118;} + &[tid='127']:before{border-color: $e-col-127;background-color: $e-col-127;} + &[tid='133']:before{border-color: $e-col-133;background-color: $e-col-133;} + &[tid='128']:before{border-color: $e-col-128;background-color: $e-col-128;} + &[tid='124']:before{border-color: $e-col-124;background-color: $e-col-124;} + &[tid='116']:before{border-color: $e-col-116;background-color: $e-col-116;} + &[tid='117']:before{border-color: $e-col-117;background-color: $e-col-117;} + &[tid='131']:before{border-color: $e-col-131;background-color: $e-col-131;} + &[tid='126']:before{border-color: $e-col-126;background-color: $e-col-126;} + &[tid='123']:before{border-color: $e-col-123;background-color: $e-col-123;} + // &.highlighted{ + // color:red; + // } } + + .entree-content{ + display: inline-block; + // outline: 1px solid green; + width:0; + overflow: hidden; + opacity: 0; + transition: all 300ms ease-in-out; + transition-property: width,opacity; + .term-description{ + display: inline-block; + margin-left: 2em; + text-align: left; + width:250px; + white-space: normal; + background-color: $transparent-bg; + padding:0.5em; + p{ + font-size: 0.65em; + } + } + } + + // &.highlighted{ + // a.term_link{ + // color: red; + // } + // } + &:not(.opened){ + a.term-link:not(:hover):not(.highlighted):before{ + background-color: #fff!important; + } + } + &.opened{ + .entree-content{ + width:350px; + opacity: 1; + } + } + // &:not(:first-of-type) .entree-content{display: none;} + } + } + } + + #block-userlogin{ + // outline: 1px solid blue; + $wh:20px; + position: relative; + width:$wh; height: $wh; + // background-color: blue; + h2{ + position: relative; + width:$wh; height:$wh; + background-image: url(../../img/user.svg); + // background-color: red; + background-size: contain; + text-indent: $wh*2; + margin: 0; + overflow: hidden; + z-index: 1; + cursor: pointer; + } + .block-content{ + z-index: 0; + position:absolute; + right:0;bottom:$wh; + padding:0.5em; + padding-bottom: 20px; + background-color: $transparent-bg; + overflow: hidden; + box-sizing:border-box; + height: 0px; + opacity:0; + pointer-events:none; + transition: all 0.5s ease-in-out; + transition-property: height,opacity; + // &:hover{ + // height:200px; + // opacity:1; + // pointer-events: auto; + // } + form{ + font-size: 0.75em; + } + .item-list{ + ul{margin:0;} + li{ + margin:0; + list-style: none; + a{ + color: inherit; + text-decoration: none; + font-size: 0.75em; + white-space:nowrap; + } + } + } + } + &:hover{ + .block-content{ + height:200px; + opacity: 1; + pointer-events:auto; } } } diff --git a/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss b/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss index 2058cb875..ff137b743 100644 --- a/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss +++ b/sites/all/themes/custom/edlptheme/assets/styles/base/_colors.scss @@ -1,21 +1,23 @@ -$entree-color-130: rgb(75, 143, 126); -$entree-color-121: rgb(134, 142, 36); -$entree-color-134: rgb(43, 143, 47); -$entree-color-121: rgb(58, 51, 182); -$entree-color-125: rgb(44, 159, 87); -$entree-color-119: rgb(196, 137, 120); -$entree-color-132: rgb(82, 112, 187); -$entree-color-122: rgb(251, 84, 211); -$entree-color-129: rgb(224, 116, 131); -$entree-color-120: rgb(101, 88, 69); -$entree-color-130: rgb(126, 8, 104); -$entree-color-118: rgb(14, 113, 33); -$entree-color-127: rgb(218, 189, 66); -$entree-color-133: rgb(3, 153, 187); -$entree-color-128: rgb(57, 154, 28); -$entree-color-124: rgb(112, 133, 64); -$entree-color-116: rgb(25, 27, 255); -$entree-color-117: rgb(39, 157, 132); -$entree-color-131: rgb(82, 25, 171); -$entree-color-126: rgb(212, 156, 182); -$entree-color-123: rgb(73, 119, 21); +$transparent-bg: rgba(255,255,255, 0.9); + +$e-col-130: rgb(75, 143, 126); +$e-col-121: rgb(134, 142, 36); +$e-col-134: rgb(43, 143, 47); +$e-col-121: rgb(58, 51, 182); +$e-col-125: rgb(44, 159, 87); +$e-col-119: rgb(196, 137, 120); +$e-col-132: rgb(82, 112, 187); +$e-col-122: rgb(251, 84, 211); +$e-col-129: rgb(224, 116, 131); +$e-col-120: rgb(101, 88, 69); +$e-col-130: rgb(126, 8, 104); +$e-col-118: rgb(14, 113, 33); +$e-col-127: rgb(218, 189, 66); +$e-col-133: rgb(3, 153, 187); +$e-col-128: rgb(57, 154, 28); +$e-col-124: rgb(112, 133, 64); +$e-col-116: rgb(25, 27, 255); +$e-col-117: rgb(39, 157, 132); +$e-col-131: rgb(82, 25, 171); +$e-col-126: rgb(212, 156, 182); +$e-col-123: rgb(73, 119, 21); diff --git a/sites/all/themes/custom/edlptheme/edlptheme.theme b/sites/all/themes/custom/edlptheme/edlptheme.theme new file mode 100644 index 000000000..9b1ab1c04 --- /dev/null +++ b/sites/all/themes/custom/edlptheme/edlptheme.theme @@ -0,0 +1,26 @@ + + {{ title_prefix }} + {% if label %} + {{ label }} + {% endif %} + {{ title_suffix }} +
+ {% block content %} + {{ content }} + {% endblock %} +
+ diff --git a/sites/all/themes/custom/edlptheme/templates/form/form-element.html.twig b/sites/all/themes/custom/edlptheme/templates/form/form-element.html.twig new file mode 100644 index 000000000..3311ebcfa --- /dev/null +++ b/sites/all/themes/custom/edlptheme/templates/form/form-element.html.twig @@ -0,0 +1,96 @@ +{# +/** + * @file + * Default theme implementation for a form element. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - errors: (optional) Any errors for this form element, may not be set. + * - prefix: (optional) The form element prefix, may not be set. + * - suffix: (optional) The form element suffix, may not be set. + * - required: The required marker, or empty if the associated form element is + * not required. + * - type: The type of the element. + * - name: The name of the element. + * - label: A rendered label element. + * - label_display: Label display setting. It can have these values: + * - before: The label is output before the element. This is the default. + * The label includes the #title and the required marker, if #required. + * - after: The label is output after the element. For example, this is used + * for radio and checkbox #type elements. If the #title is empty but the + * field is #required, the label will contain only the required marker. + * - invisible: Labels are critical for screen readers to enable them to + * properly navigate through forms but can be visually distracting. This + * property hides the label for everyone except screen readers. + * - attribute: Set the title attribute on the element to create a tooltip but + * output no label element. This is supported only for checkboxes and radios + * in \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement(). + * It is used where a visual label is not needed, such as a table of + * checkboxes where the row and column provide the context. The tooltip will + * include the title and required marker. + * - description: (optional) A list of description properties containing: + * - content: A description of the form element, may not be set. + * - attributes: (optional) A list of HTML attributes to apply to the + * description content wrapper. Will only be set when description is set. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element. This is the default + * value. + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. + * - disabled: True if the element is disabled. + * - title_display: Title display setting. + * + * @see template_preprocess_form_element() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + 'js-form-item', + 'form-item', + 'js-form-type-' ~ type|clean_class, + 'form-item-' ~ name|clean_class, + 'js-form-item-' ~ name|clean_class, + title_display not in ['after', 'before'] ? 'form-no-label', + disabled == 'disabled' ? 'form-disabled', + errors ? 'form-item--error', + ] +%} +{% + set description_classes = [ + 'description', + description_display == 'invisible' ? 'visually-hidden', + ] +%} + + {% if label_display in ['before', 'invisible'] %} + {{ label }} + {% endif %} + {% if prefix is not empty %} + {{ prefix }} + {% endif %} + {% if description_display == 'before' and description.content %} + + {{ description.content }} + + {% endif %} + {{ children }} + {% if suffix is not empty %} + {{ suffix }} + {% endif %} + {% if label_display == 'after' %} + {{ label }} + {% endif %} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {% if description_display in ['after', 'invisible'] and description.content %} + + {{ description.content }} + + {% endif %} + diff --git a/sites/all/themes/custom/edlptheme/templates/form/input--textfield.html.twig b/sites/all/themes/custom/edlptheme/templates/form/input--textfield.html.twig new file mode 100644 index 000000000..e8896e422 --- /dev/null +++ b/sites/all/themes/custom/edlptheme/templates/form/input--textfield.html.twig @@ -0,0 +1,16 @@ +{# +/** + * @file + * Default theme implementation for an 'input' #type form element. + * + * Available variables: + * - attributes: A list of HTML attributes for the input element. + * - children: Optional additional rendered elements. + * + * @see template_preprocess_input() + * + * @ingroup themeable + */ +#} +{{ children }} + diff --git a/sites/all/themes/custom/edlptheme/theme.theme b/sites/all/themes/custom/edlptheme/theme.theme deleted file mode 100644 index e45b0e99f..000000000 --- a/sites/all/themes/custom/edlptheme/theme.theme +++ /dev/null @@ -1,6 +0,0 @@ - 'databasename', - * 'username' => 'sqlusername', - * 'password' => 'sqlpassword', - * 'host' => 'localhost', - * 'port' => '3306', - * 'driver' => 'mysql', - * 'prefix' => '', - * 'collation' => 'utf8mb4_general_ci', - * ); - * @endcode - */ - $databases = array(); - -/** - * Customizing database settings. - * - * Many of the values of the $databases array can be customized for your - * particular database system. Refer to the sample in the section above as a - * starting point. - * - * The "driver" property indicates what Drupal database driver the - * connection should use. This is usually the same as the name of the - * database type, such as mysql or sqlite, but not always. The other - * properties will vary depending on the driver. For SQLite, you must - * specify a database file name in a directory that is writable by the - * webserver. For most other drivers, you must specify a - * username, password, host, and database name. - * - * Transaction support is enabled by default for all drivers that support it, - * including MySQL. To explicitly disable it, set the 'transactions' key to - * FALSE. - * Note that some configurations of MySQL, such as the MyISAM engine, don't - * support it and will proceed silently even if enabled. If you experience - * transaction related crashes with such configuration, set the 'transactions' - * key to FALSE. - * - * For each database, you may optionally specify multiple "target" databases. - * A target database allows Drupal to try to send certain queries to a - * different database if it can but fall back to the default connection if not. - * That is useful for primary/replica replication, as Drupal may try to connect - * to a replica server when appropriate and if one is not available will simply - * fall back to the single primary server (The terms primary/replica are - * traditionally referred to as master/slave in database server documentation). - * - * The general format for the $databases array is as follows: - * @code - * $databases['default']['default'] = $info_array; - * $databases['default']['replica'][] = $info_array; - * $databases['default']['replica'][] = $info_array; - * $databases['extra']['default'] = $info_array; - * @endcode - * - * In the above example, $info_array is an array of settings described above. - * The first line sets a "default" database that has one primary database - * (the second level default). The second and third lines create an array - * of potential replica databases. Drupal will select one at random for a given - * request as needed. The fourth line creates a new database with a name of - * "extra". - * - * You can optionally set prefixes for some or all database table names - * by using the 'prefix' setting. If a prefix is specified, the table - * name will be prepended with its value. Be sure to use valid database - * characters only, usually alphanumeric and underscore. If no prefixes - * are desired, leave it as an empty string ''. - * - * To have all database names prefixed, set 'prefix' as a string: - * @code - * 'prefix' => 'main_', - * @endcode - * To provide prefixes for specific tables, set 'prefix' as an array. - * The array's keys are the table names and the values are the prefixes. - * The 'default' element is mandatory and holds the prefix for any tables - * not specified elsewhere in the array. Example: - * @code - * 'prefix' => array( - * 'default' => 'main_', - * 'users' => 'shared_', - * 'sessions' => 'shared_', - * 'role' => 'shared_', - * 'authmap' => 'shared_', - * ), - * @endcode - * You can also use a reference to a schema/database as a prefix. This may be - * useful if your Drupal installation exists in a schema that is not the default - * or you want to access several databases from the same code base at the same - * time. - * Example: - * @code - * 'prefix' => array( - * 'default' => 'main.', - * 'users' => 'shared.', - * 'sessions' => 'shared.', - * 'role' => 'shared.', - * 'authmap' => 'shared.', - * ); - * @endcode - * NOTE: MySQL and SQLite's definition of a schema is a database. - * - * Advanced users can add or override initial commands to execute when - * connecting to the database server, as well as PDO connection settings. For - * example, to enable MySQL SELECT queries to exceed the max_join_size system - * variable, and to reduce the database connection timeout to 5 seconds: - * @code - * $databases['default']['default'] = array( - * 'init_commands' => array( - * 'big_selects' => 'SET SQL_BIG_SELECTS=1', - * ), - * 'pdo' => array( - * PDO::ATTR_TIMEOUT => 5, - * ), - * ); - * @endcode - * - * WARNING: The above defaults are designed for database portability. Changing - * them may cause unexpected behavior, including potential data loss. See - * https://www.drupal.org/developing/api/database/configuration for more - * information on these defaults and the potential issues. - * - * More details can be found in the constructor methods for each driver: - * - \Drupal\Core\Database\Driver\mysql\Connection::__construct() - * - \Drupal\Core\Database\Driver\pgsql\Connection::__construct() - * - \Drupal\Core\Database\Driver\sqlite\Connection::__construct() - * - * Sample Database configuration format for PostgreSQL (pgsql): - * @code - * $databases['default']['default'] = array( - * 'driver' => 'pgsql', - * 'database' => 'databasename', - * 'username' => 'sqlusername', - * 'password' => 'sqlpassword', - * 'host' => 'localhost', - * 'prefix' => '', - * ); - * @endcode - * - * Sample Database configuration format for SQLite (sqlite): - * @code - * $databases['default']['default'] = array( - * 'driver' => 'sqlite', - * 'database' => '/path/to/databasefilename', - * ); - * @endcode - */ - -/** - * Location of the site configuration files. - * - * The $config_directories array specifies the location of file system - * directories used for configuration data. On install, the "sync" directory is - * created. This is used for configuration imports. The "active" directory is - * not created by default since the default storage for active configuration is - * the database rather than the file system. (This can be changed. See "Active - * configuration settings" below). - * - * The default location for the "sync" directory is inside a randomly-named - * directory in the public files path. The setting below allows you to override - * the "sync" location. - * - * If you use files for the "active" configuration, you can tell the - * Configuration system where this directory is located by adding an entry with - * array key CONFIG_ACTIVE_DIRECTORY. - * - * Example: - * @code - * $config_directories = array( - * CONFIG_SYNC_DIRECTORY => '/directory/outside/webroot', - * ); - * @endcode - */ -$config_directories = array(); - -/** - * Settings: - * - * $settings contains environment-specific configuration, such as the files - * directory and reverse proxy address, and temporary configuration, such as - * security overrides. - * - * @see \Drupal\Core\Site\Settings::get() - */ - -/** - * The active installation profile. - * - * Changing this after installation is not recommended as it changes which - * directories are scanned during extension discovery. If this is set prior to - * installation this value will be rewritten according to the profile selected - * by the user. - * - * @see install_select_profile() - */ -# $settings['install_profile'] = ''; - -/** - * Salt for one-time login links, cancel links, form tokens, etc. - * - * This variable will be set to a random value by the installer. All one-time - * login links will be invalidated if the value is changed. Note that if your - * site is deployed on a cluster of web servers, you must ensure that this - * variable has the same value on each server. - * - * For enhanced security, you may set this variable to the contents of a file - * outside your document root; you should also ensure that this file is not - * stored with backups of your database. - * - * Example: - * @code - * $settings['hash_salt'] = file_get_contents('/home/example/salt.txt'); - * @endcode - */ -$settings['hash_salt'] = ''; - -/** - * Deployment identifier. - * - * Drupal's dependency injection container will be automatically invalidated and - * rebuilt when the Drupal core version changes. When updating contributed or - * custom code that changes the container, changing this identifier will also - * allow the container to be invalidated as soon as code is deployed. - */ -# $settings['deployment_identifier'] = \Drupal::VERSION; - -/** - * Access control for update.php script. - * - * If you are updating your Drupal installation using the update.php script but - * are not logged in using either an account with the "Administer software - * updates" permission or the site maintenance account (the account that was - * created during installation), you will need to modify the access check - * statement below. Change the FALSE to a TRUE to disable the access check. - * After finishing the upgrade, be sure to open this file again and change the - * TRUE back to a FALSE! - */ -$settings['update_free_access'] = FALSE; - -/** - * External access proxy settings: - * - * If your site must access the Internet via a web proxy then you can enter the - * proxy settings here. Set the full URL of the proxy, including the port, in - * variables: - * - $settings['http_client_config']['proxy']['http']: The proxy URL for HTTP - * requests. - * - $settings['http_client_config']['proxy']['https']: The proxy URL for HTTPS - * requests. - * You can pass in the user name and password for basic authentication in the - * URLs in these settings. - * - * You can also define an array of host names that can be accessed directly, - * bypassing the proxy, in $settings['http_client_config']['proxy']['no']. - */ -# $settings['http_client_config']['proxy']['http'] = 'http://proxy_user:proxy_pass@example.com:8080'; -# $settings['http_client_config']['proxy']['https'] = 'http://proxy_user:proxy_pass@example.com:8080'; -# $settings['http_client_config']['proxy']['no'] = ['127.0.0.1', 'localhost']; - -/** - * Reverse Proxy Configuration: - * - * Reverse proxy servers are often used to enhance the performance - * of heavily visited sites and may also provide other site caching, - * security, or encryption benefits. In an environment where Drupal - * is behind a reverse proxy, the real IP address of the client should - * be determined such that the correct client IP address is available - * to Drupal's logging, statistics, and access management systems. In - * the most simple scenario, the proxy server will add an - * X-Forwarded-For header to the request that contains the client IP - * address. However, HTTP headers are vulnerable to spoofing, where a - * malicious client could bypass restrictions by setting the - * X-Forwarded-For header directly. Therefore, Drupal's proxy - * configuration requires the IP addresses of all remote proxies to be - * specified in $settings['reverse_proxy_addresses'] to work correctly. - * - * Enable this setting to get Drupal to determine the client IP from - * the X-Forwarded-For header (or $settings['reverse_proxy_header'] if set). - * If you are unsure about this setting, do not have a reverse proxy, - * or Drupal operates in a shared hosting environment, this setting - * should remain commented out. - * - * In order for this setting to be used you must specify every possible - * reverse proxy IP address in $settings['reverse_proxy_addresses']. - * If a complete list of reverse proxies is not available in your - * environment (for example, if you use a CDN) you may set the - * $_SERVER['REMOTE_ADDR'] variable directly in settings.php. - * Be aware, however, that it is likely that this would allow IP - * address spoofing unless more advanced precautions are taken. - */ -# $settings['reverse_proxy'] = TRUE; - -/** - * Specify every reverse proxy IP address in your environment. - * This setting is required if $settings['reverse_proxy'] is TRUE. - */ -# $settings['reverse_proxy_addresses'] = array('a.b.c.d', ...); - -/** - * Set this value if your proxy server sends the client IP in a header - * other than X-Forwarded-For. - */ -# $settings['reverse_proxy_header'] = 'X_CLUSTER_CLIENT_IP'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Proto. - */ -# $settings['reverse_proxy_proto_header'] = 'X_FORWARDED_PROTO'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Host. - */ -# $settings['reverse_proxy_host_header'] = 'X_FORWARDED_HOST'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than X-Forwarded-Port. - */ -# $settings['reverse_proxy_port_header'] = 'X_FORWARDED_PORT'; - -/** - * Set this value if your proxy server sends the client protocol in a header - * other than Forwarded. - */ -# $settings['reverse_proxy_forwarded_header'] = 'FORWARDED'; - -/** - * Page caching: - * - * By default, Drupal sends a "Vary: Cookie" HTTP header for anonymous page - * views. This tells a HTTP proxy that it may return a page from its local - * cache without contacting the web server, if the user sends the same Cookie - * header as the user who originally requested the cached page. Without "Vary: - * Cookie", authenticated users would also be served the anonymous page from - * the cache. If the site has mostly anonymous users except a few known - * editors/administrators, the Vary header can be omitted. This allows for - * better caching in HTTP proxies (including reverse proxies), i.e. even if - * clients send different cookies, they still get content served from the cache. - * However, authenticated users should access the site directly (i.e. not use an - * HTTP proxy, and bypass the reverse proxy if one is used) in order to avoid - * getting cached pages from the proxy. - */ -# $settings['omit_vary_cookie'] = TRUE; - -/** - * Class Loader. - * - * If the APC extension is detected, the Symfony APC class loader is used for - * performance reasons. Detection can be prevented by setting - * class_loader_auto_detect to false, as in the example below. - */ -# $settings['class_loader_auto_detect'] = FALSE; - -/* - * If the APC extension is not detected, either because APC is missing or - * because auto-detection has been disabled, auto-loading falls back to - * Composer's ClassLoader, which is good for development as it does not break - * when code is moved in the file system. You can also decorate the base class - * loader with another cached solution than the Symfony APC class loader, as - * all production sites should have a cached class loader of some sort enabled. - * - * To do so, you may decorate and replace the local $class_loader variable. For - * example, to use Symfony's APC class loader without automatic detection, - * uncomment the code below. - */ -/* -if ($settings['hash_salt']) { - $prefix = 'drupal.' . hash('sha256', 'drupal.' . $settings['hash_salt']); - $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $class_loader); - unset($prefix); - $class_loader->unregister(); - $apc_loader->register(); - $class_loader = $apc_loader; -} -*/ - -/** - * Authorized file system operations: - * - * The Update Manager module included with Drupal provides a mechanism for - * site administrators to securely install missing updates for the site - * directly through the web user interface. On securely-configured servers, - * the Update manager will require the administrator to provide SSH or FTP - * credentials before allowing the installation to proceed; this allows the - * site to update the new files as the user who owns all the Drupal files, - * instead of as the user the webserver is running as. On servers where the - * webserver user is itself the owner of the Drupal files, the administrator - * will not be prompted for SSH or FTP credentials (note that these server - * setups are common on shared hosting, but are inherently insecure). - * - * Some sites might wish to disable the above functionality, and only update - * the code directly via SSH or FTP themselves. This setting completely - * disables all functionality related to these authorized file operations. - * - * @see https://www.drupal.org/node/244924 - * - * Remove the leading hash signs to disable. - */ -# $settings['allow_authorize_operations'] = FALSE; - -/** - * Default mode for directories and files written by Drupal. - * - * Value should be in PHP Octal Notation, with leading zero. - */ -# $settings['file_chmod_directory'] = 0775; -# $settings['file_chmod_file'] = 0664; - -/** - * Public file base URL: - * - * An alternative base URL to be used for serving public files. This must - * include any leading directory path. - * - * A different value from the domain used by Drupal to be used for accessing - * public files. This can be used for a simple CDN integration, or to improve - * security by serving user-uploaded files from a different domain or subdomain - * pointing to the same server. Do not include a trailing slash. - */ -# $settings['file_public_base_url'] = 'http://downloads.example.com/files'; - -/** - * Public file path: - * - * A local file system path where public files will be stored. This directory - * must exist and be writable by Drupal. This directory must be relative to - * the Drupal installation directory and be accessible over the web. - */ -# $settings['file_public_path'] = 'sites/default/files'; - -/** - * Private file path: - * - * A local file system path where private files will be stored. This directory - * must be absolute, outside of the Drupal installation directory and not - * accessible over the web. - * - * Note: Caches need to be cleared when this value is changed to make the - * private:// stream wrapper available to the system. - * - * See https://www.drupal.org/documentation/modules/file for more information - * about securing private files. - */ -# $settings['file_private_path'] = ''; - -/** - * Session write interval: - * - * Set the minimum interval between each session write to database. - * For performance reasons it defaults to 180. - */ -# $settings['session_write_interval'] = 180; - -/** - * String overrides: - * - * To override specific strings on your site with or without enabling the Locale - * module, add an entry to this list. This functionality allows you to change - * a small number of your site's default English language interface strings. - * - * Remove the leading hash signs to enable. - * - * The "en" part of the variable name, is dynamic and can be any langcode of - * any added language. (eg locale_custom_strings_de for german). - */ -# $settings['locale_custom_strings_en'][''] = array( -# 'forum' => 'Discussion board', -# '@count min' => '@count minutes', -# ); - -/** - * A custom theme for the offline page: - * - * This applies when the site is explicitly set to maintenance mode through the - * administration page or when the database is inactive due to an error. - * The template file should also be copied into the theme. It is located inside - * 'core/modules/system/templates/maintenance-page.html.twig'. - * - * Note: This setting does not apply to installation and update pages. - */ -# $settings['maintenance_theme'] = 'bartik'; - -/** - * PHP settings: - * - * To see what PHP settings are possible, including whether they can be set at - * runtime (by using ini_set()), read the PHP documentation: - * http://php.net/manual/ini.list.php - * See \Drupal\Core\DrupalKernel::bootEnvironment() for required runtime - * settings and the .htaccess file for non-runtime settings. - * Settings defined there should not be duplicated here so as to avoid conflict - * issues. - */ - -/** - * If you encounter a situation where users post a large amount of text, and - * the result is stripped out upon viewing but can still be edited, Drupal's - * output filter may not have sufficient memory to process it. If you - * experience this issue, you may wish to uncomment the following two lines - * and increase the limits of these variables. For more information, see - * http://php.net/manual/pcre.configuration.php. - */ -# ini_set('pcre.backtrack_limit', 200000); -# ini_set('pcre.recursion_limit', 200000); - -/** - * Active configuration settings. - * - * By default, the active configuration is stored in the database in the - * {config} table. To use a different storage mechanism for the active - * configuration, do the following prior to installing: - * - Create an "active" directory and declare its path in $config_directories - * as explained under the 'Location of the site configuration files' section - * above in this file. To enhance security, you can declare a path that is - * outside your document root. - * - Override the 'bootstrap_config_storage' setting here. It must be set to a - * callable that returns an object that implements - * \Drupal\Core\Config\StorageInterface. - * - Override the service definition 'config.storage.active'. Put this - * override in a services.yml file in the same directory as settings.php - * (definitions in this file will override service definition defaults). - */ -# $settings['bootstrap_config_storage'] = array('Drupal\Core\Config\BootstrapConfigStorageFactory', 'getFileStorage'); - -/** - * Configuration overrides. - * - * To globally override specific configuration values for this site, - * set them here. You usually don't need to use this feature. This is - * useful in a configuration file for a vhost or directory, rather than - * the default settings.php. - * - * Note that any values you provide in these variable overrides will not be - * viewable from the Drupal administration interface. The administration - * interface displays the values stored in configuration so that you can stage - * changes to other environments that don't have the overrides. - * - * There are particular configuration values that are risky to override. For - * example, overriding the list of installed modules in 'core.extension' is not - * supported as module install or uninstall has not occurred. Other examples - * include field storage configuration, because it has effects on database - * structure, and 'core.menu.static_menu_link_overrides' since this is cached in - * a way that is not config override aware. Also, note that changing - * configuration values in settings.php will not fire any of the configuration - * change events. - */ -# $config['system.site']['name'] = 'My Drupal site'; -# $config['system.theme']['default'] = 'stark'; -# $config['user.settings']['anonymous'] = 'Visitor'; - -/** - * Fast 404 pages: - * - * Drupal can generate fully themed 404 pages. However, some of these responses - * are for images or other resource files that are not displayed to the user. - * This can waste bandwidth, and also generate server load. - * - * The options below return a simple, fast 404 page for URLs matching a - * specific pattern: - * - $config['system.performance']['fast_404']['exclude_paths']: A regular - * expression to match paths to exclude, such as images generated by image - * styles, or dynamically-resized images. The default pattern provided below - * also excludes the private file system. If you need to add more paths, you - * can add '|path' to the expression. - * - $config['system.performance']['fast_404']['paths']: A regular expression to - * match paths that should return a simple 404 page, rather than the fully - * themed 404 page. If you don't have any aliases ending in htm or html you - * can add '|s?html?' to the expression. - * - $config['system.performance']['fast_404']['html']: The html to return for - * simple 404 pages. - * - * Remove the leading hash signs if you would like to alter this functionality. - */ -# $config['system.performance']['fast_404']['exclude_paths'] = '/\/(?:styles)|(?:system\/files)\//'; -# $config['system.performance']['fast_404']['paths'] = '/\.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'; -# $config['system.performance']['fast_404']['html'] = '404 Not Found

Not Found

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

'; - -/** - * Load services definition file. - */ -$settings['container_yamls'][] = __DIR__ . '/services.yml'; - -/** - * Override the default service container class. - * - * This is useful for example to trace the service container for performance - * tracking purposes, for testing a service container with an error condition or - * to test a service container that throws an exception. - */ -# $settings['container_base_class'] = '\Drupal\Core\DependencyInjection\Container'; - -/** - * Trusted host configuration. - * - * Drupal core can use the Symfony trusted host mechanism to prevent HTTP Host - * header spoofing. - * - * To enable the trusted host mechanism, you enable your allowable hosts - * in $settings['trusted_host_patterns']. This should be an array of regular - * expression patterns, without delimiters, representing the hosts you would - * like to allow. - * - * For example: - * @code - * $settings['trusted_host_patterns'] = array( - * '^www\.example\.com$', - * ); - * @endcode - * will allow the site to only run from www.example.com. - * - * If you are running multisite, or if you are running your site from - * different domain names (eg, you don't redirect http://www.example.com to - * http://example.com), you should specify all of the host patterns that are - * allowed by your site. - * - * For example: - * @code - * $settings['trusted_host_patterns'] = array( - * '^example\.com$', - * '^.+\.example\.com$', - * '^example\.org$', - * '^.+\.example\.org$', - * ); - * @endcode - * will allow the site to run off of all variants of example.com and - * example.org, with all subdomains included. - */ - -/** - * Load local development override configuration, if available. - * - * Use settings.local.php to override variables on secondary (staging, - * development, etc) installations of this site. Typically used to disable - * caching, JavaScript/CSS compression, re-routing of outgoing emails, and - * other things that should not happen on development and testing sites. - * - * Keep this code block at the end of this file to take full effect. - */ -# if (file_exists(__DIR__ . '/settings.local.php')) { -# include __DIR__ . '/settings.local.php'; -# } diff --git a/sites/development.services.yml b/sites/development.services.yml index 73cc99825..bcdcb682a 100644 --- a/sites/development.services.yml +++ b/sites/development.services.yml @@ -5,3 +5,9 @@ services: cache.backend.null: class: Drupal\Core\Cache\NullBackendFactory + +parameters: + twig.config: + debug: true + auto_reload: true + cache: false